mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-02-15 03:50:54 +01:00
Revert "Revert "Upstream sync""
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using Content.Client.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Content.Shared.Access.Components.SharedIdCardConsoleComponent;
|
||||
@@ -36,6 +37,7 @@ namespace Content.Client.Access.UI
|
||||
|
||||
_window = new IdCardConsoleWindow(this, _prototypeManager, accessLevels) {Title = _entityManager.GetComponent<MetaDataComponent>(Owner.Owner).EntityName};
|
||||
|
||||
_window.CrewManifestButton.OnPressed += _ => SendMessage(new CrewManifestOpenUiMessage());
|
||||
_window.PrivilegedIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(PrivilegedIdCardSlotId));
|
||||
_window.TargetIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(TargetIdCardSlotId));
|
||||
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
MinSize="650 290">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<GridContainer Columns="3">
|
||||
<Label Text="{Loc 'id-card-console-window-privileged-id'}" />
|
||||
<Button Name="PrivilegedIdButton" Access="Public"/>
|
||||
<Label Name="PrivilegedIdLabel" />
|
||||
<GridContainer Columns="2">
|
||||
<GridContainer Columns="3" HorizontalExpand="True">
|
||||
<Label Text="{Loc 'id-card-console-window-privileged-id'}" />
|
||||
<Button Name="PrivilegedIdButton" Access="Public"/>
|
||||
<Label Name="PrivilegedIdLabel" />
|
||||
|
||||
<Label Text="{Loc 'id-card-console-window-target-id'}" />
|
||||
<Button Name="TargetIdButton" Access="Public"/>
|
||||
<Label Name="TargetIdLabel" />
|
||||
<Label Text="{Loc 'id-card-console-window-target-id'}" />
|
||||
<Button Name="TargetIdButton" Access="Public"/>
|
||||
<Label Name="TargetIdLabel" />
|
||||
</GridContainer>
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Button Name="CrewManifestButton" Access="Public" Text="{Loc 'crew-manifest-button-label'}" />
|
||||
</BoxContainer>
|
||||
</GridContainer>
|
||||
<Control MinSize="0 8" />
|
||||
<GridContainer Columns="3" HSeparationOverride="4">
|
||||
@@ -21,6 +26,10 @@
|
||||
<Button Name="JobTitleSaveButton" Text="{Loc 'id-card-console-window-save-button'}" Disabled="True" />
|
||||
</GridContainer>
|
||||
<Control MinSize="0 8" />
|
||||
<GridContainer Columns="2">
|
||||
<Label Text="{Loc 'id-card-console-window-job-selection-label'}" />
|
||||
<OptionButton Name="JobPresetOptionButton" />
|
||||
</GridContainer>
|
||||
<GridContainer Name="AccessLevelGrid" Columns="5" HorizontalAlignment="Center">
|
||||
|
||||
<!-- Access level buttons are added here by the C# code -->
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.Access;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
@@ -16,9 +17,12 @@ namespace Content.Client.Access.UI
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class IdCardConsoleWindow : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
private readonly IdCardConsoleBoundUserInterface _owner;
|
||||
|
||||
private readonly Dictionary<string, Button> _accessButtons = new();
|
||||
private readonly List<string> _jobPrototypeIds = new();
|
||||
|
||||
private string? _lastFullName;
|
||||
private string? _lastJobTitle;
|
||||
@@ -26,6 +30,7 @@ namespace Content.Client.Access.UI
|
||||
public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeManager prototypeManager, List<string> accessLevels)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_owner = owner;
|
||||
|
||||
@@ -43,6 +48,21 @@ namespace Content.Client.Access.UI
|
||||
};
|
||||
JobTitleSaveButton.OnPressed += _ => SubmitData();
|
||||
|
||||
var jobs = _prototypeManager.EnumeratePrototypes<JobPrototype>();
|
||||
|
||||
foreach (var job in jobs)
|
||||
{
|
||||
if (!job.SetPreference)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_jobPrototypeIds.Add(job.ID);
|
||||
JobPresetOptionButton.AddItem(Loc.GetString(job.Name), _jobPrototypeIds.Count - 1);
|
||||
}
|
||||
|
||||
JobPresetOptionButton.OnItemSelected += SelectJobPreset;
|
||||
|
||||
foreach (var access in accessLevels)
|
||||
{
|
||||
if (!prototypeManager.TryIndex<AccessLevelPrototype>(access, out var accessLevel))
|
||||
@@ -62,6 +82,56 @@ namespace Content.Client.Access.UI
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearAllAccess()
|
||||
{
|
||||
foreach (var button in _accessButtons.Values)
|
||||
{
|
||||
if (button.Pressed)
|
||||
{
|
||||
button.Pressed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectJobPreset(OptionButton.ItemSelectedEventArgs args)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(_jobPrototypeIds[args.Id], out JobPrototype? job))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
JobTitleLineEdit.Text = Loc.GetString(job.Name);
|
||||
|
||||
ClearAllAccess();
|
||||
|
||||
// this is a sussy way to do this
|
||||
foreach (var access in job.Access)
|
||||
{
|
||||
if (_accessButtons.TryGetValue(access, out var button))
|
||||
{
|
||||
button.Pressed = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var group in job.AccessGroups)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(group, out AccessGroupPrototype? groupPrototype))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var access in groupPrototype.Tags)
|
||||
{
|
||||
if (_accessButtons.TryGetValue(access, out var button))
|
||||
{
|
||||
button.Pressed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SubmitData();
|
||||
}
|
||||
|
||||
public void UpdateState(IdCardConsoleBoundUserInterfaceState state)
|
||||
{
|
||||
PrivilegedIdButton.Text = state.IsPrivilegedIdPresent
|
||||
@@ -100,6 +170,8 @@ namespace Content.Client.Access.UI
|
||||
|
||||
JobTitleSaveButton.Disabled = !interfaceEnabled || !jobTitleDirty;
|
||||
|
||||
JobPresetOptionButton.Disabled = !interfaceEnabled;
|
||||
|
||||
foreach (var (accessName, button) in _accessButtons)
|
||||
{
|
||||
button.Disabled = !interfaceEnabled;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Power;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -40,7 +41,7 @@ namespace Content.Client.Atmos.Monitor
|
||||
if (!sprite.LayerMapTryGet(_layerMap, out int layer))
|
||||
return;
|
||||
|
||||
if (component.TryGetData<bool>("powered", out var powered))
|
||||
if (component.TryGetData<bool>(PowerDeviceVisuals.Powered, out var powered))
|
||||
{
|
||||
if (_hideOnDepowered != null)
|
||||
foreach (var visLayer in _hideOnDepowered)
|
||||
@@ -53,12 +54,12 @@ namespace Content.Client.Atmos.Monitor
|
||||
sprite.LayerSetState(setStateLayer, new RSI.StateId(state));
|
||||
}
|
||||
|
||||
if (component.TryGetData<Vector2>("offset", out Vector2 offset))
|
||||
if (component.TryGetData<Vector2>(AtmosMonitorVisuals.Offset, out Vector2 offset))
|
||||
{
|
||||
sprite.Offset = offset;
|
||||
}
|
||||
|
||||
if (component.TryGetData<AtmosMonitorAlarmType>("alarmType", out var alarmType)
|
||||
if (component.TryGetData<AtmosMonitorAlarmType>(AtmosMonitorVisuals.AlarmType, out var alarmType)
|
||||
&& powered)
|
||||
if (_alarmStates.TryGetValue(alarmType, out var state))
|
||||
sprite.LayerSetState(layer, new RSI.StateId(state));
|
||||
|
||||
8
Content.Client/Audio/ContentAudioSystem.cs
Normal file
8
Content.Client/Audio/ContentAudioSystem.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.Audio;
|
||||
|
||||
namespace Content.Client.Audio;
|
||||
|
||||
public sealed class ContentAudioSystem : SharedContentAudioSystem
|
||||
{
|
||||
|
||||
}
|
||||
51
Content.Client/CrewManifest/CrewManifestEui.cs
Normal file
51
Content.Client/CrewManifest/CrewManifestEui.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Content.Client.Eui;
|
||||
using Content.Client.GameTicking.Managers;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Content.Shared.Eui;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Client.CrewManifest;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class CrewManifestEui : BaseEui
|
||||
{
|
||||
private readonly ClientGameTicker _gameTicker;
|
||||
private readonly CrewManifestUi _window;
|
||||
|
||||
public CrewManifestEui()
|
||||
{
|
||||
_gameTicker = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ClientGameTicker>();
|
||||
_window = new();
|
||||
|
||||
_window.OnClose += () =>
|
||||
{
|
||||
SendMessage(new CrewManifestEuiClosed());
|
||||
};
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
base.Opened();
|
||||
|
||||
_window.OpenCentered();
|
||||
}
|
||||
|
||||
public override void Closed()
|
||||
{
|
||||
base.Closed();
|
||||
|
||||
_window.Close();
|
||||
}
|
||||
|
||||
public override void HandleState(EuiStateBase state)
|
||||
{
|
||||
base.HandleState(state);
|
||||
|
||||
if (state is not CrewManifestEuiState cast)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_window.Populate(cast.StationName, cast.Entries);
|
||||
}
|
||||
}
|
||||
82
Content.Client/CrewManifest/CrewManifestSystem.cs
Normal file
82
Content.Client/CrewManifest/CrewManifestSystem.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using Content.Client.GameTicking.Managers;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.CrewManifest;
|
||||
|
||||
public sealed class CrewManifestSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
private Dictionary<string, Dictionary<string, int>> _jobDepartmentLookup = new();
|
||||
private HashSet<string> _departments = new();
|
||||
|
||||
public IReadOnlySet<string> Departments => _departments;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
BuildDepartmentLookup();
|
||||
_prototypeManager.PrototypesReloaded += OnPrototypesReload;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
_prototypeManager.PrototypesReloaded -= OnPrototypesReload;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests a crew manifest from the server.
|
||||
/// </summary>
|
||||
/// <param name="uid">EntityUid of the entity we're requesting the crew manifest from.</param>
|
||||
public void RequestCrewManifest(EntityUid uid)
|
||||
{
|
||||
RaiseNetworkEvent(new RequestCrewManifestMessage(uid));
|
||||
}
|
||||
|
||||
private void OnPrototypesReload(PrototypesReloadedEventArgs _)
|
||||
{
|
||||
_jobDepartmentLookup.Clear();
|
||||
_departments.Clear();
|
||||
|
||||
BuildDepartmentLookup();
|
||||
}
|
||||
|
||||
private void BuildDepartmentLookup()
|
||||
{
|
||||
foreach (var department in _prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
|
||||
{
|
||||
_departments.Add(department.ID);
|
||||
|
||||
for (var i = 1; i <= department.Roles.Count; i++)
|
||||
{
|
||||
if (!_jobDepartmentLookup.TryGetValue(department.Roles[i - 1], out var departments))
|
||||
{
|
||||
departments = new();
|
||||
_jobDepartmentLookup.Add(department.Roles[i - 1], departments);
|
||||
}
|
||||
|
||||
departments.Add(department.ID, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int GetDepartmentOrder(string department, string jobPrototype)
|
||||
{
|
||||
if (!Departments.Contains(department))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!_jobDepartmentLookup.TryGetValue(jobPrototype, out var departments))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return departments.TryGetValue(department, out var order)
|
||||
? order
|
||||
: -1;
|
||||
}
|
||||
}
|
||||
21
Content.Client/CrewManifest/CrewManifestUi.xaml
Normal file
21
Content.Client/CrewManifest/CrewManifestUi.xaml
Normal file
@@ -0,0 +1,21 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.HUD.UI"
|
||||
Title="{Loc 'crew-manifest-window-title'}"
|
||||
MinSize="450 750">
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True">
|
||||
<ui:StripeBack Name="StationNameContainer">
|
||||
<PanelContainer>
|
||||
<Label Name="StationName" Align="Center" />
|
||||
</PanelContainer>
|
||||
</ui:StripeBack>
|
||||
<BoxContainer HorizontalExpand="True" VerticalExpand="True">
|
||||
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
|
||||
<!-- this MIGHT have race conditions -->
|
||||
<BoxContainer Name="CrewManifestListing" Orientation="Vertical" HorizontalExpand="True">
|
||||
<Label Text="{Loc 'crew-manifest-no-valid-station'}" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<!-- Crew manifest goes here. -->
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
186
Content.Client/CrewManifest/CrewManifestUi.xaml.cs
Normal file
186
Content.Client/CrewManifest/CrewManifestUi.xaml.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.CrewManifest;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CrewManifestUi : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
|
||||
private readonly CrewManifestSystem _crewManifestSystem;
|
||||
|
||||
private EntityUid? _station;
|
||||
|
||||
public CrewManifestUi()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_crewManifestSystem = _entitySystemManager.GetEntitySystem<CrewManifestSystem>();
|
||||
|
||||
StationName.AddStyleClass("LabelBig");
|
||||
}
|
||||
|
||||
public void Populate(string name, CrewManifestEntries? entries)
|
||||
{
|
||||
CrewManifestListing.DisposeAllChildren();
|
||||
CrewManifestListing.RemoveAllChildren();
|
||||
|
||||
StationNameContainer.Visible = entries != null;
|
||||
StationName.Text = name;
|
||||
|
||||
if (entries == null) return;
|
||||
|
||||
var entryList = SortEntries(entries);
|
||||
|
||||
foreach (var item in entryList)
|
||||
{
|
||||
CrewManifestListing.AddChild(new CrewManifestSection(item.section, item.entries, _resourceCache, _crewManifestSystem));
|
||||
}
|
||||
}
|
||||
|
||||
private List<(string section, List<CrewManifestEntry> entries)> SortEntries(CrewManifestEntries entries)
|
||||
{
|
||||
var entryDict = new Dictionary<string, List<CrewManifestEntry>>();
|
||||
|
||||
foreach (var entry in entries.Entries)
|
||||
{
|
||||
foreach (var department in _prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
|
||||
{
|
||||
// this is a little expensive, and could be better
|
||||
if (department.Roles.Contains(entry.JobPrototype))
|
||||
{
|
||||
entryDict.GetOrNew(department.ID).Add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var entryList = new List<(string section, List<CrewManifestEntry> entries)>();
|
||||
|
||||
foreach (var (section, listing) in entryDict)
|
||||
{
|
||||
entryList.Add((section, listing));
|
||||
}
|
||||
|
||||
var sortOrder = _configManager.GetCVar(CCVars.CrewManifestOrdering).Split(",").ToList();
|
||||
|
||||
entryList.Sort((a, b) =>
|
||||
{
|
||||
var ai = sortOrder.IndexOf(a.section);
|
||||
var bi = sortOrder.IndexOf(b.section);
|
||||
|
||||
// this is up here so -1 == -1 occurs first
|
||||
if (ai == bi)
|
||||
return 0;
|
||||
|
||||
if (ai == -1)
|
||||
return -1;
|
||||
|
||||
if (bi == -1)
|
||||
return 1;
|
||||
|
||||
return ai.CompareTo(bi);
|
||||
});
|
||||
|
||||
return entryList;
|
||||
}
|
||||
|
||||
private sealed class CrewManifestSection : BoxContainer
|
||||
{
|
||||
public CrewManifestSection(string sectionTitle, List<CrewManifestEntry> entries, IResourceCache cache, CrewManifestSystem crewManifestSystem)
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical;
|
||||
HorizontalExpand = true;
|
||||
|
||||
AddChild(new Label()
|
||||
{
|
||||
StyleClasses = { "LabelBig" },
|
||||
Text = Loc.GetString(sectionTitle)
|
||||
});
|
||||
|
||||
entries.Sort((a, b) =>
|
||||
{
|
||||
var posA = crewManifestSystem.GetDepartmentOrder(sectionTitle, a.JobPrototype);
|
||||
var posB = crewManifestSystem.GetDepartmentOrder(sectionTitle, b.JobPrototype);
|
||||
|
||||
return posA.CompareTo(posB);
|
||||
});
|
||||
|
||||
var gridContainer = new GridContainer()
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
Columns = 2
|
||||
};
|
||||
|
||||
AddChild(gridContainer);
|
||||
|
||||
var path = new ResourcePath("/Textures/Interface/Misc/job_icons.rsi");
|
||||
cache.TryGetResource(path, out RSIResource? rsi);
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
var name = new Label()
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
Text = entry.Name
|
||||
};
|
||||
|
||||
var titleContainer = new BoxContainer()
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
HorizontalExpand = true
|
||||
};
|
||||
|
||||
var title = new Label()
|
||||
{
|
||||
Text = Loc.GetString(entry.JobTitle)
|
||||
};
|
||||
|
||||
|
||||
if (rsi != null)
|
||||
{
|
||||
var icon = new TextureRect()
|
||||
{
|
||||
TextureScale = (2, 2),
|
||||
Stretch = TextureRect.StretchMode.KeepCentered
|
||||
};
|
||||
|
||||
if (rsi.RSI.TryGetState(entry.JobIcon, out _))
|
||||
{
|
||||
var specifier = new SpriteSpecifier.Rsi(path, entry.JobIcon);
|
||||
icon.Texture = specifier.Frame0();
|
||||
}
|
||||
else if (rsi.RSI.TryGetState("Unknown", out _))
|
||||
{
|
||||
var specifier = new SpriteSpecifier.Rsi(path, "Unknown");
|
||||
icon.Texture = specifier.Frame0();
|
||||
}
|
||||
|
||||
titleContainer.AddChild(icon);
|
||||
titleContainer.AddChild(title);
|
||||
}
|
||||
else
|
||||
{
|
||||
titleContainer.AddChild(title);
|
||||
}
|
||||
|
||||
gridContainer.AddChild(name);
|
||||
gridContainer.AddChild(titleContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ namespace Content.Client.Damage
|
||||
/// completely invisible.
|
||||
/// </remarks>
|
||||
[DataField("targetLayers")]
|
||||
private List<string>? _targetLayers;
|
||||
private List<Enum>? _targetLayers;
|
||||
|
||||
/// <summary>
|
||||
/// The actual sprites for every damage group
|
||||
@@ -383,18 +383,8 @@ namespace Content.Client.Damage
|
||||
//
|
||||
// If the layer doesn't have a base state, or
|
||||
// the layer key just doesn't exist, we skip it.
|
||||
foreach (var keyString in _targetLayers)
|
||||
foreach (var key in _targetLayers)
|
||||
{
|
||||
object key;
|
||||
if (IoCManager.Resolve<IReflectionManager>().TryParseEnumReference(keyString, out var @enum))
|
||||
{
|
||||
key = @enum;
|
||||
}
|
||||
else
|
||||
{
|
||||
key = keyString;
|
||||
}
|
||||
|
||||
if (!spriteComponent.LayerMapTryGet(key, out int index)
|
||||
|| spriteComponent.LayerGetState(index).ToString() == null)
|
||||
{
|
||||
@@ -562,20 +552,11 @@ namespace Content.Client.Damage
|
||||
/// </summary>
|
||||
private void UpdateDisabledLayers(SpriteComponent spriteComponent, AppearanceComponent component, DamageVisualizerDataComponent damageData)
|
||||
{
|
||||
foreach (object layer in damageData.TargetLayerMapKeys)
|
||||
foreach (var layer in damageData.TargetLayerMapKeys)
|
||||
{
|
||||
bool? layerStatus = null;
|
||||
switch (layer)
|
||||
{
|
||||
case Enum layerEnum:
|
||||
if (component.TryGetData<bool>(layerEnum, out var layerStateEnum))
|
||||
layerStatus = layerStateEnum;
|
||||
break;
|
||||
case string layerString:
|
||||
if (component.TryGetData<bool>(layerString, out var layerStateString))
|
||||
layerStatus = layerStateString;
|
||||
break;
|
||||
}
|
||||
if (component.TryGetData<bool>(layer, out var layerStateEnum))
|
||||
layerStatus = layerStateEnum;
|
||||
|
||||
if (layerStatus == null)
|
||||
continue;
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Content.Client.Damage
|
||||
[RegisterComponent]
|
||||
public sealed class DamageVisualizerDataComponent : Component
|
||||
{
|
||||
public List<object> TargetLayerMapKeys = new();
|
||||
public List<Enum> TargetLayerMapKeys = new();
|
||||
public bool Disabled = false;
|
||||
public bool Valid = true;
|
||||
public FixedPoint2 LastDamageThreshold = FixedPoint2.Zero;
|
||||
|
||||
9
Content.Client/Dragon/DragonRiftComponent.cs
Normal file
9
Content.Client/Dragon/DragonRiftComponent.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Content.Shared.Dragon;
|
||||
|
||||
namespace Content.Client.Dragon;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class DragonRiftComponent : SharedDragonRiftComponent
|
||||
{
|
||||
|
||||
}
|
||||
51
Content.Client/Dragon/DragonSystem.cs
Normal file
51
Content.Client/Dragon/DragonSystem.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Content.Shared.Dragon;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Client.Dragon;
|
||||
|
||||
public sealed class DragonSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<DragonRiftComponent, ComponentHandleState>(OnRiftHandleState);
|
||||
}
|
||||
|
||||
private void OnRiftHandleState(EntityUid uid, DragonRiftComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not DragonRiftComponentState state)
|
||||
return;
|
||||
|
||||
if (component.State == state.State) return;
|
||||
|
||||
component.State = state.State;
|
||||
TryComp<SpriteComponent>(uid, out var sprite);
|
||||
TryComp<PointLightComponent>(uid, out var light);
|
||||
|
||||
if (sprite == null && light == null)
|
||||
return;
|
||||
|
||||
switch (state.State)
|
||||
{
|
||||
case DragonRiftState.Charging:
|
||||
sprite?.LayerSetColor(0, Color.FromHex("#569fff"));
|
||||
|
||||
if (light != null)
|
||||
light.Color = Color.FromHex("#366db5");
|
||||
break;
|
||||
case DragonRiftState.AlmostFinished:
|
||||
sprite?.LayerSetColor(0, Color.FromHex("#cf4cff"));
|
||||
|
||||
if (light != null)
|
||||
light.Color = Color.FromHex("#9e2fc1");
|
||||
break;
|
||||
case DragonRiftState.Finished:
|
||||
sprite?.LayerSetColor(0, Color.FromHex("#edbc36"));
|
||||
|
||||
if (light != null)
|
||||
light.Color = Color.FromHex("#cbaf20");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ namespace Content.Client.Info
|
||||
|
||||
public static Control MakeRules(IConfigurationManager cfg, IResourceManager res)
|
||||
{
|
||||
return MakeSection(Loc.GetString("ui-rules-header"), cfg.GetCVar(CCVars.RulesFile), true, res);
|
||||
return MakeSection(Loc.GetString(cfg.GetCVar(CCVars.RulesHeader)), cfg.GetCVar(CCVars.RulesFile), true, res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
using System.Linq;
|
||||
using Content.Client.CrewManifest;
|
||||
using Content.Client.Eui;
|
||||
using Content.Client.GameTicking.Managers;
|
||||
using Content.Client.HUD.UI;
|
||||
using Content.Client.Players.PlayTimeTracking;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
@@ -17,6 +23,7 @@ namespace Content.Client.LateJoin
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
|
||||
public event Action<(EntityUid, string)> SelectedId;
|
||||
|
||||
@@ -64,6 +71,7 @@ namespace Content.Client.LateJoin
|
||||
_jobCategories.Clear();
|
||||
|
||||
var gameTicker = EntitySystem.Get<ClientGameTicker>();
|
||||
var tracker = IoCManager.Resolve<PlayTimeTrackingManager>();
|
||||
|
||||
if (!gameTicker.DisallowedLateJoin && gameTicker.StationNames.Count == 0)
|
||||
Logger.Warning("No stations exist, nothing to display in late-join GUI");
|
||||
@@ -109,6 +117,21 @@ namespace Content.Client.LateJoin
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (_configManager.GetCVar<bool>(CCVars.CrewManifestWithoutEntity))
|
||||
{
|
||||
var crewManifestButton = new Button()
|
||||
{
|
||||
Text = Loc.GetString("crew-manifest-button-label")
|
||||
};
|
||||
crewManifestButton.OnPressed += args =>
|
||||
{
|
||||
EntitySystem.Get<CrewManifestSystem>().RequestCrewManifest(id);
|
||||
};
|
||||
|
||||
_base.AddChild(crewManifestButton);
|
||||
}
|
||||
|
||||
var jobListScroll = new ScrollContainer()
|
||||
{
|
||||
VerticalExpand = true,
|
||||
@@ -202,12 +225,8 @@ namespace Content.Client.LateJoin
|
||||
Stretch = TextureRect.StretchMode.KeepCentered
|
||||
};
|
||||
|
||||
if (prototype.Icon != null)
|
||||
{
|
||||
var specifier = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Interface/Misc/job_icons.rsi"), prototype.Icon);
|
||||
icon.Texture = specifier.Frame0();
|
||||
}
|
||||
|
||||
var specifier = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Interface/Misc/job_icons.rsi"), prototype.Icon);
|
||||
icon.Texture = specifier.Frame0();
|
||||
jobSelector.AddChild(icon);
|
||||
|
||||
var jobLabel = new Label
|
||||
@@ -226,9 +245,16 @@ namespace Content.Client.LateJoin
|
||||
SelectedId?.Invoke((id, jobButton.JobId));
|
||||
};
|
||||
|
||||
if (value == 0)
|
||||
string? reason = null;
|
||||
|
||||
if (value == 0 || !tracker.IsAllowed(prototype, out reason))
|
||||
{
|
||||
jobButton.Disabled = true;
|
||||
|
||||
if (!string.IsNullOrEmpty(reason))
|
||||
{
|
||||
jobButton.ToolTip = reason;
|
||||
}
|
||||
}
|
||||
|
||||
_jobButtons[id][prototype.ID] = jobButton;
|
||||
|
||||
@@ -80,7 +80,7 @@ public sealed partial class MappingSystem : EntitySystem
|
||||
|
||||
var tileIcon = contentTileDef.IsSpace
|
||||
? _spaceIcon
|
||||
: new SpriteSpecifier.Texture(new ResourcePath(tileDef.Path) / $"{tileDef.SpriteName}.png");
|
||||
: new SpriteSpecifier.Texture(contentTileDef.Sprite!);
|
||||
|
||||
ev.Action = new InstantAction()
|
||||
{
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
using Content.Client.Message;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Content.Shared.PDA;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Client.PDA
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class PDABoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
|
||||
private PDAMenu? _menu;
|
||||
|
||||
public PDABoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
@@ -27,6 +33,15 @@ namespace Content.Client.PDA
|
||||
SendMessage(new PDAToggleFlashlightMessage());
|
||||
};
|
||||
|
||||
if (_configManager.GetCVar(CCVars.CrewManifestUnsecure))
|
||||
{
|
||||
_menu.CrewManifestButton.Visible = true;
|
||||
_menu.CrewManifestButton.OnPressed += _ =>
|
||||
{
|
||||
SendMessage(new CrewManifestOpenUiMessage());
|
||||
};
|
||||
}
|
||||
|
||||
_menu.EjectIdButton.OnPressed += _ =>
|
||||
{
|
||||
SendMessage(new ItemSlotButtonPressedEvent(PDAComponent.PDAIdSlotId));
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
Access="Public"
|
||||
Text="{Loc 'comp-pda-ui-toggle-flashlight-button'}"
|
||||
ToggleMode="True" />
|
||||
<Button Name="CrewManifestButton"
|
||||
Access="Public"
|
||||
Text="{Loc 'crew-manifest-button-label'}"
|
||||
Visible="False" />
|
||||
<Button Name="ActivateUplinkButton"
|
||||
Access="Public"
|
||||
Text="{Loc 'pda-bound-user-interface-uplink-tab-title'}" />
|
||||
|
||||
@@ -45,7 +45,8 @@ namespace Content.Client.ParticleAccelerator.UI
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
_menu?.Close();
|
||||
_menu?.Dispose();
|
||||
_menu = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,11 +334,13 @@ namespace Content.Client.ParticleAccelerator.UI
|
||||
{
|
||||
return new(this, resourceCache, name, state);
|
||||
}
|
||||
|
||||
UpdateUI(false, false, false, false);
|
||||
}
|
||||
|
||||
private bool StrengthSpinBoxValid(int n)
|
||||
{
|
||||
return (n >= 0 && n <= 4 && !_blockSpinBox);
|
||||
return (n >= 0 && n <= 3 && !_blockSpinBox);
|
||||
}
|
||||
|
||||
private void PowerStateChanged(object? sender, ValueChangedEventArgs e)
|
||||
@@ -358,13 +360,15 @@ namespace Content.Client.ParticleAccelerator.UI
|
||||
case 3:
|
||||
newState = ParticleAcceleratorPowerState.Level2;
|
||||
break;
|
||||
case 4:
|
||||
newState = ParticleAcceleratorPowerState.Level3;
|
||||
break;
|
||||
// They can't reach this level anyway and I just want to fix the bugginess for now.
|
||||
//case 4:
|
||||
// newState = ParticleAcceleratorPowerState.Level3;
|
||||
// break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
_stateSpinBox.SetButtonDisabled(true);
|
||||
Owner.SendPowerStateMessage(newState);
|
||||
}
|
||||
|
||||
|
||||
64
Content.Client/Shuttles/BUI/IFFConsoleBoundUserInterface.cs
Normal file
64
Content.Client/Shuttles/BUI/IFFConsoleBoundUserInterface.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using Content.Client.Shuttles.UI;
|
||||
using Content.Shared.Shuttles.BUIStates;
|
||||
using Content.Shared.Shuttles.Events;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Shuttles.BUI;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class IFFConsoleBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private IFFConsoleWindow? _window;
|
||||
|
||||
public IFFConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
_window = new IFFConsoleWindow();
|
||||
_window.OnClose += Close;
|
||||
_window.ShowIFF += SendIFFMessage;
|
||||
_window.ShowVessel += SendVesselMessage;
|
||||
_window.OpenCenteredLeft();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (state is not IFFConsoleBoundUserInterfaceState bState)
|
||||
return;
|
||||
|
||||
_window?.UpdateState(bState);
|
||||
}
|
||||
|
||||
private void SendIFFMessage(bool obj)
|
||||
{
|
||||
SendMessage(new IFFShowIFFMessage()
|
||||
{
|
||||
Show = obj,
|
||||
});
|
||||
}
|
||||
|
||||
private void SendVesselMessage(bool obj)
|
||||
{
|
||||
SendMessage(new IFFShowVesselMessage()
|
||||
{
|
||||
Show = obj,
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (disposing)
|
||||
{
|
||||
_window?.Close();
|
||||
_window = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<userInterface:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:userInterface="clr-namespace:Content.Client.UserInterface"
|
||||
Title="Emergency Shuttle Console"
|
||||
Title="{Loc 'emergency-shuttle-console-window-title'}"
|
||||
MinSize="400 400">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
Margin="5">
|
||||
|
||||
20
Content.Client/Shuttles/UI/IFFConsoleWindow.xaml
Normal file
20
Content.Client/Shuttles/UI/IFFConsoleWindow.xaml
Normal file
@@ -0,0 +1,20 @@
|
||||
<userInterface:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:userInterface="clr-namespace:Content.Client.UserInterface"
|
||||
Title="{Loc 'iff-console-window-title'}"
|
||||
MinSize="200 200">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<GridContainer Margin="4 0 0 0" Columns="2">
|
||||
<Label Name="ShowIFFLabel" Text="{Loc 'iff-console-show-iff-label'}" HorizontalExpand="True" StyleClasses="StatusFieldTitle" />
|
||||
<BoxContainer Orientation="Horizontal" MinWidth="120">
|
||||
<Button Name="ShowIFFOnButton" Text="{Loc 'iff-console-on'}" StyleClasses="OpenRight" />
|
||||
<Button Name="ShowIFFOffButton" Text="{Loc 'iff-console-off'}" StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
|
||||
<Label Name="ShowVesselLabel" Text="{Loc 'iff-console-show-vessel-label'}" HorizontalExpand="True" StyleClasses="StatusFieldTitle" />
|
||||
<BoxContainer Orientation="Horizontal" MinWidth="120">
|
||||
<Button Name="ShowVesselOnButton" Text="{Loc 'iff-console-on'}" StyleClasses="OpenRight" />
|
||||
<Button Name="ShowVesselOffButton" Text="{Loc 'iff-console-off'}" StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
</GridContainer>
|
||||
</BoxContainer>
|
||||
</userInterface:FancyWindow>
|
||||
86
Content.Client/Shuttles/UI/IFFConsoleWindow.xaml.cs
Normal file
86
Content.Client/Shuttles/UI/IFFConsoleWindow.xaml.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using Content.Client.Computer;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Shared.Shuttles.BUIStates;
|
||||
using Content.Shared.Shuttles.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Shuttles.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class IFFConsoleWindow : FancyWindow,
|
||||
IComputerWindow<IFFConsoleBoundUserInterfaceState>
|
||||
{
|
||||
private readonly ButtonGroup _showIFFButtonGroup = new();
|
||||
private readonly ButtonGroup _showVesselButtonGroup = new();
|
||||
public event Action<bool>? ShowIFF;
|
||||
public event Action<bool>? ShowVessel;
|
||||
|
||||
public IFFConsoleWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
ShowIFFOffButton.Group = _showIFFButtonGroup;
|
||||
ShowIFFOnButton.Group = _showIFFButtonGroup;
|
||||
ShowIFFOnButton.OnPressed += args => ShowIFFPressed(true);
|
||||
ShowIFFOffButton.OnPressed += args => ShowIFFPressed(false);
|
||||
|
||||
ShowVesselOffButton.Group = _showVesselButtonGroup;
|
||||
ShowVesselOnButton.Group = _showVesselButtonGroup;
|
||||
ShowVesselOnButton.OnPressed += args => ShowVesselPressed(true);
|
||||
ShowVesselOffButton.OnPressed += args => ShowVesselPressed(false);
|
||||
}
|
||||
|
||||
private void ShowIFFPressed(bool pressed)
|
||||
{
|
||||
ShowIFF?.Invoke(pressed);
|
||||
}
|
||||
|
||||
private void ShowVesselPressed(bool pressed)
|
||||
{
|
||||
ShowVessel?.Invoke(pressed);
|
||||
}
|
||||
|
||||
public void UpdateState(IFFConsoleBoundUserInterfaceState state)
|
||||
{
|
||||
if ((state.AllowedFlags & IFFFlags.HideLabel) != 0x0)
|
||||
{
|
||||
ShowIFFOffButton.Disabled = false;
|
||||
ShowIFFOnButton.Disabled = false;
|
||||
|
||||
if ((state.Flags & IFFFlags.HideLabel) != 0x0)
|
||||
{
|
||||
ShowIFFOffButton.Pressed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowIFFOnButton.Pressed = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowIFFOffButton.Disabled = true;
|
||||
ShowIFFOnButton.Disabled = true;
|
||||
}
|
||||
|
||||
if ((state.AllowedFlags & IFFFlags.Hide) != 0x0)
|
||||
{
|
||||
ShowVesselOffButton.Disabled = false;
|
||||
ShowVesselOnButton.Disabled = false;
|
||||
|
||||
if ((state.Flags & IFFFlags.Hide) != 0x0)
|
||||
{
|
||||
ShowVesselOffButton.Pressed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowVesselOnButton.Pressed = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowVesselOffButton.Disabled = true;
|
||||
ShowVesselOnButton.Disabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Shuttles.BUIStates;
|
||||
using Content.Shared.Shuttles.Components;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
@@ -200,7 +201,8 @@ public sealed class RadarControl : Control
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapPosition.MapId,
|
||||
new Box2(mapPosition.Position - MaxRadarRange, mapPosition.Position + MaxRadarRange)))
|
||||
{
|
||||
if (grid.GridEntityId == ourGridId) continue;
|
||||
if (grid.GridEntityId == ourGridId)
|
||||
continue;
|
||||
|
||||
var gridBody = bodyQuery.GetComponent(grid.GridEntityId);
|
||||
if (gridBody.Mass < 10f)
|
||||
@@ -209,6 +211,15 @@ public sealed class RadarControl : Control
|
||||
continue;
|
||||
}
|
||||
|
||||
_entManager.TryGetComponent<IFFComponent>(grid.GridEntityId, out var iff);
|
||||
|
||||
// Hide it entirely.
|
||||
if (iff != null &&
|
||||
(iff.Flags & IFFFlags.Hide) != 0x0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
shown.Add(grid.GridEntityId);
|
||||
var name = metaQuery.GetComponent(grid.GridEntityId).EntityName;
|
||||
|
||||
@@ -219,9 +230,13 @@ public sealed class RadarControl : Control
|
||||
var gridFixtures = fixturesQuery.GetComponent(grid.GridEntityId);
|
||||
var gridMatrix = gridXform.WorldMatrix;
|
||||
Matrix3.Multiply(in gridMatrix, in offsetMatrix, out var matty);
|
||||
var color = iff?.Color ?? IFFComponent.IFFColor;
|
||||
|
||||
if (ShowIFF)
|
||||
if (ShowIFF &&
|
||||
(iff == null && IFFComponent.ShowIFFDefault ||
|
||||
(iff.Flags & IFFFlags.HideLabel) == 0x0))
|
||||
{
|
||||
var gridBounds = grid.LocalAABB;
|
||||
Label label;
|
||||
|
||||
if (!_iffControls.TryGetValue(grid.GridEntityId, out var control))
|
||||
@@ -229,37 +244,35 @@ public sealed class RadarControl : Control
|
||||
label = new Label()
|
||||
{
|
||||
HorizontalAlignment = HAlignment.Left,
|
||||
FontColorOverride = Color.Aquamarine,
|
||||
};
|
||||
|
||||
control = new PanelContainer()
|
||||
{
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
Children =
|
||||
{
|
||||
label
|
||||
},
|
||||
StyleClasses = { StyleNano.StyleClassBorderedWindowPanel },
|
||||
};
|
||||
|
||||
_iffControls[grid.GridEntityId] = control;
|
||||
AddChild(control);
|
||||
_iffControls[grid.GridEntityId] = label;
|
||||
AddChild(label);
|
||||
}
|
||||
else
|
||||
{
|
||||
label = (Label) control;
|
||||
}
|
||||
|
||||
label.FontColorOverride = color;
|
||||
var gridCentre = matty.Transform(gridBody.LocalCenter);
|
||||
gridCentre.Y = -gridCentre.Y;
|
||||
var distance = gridCentre.Length;
|
||||
|
||||
if (gridCentre.Length > RadarRange)
|
||||
{
|
||||
gridCentre = gridCentre.Normalized * RadarRange;
|
||||
}
|
||||
// y-offset the control to always render below the grid (vertically)
|
||||
var yOffset = Math.Max(gridBounds.Height, gridBounds.Width) * MinimapScale / 1.8f / UIScale;
|
||||
|
||||
control.Visible = true;
|
||||
label = (Label) control.GetChild(0);
|
||||
// The actual position in the UI. We offset the matrix position to render it off by half its width
|
||||
// plus by the offset.
|
||||
var uiPosition = ScalePosition(gridCentre) / UIScale - new Vector2(label.Width / 2f, -yOffset);
|
||||
|
||||
// Look this is uggo so feel free to cleanup. We just need to clamp the UI position to within the viewport.
|
||||
uiPosition = new Vector2(Math.Clamp(uiPosition.X, 0f, Width - label.Width),
|
||||
Math.Clamp(uiPosition.Y, 10f, Height - label.Height));
|
||||
|
||||
label.Visible = true;
|
||||
label.Text = Loc.GetString("shuttle-console-iff-label", ("name", name), ("distance", $"{distance:0.0}"));
|
||||
LayoutContainer.SetPosition(control, ScalePosition(gridCentre) / UIScale);
|
||||
LayoutContainer.SetPosition(label, uiPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -267,7 +280,7 @@ public sealed class RadarControl : Control
|
||||
}
|
||||
|
||||
// Detailed view
|
||||
DrawGrid(handle, matty, gridFixtures, Color.Aquamarine);
|
||||
DrawGrid(handle, matty, gridFixtures, color);
|
||||
|
||||
DrawDocks(handle, grid.GridEntityId, matty);
|
||||
}
|
||||
|
||||
58
Content.Client/Sprite/RandomSpriteSystem.cs
Normal file
58
Content.Client/Sprite/RandomSpriteSystem.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using Content.Shared.Sprite;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Reflection;
|
||||
|
||||
namespace Content.Client.Sprite;
|
||||
|
||||
public sealed class RandomSpriteSystem : SharedRandomSpriteSystem
|
||||
{
|
||||
[Dependency] private readonly IReflectionManager _reflection = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<RandomSpriteComponent, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, RandomSpriteComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not RandomSpriteColorComponentState state)
|
||||
return;
|
||||
|
||||
if (state.Selected.Equals(component.Selected))
|
||||
return;
|
||||
|
||||
component.Selected.Clear();
|
||||
component.Selected.EnsureCapacity(state.Selected.Count);
|
||||
|
||||
foreach (var layer in state.Selected)
|
||||
{
|
||||
component.Selected.Add(layer.Key, layer.Value);
|
||||
}
|
||||
|
||||
UpdateAppearance(uid, component);
|
||||
}
|
||||
|
||||
private void UpdateAppearance(EntityUid uid, RandomSpriteComponent component, SpriteComponent? sprite = null)
|
||||
{
|
||||
if (!Resolve(uid, ref sprite, false))
|
||||
return;
|
||||
|
||||
foreach (var layer in component.Selected)
|
||||
{
|
||||
object key;
|
||||
if (_reflection.TryParseEnumReference(layer.Key, out var @enum))
|
||||
{
|
||||
key = @enum;
|
||||
}
|
||||
else
|
||||
{
|
||||
key = layer.Key;
|
||||
}
|
||||
|
||||
sprite.LayerSetState(key, layer.Value.State);
|
||||
sprite.LayerSetColor(key, layer.Value.Color ?? Color.White);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.StationRecords;
|
||||
|
||||
public sealed class GeneralStationRecordConsoleBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private GeneralStationRecordConsoleWindow? _window = default!;
|
||||
|
||||
public GeneralStationRecordConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||
{}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = new();
|
||||
_window.OnKeySelected += OnKeySelected;
|
||||
_window.OnClose += Close;
|
||||
|
||||
_window.OpenCentered();
|
||||
}
|
||||
|
||||
private void OnKeySelected(StationRecordKey? key)
|
||||
{
|
||||
SendMessage(new SelectGeneralStationRecord(key));
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (state is not GeneralStationRecordConsoleState cast)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_window?.UpdateState(cast);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
_window?.Close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc 'general-station-record-console-window-title'}"
|
||||
MinSize="750 500">
|
||||
<BoxContainer>
|
||||
<!-- Record listing -->
|
||||
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" MinWidth="250" VerticalExpand="True">
|
||||
<Label Name="RecordListingStatus" Visible="False" />
|
||||
<ScrollContainer VerticalExpand="True">
|
||||
<ItemList Name="RecordListing" />
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical" Margin="5 5 5 5">
|
||||
<Label Name="RecordContainerStatus" Visible="False" Text="{Loc 'general-station-record-console-select-record-info'}"/>
|
||||
<BoxContainer Name="RecordContainer" Orientation="Vertical" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
@@ -0,0 +1,126 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.StationRecords;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GeneralStationRecordConsoleWindow : DefaultWindow
|
||||
{
|
||||
public Action<StationRecordKey?>? OnKeySelected;
|
||||
private bool _isPopulating;
|
||||
|
||||
public GeneralStationRecordConsoleWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
RecordListing.OnItemSelected += args =>
|
||||
{
|
||||
if (_isPopulating || RecordListing[args.ItemIndex].Metadata is not StationRecordKey cast)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OnKeySelected?.Invoke(cast);
|
||||
};
|
||||
|
||||
RecordListing.OnItemDeselected += _ =>
|
||||
{
|
||||
if (!_isPopulating)
|
||||
OnKeySelected?.Invoke(null);
|
||||
};
|
||||
}
|
||||
|
||||
public void UpdateState(GeneralStationRecordConsoleState state)
|
||||
{
|
||||
if (state.RecordListing == null)
|
||||
{
|
||||
RecordListingStatus.Visible = true;
|
||||
RecordListing.Visible = false;
|
||||
RecordListingStatus.Text = Loc.GetString("general-station-record-console-empty-state");
|
||||
return;
|
||||
}
|
||||
|
||||
RecordListingStatus.Visible = false;
|
||||
RecordListing.Visible = true;
|
||||
PopulateRecordListing(state.RecordListing!, state.SelectedKey);
|
||||
|
||||
RecordContainerStatus.Visible = state.Record == null;
|
||||
|
||||
if (state.Record != null)
|
||||
{
|
||||
RecordContainerStatus.Visible = state.SelectedKey == null;
|
||||
RecordContainerStatus.Text = state.SelectedKey == null
|
||||
? Loc.GetString("general-station-record-console-no-record-found")
|
||||
: Loc.GetString("general-station-record-console-select-record-info");
|
||||
PopulateRecordContainer(state.Record);
|
||||
}
|
||||
else
|
||||
{
|
||||
RecordContainer.DisposeAllChildren();
|
||||
RecordContainer.RemoveAllChildren();
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateRecordListing(Dictionary<StationRecordKey, string> listing, StationRecordKey? selected)
|
||||
{
|
||||
RecordListing.Clear();
|
||||
RecordListing.ClearSelected();
|
||||
|
||||
_isPopulating = true;
|
||||
foreach (var (key, name) in listing)
|
||||
{
|
||||
var item = RecordListing.AddItem(name);
|
||||
item.Metadata = key;
|
||||
|
||||
if (selected != null && key.ID == selected.Value.ID)
|
||||
{
|
||||
item.Selected = true;
|
||||
}
|
||||
}
|
||||
_isPopulating = false;
|
||||
|
||||
RecordListing.SortItemsByText();
|
||||
}
|
||||
|
||||
private void PopulateRecordContainer(GeneralStationRecord record)
|
||||
{
|
||||
RecordContainer.DisposeAllChildren();
|
||||
RecordContainer.RemoveAllChildren();
|
||||
// sure
|
||||
var recordControls = new Control[]
|
||||
{
|
||||
new Label()
|
||||
{
|
||||
Text = record.Name,
|
||||
StyleClasses = { "LabelBig" }
|
||||
},
|
||||
new Label()
|
||||
{
|
||||
Text = Loc.GetString("general-station-record-console-record-age", ("age", record.Age.ToString()))
|
||||
|
||||
},
|
||||
new Label()
|
||||
{
|
||||
Text = Loc.GetString("general-station-record-console-record-title", ("job", Loc.GetString(record.JobTitle)))
|
||||
},
|
||||
new Label()
|
||||
{
|
||||
Text = Loc.GetString("general-station-record-console-record-species", ("species", record.Species))
|
||||
},
|
||||
new Label()
|
||||
{
|
||||
Text = Loc.GetString("general-station-record-console-record-gender", ("gender", record.Gender.ToString()))
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var control in recordControls)
|
||||
{
|
||||
RecordContainer.AddChild(control);
|
||||
}
|
||||
}
|
||||
}
|
||||
65
Content.Client/Storage/Systems/ItemMapperSystem.cs
Normal file
65
Content.Client/Storage/Systems/ItemMapperSystem.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Storage.Components;
|
||||
using Content.Shared.Storage.EntitySystems;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Storage.Systems;
|
||||
|
||||
public sealed class ItemMapperSystem : SharedItemMapperSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<ItemMapperComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<ItemMapperComponent, AppearanceChangeEvent>(OnAppearance);
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, ItemMapperComponent component, ComponentStartup args)
|
||||
{
|
||||
if (TryComp<SpriteComponent>(uid, out var sprite))
|
||||
{
|
||||
component.RSIPath ??= sprite.BaseRSI!.Path!;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAppearance(EntityUid uid, ItemMapperComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (TryComp<SpriteComponent>(component.Owner, out var spriteComponent))
|
||||
{
|
||||
if (component.SpriteLayers.Count == 0)
|
||||
{
|
||||
InitLayers(component, spriteComponent, args.Component);
|
||||
}
|
||||
|
||||
EnableLayers(component, spriteComponent, args.Component);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitLayers(ItemMapperComponent component, SpriteComponent spriteComponent, AppearanceComponent appearance)
|
||||
{
|
||||
if (!appearance.TryGetData<ShowLayerData>(StorageMapVisuals.InitLayers, out var wrapper))
|
||||
return;
|
||||
|
||||
component.SpriteLayers.AddRange(wrapper.QueuedEntities);
|
||||
|
||||
foreach (var sprite in component.SpriteLayers)
|
||||
{
|
||||
spriteComponent.LayerMapReserveBlank(sprite);
|
||||
spriteComponent.LayerSetSprite(sprite, new SpriteSpecifier.Rsi(component.RSIPath!, sprite));
|
||||
spriteComponent.LayerSetVisible(sprite, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnableLayers(ItemMapperComponent component, SpriteComponent spriteComponent, AppearanceComponent appearance)
|
||||
{
|
||||
if (!appearance.TryGetData<ShowLayerData>(StorageMapVisuals.LayerChanged, out var wrapper))
|
||||
return;
|
||||
|
||||
foreach (var layerName in component.SpriteLayers)
|
||||
{
|
||||
var show = wrapper.QueuedEntities.Contains(layerName);
|
||||
spriteComponent.LayerSetVisible(layerName, show);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using Content.Shared.Storage;
|
||||
using Content.Client.Animations;
|
||||
using Content.Client.Animations;
|
||||
using Content.Shared.Storage;
|
||||
|
||||
namespace Content.Client.Storage;
|
||||
namespace Content.Client.Storage.Systems;
|
||||
|
||||
// TODO kill this is all horrid.
|
||||
public sealed class StorageSystem : EntitySystem
|
||||
@@ -1,74 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.Storage.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Storage.Visualizers
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class MappedItemVisualizer : AppearanceVisualizer
|
||||
{
|
||||
[DataField("sprite")] private ResourcePath? _rsiPath;
|
||||
private List<string> _spriteLayers = new();
|
||||
|
||||
public override void InitializeEntity(EntityUid entity)
|
||||
{
|
||||
base.InitializeEntity(entity);
|
||||
|
||||
if (IoCManager.Resolve<IEntityManager>().TryGetComponent<ISpriteComponent?>(entity, out var spriteComponent))
|
||||
{
|
||||
_rsiPath ??= spriteComponent.BaseRSI!.Path!;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
var entities = IoCManager.Resolve<IEntityManager>();
|
||||
if (entities.TryGetComponent(component.Owner, out ISpriteComponent? spriteComponent))
|
||||
{
|
||||
if (_spriteLayers.Count == 0)
|
||||
{
|
||||
InitLayers(spriteComponent, component);
|
||||
}
|
||||
|
||||
EnableLayers(spriteComponent, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitLayers(ISpriteComponent spriteComponent, AppearanceComponent component)
|
||||
{
|
||||
if (!component.TryGetData<ShowLayerData>(StorageMapVisuals.InitLayers, out var wrapper))
|
||||
return;
|
||||
|
||||
_spriteLayers.AddRange(wrapper.QueuedEntities);
|
||||
|
||||
foreach (var sprite in _spriteLayers)
|
||||
{
|
||||
spriteComponent.LayerMapReserveBlank(sprite);
|
||||
spriteComponent.LayerSetSprite(sprite, new SpriteSpecifier.Rsi(_rsiPath!, sprite));
|
||||
spriteComponent.LayerSetVisible(sprite, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnableLayers(ISpriteComponent spriteComponent, AppearanceComponent component)
|
||||
{
|
||||
if (!component.TryGetData<ShowLayerData>(StorageMapVisuals.LayerChanged, out var wrapper))
|
||||
return;
|
||||
|
||||
|
||||
foreach (var layerName in _spriteLayers)
|
||||
{
|
||||
var show = wrapper.QueuedEntities.Contains(layerName);
|
||||
spriteComponent.LayerSetVisible(layerName, show);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using static Content.Shared.Foldable.SharedFoldableSystem;
|
||||
|
||||
namespace Content.Client.Visualizer;
|
||||
|
||||
@@ -19,7 +20,7 @@ public sealed class FoldableVisualizer : AppearanceVisualizer
|
||||
|
||||
if (!entManager.TryGetComponent(appearance.Owner, out SpriteComponent? sprite)) return;
|
||||
|
||||
if (appearance.TryGetData("FoldedState", out bool folded) && folded)
|
||||
if (appearance.TryGetData(FoldedVisuals.State, out bool folded) && folded)
|
||||
{
|
||||
sprite.LayerSetState(FoldableVisualLayers.Base, $"{_key}_folded");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using JetBrains.Annotations;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -20,7 +21,7 @@ namespace Content.Client.Visualizer
|
||||
|
||||
if (!entManager.TryGetComponent(appearance.Owner, out SpriteComponent? sprite)) return;
|
||||
|
||||
if (appearance.TryGetData("StrapState", out bool strapped) && strapped)
|
||||
if (appearance.TryGetData(StrapVisuals.State, out bool strapped) && strapped)
|
||||
{
|
||||
sprite.LayerSetState(0, $"{_key}_buckled");
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
base.Initialize();
|
||||
UpdatesOutsidePrediction = true;
|
||||
SubscribeLocalEvent<AmmoCounterComponent, ItemStatusCollectMessage>(OnAmmoCounterCollect);
|
||||
SubscribeLocalEvent<GunComponent, MuzzleFlashEvent>(OnMuzzleFlash);
|
||||
SubscribeAllEvent<MuzzleFlashEvent>(OnMuzzleFlash);
|
||||
|
||||
// Plays animated effects on the client.
|
||||
SubscribeNetworkEvent<HitscanEvent>(OnHitscan);
|
||||
@@ -72,9 +72,9 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
InitializeSpentAmmo();
|
||||
}
|
||||
|
||||
private void OnMuzzleFlash(EntityUid uid, GunComponent component, MuzzleFlashEvent args)
|
||||
private void OnMuzzleFlash(MuzzleFlashEvent args)
|
||||
{
|
||||
CreateEffect(uid, args);
|
||||
CreateEffect(args.Uid, args);
|
||||
}
|
||||
|
||||
private void OnHitscan(HitscanEvent ev)
|
||||
|
||||
@@ -436,7 +436,7 @@ public static class PoolManager
|
||||
mapData.MapGrid = mapManager.CreateGrid(mapData.MapId);
|
||||
mapData.GridCoords = new EntityCoordinates(mapData.MapGrid.GridEntityId, 0, 0);
|
||||
var tileDefinitionManager = IoCManager.Resolve<ITileDefinitionManager>();
|
||||
var plating = tileDefinitionManager["plating"];
|
||||
var plating = tileDefinitionManager["Plating"];
|
||||
var platingTile = new Tile(plating.TileId);
|
||||
mapData.MapGrid.SetTile(mapData.GridCoords, platingTile);
|
||||
mapData.MapCoords = new MapCoordinates(0, 0, mapData.MapId);
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
grid = mapManager.CreateGrid(mapId);
|
||||
|
||||
var tileDefinition = tileDefinitionManager["underplating"];
|
||||
var tileDefinition = tileDefinitionManager["UnderPlating"];
|
||||
var tile = new Tile(tileDefinition.TileId);
|
||||
var coordinates = grid.ToCoordinates();
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
grid = mapManager.CreateGrid(mapId);
|
||||
|
||||
var tileDefinition = tileDefinitionManager["underplating"];
|
||||
var tileDefinition = tileDefinitionManager["UnderPlating"];
|
||||
var tile = new Tile(tileDefinition.TileId);
|
||||
var coordinates = grid.ToCoordinates();
|
||||
|
||||
@@ -223,7 +223,7 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
grid = mapManager.CreateGrid(mapId);
|
||||
|
||||
var tileDefinition = tileDefinitionManager["underplating"];
|
||||
var tileDefinition = tileDefinitionManager["UnderPlating"];
|
||||
var tile = new Tile(tileDefinition.TileId);
|
||||
|
||||
grid.SetTile(Vector2i.Zero, tile);
|
||||
|
||||
@@ -106,7 +106,7 @@ namespace Content.IntegrationTests.Tests.Fluids
|
||||
sGridId = sGrid.GridEntityId;
|
||||
metaSystem.SetEntityPaused(sGridId, true); // See https://github.com/space-wizards/RobustToolbox/issues/1444
|
||||
|
||||
var tileDefinition = sTileDefinitionManager["underplating"];
|
||||
var tileDefinition = sTileDefinitionManager["UnderPlating"];
|
||||
var tile = new Tile(tileDefinition.TileId);
|
||||
sCoordinates = sGrid.ToCoordinates();
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Map;
|
||||
@@ -41,10 +42,15 @@ namespace Content.MapRenderer.Painters
|
||||
|
||||
grid.GetAllTiles().AsParallel().ForAll(tile =>
|
||||
{
|
||||
var sprite = _sTileDefinitionManager[tile.Tile.TypeId].Sprite;
|
||||
|
||||
if (sprite == null)
|
||||
return;
|
||||
|
||||
var x = (int) (tile.X + xOffset);
|
||||
var y = (int) (tile.Y + yOffset);
|
||||
var sprite = _sTileDefinitionManager[tile.Tile.TypeId].SpriteName;
|
||||
var image = images[sprite][tile.Tile.Variant];
|
||||
var path = sprite.ToString();
|
||||
var image = images[path][tile.Tile.Variant];
|
||||
|
||||
gridCanvas.Mutate(o => o.DrawImage(image, new Point(x * tileSize, y * tileSize), 1));
|
||||
|
||||
@@ -66,15 +72,15 @@ namespace Content.MapRenderer.Painters
|
||||
|
||||
foreach (var definition in tileDefinitionManager)
|
||||
{
|
||||
var sprite = definition.SpriteName;
|
||||
images[sprite] = new List<Image>(definition.Variants);
|
||||
var sprite = definition.Sprite;
|
||||
|
||||
if (string.IsNullOrEmpty(sprite))
|
||||
{
|
||||
if (sprite == null)
|
||||
continue;
|
||||
}
|
||||
|
||||
using var stream = resourceCache.ContentFileRead($"{TilesPath}{sprite}.png");
|
||||
var path = sprite.ToString();
|
||||
images[path] = new List<Image>(definition.Variants);
|
||||
|
||||
using var stream = resourceCache.ContentFileRead(path);
|
||||
Image tileSheet = Image.Load<Rgba32>(stream);
|
||||
|
||||
if (tileSheet.Width != tileSize * definition.Variants || tileSheet.Height != tileSize)
|
||||
@@ -85,7 +91,7 @@ namespace Content.MapRenderer.Painters
|
||||
for (var i = 0; i < definition.Variants; i++)
|
||||
{
|
||||
var tileImage = tileSheet.Clone(o => o.Crop(new Rectangle(tileSize * i, 0, 32, 32)));
|
||||
images[sprite].Add(tileImage);
|
||||
images[path].Add(tileImage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Access.Systems;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.StationRecords;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.StationRecords;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
@@ -91,6 +93,25 @@ namespace Content.Server.Access.Components
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{_entities.ToPrettyString(player):player} has modified {_entities.ToPrettyString(targetIdEntity):entity} with the following accesses: [{string.Join(", ", newAccessList)}]");
|
||||
|
||||
UpdateStationRecord(targetIdEntity, newFullName, newJobTitle);
|
||||
}
|
||||
|
||||
private void UpdateStationRecord(EntityUid idCard, string newFullName, string newJobTitle)
|
||||
{
|
||||
var station = EntitySystem.Get<StationSystem>().GetOwningStation(Owner);
|
||||
var recordSystem = EntitySystem.Get<StationRecordsSystem>();
|
||||
if (station == null
|
||||
|| !_entities.TryGetComponent(idCard, out StationRecordKeyStorageComponent? keyStorage)
|
||||
|| keyStorage.Key == null
|
||||
|| !recordSystem.TryGetRecord(station.Value, keyStorage.Key.Value, out GeneralStationRecord? record))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
record.Name = newFullName;
|
||||
record.JobTitle = newJobTitle;
|
||||
|
||||
recordSystem.Synchronize(station.Value);
|
||||
}
|
||||
|
||||
public void UpdateUserInterface()
|
||||
|
||||
@@ -188,8 +188,14 @@ namespace Content.Server.Atmos.Miasma
|
||||
|
||||
private void OnExamined(EntityUid uid, PerishableComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (component.Rotting)
|
||||
args.PushMarkup(Loc.GetString("miasma-rotting"));
|
||||
if (!component.Rotting)
|
||||
return;
|
||||
var stage = component.DeathAccumulator / component.RotAfter.TotalSeconds;
|
||||
var description = stage switch {
|
||||
>= 3 => "miasma-extremely-bloated",
|
||||
>= 2 => "miasma-bloated",
|
||||
_ => "miasma-rotting"};
|
||||
args.PushMarkup(Loc.GetString(description));
|
||||
}
|
||||
|
||||
/// Containers
|
||||
@@ -204,7 +210,7 @@ namespace Content.Server.Atmos.Miasma
|
||||
}
|
||||
private void OnEntRemoved(EntityUid uid, AntiRottingContainerComponent component, EntRemovedFromContainerMessage args)
|
||||
{
|
||||
if (TryComp<PerishableComponent>(args.Entity, out var perishable))
|
||||
if (TryComp<PerishableComponent>(args.Entity, out var perishable) && !Terminating(uid))
|
||||
{
|
||||
ModifyPreservationSource(args.Entity, false);
|
||||
ToggleDecomposition(args.Entity, true, perishable);
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
coords = coords.Offset(rotPos);
|
||||
transform.Coordinates = coords;
|
||||
|
||||
appearance.SetData("offset", - new Vector2i(0, -1));
|
||||
appearance.SetData(AtmosMonitorVisuals.Offset, - new Vector2i(0, -1));
|
||||
|
||||
transform.Anchored = true;
|
||||
}
|
||||
@@ -192,7 +192,7 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
if (component.DisplayMaxAlarmInNet)
|
||||
{
|
||||
if (EntityManager.TryGetComponent(component.Owner, out AppearanceComponent? appearanceComponent))
|
||||
appearanceComponent.SetData("alarmType", component.HighestAlarmInNetwork);
|
||||
appearanceComponent.SetData(AtmosMonitorVisuals.AlarmType, component.HighestAlarmInNetwork);
|
||||
|
||||
if (component.HighestAlarmInNetwork == AtmosMonitorAlarmType.Danger) PlayAlertSound(uid, component);
|
||||
}
|
||||
@@ -227,10 +227,7 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
}
|
||||
|
||||
if (EntityManager.TryGetComponent(component.Owner, out AppearanceComponent? appearanceComponent))
|
||||
{
|
||||
appearanceComponent.SetData("powered", args.Powered);
|
||||
appearanceComponent.SetData("alarmType", component.LastAlarmState);
|
||||
}
|
||||
appearanceComponent.SetData(AtmosMonitorVisuals.AlarmType, component.LastAlarmState);
|
||||
}
|
||||
|
||||
private void OnFireEvent(EntityUid uid, AtmosMonitorComponent component, ref TileFireEvent args)
|
||||
@@ -341,7 +338,7 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
if (!Resolve(uid, ref monitor)) return;
|
||||
monitor.LastAlarmState = state;
|
||||
if (EntityManager.TryGetComponent(monitor.Owner, out AppearanceComponent? appearanceComponent))
|
||||
appearanceComponent.SetData("alarmType", monitor.LastAlarmState);
|
||||
appearanceComponent.SetData(AtmosMonitorVisuals.AlarmType, monitor.LastAlarmState);
|
||||
|
||||
BroadcastAlertPacket(monitor, alarms);
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using Content.Shared.Atmos;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Trinary.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class PressureControlledValveComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("inlet")]
|
||||
public string InletName { get; set; } = "inlet";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("control")]
|
||||
public string ControlName { get; set; } = "control";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("outlet")]
|
||||
public string OutletName { get; set; } = "outlet";
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
[DataField("enabled")]
|
||||
public bool Enabled { get; set; } = false;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("gain")]
|
||||
public float Gain { get; set; } = 10;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("threshold")]
|
||||
public float Threshold { get; set; } = Atmospherics.OneAtmosphere;
|
||||
|
||||
[DataField("maxTransferRate")]
|
||||
public float MaxTransferRate { get; set; } = Atmospherics.MaxTransferRate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.Atmos.Piping.Trinary.Components;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.Atmos.Piping;
|
||||
using Content.Shared.Audio;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Trinary.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class PressureControlledValveSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
[Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<PressureControlledValveComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<PressureControlledValveComponent, AtmosDeviceUpdateEvent>(OnUpdate);
|
||||
SubscribeLocalEvent<PressureControlledValveComponent, AtmosDeviceDisabledEvent>(OnFilterLeaveAtmosphere);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, PressureControlledValveComponent comp, ComponentInit args)
|
||||
{
|
||||
UpdateAppearance(uid, comp);
|
||||
}
|
||||
|
||||
private void OnUpdate(EntityUid uid, PressureControlledValveComponent comp, AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)
|
||||
|| !EntityManager.TryGetComponent(uid, out AtmosDeviceComponent? device)
|
||||
|| !nodeContainer.TryGetNode(comp.InletName, out PipeNode? inletNode)
|
||||
|| !nodeContainer.TryGetNode(comp.ControlName, out PipeNode? controlNode)
|
||||
|| !nodeContainer.TryGetNode(comp.OutletName, out PipeNode? outletNode))
|
||||
{
|
||||
_ambientSoundSystem.SetAmbience(comp.Owner, false);
|
||||
comp.Enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// If output is higher than input, flip input/output to enable bidirectional flow.
|
||||
if (outletNode.Air.Pressure > inletNode.Air.Pressure)
|
||||
{
|
||||
PipeNode temp = outletNode;
|
||||
outletNode = inletNode;
|
||||
inletNode = temp;
|
||||
}
|
||||
|
||||
float control = (controlNode.Air.Pressure - outletNode.Air.Pressure) - comp.Threshold;
|
||||
float transferRate;
|
||||
if (control < 0)
|
||||
{
|
||||
comp.Enabled = false;
|
||||
transferRate = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
comp.Enabled = true;
|
||||
transferRate = Math.Min(control * comp.Gain, comp.MaxTransferRate);
|
||||
}
|
||||
UpdateAppearance(uid, comp);
|
||||
|
||||
// We multiply the transfer rate in L/s by the seconds passed since the last process to get the liters.
|
||||
var transferRatio = (float)(transferRate * (_gameTiming.CurTime - device.LastProcess).TotalSeconds) / inletNode.Air.Volume;
|
||||
if (transferRatio <= 0)
|
||||
{
|
||||
_ambientSoundSystem.SetAmbience(comp.Owner, false);
|
||||
return;
|
||||
}
|
||||
|
||||
_ambientSoundSystem.SetAmbience(comp.Owner, true);
|
||||
var removed = inletNode.Air.RemoveRatio(transferRatio);
|
||||
_atmosphereSystem.Merge(outletNode.Air, removed);
|
||||
}
|
||||
|
||||
private void OnFilterLeaveAtmosphere(EntityUid uid, PressureControlledValveComponent comp, AtmosDeviceDisabledEvent args)
|
||||
{
|
||||
comp.Enabled = false;
|
||||
UpdateAppearance(uid, comp);
|
||||
_ambientSoundSystem.SetAmbience(comp.Owner, false);
|
||||
}
|
||||
|
||||
private void UpdateAppearance(EntityUid uid, PressureControlledValveComponent? comp = null, AppearanceComponent? appearance = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp, ref appearance, false))
|
||||
return;
|
||||
|
||||
appearance.SetData(FilterVisuals.Enabled, comp.Enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.Atmos.Piping.Unary.Components;
|
||||
using Content.Server.Cargo.Systems;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
@@ -23,6 +24,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly PricingSystem _pricing = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -36,6 +38,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
SubscribeLocalEvent<GasCanisterComponent, InteractUsingEvent>(OnCanisterInteractUsing);
|
||||
SubscribeLocalEvent<GasCanisterComponent, EntInsertedIntoContainerMessage>(OnCanisterContainerInserted);
|
||||
SubscribeLocalEvent<GasCanisterComponent, EntRemovedFromContainerMessage>(OnCanisterContainerRemoved);
|
||||
SubscribeLocalEvent<GasCanisterComponent, PriceCalculationEvent>(CalculateCanisterPrice);
|
||||
// Bound UI subscriptions
|
||||
SubscribeLocalEvent<GasCanisterComponent, GasCanisterHoldingTankEjectMessage>(OnHoldingTankEjectMessage);
|
||||
SubscribeLocalEvent<GasCanisterComponent, GasCanisterChangeReleasePressureMessage>(OnCanisterChangeReleasePressure);
|
||||
@@ -292,5 +295,25 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
_atmosphereSystem.Merge(containerAir, buffer);
|
||||
containerAir.Multiply(containerAir.Volume / buffer.Volume);
|
||||
}
|
||||
|
||||
private void CalculateCanisterPrice(EntityUid uid, GasCanisterComponent component, ref PriceCalculationEvent args)
|
||||
{
|
||||
float basePrice = 0; // moles of gas * price/mole
|
||||
float totalMoles = 0; // total number of moles in can
|
||||
float maxComponent = 0; // moles of the dominant gas
|
||||
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
||||
{
|
||||
basePrice += component.Air.Moles[i] * _atmosphereSystem.GetGas(i).PricePerMole;
|
||||
totalMoles += component.Air.Moles[i];
|
||||
maxComponent = Math.Max(maxComponent, component.Air.Moles[i]);
|
||||
}
|
||||
|
||||
// Pay more for gas canisters that are more pure
|
||||
float purity = 1;
|
||||
if (totalMoles > 0) {
|
||||
purity = maxComponent / totalMoles;
|
||||
}
|
||||
args.Price += basePrice * purity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
8
Content.Server/Audio/ContentAudioSystem.cs
Normal file
8
Content.Server/Audio/ContentAudioSystem.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Audio;
|
||||
|
||||
public sealed class ContentAudioSystem : SharedContentAudioSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.Botany.Components;
|
||||
using Content.Server.Botany.Systems;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
@@ -63,7 +64,7 @@ public struct SeedChemQuantity
|
||||
|
||||
// TODO reduce the number of friends to a reasonable level. Requires ECS-ing things like plant holder component.
|
||||
[Virtual, DataDefinition]
|
||||
[Access(typeof(BotanySystem), typeof(PlantHolderSystem), typeof(SeedExtractorSystem), typeof(PlantHolderComponent))]
|
||||
[Access(typeof(BotanySystem), typeof(PlantHolderSystem), typeof(SeedExtractorSystem), typeof(PlantHolderComponent), typeof(ReagentEffect))]
|
||||
public class SeedData
|
||||
{
|
||||
#region Tracking
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace Content.Server.Buckle.Components
|
||||
// Update the visuals of the strap object
|
||||
if (IoCManager.Resolve<IEntityManager>().TryGetComponent<AppearanceComponent>(Owner, out var appearance))
|
||||
{
|
||||
appearance.SetData("StrapState", true);
|
||||
appearance.SetData(StrapVisuals.State, true);
|
||||
}
|
||||
|
||||
Dirty();
|
||||
@@ -133,7 +133,7 @@ namespace Content.Server.Buckle.Components
|
||||
{
|
||||
if (IoCManager.Resolve<IEntityManager>().TryGetComponent<AppearanceComponent>(Owner, out var appearance))
|
||||
{
|
||||
appearance.SetData("StrapState", false);
|
||||
appearance.SetData(StrapVisuals.State, false);
|
||||
}
|
||||
|
||||
_occupiedSize -= buckle.Size;
|
||||
|
||||
@@ -266,6 +266,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
("entityName", Name(source)));
|
||||
|
||||
SendInVoiceRange(ChatChannel.Local, message, messageWrap, source, hideChat);
|
||||
_listener.PingListeners(source, message, null);
|
||||
|
||||
var ev = new EntitySpokeEvent(message);
|
||||
RaiseLocalEvent(source, ev);
|
||||
|
||||
@@ -18,14 +18,14 @@ namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
|
||||
|
||||
var random = IoCManager.Resolve<IRobustRandom>();
|
||||
|
||||
if (plantHolderComp.Seed.Potency < 100 && random.Prob(0.1f))
|
||||
if (plantHolderComp.Seed.Potency < 100)
|
||||
{
|
||||
plantHolderComp.EnsureUniqueSeed();
|
||||
plantHolderComp.Seed.Potency++;
|
||||
plantHolderComp.Seed.Potency = Math.Min(plantHolderComp.Seed.Potency + 3, 100);
|
||||
}
|
||||
|
||||
if (plantHolderComp.Seed.Yield > 1 && random.Prob(0.1f))
|
||||
else if (plantHolderComp.Seed.Yield > 1 && random.Prob(0.1f))
|
||||
{
|
||||
// Too much of a good thing reduces yield
|
||||
plantHolderComp.EnsureUniqueSeed();
|
||||
plantHolderComp.Seed.Yield--;
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ The ban reason is: ""{ban.Reason}""
|
||||
&& await _db.GetWhitelistStatusAsync(userId) == false
|
||||
&& adminData is null)
|
||||
{
|
||||
return (ConnectionDenyReason.Whitelist, Loc.GetString("whitelist-not-whitelisted"), null);
|
||||
return (ConnectionDenyReason.Whitelist, Loc.GetString(_cfg.GetCVar(CCVars.WhitelistReason)), null);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Coordinates.Helpers;
|
||||
using Content.Server.Popups;
|
||||
@@ -11,6 +10,7 @@ using Content.Shared.Database;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Pulling.Components;
|
||||
using Content.Shared.Tools.Components;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Construction
|
||||
@@ -18,6 +18,7 @@ namespace Content.Server.Construction
|
||||
public sealed class AnchorableSystem : SharedAnchorableSystem
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly ToolSystem _toolSystem = default!;
|
||||
[Dependency] private readonly PullingSystem _pullingSystem = default!;
|
||||
@@ -71,6 +72,12 @@ namespace Content.Server.Construction
|
||||
{
|
||||
component.CancelToken = null;
|
||||
var xform = Transform(uid);
|
||||
if (TryComp<PhysicsComponent>(uid, out var anchorBody) &&
|
||||
!TileFree(xform.Coordinates, anchorBody))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("anchorable-occupied"), uid, Filter.Entities(args.User));
|
||||
return;
|
||||
}
|
||||
|
||||
// Snap rotation to cardinal (multiple of 90)
|
||||
var rot = xform.LocalRotation;
|
||||
@@ -81,8 +88,9 @@ namespace Content.Server.Construction
|
||||
_pullingSystem.TryStopPull(pullable);
|
||||
}
|
||||
|
||||
// TODO: Anchoring snaps rn anyway!
|
||||
if (component.Snap)
|
||||
xform.Coordinates = xform.Coordinates.SnapToGrid();
|
||||
xform.Coordinates = xform.Coordinates.SnapToGrid(EntityManager, _mapManager);
|
||||
|
||||
RaiseLocalEvent(uid, new BeforeAnchoredEvent(args.User, args.Using), false);
|
||||
xform.Anchored = true;
|
||||
@@ -97,6 +105,34 @@ namespace Content.Server.Construction
|
||||
);
|
||||
}
|
||||
|
||||
private bool TileFree(EntityCoordinates coordinates, PhysicsComponent anchorBody)
|
||||
{
|
||||
// Probably ignore CanCollide on the anchoring body?
|
||||
var gridUid = coordinates.GetGridUid(EntityManager);
|
||||
|
||||
if (!_mapManager.TryGetGrid(gridUid, out var grid))
|
||||
return false;
|
||||
|
||||
var tileIndices = grid.TileIndicesFor(coordinates);
|
||||
var enumerator = grid.GetAnchoredEntitiesEnumerator(tileIndices);
|
||||
var bodyQuery = GetEntityQuery<PhysicsComponent>();
|
||||
|
||||
while (enumerator.MoveNext(out var ent))
|
||||
{
|
||||
if (!bodyQuery.TryGetComponent(ent, out var body) ||
|
||||
!body.CanCollide)
|
||||
continue;
|
||||
|
||||
if ((body.CollisionMask & anchorBody.CollisionLayer) != 0x0 ||
|
||||
(body.CollisionLayer & anchorBody.CollisionMask) != 0x0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a tool can change the anchored status.
|
||||
/// </summary>
|
||||
@@ -135,14 +171,24 @@ namespace Content.Server.Construction
|
||||
SharedPullableComponent? pullable = null,
|
||||
ToolComponent? usingTool = null)
|
||||
{
|
||||
if (!Resolve(uid, ref anchorable, ref transform)) return;
|
||||
if (!Resolve(uid, ref anchorable, ref transform))
|
||||
return;
|
||||
|
||||
// Optional resolves.
|
||||
Resolve(uid, ref pullable, false);
|
||||
|
||||
if (!Resolve(usingUid, ref usingTool)) return;
|
||||
if (!Resolve(usingUid, ref usingTool))
|
||||
return;
|
||||
|
||||
if (!Valid(uid, userUid, usingUid, true, anchorable, usingTool)) return;
|
||||
if (!Valid(uid, userUid, usingUid, true, anchorable, usingTool))
|
||||
return;
|
||||
|
||||
if (TryComp<PhysicsComponent>(uid, out var anchorBody) &&
|
||||
!TileFree(transform.Coordinates, anchorBody))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("anchorable-occupied"), uid, Filter.Entities(userUid));
|
||||
return;
|
||||
}
|
||||
|
||||
anchorable.CancelToken = new CancellationTokenSource();
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Content.Server.Construction.Commands
|
||||
public string Description => "Puts an underplating tile below every wall on a grid.";
|
||||
public string Help => $"Usage: {Command} <gridId> | {Command}";
|
||||
|
||||
public const string TilePrototypeID = "plating";
|
||||
public const string TilePrototypeID = "Plating";
|
||||
public const string WallTag = "Wall";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Construction;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -22,10 +22,6 @@ namespace Content.Server.Construction.Completions
|
||||
{
|
||||
appearance.SetData(@enum, Data);
|
||||
}
|
||||
else
|
||||
{
|
||||
appearance.SetData(Key, Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Content.Server.Contests
|
||||
/// </summary>
|
||||
public float MassContest(EntityUid roller, EntityUid target, PhysicsComponent? rollerPhysics = null, PhysicsComponent? targetPhysics = null)
|
||||
{
|
||||
if (!Resolve(roller, ref rollerPhysics) || !Resolve(target, ref targetPhysics))
|
||||
if (!Resolve(roller, ref rollerPhysics, false) || !Resolve(target, ref targetPhysics, false))
|
||||
return 1f;
|
||||
|
||||
if (rollerPhysics == null || targetPhysics == null)
|
||||
@@ -43,7 +43,7 @@ namespace Content.Server.Contests
|
||||
/// <summary>
|
||||
public float DamageContest(EntityUid roller, EntityUid target, DamageableComponent? rollerDamage = null, DamageableComponent? targetDamage = null)
|
||||
{
|
||||
if (!Resolve(roller, ref rollerDamage) || !Resolve(target, ref targetDamage))
|
||||
if (!Resolve(roller, ref rollerDamage, false) || !Resolve(target, ref targetDamage, false))
|
||||
return 1f;
|
||||
|
||||
if (rollerDamage == null || targetDamage == null)
|
||||
|
||||
47
Content.Server/CrewManifest/CrewManifestEui.cs
Normal file
47
Content.Server/CrewManifest/CrewManifestEui.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Content.Server.EUI;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Content.Shared.Eui;
|
||||
|
||||
namespace Content.Server.CrewManifest;
|
||||
|
||||
public sealed class CrewManifestEui : BaseEui
|
||||
{
|
||||
private readonly CrewManifestSystem _crewManifest;
|
||||
|
||||
/// <summary>
|
||||
/// Station this EUI instance is currently tracking.
|
||||
/// </summary>
|
||||
private readonly EntityUid _station;
|
||||
|
||||
/// <summary>
|
||||
/// Current owner of this UI, if it has one. This is
|
||||
/// to ensure that if a BUI is closed, the EUIs related
|
||||
/// to the BUI are closed as well.
|
||||
/// </summary>
|
||||
public readonly EntityUid? Owner;
|
||||
|
||||
public CrewManifestEui(EntityUid station, EntityUid? owner, CrewManifestSystem crewManifestSystem)
|
||||
{
|
||||
_station = station;
|
||||
Owner = owner;
|
||||
_crewManifest = crewManifestSystem;
|
||||
}
|
||||
|
||||
public override CrewManifestEuiState GetNewState()
|
||||
{
|
||||
var (name, entries) = _crewManifest.GetCrewManifest(_station);
|
||||
return new(name, entries);
|
||||
}
|
||||
|
||||
public override void HandleMessage(EuiMessageBase msg)
|
||||
{
|
||||
base.HandleMessage(msg);
|
||||
|
||||
switch (msg)
|
||||
{
|
||||
case CrewManifestEuiClosed:
|
||||
_crewManifest.CloseEui(_station, Player, Owner);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
263
Content.Server/CrewManifest/CrewManifestSystem.cs
Normal file
263
Content.Server/CrewManifest/CrewManifestSystem.cs
Normal file
@@ -0,0 +1,263 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.EUI;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.StationRecords;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.CrewManifest;
|
||||
|
||||
public sealed class CrewManifestSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
[Dependency] private readonly StationRecordsSystem _recordsSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly EuiManager _euiManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Cached crew manifest entries. The alternative is to outright
|
||||
/// rebuild the crew manifest every time the state is requested:
|
||||
/// this is inefficient.
|
||||
/// </summary>
|
||||
private readonly Dictionary<EntityUid, CrewManifestEntries> _cachedEntries = new();
|
||||
|
||||
private readonly Dictionary<EntityUid, Dictionary<IPlayerSession, CrewManifestEui>> _openEuis = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<AfterGeneralRecordCreatedEvent>(AfterGeneralRecordCreated);
|
||||
SubscribeLocalEvent<RecordModifiedEvent>(OnRecordModified);
|
||||
SubscribeLocalEvent<CrewManifestViewerComponent, BoundUIClosedEvent>(OnBoundUiClose);
|
||||
SubscribeLocalEvent<CrewManifestViewerComponent, CrewManifestOpenUiMessage>(OpenEuiFromBui);
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart);
|
||||
SubscribeNetworkEvent<RequestCrewManifestMessage>(OnRequestCrewManifest);
|
||||
}
|
||||
|
||||
private void OnRoundRestart(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
foreach (var (_, euis) in _openEuis)
|
||||
{
|
||||
foreach (var (_, eui) in euis)
|
||||
{
|
||||
eui.Close();
|
||||
}
|
||||
}
|
||||
|
||||
_openEuis.Clear();
|
||||
_cachedEntries.Clear();
|
||||
}
|
||||
|
||||
private void OnRequestCrewManifest(RequestCrewManifestMessage message, EntitySessionEventArgs args)
|
||||
{
|
||||
if (args.SenderSession is not IPlayerSession sessionCast
|
||||
|| !_configManager.GetCVar(CCVars.CrewManifestWithoutEntity))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OpenEui(message.Id, sessionCast);
|
||||
}
|
||||
|
||||
// Not a big fan of this one. Rebuilds the crew manifest every time
|
||||
// somebody spawns in, meaning that at round start, it rebuilds the crew manifest
|
||||
// wrt the amount of players readied up.
|
||||
private void AfterGeneralRecordCreated(AfterGeneralRecordCreatedEvent ev)
|
||||
{
|
||||
BuildCrewManifest(ev.Key.OriginStation);
|
||||
UpdateEuis(ev.Key.OriginStation);
|
||||
}
|
||||
|
||||
private void OnRecordModified(RecordModifiedEvent ev)
|
||||
{
|
||||
BuildCrewManifest(ev.Key.OriginStation);
|
||||
UpdateEuis(ev.Key.OriginStation);
|
||||
}
|
||||
|
||||
private void OnBoundUiClose(EntityUid uid, CrewManifestViewerComponent component, BoundUIClosedEvent ev)
|
||||
{
|
||||
var owningStation = _stationSystem.GetOwningStation(uid);
|
||||
if (owningStation == null || ev.Session is not IPlayerSession sessionCast)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CloseEui(owningStation.Value, sessionCast, uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the crew manifest for a given station, along with the name of the station.
|
||||
/// </summary>
|
||||
/// <param name="station">Entity uid of the station.</param>
|
||||
/// <returns>The name and crew manifest entries (unordered) of the station.</returns>
|
||||
public (string name, CrewManifestEntries? entries) GetCrewManifest(EntityUid station)
|
||||
{
|
||||
var valid = _cachedEntries.TryGetValue(station, out var manifest);
|
||||
return (valid ? MetaData(station).EntityName : string.Empty, valid ? manifest : null);
|
||||
}
|
||||
|
||||
private void UpdateEuis(EntityUid station)
|
||||
{
|
||||
if (_openEuis.TryGetValue(station, out var euis))
|
||||
{
|
||||
foreach (var eui in euis.Values)
|
||||
{
|
||||
eui.StateDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenEuiFromBui(EntityUid uid, CrewManifestViewerComponent component, CrewManifestOpenUiMessage msg)
|
||||
{
|
||||
var owningStation = _stationSystem.GetOwningStation(uid);
|
||||
if (owningStation == null || msg.Session is not IPlayerSession sessionCast)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_configManager.GetCVar(CCVars.CrewManifestUnsecure) && component.Unsecure)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OpenEui(owningStation.Value, sessionCast, uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a crew manifest EUI for a given player.
|
||||
/// </summary>
|
||||
/// <param name="station">Station that we're displaying the crew manifest for.</param>
|
||||
/// <param name="session">The player's session.</param>
|
||||
/// <param name="owner">If this EUI should be 'owned' by an entity.</param>
|
||||
public void OpenEui(EntityUid station, IPlayerSession session, EntityUid? owner = null)
|
||||
{
|
||||
if (!HasComp<StationRecordsComponent>(station))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_openEuis.TryGetValue(station, out var euis))
|
||||
{
|
||||
euis = new();
|
||||
_openEuis.Add(station, euis);
|
||||
}
|
||||
|
||||
if (euis.ContainsKey(session))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var eui = new CrewManifestEui(station, owner, this);
|
||||
euis.Add(session, eui);
|
||||
|
||||
_euiManager.OpenEui(eui, session);
|
||||
eui.StateDirty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes an EUI for a given player.
|
||||
/// </summary>
|
||||
/// <param name="station">Station that we're displaying the crew manifest for.</param>
|
||||
/// <param name="session">The player's session.</param>
|
||||
/// <param name="owner">The owner of this EUI, if there was one.</param>
|
||||
public void CloseEui(EntityUid station, IPlayerSession session, EntityUid? owner = null)
|
||||
{
|
||||
if (!HasComp<StationRecordsComponent>(station))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_openEuis.TryGetValue(station, out var euis)
|
||||
|| !euis.TryGetValue(session, out var eui))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (eui.Owner == owner)
|
||||
{
|
||||
eui.Close();
|
||||
euis.Remove(session);
|
||||
}
|
||||
|
||||
if (euis.Count == 0)
|
||||
{
|
||||
_openEuis.Remove(station);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the crew manifest for a station. Stores it in the cache afterwards.
|
||||
/// </summary>
|
||||
/// <param name="station"></param>
|
||||
private void BuildCrewManifest(EntityUid station)
|
||||
{
|
||||
var iter = _recordsSystem.GetRecordsOfType<GeneralStationRecord>(station);
|
||||
|
||||
var entries = new CrewManifestEntries();
|
||||
|
||||
foreach (var recordObject in iter)
|
||||
{
|
||||
var record = recordObject.Item2;
|
||||
var entry = new CrewManifestEntry(record.Name, record.JobTitle, record.JobIcon, record.JobPrototype);
|
||||
|
||||
entries.Entries.Add(entry);
|
||||
}
|
||||
|
||||
if (_cachedEntries.ContainsKey(station))
|
||||
{
|
||||
_cachedEntries[station] = entries;
|
||||
}
|
||||
else
|
||||
{
|
||||
_cachedEntries.Add(station, entries);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
public sealed class CrewManifestCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "crewmanifest";
|
||||
public string Description => "Opens the crew manifest for the given station.";
|
||||
public string Help => $"Usage: {Command} <entity uid>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteLine($"Invalid argument count.\n{Help}");
|
||||
return;
|
||||
}
|
||||
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
if (!EntityUid.TryParse(args[0], out var uid))
|
||||
{
|
||||
shell.WriteLine($"{args[0]} is not a valid entity UID.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (shell.Player == null || shell.Player is not IPlayerSession session)
|
||||
{
|
||||
shell.WriteLine("You must run this from a client.");
|
||||
return;
|
||||
}
|
||||
|
||||
var crewManifestSystem = entMan.EntitySysManager.GetEntitySystem<CrewManifestSystem>();
|
||||
|
||||
crewManifestSystem.OpenEui(uid, session);
|
||||
}
|
||||
}
|
||||
12
Content.Server/CrewManifest/CrewManifestViewerComponent.cs
Normal file
12
Content.Server/CrewManifest/CrewManifestViewerComponent.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Content.Server.CrewManifest;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class CrewManifestViewerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// If this manifest viewer is unsecure or not. If it is,
|
||||
/// CCVars.CrewManifestUnsecure being false will
|
||||
/// not allow this entity to be processed by CrewManifestSystem.
|
||||
/// </summary>
|
||||
[DataField("unsecure")] public bool Unsecure;
|
||||
}
|
||||
@@ -32,11 +32,42 @@ namespace Content.Server.Dragon
|
||||
[DataField("devourAction")]
|
||||
public EntityTargetAction? DevourAction;
|
||||
|
||||
[DataField("spawnActionId", customTypeSerializer: typeof(PrototypeIdSerializer<InstantActionPrototype>))]
|
||||
public string SpawnActionId = "DragonSpawn";
|
||||
/// <summary>
|
||||
/// If we have active rifts.
|
||||
/// </summary>
|
||||
[ViewVariables, DataField("rifts")]
|
||||
public List<EntityUid> Rifts = new();
|
||||
|
||||
[DataField("spawnAction")]
|
||||
public InstantAction? SpawnAction;
|
||||
public bool Weakened => WeakenedAccumulator > 0f;
|
||||
|
||||
/// <summary>
|
||||
/// When any rift is destroyed how long is the dragon weakened for
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("weakenedDuration")]
|
||||
public float WeakenedDuration = 120f;
|
||||
|
||||
/// <summary>
|
||||
/// Has a rift been destroyed and the dragon in a temporary weakened state?
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("weakenedAccumulator")]
|
||||
public float WeakenedAccumulator = 0f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("riftAccumulator")]
|
||||
public float RiftAccumulator = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum time the dragon can go without spawning a rift before they die.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("maxAccumulator")] public float RiftMaxAccumulator = 300f;
|
||||
|
||||
/// <summary>
|
||||
/// Spawns a rift which can summon more mobs.
|
||||
/// </summary>
|
||||
[ViewVariables, DataField("spawnRiftAction")]
|
||||
public InstantAction? SpawnRiftAction;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("riftPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string RiftPrototype = "CarpRift";
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time it takes to devour something
|
||||
@@ -48,14 +79,7 @@ namespace Content.Server.Dragon
|
||||
public float StructureDevourTime = 10f;
|
||||
|
||||
[DataField("devourTime")]
|
||||
public float DevourTime = 2f;
|
||||
|
||||
[DataField("spawnCount")] public int SpawnsLeft = 2;
|
||||
|
||||
[DataField("maxSpawnCount")] public int MaxSpawns = 2;
|
||||
|
||||
[DataField("spawnProto", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? SpawnPrototype = "MobCarpDragon";
|
||||
public float DevourTime = 3f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("soundDeath")]
|
||||
public SoundSpecifier? SoundDeath = new SoundPathSpecifier("/Audio/Animals/space_dragon_roar.ogg");
|
||||
@@ -76,7 +100,7 @@ namespace Content.Server.Dragon
|
||||
public SoundSpecifier? SoundRoar =
|
||||
new SoundPathSpecifier("/Audio/Animals/space_dragon_roar.ogg")
|
||||
{
|
||||
Params = AudioParams.Default.WithVolume(-3f),
|
||||
Params = AudioParams.Default.WithVolume(3f),
|
||||
};
|
||||
|
||||
public CancellationTokenSource? CancelToken;
|
||||
@@ -103,5 +127,5 @@ namespace Content.Server.Dragon
|
||||
|
||||
public sealed class DragonDevourActionEvent : EntityTargetActionEvent {}
|
||||
|
||||
public sealed class DragonSpawnActionEvent : InstantActionEvent {}
|
||||
public sealed class DragonSpawnRiftActionEvent : InstantActionEvent {}
|
||||
}
|
||||
40
Content.Server/Dragon/Components/DragonRiftComponent.cs
Normal file
40
Content.Server/Dragon/Components/DragonRiftComponent.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using Content.Shared.Dragon;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Dragon;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class DragonRiftComponent : SharedDragonRiftComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Dragon that spawned this rift.
|
||||
/// </summary>
|
||||
[ViewVariables, DataField("dragon")] public EntityUid Dragon;
|
||||
|
||||
/// <summary>
|
||||
/// How long the rift has been active.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("accumulator")]
|
||||
public float Accumulator = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum amount we can accumulate before becoming impervious.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("maxAccumuluator")] public float MaxAccumulator = 300f;
|
||||
|
||||
/// <summary>
|
||||
/// Accumulation of the spawn timer.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("spawnAccumulator")]
|
||||
public float SpawnAccumulator = 30f;
|
||||
|
||||
/// <summary>
|
||||
/// How long it takes for a new spawn to be added.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("spawnCooldown")]
|
||||
public float SpawnCooldown = 30f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("spawn", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string SpawnPrototype = "MobCarpDragon";
|
||||
}
|
||||
67
Content.Server/Dragon/DragonSystem.Rule.cs
Normal file
67
Content.Server/Dragon/DragonSystem.Rule.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.Dragon;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Dragon;
|
||||
|
||||
public sealed partial class DragonSystem
|
||||
{
|
||||
public override string Prototype => "Dragon";
|
||||
|
||||
private int RiftsMet(DragonComponent component)
|
||||
{
|
||||
var finished = 0;
|
||||
|
||||
foreach (var rift in component.Rifts)
|
||||
{
|
||||
if (!TryComp<DragonRiftComponent>(rift, out var drift) ||
|
||||
drift.State != DragonRiftState.Finished)
|
||||
continue;
|
||||
|
||||
finished++;
|
||||
}
|
||||
|
||||
return finished;
|
||||
}
|
||||
|
||||
public override void Started()
|
||||
{
|
||||
var spawnLocations = EntityManager.EntityQuery<IMapGridComponent, TransformComponent>().ToList();
|
||||
|
||||
if (spawnLocations.Count == 0)
|
||||
return;
|
||||
|
||||
var location = _random.Pick(spawnLocations);
|
||||
Spawn("MobDragon", location.Item2.MapPosition);
|
||||
}
|
||||
|
||||
public override void Ended()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
private void OnRiftRoundEnd(RoundEndTextAppendEvent args)
|
||||
{
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
|
||||
args.AddLine(Loc.GetString("dragon-round-end-summary"));
|
||||
|
||||
foreach (var dragon in EntityQuery<DragonComponent>(true))
|
||||
{
|
||||
var met = RiftsMet(dragon);
|
||||
|
||||
if (TryComp<ActorComponent>(dragon.Owner, out var actor))
|
||||
{
|
||||
args.AddLine(Loc.GetString("dragon-round-end-dragon-player", ("name", dragon.Owner), ("count", met), ("player", actor.PlayerSession)));
|
||||
}
|
||||
else
|
||||
{
|
||||
args.AddLine(Loc.GetString("dragon-round-end-dragon", ("name", dragon.Owner), ("count", met)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,30 +9,263 @@ using Content.Shared.MobState.Components;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
using System.Threading;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Dragon;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Dragon
|
||||
{
|
||||
public sealed class DragonSystem : EntitySystem
|
||||
public sealed partial class DragonSystem : GameRuleSystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDef = default!;
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
|
||||
[Dependency] private readonly MovementSpeedModifierSystem _movement = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum distance between 2 rifts allowed.
|
||||
/// </summary>
|
||||
private const int RiftRange = 15;
|
||||
|
||||
/// <summary>
|
||||
/// Radius of tiles
|
||||
/// </summary>
|
||||
private const int RiftTileRadius = 2;
|
||||
|
||||
private const int RiftsAllowed = 3;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DragonComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<DragonComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<DragonComponent, DragonDevourComplete>(OnDragonDevourComplete);
|
||||
SubscribeLocalEvent<DragonComponent, DragonDevourActionEvent>(OnDevourAction);
|
||||
SubscribeLocalEvent<DragonComponent, DragonSpawnActionEvent>(OnDragonSpawnAction);
|
||||
SubscribeLocalEvent<DragonComponent, DragonSpawnRiftActionEvent>(OnDragonRift);
|
||||
SubscribeLocalEvent<DragonComponent, RefreshMovementSpeedModifiersEvent>(OnDragonMove);
|
||||
|
||||
SubscribeLocalEvent<DragonComponent, DragonStructureDevourComplete>(OnDragonStructureDevourComplete);
|
||||
SubscribeLocalEvent<DragonComponent, DragonDevourCancelledEvent>(OnDragonDevourCancelled);
|
||||
SubscribeLocalEvent<DragonComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
|
||||
SubscribeLocalEvent<DragonRiftComponent, ComponentShutdown>(OnRiftShutdown);
|
||||
SubscribeLocalEvent<DragonRiftComponent, ComponentGetState>(OnRiftGetState);
|
||||
SubscribeLocalEvent<DragonRiftComponent, AnchorStateChangedEvent>(OnAnchorChange);
|
||||
SubscribeLocalEvent<DragonRiftComponent, ExaminedEvent>(OnRiftExamined);
|
||||
|
||||
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRiftRoundEnd);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
foreach (var comp in EntityQuery<DragonComponent>())
|
||||
{
|
||||
if (comp.WeakenedAccumulator > 0f)
|
||||
{
|
||||
comp.WeakenedAccumulator -= frameTime;
|
||||
|
||||
// No longer weakened.
|
||||
if (comp.WeakenedAccumulator < 0f)
|
||||
{
|
||||
comp.WeakenedAccumulator = 0f;
|
||||
_movement.RefreshMovementSpeedModifiers(comp.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
// At max rifts
|
||||
if (comp.Rifts.Count >= RiftsAllowed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If there's an active rift don't accumulate.
|
||||
if (comp.Rifts.Count > 0)
|
||||
{
|
||||
var lastRift = comp.Rifts[^1];
|
||||
|
||||
if (TryComp<DragonRiftComponent>(lastRift, out var rift) && rift.State != DragonRiftState.Finished)
|
||||
{
|
||||
comp.RiftAccumulator = 0f;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
comp.RiftAccumulator += frameTime;
|
||||
|
||||
// Delete it, naughty dragon!
|
||||
if (comp.RiftAccumulator >= comp.RiftMaxAccumulator)
|
||||
{
|
||||
Roar(comp);
|
||||
QueueDel(comp.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var comp in EntityQuery<DragonRiftComponent>())
|
||||
{
|
||||
if (comp.State != DragonRiftState.Finished && comp.Accumulator >= comp.MaxAccumulator)
|
||||
{
|
||||
// TODO: When we get autocall you can buff if the rift finishes / 3 rifts are up
|
||||
// for now they just keep 3 rifts up.
|
||||
|
||||
comp.Accumulator = comp.MaxAccumulator;
|
||||
RemComp<DamageableComponent>(comp.Owner);
|
||||
comp.State = DragonRiftState.Finished;
|
||||
Dirty(comp);
|
||||
}
|
||||
else if (comp.State != DragonRiftState.Finished)
|
||||
{
|
||||
comp.Accumulator += frameTime;
|
||||
}
|
||||
|
||||
comp.SpawnAccumulator += frameTime;
|
||||
|
||||
if (comp.State < DragonRiftState.AlmostFinished && comp.Accumulator > comp.MaxAccumulator / 2f)
|
||||
{
|
||||
comp.State = DragonRiftState.AlmostFinished;
|
||||
Dirty(comp);
|
||||
var location = Transform(comp.Owner).LocalPosition;
|
||||
|
||||
_chat.DispatchGlobalAnnouncement(Loc.GetString("carp-rift-warning", ("location", location)), playSound: false, colorOverride: Color.Red);
|
||||
_audioSystem.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast());
|
||||
}
|
||||
|
||||
if (comp.SpawnAccumulator > comp.SpawnCooldown)
|
||||
{
|
||||
comp.SpawnAccumulator -= comp.SpawnCooldown;
|
||||
Spawn(comp.SpawnPrototype, Transform(comp.Owner).MapPosition);
|
||||
// TODO: When NPC refactor make it guard the rift.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Rift
|
||||
|
||||
private void OnRiftExamined(EntityUid uid, DragonRiftComponent component, ExaminedEvent args)
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("carp-rift-examine", ("percentage", MathF.Round(component.Accumulator / component.MaxAccumulator * 100))));
|
||||
}
|
||||
|
||||
private void OnAnchorChange(EntityUid uid, DragonRiftComponent component, ref AnchorStateChangedEvent args)
|
||||
{
|
||||
if (!args.Anchored && component.State == DragonRiftState.Charging)
|
||||
{
|
||||
QueueDel(uid);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRiftShutdown(EntityUid uid, DragonRiftComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (TryComp<DragonComponent>(component.Dragon, out var dragon) && !dragon.Weakened)
|
||||
{
|
||||
foreach (var rift in dragon.Rifts)
|
||||
{
|
||||
QueueDel(rift);
|
||||
}
|
||||
|
||||
dragon.Rifts.Clear();
|
||||
|
||||
// We can't predict the rift being destroyed anyway so no point adding weakened to shared.
|
||||
dragon.WeakenedAccumulator = dragon.WeakenedDuration;
|
||||
_movement.RefreshMovementSpeedModifiers(component.Dragon);
|
||||
_popupSystem.PopupEntity(Loc.GetString("carp-rift-destroyed"), component.Dragon, Filter.Entities(component.Dragon));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRiftGetState(EntityUid uid, DragonRiftComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new DragonRiftComponentState()
|
||||
{
|
||||
State = component.State
|
||||
};
|
||||
}
|
||||
|
||||
private void OnDragonMove(EntityUid uid, DragonComponent component, RefreshMovementSpeedModifiersEvent args)
|
||||
{
|
||||
if (component.Weakened)
|
||||
{
|
||||
args.ModifySpeed(0.5f, 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDragonRift(EntityUid uid, DragonComponent component, DragonSpawnRiftActionEvent args)
|
||||
{
|
||||
if (component.Weakened)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("carp-rift-weakened"), uid, Filter.Entities(uid));
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.Rifts.Count >= RiftsAllowed)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("carp-rift-max"), uid, Filter.Entities(uid));
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.Rifts.Count > 0 && TryComp<DragonRiftComponent>(component.Rifts[^1], out var rift) && rift.State != DragonRiftState.Finished)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("carp-rift-duplicate"), uid, Filter.Entities(uid));
|
||||
return;
|
||||
}
|
||||
|
||||
var xform = Transform(uid);
|
||||
|
||||
// Have to be on a grid fam
|
||||
if (!_mapManager.TryGetGrid(xform.GridUid, out var grid))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("carp-rift-anchor"), uid, Filter.Entities(uid));
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (_, riftXform) in EntityQuery<DragonRiftComponent, TransformComponent>(true))
|
||||
{
|
||||
if (riftXform.Coordinates.InRange(EntityManager, xform.Coordinates, RiftRange))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("carp-rift-proximity", ("proximity", RiftRange)), uid, Filter.Entities(uid));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var tile in grid.GetTilesIntersecting(new Circle(xform.WorldPosition, RiftTileRadius), false))
|
||||
{
|
||||
if (!tile.IsSpace(_tileDef))
|
||||
continue;
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("carp-rift-space-proximity", ("proximity", RiftTileRadius)), uid, Filter.Entities(uid));
|
||||
return;
|
||||
}
|
||||
|
||||
var carpUid = Spawn(component.RiftPrototype, xform.MapPosition);
|
||||
component.Rifts.Add(carpUid);
|
||||
Comp<DragonRiftComponent>(carpUid).Dragon = uid;
|
||||
_audioSystem.Play("/Audio/Weapons/Guns/Gunshots/rocket_launcher.ogg", Filter.Pvs(carpUid, entityManager: EntityManager), carpUid);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void OnShutdown(EntityUid uid, DragonComponent component, ComponentShutdown args)
|
||||
{
|
||||
foreach (var rift in component.Rifts)
|
||||
{
|
||||
QueueDel(rift);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMobStateChanged(EntityUid uid, DragonComponent component, MobStateChangedEvent args)
|
||||
@@ -45,6 +278,13 @@ namespace Content.Server.Dragon
|
||||
_audioSystem.PlayPvs(component.SoundDeath, uid, component.SoundDeath.Params);
|
||||
|
||||
component.DragonStomach.EmptyContainer();
|
||||
|
||||
foreach (var rift in component.Rifts)
|
||||
{
|
||||
QueueDel(rift);
|
||||
}
|
||||
|
||||
component.Rifts.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,13 +299,7 @@ namespace Content.Server.Dragon
|
||||
var ichorInjection = new Solution(component.DevourChem, component.DevourHealRate);
|
||||
|
||||
//Humanoid devours allow dragon to get eggs, corpses included
|
||||
if (EntityManager.HasComponent<HumanoidAppearanceComponent>(args.Target))
|
||||
{
|
||||
// Add a spawn for a consumed humanoid
|
||||
component.SpawnsLeft = Math.Min(component.SpawnsLeft + 1, component.MaxSpawns);
|
||||
}
|
||||
//Non-humanoid mobs can only heal dragon for half the normal amount, with no additional spawn tickets
|
||||
else
|
||||
if (!EntityManager.HasComponent<HumanoidAppearanceComponent>(args.Target))
|
||||
{
|
||||
ichorInjection.ScaleSolution(0.5f);
|
||||
}
|
||||
@@ -87,10 +321,14 @@ namespace Content.Server.Dragon
|
||||
_audioSystem.PlayPvs(component.SoundDevour, uid, component.SoundDevour.Params);
|
||||
}
|
||||
|
||||
private void Roar(DragonComponent component)
|
||||
{
|
||||
if (component.SoundRoar != null)
|
||||
_audioSystem.Play(component.SoundRoar, Filter.Pvs(component.Owner, 4f, EntityManager), component.Owner, component.SoundRoar.Params);
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, DragonComponent component, ComponentStartup args)
|
||||
{
|
||||
component.SpawnsLeft = Math.Min(component.SpawnsLeft, component.MaxSpawns);
|
||||
|
||||
//Dragon doesn't actually chew, since he sends targets right into his stomach.
|
||||
//I did it mom, I added ERP content into upstream. Legally!
|
||||
component.DragonStomach = _containerSystem.EnsureContainer<Container>(uid, "dragon_stomach");
|
||||
@@ -98,11 +336,10 @@ namespace Content.Server.Dragon
|
||||
if (component.DevourAction != null)
|
||||
_actionsSystem.AddAction(uid, component.DevourAction, null);
|
||||
|
||||
if (component.SpawnAction != null)
|
||||
_actionsSystem.AddAction(uid, component.SpawnAction, null);
|
||||
if (component.SpawnRiftAction != null)
|
||||
_actionsSystem.AddAction(uid, component.SpawnRiftAction, null);
|
||||
|
||||
if (component.SoundRoar != null)
|
||||
_audioSystem.Play(component.SoundRoar, Filter.Pvs(uid, 4f, EntityManager), uid, component.SoundRoar.Params);
|
||||
Roar(component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -117,7 +354,6 @@ namespace Content.Server.Dragon
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
args.Handled = true;
|
||||
var target = args.Target;
|
||||
|
||||
@@ -164,22 +400,6 @@ namespace Content.Server.Dragon
|
||||
});
|
||||
}
|
||||
|
||||
private void OnDragonSpawnAction(EntityUid dragonuid, DragonComponent component, DragonSpawnActionEvent args)
|
||||
{
|
||||
if (component.SpawnPrototype == null)
|
||||
return;
|
||||
|
||||
// If dragon has spawns then add one.
|
||||
if (component.SpawnsLeft > 0)
|
||||
{
|
||||
Spawn(component.SpawnPrototype, Transform(dragonuid).Coordinates);
|
||||
component.SpawnsLeft--;
|
||||
return;
|
||||
}
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("dragon-spawn-action-popup-message-fail-no-eggs"), dragonuid, Filter.Entities(dragonuid));
|
||||
}
|
||||
|
||||
private sealed class DragonDevourComplete : EntityEventArgs
|
||||
{
|
||||
public EntityUid User { get; }
|
||||
|
||||
@@ -53,6 +53,7 @@ namespace Content.Server.Entry
|
||||
var prototypes = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
||||
factory.DoAutoRegistrations();
|
||||
factory.IgnoreMissingComponents("Visuals");
|
||||
|
||||
foreach (var ignoreName in IgnoredComponents.List)
|
||||
{
|
||||
|
||||
@@ -6,40 +6,17 @@ namespace Content.Server.Entry
|
||||
public static string[] List => new[] {
|
||||
"ConstructionGhost",
|
||||
"IconSmooth",
|
||||
"StasisBedVisuals",
|
||||
"InteractionOutline",
|
||||
"MeleeWeaponArcAnimation",
|
||||
"EffectVisuals",
|
||||
"DamageStateVisuals",
|
||||
"PortableScrubberVisuals",
|
||||
"AnimationsTest",
|
||||
"ItemStatus",
|
||||
"VehicleVisuals",
|
||||
"Marker",
|
||||
"Clickable",
|
||||
"Icon",
|
||||
"ClientEntitySpawner",
|
||||
"CharacterInfo",
|
||||
"ItemCabinetVisuals",
|
||||
"LatheVisuals",
|
||||
"DiseaseMachineVisuals",
|
||||
"HandheldGPS",
|
||||
"SpentAmmoVisuals",
|
||||
"MagazineVisuals",
|
||||
"SolutionContainerVisuals",
|
||||
"PowerCellVisuals",
|
||||
"ToggleableLightVisuals",
|
||||
"CableVisualizer",
|
||||
"PotencyVisuals",
|
||||
"PaperVisuals",
|
||||
"SurveillanceCameraVisuals",
|
||||
"KudzuVisuals",
|
||||
"AMEControllerVisuals",
|
||||
"AMEShieldingVisuals",
|
||||
"PipeColorVisuals",
|
||||
"FireVisuals",
|
||||
"CrematoriumVisuals",
|
||||
"PlantHolderVisuals",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
using System.Linq;
|
||||
using Content.Server.CharacterAppearance.Components;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.GameTicking.Rules.Configurations;
|
||||
using Content.Server.Nuke;
|
||||
using Content.Server.Players;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.RoundEnd;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Server.Spawners.Components;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.MobState;
|
||||
@@ -23,7 +20,8 @@ using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Server.Traitor;
|
||||
using System.Data;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules;
|
||||
|
||||
@@ -36,7 +34,6 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
|
||||
[Dependency] private readonly IMapLoader _mapLoader = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly StationSpawningSystem _stationSpawningSystem = default!;
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
|
||||
|
||||
private Dictionary<Mind.Mind, bool> _aliveNukeops = new();
|
||||
@@ -44,6 +41,8 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
|
||||
|
||||
public override string Prototype => "Nukeops";
|
||||
|
||||
private readonly SoundSpecifier _greetSound = new SoundPathSpecifier("/Audio/Misc/nukeops.ogg");
|
||||
|
||||
private const string NukeopsPrototypeId = "Nukeops";
|
||||
private const string NukeopsCommanderPrototypeId = "NukeopsCommander";
|
||||
|
||||
@@ -293,6 +292,12 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
|
||||
|
||||
GameTicker.PlayerJoinGame(session);
|
||||
}
|
||||
|
||||
SoundSystem.Play(_greetSound.GetSound(), Filter.Empty().AddWhere(s =>
|
||||
{
|
||||
var mind = ((IPlayerSession) s).Data.ContentData()?.Mind;
|
||||
return mind != null && _aliveNukeops.ContainsKey(mind);
|
||||
}), AudioParams.Default);
|
||||
}
|
||||
|
||||
//For admins forcing someone to nukeOps.
|
||||
|
||||
@@ -45,9 +45,9 @@ namespace Content.Server.Headset
|
||||
_radioSystem = EntitySystem.Get<RadioSystem>();
|
||||
}
|
||||
|
||||
public bool CanListen(string message, EntityUid source, RadioChannelPrototype prototype)
|
||||
public bool CanListen(string message, EntityUid source, RadioChannelPrototype? prototype)
|
||||
{
|
||||
return Channels.Contains(prototype.ID) && RadioRequested;
|
||||
return prototype != null && Channels.Contains(prototype.ID) && RadioRequested;
|
||||
}
|
||||
|
||||
public void Receive(string message, RadioChannelPrototype channel, EntityUid source)
|
||||
@@ -73,8 +73,13 @@ namespace Content.Server.Headset
|
||||
_netManager.ServerSendMessage(msg, playerChannel);
|
||||
}
|
||||
|
||||
public void Listen(string message, EntityUid speaker, RadioChannelPrototype channel)
|
||||
public void Listen(string message, EntityUid speaker, RadioChannelPrototype? channel)
|
||||
{
|
||||
if (channel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Broadcast(message, speaker, channel);
|
||||
}
|
||||
|
||||
|
||||
@@ -66,8 +66,8 @@ namespace Content.Server.Interaction
|
||||
|
||||
if (!tileDef.CanCrowbar) continue;
|
||||
|
||||
var underplating = tileDefinitionManager["underplating"];
|
||||
mapGrid.SetTile(coordinates, new Robust.Shared.Map.Tile(underplating.TileId));
|
||||
var underplating = tileDefinitionManager["UnderPlating"];
|
||||
mapGrid.SetTile(coordinates, new Tile(underplating.TileId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Player;
|
||||
using JetBrains.Annotations;
|
||||
using System.Linq;
|
||||
using Content.Server.Power.Components;
|
||||
using Robust.Server.Player;
|
||||
|
||||
namespace Content.Server.Lathe
|
||||
@@ -27,7 +28,7 @@ namespace Content.Server.Lathe
|
||||
[Dependency] private readonly SharedAudioSystem _audioSys = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSys = default!;
|
||||
[Dependency] private readonly ResearchSystem _researchSys = default!;
|
||||
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -36,6 +37,7 @@ namespace Content.Server.Lathe
|
||||
SubscribeLocalEvent<LatheComponent, LatheQueueRecipeMessage>(OnLatheQueueRecipeMessage);
|
||||
SubscribeLocalEvent<LatheComponent, LatheSyncRequestMessage>(OnLatheSyncRequestMessage);
|
||||
SubscribeLocalEvent<LatheComponent, LatheServerSelectionMessage>(OnLatheServerSelectionMessage);
|
||||
SubscribeLocalEvent<LatheComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<TechnologyDatabaseComponent, LatheServerSyncMessage>(OnLatheServerSyncMessage);
|
||||
}
|
||||
|
||||
@@ -156,7 +158,7 @@ namespace Content.Server.Lathe
|
||||
EntityManager.QueueDeleteEntity(args.Used);
|
||||
|
||||
EnsureComp<LatheInsertingComponent>(uid).TimeRemaining = component.InsertionTime;
|
||||
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("machine-insert-item", ("machine", uid),
|
||||
("item", args.Used)), uid, Filter.Entities(args.User));
|
||||
|
||||
@@ -167,6 +169,14 @@ namespace Content.Server.Lathe
|
||||
UpdateInsertingAppearance(uid, true);
|
||||
}
|
||||
|
||||
|
||||
private void OnPowerChanged(EntityUid uid, LatheComponent component, PowerChangedEvent args)
|
||||
{
|
||||
//if the power state changes, try to produce.
|
||||
//aka, if you went from unpowered --> powered, resume lathe queue.
|
||||
TryStartProducing(uid, component: component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This handles the checks to start producing an item, and
|
||||
/// starts up the sound and visuals
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Content.Server.Objectives.Conditions
|
||||
/// instead of "steal advanced magboots. Should be a loc string.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("owner", required: true)] private string _owner = string.Empty;
|
||||
[DataField("owner")] private string? _owner = null;
|
||||
|
||||
public IObjectiveCondition GetAssigned(Mind.Mind mind)
|
||||
{
|
||||
@@ -38,7 +38,10 @@ namespace Content.Server.Objectives.Conditions
|
||||
? prototype.Name
|
||||
: "[CANNOT FIND NAME]";
|
||||
|
||||
public string Title => Loc.GetString("objective-condition-steal-title", ("owner", Loc.GetString(_owner)), ("itemName", Loc.GetString(PrototypeName)));
|
||||
public string Title =>
|
||||
_owner == null
|
||||
? Loc.GetString("objective-condition-steal-title-no-owner", ("itemName", Loc.GetString(PrototypeName)))
|
||||
: Loc.GetString("objective-condition-steal-title", ("owner", Loc.GetString(_owner)), ("itemName", Loc.GetString(PrototypeName)));
|
||||
|
||||
public string Description => Loc.GetString("objective-condition-steal-description",("itemName", Loc.GetString(PrototypeName)));
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using Content.Shared.Interaction;
|
||||
using Content.Shared.Pinpointer;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Server.Shuttles.Events;
|
||||
|
||||
namespace Content.Server.Pinpointer
|
||||
{
|
||||
@@ -13,12 +14,29 @@ namespace Content.Server.Pinpointer
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<PinpointerComponent, ActivateInWorldEvent>(OnActivate);
|
||||
SubscribeLocalEvent<HyperspaceJumpCompletedEvent>(OnLocateTarget);
|
||||
}
|
||||
|
||||
private void OnActivate(EntityUid uid, PinpointerComponent component, ActivateInWorldEvent args)
|
||||
{
|
||||
TogglePinpointer(uid, component);
|
||||
LocateTarget(uid, component);
|
||||
}
|
||||
|
||||
private void OnLocateTarget(HyperspaceJumpCompletedEvent ev)
|
||||
{
|
||||
// This feels kind of expensive, but it only happens once per hyperspace jump
|
||||
foreach (var uid in ActivePinpointers)
|
||||
{
|
||||
if (TryComp<PinpointerComponent>(uid, out var component))
|
||||
{
|
||||
LocateTarget(uid, component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LocateTarget(EntityUid uid, PinpointerComponent component)
|
||||
{
|
||||
// try to find target from whitelist
|
||||
if (component.IsActive && component.Component != null)
|
||||
{
|
||||
|
||||
@@ -219,6 +219,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
||||
|
||||
jobs.RemoveSwap(i);
|
||||
i--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Buckle.Components;
|
||||
using Content.Server.Inventory;
|
||||
using Content.Server.Mind.Commands;
|
||||
@@ -13,11 +12,8 @@ using Content.Shared.Damage;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Polymorph;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -90,6 +86,10 @@ namespace Content.Server.Polymorph.Systems
|
||||
/// logic and conditions specified in the prototype, and everything else that may be needed.
|
||||
/// I am clinically insane - emo
|
||||
|
||||
// if it's already morphed, don't allow it again with this condition active.
|
||||
if (!proto.AllowRepeatedMorphs && HasComp<PolymorphedEntityComponent>(target))
|
||||
return null;
|
||||
|
||||
// mostly just for vehicles
|
||||
if (TryComp<BuckleComponent>(target, out var buckle))
|
||||
buckle.TryUnbuckle(target, true);
|
||||
|
||||
@@ -113,8 +113,8 @@ namespace Content.Server.RCD.Systems
|
||||
{
|
||||
//Floor mode just needs the tile to be a space tile (subFloor)
|
||||
case RcdMode.Floors:
|
||||
mapGrid.SetTile(snapPos, new Tile(_tileDefinitionManager["floor_steel"].TileId));
|
||||
_adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(args.User):user} used RCD to set grid: {tile.GridIndex} {snapPos} to floor_steel");
|
||||
mapGrid.SetTile(snapPos, new Tile(_tileDefinitionManager["FloorSteel"].TileId));
|
||||
_adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(args.User):user} used RCD to set grid: {tile.GridIndex} {snapPos} to FloorSteel");
|
||||
break;
|
||||
//We don't want to place a space tile on something that's already a space tile. Let's do the inverse of the last check.
|
||||
case RcdMode.Deconstruct:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Chat;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Radio.EntitySystems;
|
||||
@@ -20,6 +21,7 @@ namespace Content.Server.Radio.Components
|
||||
{
|
||||
private ChatSystem _chatSystem = default!;
|
||||
private RadioSystem _radioSystem = default!;
|
||||
private IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
private bool _radioOn;
|
||||
[DataField("channels", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<RadioChannelPrototype>))]
|
||||
@@ -52,6 +54,7 @@ namespace Content.Server.Radio.Components
|
||||
|
||||
_radioSystem = EntitySystem.Get<RadioSystem>();
|
||||
_chatSystem = EntitySystem.Get<ChatSystem>();
|
||||
IoCManager.Resolve(ref _prototypeManager);
|
||||
|
||||
RadioOn = false;
|
||||
}
|
||||
@@ -72,12 +75,16 @@ namespace Content.Server.Radio.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanListen(string message, EntityUid source, RadioChannelPrototype prototype)
|
||||
public bool CanListen(string message, EntityUid source, RadioChannelPrototype? prototype)
|
||||
{
|
||||
if (!_channels.Contains(prototype.ID)) return false;
|
||||
if (prototype != null && !_channels.Contains(prototype.ID)
|
||||
|| !_prototypeManager.HasIndex<RadioChannelPrototype>(BroadcastChannel))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return RadioOn &&
|
||||
EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(Owner, source, range: ListenRange);
|
||||
return RadioOn
|
||||
&& EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(Owner, source, range: ListenRange);
|
||||
}
|
||||
|
||||
public void Receive(string message, RadioChannelPrototype channel, EntityUid speaker)
|
||||
@@ -88,9 +95,16 @@ namespace Content.Server.Radio.Components
|
||||
}
|
||||
}
|
||||
|
||||
public void Listen(string message, EntityUid speaker, RadioChannelPrototype channel)
|
||||
public void Listen(string message, EntityUid speaker, RadioChannelPrototype? prototype)
|
||||
{
|
||||
Broadcast(message, speaker, channel);
|
||||
// if we can't get the channel, we need to just use the broadcast frequency
|
||||
if (prototype == null
|
||||
&& !_prototypeManager.TryIndex(BroadcastChannel, out prototype))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Broadcast(message, speaker, prototype);
|
||||
}
|
||||
|
||||
public void Broadcast(string message, EntityUid speaker, RadioChannelPrototype channel)
|
||||
|
||||
@@ -10,8 +10,8 @@ namespace Content.Server.Radio.Components
|
||||
{
|
||||
int ListenRange { get; }
|
||||
|
||||
bool CanListen(string message, EntityUid source, RadioChannelPrototype channelPrototype);
|
||||
bool CanListen(string message, EntityUid source, RadioChannelPrototype? channelPrototype);
|
||||
|
||||
void Listen(string message, EntityUid speaker, RadioChannelPrototype channel);
|
||||
void Listen(string message, EntityUid speaker, RadioChannelPrototype? channel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Content.Server.Radio.EntitySystems
|
||||
[UsedImplicitly]
|
||||
public sealed class ListeningSystem : EntitySystem
|
||||
{
|
||||
public void PingListeners(EntityUid source, string message, RadioChannelPrototype channel)
|
||||
public void PingListeners(EntityUid source, string message, RadioChannelPrototype? channel)
|
||||
{
|
||||
foreach (var listener in EntityManager.EntityQuery<IListen>(true))
|
||||
{
|
||||
|
||||
@@ -15,8 +15,6 @@ namespace Content.Server.RatKing
|
||||
{
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly ActionsSystem _action = default!;
|
||||
[Dependency] private readonly DiseaseSystem _disease = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmos = default!;
|
||||
[Dependency] private readonly TransformSystem _xform = default!;
|
||||
|
||||
|
||||
@@ -6,17 +6,11 @@ namespace Content.Server.Resist;
|
||||
public sealed class CanEscapeInventoryComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How long it takes to break out of storage. Default at 5 seconds.
|
||||
/// Base doafter length for uncontested breakouts.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("resistTime")]
|
||||
public float ResistTime = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// For quick exit if the player attempts to move while already resisting
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool IsResisting = false;
|
||||
[DataField("baseResistTime")]
|
||||
public float BaseResistTime = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// Cancellation token used to cancel the DoAfter if the mob is removed before it's complete
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
using Content.Shared.Movement;
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Server.Contests;
|
||||
using Robust.Shared.Containers;
|
||||
using Content.Server.Popups;
|
||||
using Robust.Shared.Player;
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Movement.Events;
|
||||
using Content.Shared.Interaction.Events;
|
||||
|
||||
namespace Content.Server.Resist;
|
||||
|
||||
@@ -17,6 +18,13 @@ public sealed class EscapeInventorySystem : EntitySystem
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly ContestsSystem _contests = default!;
|
||||
|
||||
/// <summary>
|
||||
/// You can't escape the hands of an entity this many times more massive than you.
|
||||
/// </summary>
|
||||
public const float MaximumMassDisadvantage = 6f;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -26,20 +34,38 @@ public sealed class EscapeInventorySystem : EntitySystem
|
||||
SubscribeLocalEvent<CanEscapeInventoryComponent, UpdateCanMoveEvent>(OnMoveAttempt);
|
||||
SubscribeLocalEvent<CanEscapeInventoryComponent, EscapeDoAfterComplete>(OnEscapeComplete);
|
||||
SubscribeLocalEvent<CanEscapeInventoryComponent, EscapeDoAfterCancel>(OnEscapeFail);
|
||||
SubscribeLocalEvent<CanEscapeInventoryComponent, DroppedEvent>(OnDropped);
|
||||
}
|
||||
|
||||
private void OnRelayMovement(EntityUid uid, CanEscapeInventoryComponent component, ref MoveInputEvent args)
|
||||
{
|
||||
//Prevents the user from creating multiple DoAfters if they're already resisting.
|
||||
if (component.IsResisting == true)
|
||||
if (component.CancelToken != null)
|
||||
return;
|
||||
|
||||
if (_containerSystem.TryGetContainingContainer(uid, out var container)
|
||||
&& (HasComp<SharedStorageComponent>(container.Owner) || HasComp<InventoryComponent>(container.Owner) || HasComp<SharedHandsComponent>(container.Owner)))
|
||||
if (!_containerSystem.TryGetContainingContainer(uid, out var container) || !_actionBlockerSystem.CanInteract(uid, container.Owner))
|
||||
return;
|
||||
|
||||
// Contested
|
||||
if (_handsSystem.IsHolding(container.Owner, uid, out var inHand))
|
||||
{
|
||||
if (_actionBlockerSystem.CanInteract(uid, container.Owner))
|
||||
AttemptEscape(uid, container.Owner, component);
|
||||
var contestResults = _contests.MassContest(uid, container.Owner);
|
||||
|
||||
// Inverse if we aren't going to divide by 0, otherwise just use a default multiplier of 1.
|
||||
if (contestResults != 0)
|
||||
contestResults = 1 / contestResults;
|
||||
else
|
||||
contestResults = 1;
|
||||
|
||||
if (contestResults >= MaximumMassDisadvantage)
|
||||
return;
|
||||
|
||||
AttemptEscape(uid, container.Owner, component, contestResults);
|
||||
return;
|
||||
}
|
||||
|
||||
// Uncontested
|
||||
if (HasComp<SharedStorageComponent>(container.Owner) || HasComp<InventoryComponent>(container.Owner))
|
||||
AttemptEscape(uid, container.Owner, component);
|
||||
}
|
||||
|
||||
private void OnMoveAttempt(EntityUid uid, CanEscapeInventoryComponent component, UpdateCanMoveEvent args)
|
||||
@@ -48,10 +74,10 @@ public sealed class EscapeInventorySystem : EntitySystem
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void AttemptEscape(EntityUid user, EntityUid container, CanEscapeInventoryComponent component)
|
||||
private void AttemptEscape(EntityUid user, EntityUid container, CanEscapeInventoryComponent component, float multiplier = 1f)
|
||||
{
|
||||
component.CancelToken = new();
|
||||
var doAfterEventArgs = new DoAfterEventArgs(user, component.ResistTime, component.CancelToken.Token, container)
|
||||
var doAfterEventArgs = new DoAfterEventArgs(user, component.BaseResistTime * multiplier, component.CancelToken.Token, container)
|
||||
{
|
||||
BreakOnTargetMove = false,
|
||||
BreakOnUserMove = false,
|
||||
@@ -62,7 +88,6 @@ public sealed class EscapeInventorySystem : EntitySystem
|
||||
UserCancelledEvent = new EscapeDoAfterCancel(),
|
||||
};
|
||||
|
||||
component.IsResisting = true;
|
||||
_popupSystem.PopupEntity(Loc.GetString("escape-inventory-component-start-resisting"), user, Filter.Entities(user));
|
||||
_popupSystem.PopupEntity(Loc.GetString("escape-inventory-component-start-resisting-target"), container, Filter.Entities(container));
|
||||
_doAfterSystem.DoAfter(doAfterEventArgs);
|
||||
@@ -72,12 +97,17 @@ public sealed class EscapeInventorySystem : EntitySystem
|
||||
{
|
||||
//Drops the mob on the tile below the container
|
||||
Transform(uid).AttachParentToContainerOrGrid(EntityManager);
|
||||
component.IsResisting = false;
|
||||
component.CancelToken = null;
|
||||
}
|
||||
|
||||
private void OnEscapeFail(EntityUid uid, CanEscapeInventoryComponent component, EscapeDoAfterCancel ev)
|
||||
{
|
||||
component.IsResisting = false;
|
||||
component.CancelToken = null;
|
||||
}
|
||||
|
||||
private void OnDropped(EntityUid uid, CanEscapeInventoryComponent component, DroppedEvent args)
|
||||
{
|
||||
component.CancelToken?.Cancel();
|
||||
}
|
||||
|
||||
private sealed class EscapeDoAfterComplete : EntityEventArgs { }
|
||||
|
||||
14
Content.Server/Shuttles/Components/IFFConsoleComponent.cs
Normal file
14
Content.Server/Shuttles/Components/IFFConsoleComponent.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Shared.Shuttles.Components;
|
||||
|
||||
namespace Content.Server.Shuttles.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(ShuttleSystem))]
|
||||
public sealed class IFFConsoleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Flags that this console is allowed to set.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("allowedFlags")]
|
||||
public IFFFlags AllowedFlags = IFFFlags.HideLabel;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Content.Server.Shuttles.Systems;
|
||||
|
||||
namespace Content.Server.Shuttles.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when <see cref="ShuttleSystem.FasterThanLight"/> has completed FTL Travel.
|
||||
/// </summary>
|
||||
public sealed class HyperspaceJumpCompletedEvent: EntityEventArgs {}
|
||||
@@ -13,6 +13,7 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Shuttles.Events;
|
||||
|
||||
namespace Content.Server.Shuttles.Systems;
|
||||
|
||||
@@ -307,6 +308,7 @@ public sealed partial class ShuttleSystem
|
||||
comp.State = FTLState.Cooldown;
|
||||
comp.Accumulator += FTLCooldown;
|
||||
_console.RefreshShuttleConsoles(comp.Owner);
|
||||
RaiseLocalEvent(new HyperspaceJumpCompletedEvent());
|
||||
break;
|
||||
case FTLState.Cooldown:
|
||||
RemComp<FTLComponent>(comp.Owner);
|
||||
|
||||
91
Content.Server/Shuttles/Systems/ShuttleSystem.IFF.cs
Normal file
91
Content.Server/Shuttles/Systems/ShuttleSystem.IFF.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Shared.Shuttles.BUIStates;
|
||||
using Content.Shared.Shuttles.Components;
|
||||
using Content.Shared.Shuttles.Events;
|
||||
|
||||
namespace Content.Server.Shuttles.Systems;
|
||||
|
||||
public sealed partial class ShuttleSystem
|
||||
{
|
||||
private void InitializeIFF()
|
||||
{
|
||||
SubscribeLocalEvent<IFFConsoleComponent, AnchorStateChangedEvent>(OnIFFConsoleAnchor);
|
||||
SubscribeLocalEvent<IFFConsoleComponent, IFFShowIFFMessage>(OnIFFShow);
|
||||
SubscribeLocalEvent<IFFConsoleComponent, IFFShowVesselMessage>(OnIFFShowVessel);
|
||||
}
|
||||
|
||||
private void OnIFFShow(EntityUid uid, IFFConsoleComponent component, IFFShowIFFMessage args)
|
||||
{
|
||||
if (!TryComp<TransformComponent>(uid, out var xform) || xform.GridUid == null ||
|
||||
(component.AllowedFlags & IFFFlags.HideLabel) == 0x0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args.Show)
|
||||
{
|
||||
AddIFFFlag(xform.GridUid.Value, IFFFlags.HideLabel);
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveIFFFlag(xform.GridUid.Value, IFFFlags.HideLabel);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnIFFShowVessel(EntityUid uid, IFFConsoleComponent component, IFFShowVesselMessage args)
|
||||
{
|
||||
if (!TryComp<TransformComponent>(uid, out var xform) || xform.GridUid == null ||
|
||||
(component.AllowedFlags & IFFFlags.Hide) == 0x0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args.Show)
|
||||
{
|
||||
AddIFFFlag(xform.GridUid.Value, IFFFlags.Hide);
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveIFFFlag(xform.GridUid.Value, IFFFlags.Hide);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnIFFConsoleAnchor(EntityUid uid, IFFConsoleComponent component, ref AnchorStateChangedEvent args)
|
||||
{
|
||||
// If we anchor / re-anchor then make sure flags up to date.
|
||||
if (!args.Anchored ||
|
||||
!TryComp<TransformComponent>(uid, out var xform) ||
|
||||
!TryComp<IFFComponent>(xform.GridUid, out var iff))
|
||||
{
|
||||
_uiSystem.TrySetUiState(uid, IFFConsoleUiKey.Key, new IFFConsoleBoundUserInterfaceState()
|
||||
{
|
||||
AllowedFlags = component.AllowedFlags,
|
||||
Flags = IFFFlags.None,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_uiSystem.TrySetUiState(uid, IFFConsoleUiKey.Key, new IFFConsoleBoundUserInterfaceState()
|
||||
{
|
||||
AllowedFlags = component.AllowedFlags,
|
||||
Flags = iff.Flags,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateIFFInterfaces(EntityUid gridUid, IFFComponent component)
|
||||
{
|
||||
base.UpdateIFFInterfaces(gridUid, component);
|
||||
foreach (var (comp, xform) in EntityQuery<IFFConsoleComponent, TransformComponent>(true))
|
||||
{
|
||||
if (xform.GridUid != gridUid)
|
||||
continue;
|
||||
|
||||
_uiSystem.TrySetUiState(comp.Owner, IFFConsoleUiKey.Key, new IFFConsoleBoundUserInterfaceState()
|
||||
{
|
||||
AllowedFlags = comp.AllowedFlags,
|
||||
Flags = component.Flags,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ namespace Content.Server.Shuttles.Systems
|
||||
InitializeEmergencyConsole();
|
||||
InitializeEscape();
|
||||
InitializeFTL();
|
||||
InitializeIFF();
|
||||
|
||||
SubscribeLocalEvent<ShuttleComponent, ComponentAdd>(OnShuttleAdd);
|
||||
SubscribeLocalEvent<ShuttleComponent, ComponentStartup>(OnShuttleStartup);
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Content.Server.Singularity.Components
|
||||
// For the "emitter fired" sound
|
||||
public const float Variation = 0.25f;
|
||||
public const float Volume = 0.5f;
|
||||
public const float Distance = 3f;
|
||||
public const float Distance = 6f;
|
||||
|
||||
[ViewVariables] public int FireShotCounter;
|
||||
|
||||
|
||||
10
Content.Server/Sound/Components/EmitSoundOnDropComponent.cs
Normal file
10
Content.Server/Sound/Components/EmitSoundOnDropComponent.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Content.Server.Sound.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple sound emitter that emits sound on entity drop
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class EmitSoundOnDropComponent : BaseEmitSoundComponent
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Content.Server.Sound.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple sound emitter that emits sound on entity pickup
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class EmitSoundOnPickupComponent : BaseEmitSoundComponent
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,10 @@ using Content.Server.Sound.Components;
|
||||
using Content.Server.Throwing;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Throwing;
|
||||
using JetBrains.Annotations;
|
||||
@@ -58,6 +60,8 @@ namespace Content.Server.Sound
|
||||
SubscribeLocalEvent<EmitSoundOnActivateComponent, ActivateInWorldEvent>(HandleEmitSoundOnActivateInWorld);
|
||||
SubscribeLocalEvent<EmitSoundOnTriggerComponent, TriggerEvent>(HandleEmitSoundOnTrigger);
|
||||
SubscribeLocalEvent<EmitSoundOnUIOpenComponent, AfterActivatableUIOpenEvent>(HandleEmitSoundOnUIOpen);
|
||||
SubscribeLocalEvent<EmitSoundOnPickupComponent, GotEquippedHandEvent>(HandleEmitSoundOnPickup);
|
||||
SubscribeLocalEvent<EmitSoundOnDropComponent, DroppedEvent>(HandleEmitSoundOnDrop);
|
||||
}
|
||||
|
||||
private void HandleEmitSoundOnTrigger(EntityUid uid, EmitSoundOnTriggerComponent component, TriggerEvent args)
|
||||
@@ -106,6 +110,16 @@ namespace Content.Server.Sound
|
||||
TryEmitSound(component);
|
||||
}
|
||||
|
||||
private void HandleEmitSoundOnPickup(EntityUid uid, EmitSoundOnPickupComponent component, GotEquippedHandEvent args)
|
||||
{
|
||||
TryEmitSound(component);
|
||||
}
|
||||
|
||||
private void HandleEmitSoundOnDrop(EntityUid uid, EmitSoundOnDropComponent component, DroppedEvent args)
|
||||
{
|
||||
TryEmitSound(component);
|
||||
}
|
||||
|
||||
private void TryEmitSound(BaseEmitSoundComponent component)
|
||||
{
|
||||
_audioSystem.PlayPvs(component.Sound, component.Owner, component.Sound.Params.AddVolume(-2f));
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace Content.Server.Sprite.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class RandomSpriteColorComponent : Component
|
||||
{
|
||||
// This should handle random states + colors for layers.
|
||||
// Saame with RandomSpriteState
|
||||
[DataField("selected")] public string? SelectedColor;
|
||||
[DataField("state")] public string BaseState = "error";
|
||||
|
||||
[DataField("colors")] public readonly Dictionary<string, Color> Colors = new();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace Content.Server.Sprite.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class RandomSpriteStateComponent : Component
|
||||
{
|
||||
[DataField("spriteStates")] public List<string>? SpriteStates;
|
||||
|
||||
[DataField("spriteLayer")] public int SpriteLayer;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,53 @@
|
||||
using Content.Server.Sprite.Components;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Robust.Server.GameObjects;
|
||||
using System.Linq;
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Sprite;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Sprite;
|
||||
|
||||
public sealed class RandomSpriteSystem: EntitySystem
|
||||
public sealed class RandomSpriteSystem: SharedRandomSpriteSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<RandomSpriteColorComponent, ComponentStartup>(OnSpriteColorStartup);
|
||||
SubscribeLocalEvent<RandomSpriteColorComponent, MapInitEvent>(OnSpriteColorMapInit);
|
||||
|
||||
SubscribeLocalEvent<RandomSpriteStateComponent, MapInitEvent>(OnSpriteStateMapInit);
|
||||
SubscribeLocalEvent<RandomSpriteComponent, ComponentGetState>(OnGetState);
|
||||
SubscribeLocalEvent<RandomSpriteComponent, MapInitEvent>(OnMapInit);
|
||||
}
|
||||
|
||||
private void OnSpriteColorStartup(EntityUid uid, RandomSpriteColorComponent component, ComponentStartup args)
|
||||
private void OnMapInit(EntityUid uid, RandomSpriteComponent component, MapInitEvent args)
|
||||
{
|
||||
UpdateColor(component);
|
||||
if (component.Selected.Count > 0)
|
||||
return;
|
||||
|
||||
if (component.Available.Count == 0)
|
||||
return;
|
||||
|
||||
var group = _random.Pick(component.Available);
|
||||
component.Selected.EnsureCapacity(group.Count);
|
||||
|
||||
foreach (var layer in group)
|
||||
{
|
||||
Color? color = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(layer.Value.Color))
|
||||
color = _random.Pick(_prototype.Index<ColorPalettePrototype>(layer.Value.Color).Colors.Values);
|
||||
|
||||
component.Selected.Add(layer.Key, (layer.Value.State, color));
|
||||
}
|
||||
|
||||
Dirty(component);
|
||||
}
|
||||
|
||||
private void OnSpriteColorMapInit(EntityUid uid, RandomSpriteColorComponent component, MapInitEvent args)
|
||||
private void OnGetState(EntityUid uid, RandomSpriteComponent component, ref ComponentGetState args)
|
||||
{
|
||||
component.SelectedColor = _random.Pick(component.Colors.Keys);
|
||||
UpdateColor(component);
|
||||
}
|
||||
|
||||
private void OnSpriteStateMapInit(EntityUid uid, RandomSpriteStateComponent component, MapInitEvent args)
|
||||
{
|
||||
if (component.SpriteStates == null) return;
|
||||
if (!TryComp<SpriteComponent>(uid, out var spriteComponent)) return;
|
||||
spriteComponent.LayerSetState(component.SpriteLayer, _random.Pick(component.SpriteStates));
|
||||
}
|
||||
|
||||
private void UpdateColor(RandomSpriteColorComponent component)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(component.Owner, out var spriteComponent) || component.SelectedColor == null) return;
|
||||
|
||||
spriteComponent.LayerSetState(0, component.BaseState);
|
||||
spriteComponent.LayerSetColor(0, component.Colors[component.SelectedColor]);
|
||||
args.State = new RandomSpriteColorComponentState()
|
||||
{
|
||||
Selected = component.Selected,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.StationEvents
|
||||
{
|
||||
@@ -43,16 +42,21 @@ namespace Content.Server.StationEvents
|
||||
|
||||
// Can't just check debug / release for a default given mappers need to use release mode
|
||||
// As such we'll always pause it by default.
|
||||
_configurationManager.OnValueChanged(CCVars.EventsEnabled, value => RuleAdded = value, true);
|
||||
_configurationManager.OnValueChanged(CCVars.EventsEnabled, SetEnabled, true);
|
||||
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
|
||||
}
|
||||
|
||||
public override void Started()
|
||||
public override void Shutdown()
|
||||
{
|
||||
if (!_configurationManager.GetCVar(CCVars.EventsEnabled))
|
||||
RuleAdded = false;
|
||||
base.Shutdown();
|
||||
_configurationManager.UnsubValueChanged(CCVars.EventsEnabled, SetEnabled);
|
||||
}
|
||||
|
||||
public bool EventsEnabled { get; private set; }
|
||||
private void SetEnabled(bool value) => EventsEnabled = value;
|
||||
|
||||
public override void Started() { }
|
||||
public override void Ended() { }
|
||||
|
||||
/// <summary>
|
||||
@@ -86,7 +90,7 @@ namespace Content.Server.StationEvents
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!RuleStarted)
|
||||
if (!RuleStarted || !EventsEnabled)
|
||||
return;
|
||||
|
||||
if (_timeUntilNextEvent > 0)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using Content.Shared.StationRecords;
|
||||
|
||||
namespace Content.Server.StationRecords;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class GeneralStationRecordConsoleComponent : Component
|
||||
{
|
||||
public StationRecordKey? ActiveKey { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Content.Shared.StationRecords;
|
||||
|
||||
namespace Content.Server.StationRecords;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class StationRecordKeyStorageComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The key stored in this component.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public StationRecordKey? Key;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Content.Server.StationRecords;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class StationRecordsComponent : Component
|
||||
{
|
||||
// Every single record in this station, by key.
|
||||
// Essentially a columnar database, but I really suck
|
||||
// at implementing that so
|
||||
[ViewVariables]
|
||||
public StationRecordSet Records = new();
|
||||
}
|
||||
158
Content.Server/StationRecords/StationRecordSet.cs
Normal file
158
Content.Server/StationRecords/StationRecordSet.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.StationRecords;
|
||||
|
||||
namespace Content.Server.StationRecords;
|
||||
|
||||
/// <summary>
|
||||
/// Set of station records. StationRecordsComponent stores these.
|
||||
/// Keyed by StationRecordKey, which should be obtained from
|
||||
/// an entity that stores a reference to it.
|
||||
/// </summary>
|
||||
public sealed class StationRecordSet
|
||||
{
|
||||
private uint _currentRecordId;
|
||||
|
||||
private HashSet<StationRecordKey> _keys = new();
|
||||
|
||||
private HashSet<StationRecordKey> _recentlyAccessed = new();
|
||||
|
||||
[ViewVariables]
|
||||
private Dictionary<Type, Dictionary<StationRecordKey, object>> _tables = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets all records of a specific type stored in the record set.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of record to fetch.</typeparam>
|
||||
/// <returns>An enumerable object that contains a pair of both a station key, and the record associated with it.</returns>
|
||||
public IEnumerable<(StationRecordKey, T)> GetRecordsOfType<T>()
|
||||
{
|
||||
if (!_tables.ContainsKey(typeof(T)))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var (key, entry) in _tables[typeof(T)])
|
||||
{
|
||||
if (entry is not T cast)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_recentlyAccessed.Add(key);
|
||||
|
||||
yield return (key, cast);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new record into this set of entries.
|
||||
/// </summary>
|
||||
/// <param name="station">Station that we're adding the record for.</param>
|
||||
/// <returns>A key that represents the record in this set.</returns>
|
||||
public StationRecordKey AddRecord(EntityUid station)
|
||||
{
|
||||
var key = new StationRecordKey(_currentRecordId++, station);
|
||||
|
||||
_keys.Add(key);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an entry into a record.
|
||||
/// </summary>
|
||||
/// <param name="key">Key for the record.</param>
|
||||
/// <param name="entry">Entry to add.</param>
|
||||
/// <typeparam name="T">Type of the entry that's being added.</typeparam>
|
||||
public void AddRecordEntry<T>(StationRecordKey key, T entry)
|
||||
{
|
||||
if (!_keys.Contains(key) || entry == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_tables.TryGetValue(typeof(T), out var table))
|
||||
{
|
||||
table = new();
|
||||
_tables.Add(typeof(T), table);
|
||||
}
|
||||
|
||||
table.Add(key, entry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to get an record entry by type, from this record key.
|
||||
/// </summary>
|
||||
/// <param name="key">The StationRecordKey to get the entries from.</param>
|
||||
/// <param name="entry">The entry that is retrieved from the record set.</param>
|
||||
/// <typeparam name="T">The type of entry to search for.</typeparam>
|
||||
/// <returns>True if the record exists and was retrieved, false otherwise.</returns>
|
||||
public bool TryGetRecordEntry<T>(StationRecordKey key, [NotNullWhen(true)] out T? entry)
|
||||
{
|
||||
entry = default;
|
||||
|
||||
if (!_keys.Contains(key)
|
||||
|| !_tables.TryGetValue(typeof(T), out var table)
|
||||
|| !table.TryGetValue(key, out var entryObject))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
entry = (T) entryObject;
|
||||
_recentlyAccessed.Add(key);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the record associated with this key has an entry of a certain type.
|
||||
/// </summary>
|
||||
/// <param name="key">The record key.</param>
|
||||
/// <typeparam name="T">Type to check.</typeparam>
|
||||
/// <returns>True if the entry exists, false otherwise.</returns>
|
||||
public bool HasRecordEntry<T>(StationRecordKey key)
|
||||
{
|
||||
return _keys.Contains(key)
|
||||
&& _tables.TryGetValue(typeof(T), out var table)
|
||||
&& table.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the recently accessed keys from this record set.
|
||||
/// </summary>
|
||||
/// <returns>All recently accessed keys from this record set.</returns>
|
||||
public IEnumerable<StationRecordKey> GetRecentlyAccessed()
|
||||
{
|
||||
return _recentlyAccessed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the recently accessed keys from the set.
|
||||
/// </summary>
|
||||
public void ClearRecentlyAccessed()
|
||||
{
|
||||
_recentlyAccessed.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all record entries related to this key from this set.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to remove.</param>
|
||||
/// <returns>True if successful, false otherwise.</returns>
|
||||
public bool RemoveAllRecords(StationRecordKey key)
|
||||
{
|
||||
if (!_keys.Remove(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var table in _tables.Values)
|
||||
{
|
||||
table.Remove(key);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server.StationRecords.Systems;
|
||||
|
||||
public sealed class GeneralStationRecordConsoleSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
[Dependency] private readonly StationRecordsSystem _stationRecordsSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<GeneralStationRecordConsoleComponent, BoundUIOpenedEvent>(UpdateUserInterface);
|
||||
SubscribeLocalEvent<GeneralStationRecordConsoleComponent, SelectGeneralStationRecord>(OnKeySelected);
|
||||
SubscribeLocalEvent<GeneralStationRecordConsoleComponent, RecordModifiedEvent>(UpdateUserInterface);
|
||||
SubscribeLocalEvent<GeneralStationRecordConsoleComponent, AfterGeneralRecordCreatedEvent>(UpdateUserInterface);
|
||||
}
|
||||
|
||||
private void UpdateUserInterface<T>(EntityUid uid, GeneralStationRecordConsoleComponent component, T ev)
|
||||
{
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
private void OnKeySelected(EntityUid uid, GeneralStationRecordConsoleComponent component,
|
||||
SelectGeneralStationRecord msg)
|
||||
{
|
||||
component.ActiveKey = msg.SelectedKey;
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
private void UpdateUserInterface(EntityUid uid, GeneralStationRecordConsoleComponent? console = null)
|
||||
{
|
||||
if (!Resolve(uid, ref console))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var owningStation = _stationSystem.GetOwningStation(uid);
|
||||
|
||||
|
||||
|
||||
if (!TryComp<StationRecordsComponent>(owningStation, out var stationRecordsComponent))
|
||||
{
|
||||
_userInterface.GetUiOrNull(uid, GeneralStationRecordConsoleKey.Key)?.SetState(new GeneralStationRecordConsoleState(null, null, null));
|
||||
return;
|
||||
}
|
||||
|
||||
var enumerator = _stationRecordsSystem.GetRecordsOfType<GeneralStationRecord>(owningStation.Value, stationRecordsComponent);
|
||||
|
||||
var listing = new Dictionary<StationRecordKey, string>();
|
||||
foreach (var pair in enumerator)
|
||||
{
|
||||
listing.Add(pair.Item1, pair.Item2.Name);
|
||||
}
|
||||
|
||||
if (listing.Count == 0)
|
||||
{
|
||||
_userInterface.GetUiOrNull(uid, GeneralStationRecordConsoleKey.Key)?.SetState(new GeneralStationRecordConsoleState(null, null, null));
|
||||
return;
|
||||
}
|
||||
|
||||
GeneralStationRecord? record = null;
|
||||
if (console.ActiveKey != null)
|
||||
{
|
||||
_stationRecordsSystem.TryGetRecord(owningStation.Value, console.ActiveKey.Value, out record,
|
||||
stationRecordsComponent);
|
||||
}
|
||||
|
||||
_userInterface
|
||||
.GetUiOrNull(uid, GeneralStationRecordConsoleKey.Key)?
|
||||
.SetState(new GeneralStationRecordConsoleState(console.ActiveKey, record, listing));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using Content.Shared.StationRecords;
|
||||
|
||||
namespace Content.Server.StationRecords.Systems;
|
||||
|
||||
public sealed class StationRecordKeyStorageSystem : EntitySystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Assigns a station record key to an entity.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="keyStorage"></param>
|
||||
public void AssignKey(EntityUid uid, StationRecordKey key, StationRecordKeyStorageComponent? keyStorage = null)
|
||||
{
|
||||
if (!Resolve(uid, ref keyStorage))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
keyStorage.Key = key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a station record key from an entity.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="keyStorage"></param>
|
||||
/// <returns></returns>
|
||||
public StationRecordKey? RemoveKey(EntityUid uid, StationRecordKeyStorageComponent? keyStorage = null)
|
||||
{
|
||||
if (!Resolve(uid, ref keyStorage) || keyStorage.Key == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var key = keyStorage.Key;
|
||||
keyStorage.Key = null;
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an entity currently contains a station record key.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="keyStorage"></param>
|
||||
/// <returns></returns>
|
||||
public bool CheckKey(EntityUid uid, StationRecordKeyStorageComponent? keyStorage = null)
|
||||
{
|
||||
if (!Resolve(uid, ref keyStorage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return keyStorage.Key != null;
|
||||
}
|
||||
}
|
||||
282
Content.Server/StationRecords/Systems/StationRecordsSystem.cs
Normal file
282
Content.Server/StationRecords/Systems/StationRecordsSystem.cs
Normal file
@@ -0,0 +1,282 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Access.Systems;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.StationRecords;
|
||||
using Content.Server.StationRecords.Systems;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
/// <summary>
|
||||
/// Station records.
|
||||
///
|
||||
/// A station record is tied to an ID card, or anything that holds
|
||||
/// a station record's key. This key will determine access to a
|
||||
/// station record set's record entries, and it is imperative not
|
||||
/// to lose the item that holds the key under any circumstance.
|
||||
///
|
||||
/// Records are mostly a roleplaying tool, but can have some
|
||||
/// functionality as well (i.e., security records indicating that
|
||||
/// a specific person holding an ID card with a linked key is
|
||||
/// currently under warrant, showing a crew manifest with user
|
||||
/// settable, custom titles).
|
||||
///
|
||||
/// General records are tied into this system, as most crewmembers
|
||||
/// should have a general record - and most systems should probably
|
||||
/// depend on this general record being created. This is subject
|
||||
/// to change.
|
||||
/// </summary>
|
||||
public sealed class StationRecordsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
[Dependency] private readonly StationRecordKeyStorageSystem _keyStorageSystem = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<StationInitializedEvent>(OnStationInitialize);
|
||||
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnPlayerSpawn);
|
||||
}
|
||||
|
||||
private void OnStationInitialize(StationInitializedEvent args)
|
||||
{
|
||||
AddComp<StationRecordsComponent>(args.Station);
|
||||
}
|
||||
|
||||
private void OnPlayerSpawn(PlayerSpawnCompleteEvent args)
|
||||
{
|
||||
CreateGeneralRecord(args.Station, args.Mob, args.Profile, args.JobId);
|
||||
}
|
||||
|
||||
private void CreateGeneralRecord(EntityUid station, EntityUid player, HumanoidCharacterProfile profile,
|
||||
string? jobId, StationRecordsComponent? records = null)
|
||||
{
|
||||
if (!Resolve(station, ref records)
|
||||
|| String.IsNullOrEmpty(jobId)
|
||||
|| !_prototypeManager.HasIndex<JobPrototype>(jobId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_inventorySystem.TryGetSlotEntity(player, "id", out var idUid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CreateGeneralRecord(station, idUid.Value, profile.Name, profile.Age, profile.Species, profile.Gender, jobId, profile, records);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a general record to store in a station's record set.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is tied into the record system, as any crew member's
|
||||
/// records should generally be dependent on some generic
|
||||
/// record with the bare minimum of information involved.
|
||||
/// </remarks>
|
||||
/// <param name="station">The entity uid of the station.</param>
|
||||
/// <param name="idUid">The entity uid of an entity's ID card. Can be null.</param>
|
||||
/// <param name="name">Name of the character.</param>
|
||||
/// <param name="species">Species of the character.</param>
|
||||
/// <param name="gender">Gender of the character.</param>
|
||||
/// <param name="jobId">
|
||||
/// The job to initially tie this record to. This must be a valid job loaded in, otherwise
|
||||
/// this call will cause an exception. Ensure that a general record starts out with a job
|
||||
/// that is currently a valid job prototype.
|
||||
/// </param>
|
||||
/// <param name="profile">
|
||||
/// Profile for the related player. This is so that other systems can get further information
|
||||
/// about the player character.
|
||||
/// Optional - other systems should anticipate this.
|
||||
/// </param>
|
||||
/// <param name="records">Station records component.</param>
|
||||
public void CreateGeneralRecord(EntityUid station, EntityUid? idUid, string name, int age, string species, Gender gender, string jobId, HumanoidCharacterProfile? profile = null,
|
||||
StationRecordsComponent? records = null)
|
||||
{
|
||||
if (!Resolve(station, ref records))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_prototypeManager.TryIndex(jobId, out JobPrototype? jobPrototype))
|
||||
{
|
||||
throw new ArgumentException($"Invalid job prototype ID: {jobId}");
|
||||
}
|
||||
|
||||
var record = new GeneralStationRecord()
|
||||
{
|
||||
Name = name,
|
||||
Age = age,
|
||||
JobTitle = jobPrototype.Name,
|
||||
JobIcon = jobPrototype.Icon,
|
||||
JobPrototype = jobId,
|
||||
Species = species,
|
||||
Gender = gender,
|
||||
DisplayPriority = jobPrototype.Weight
|
||||
};
|
||||
|
||||
var key = records.Records.AddRecord(station);
|
||||
records.Records.AddRecordEntry(key, record);
|
||||
// entry.Entries.Add(typeof(GeneralStationRecord), record);
|
||||
|
||||
if (idUid != null)
|
||||
{
|
||||
var keyStorageEntity = idUid;
|
||||
if (TryComp(idUid, out PDAComponent? pdaComponent) && pdaComponent.ContainedID != null)
|
||||
{
|
||||
keyStorageEntity = pdaComponent.IdSlot.Item;
|
||||
}
|
||||
|
||||
if (keyStorageEntity != null)
|
||||
{
|
||||
_keyStorageSystem.AssignKey(keyStorageEntity.Value, key);
|
||||
}
|
||||
}
|
||||
|
||||
RaiseLocalEvent(new AfterGeneralRecordCreatedEvent(key, record, profile));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a record from this station.
|
||||
/// </summary>
|
||||
/// <param name="station">Station to remove the record from.</param>
|
||||
/// <param name="key">The key to remove.</param>
|
||||
/// <param name="records">Station records component.</param>
|
||||
/// <returns>True if the record was removed, false otherwise.</returns>
|
||||
public bool RemoveRecord(EntityUid station, StationRecordKey key, StationRecordsComponent? records = null)
|
||||
{
|
||||
if (station != key.OriginStation || !Resolve(station, ref records))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RaiseLocalEvent(new RecordRemovedEvent(key));
|
||||
|
||||
return records.Records.RemoveAllRecords(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to get a record from this station's record entries,
|
||||
/// from the provided station record key. Will always return
|
||||
/// null if the key does not match the station.
|
||||
/// </summary>
|
||||
/// <param name="station">Station to get the record from.</param>
|
||||
/// <param name="key">Key to try and index from the record set.</param>
|
||||
/// <param name="entry">The resulting entry.</param>
|
||||
/// <param name="records">Station record component.</param>
|
||||
/// <typeparam name="T">Type to get from the record set.</typeparam>
|
||||
/// <returns>True if the record was obtained, false otherwise.</returns>
|
||||
public bool TryGetRecord<T>(EntityUid station, StationRecordKey key, [NotNullWhen(true)] out T? entry, StationRecordsComponent? records = null)
|
||||
{
|
||||
entry = default;
|
||||
|
||||
if (key.OriginStation != station || !Resolve(station, ref records))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return records.Records.TryGetRecordEntry(key, out entry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all records of a specific type from a station.
|
||||
/// </summary>
|
||||
/// <param name="station">The station to get the records from.</param>
|
||||
/// <param name="records">Station records component.</param>
|
||||
/// <typeparam name="T">Type of record to fetch</typeparam>
|
||||
/// <returns>Enumerable of pairs with a station record key, and the entry in question of type T.</returns>
|
||||
public IEnumerable<(StationRecordKey, T)> GetRecordsOfType<T>(EntityUid station, StationRecordsComponent? records = null)
|
||||
{
|
||||
if (!Resolve(station, ref records))
|
||||
{
|
||||
return new (StationRecordKey, T)[]{};
|
||||
}
|
||||
|
||||
return records.Records.GetRecordsOfType<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes a station's records with any systems that need it.
|
||||
/// </summary>
|
||||
/// <param name="station">The station to synchronize any recently accessed records with..</param>
|
||||
/// <param name="records">Station records component.</param>
|
||||
public void Synchronize(EntityUid station, StationRecordsComponent? records = null)
|
||||
{
|
||||
if (!Resolve(station, ref records))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var key in records.Records.GetRecentlyAccessed())
|
||||
{
|
||||
RaiseLocalEvent(new RecordModifiedEvent(key));
|
||||
}
|
||||
|
||||
records.Records.ClearRecentlyAccessed();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised after the player's general profile is created.
|
||||
/// Systems that modify records on a station would have more use
|
||||
/// listening to this event, as it contains the character's record key.
|
||||
/// Also stores the general record reference, to save some time.
|
||||
/// </summary>
|
||||
public sealed class AfterGeneralRecordCreatedEvent : EntityEventArgs
|
||||
{
|
||||
public StationRecordKey Key { get; }
|
||||
public GeneralStationRecord Record { get; }
|
||||
/// <summary>
|
||||
/// Profile for the related player. This is so that other systems can get further information
|
||||
/// about the player character.
|
||||
/// Optional - other systems should anticipate this.
|
||||
/// </summary>
|
||||
public HumanoidCharacterProfile? Profile { get; }
|
||||
|
||||
public AfterGeneralRecordCreatedEvent(StationRecordKey key, GeneralStationRecord record, HumanoidCharacterProfile? profile)
|
||||
{
|
||||
Key = key;
|
||||
Record = record;
|
||||
Profile = profile;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised after a record is removed. Only the key is given
|
||||
/// when the record is removed, so that any relevant systems/components
|
||||
/// that store record keys can then remove the key from their internal
|
||||
/// fields.
|
||||
/// </summary>
|
||||
public sealed class RecordRemovedEvent : EntityEventArgs
|
||||
{
|
||||
public StationRecordKey Key { get; }
|
||||
|
||||
public RecordRemovedEvent(StationRecordKey key)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised after a record is modified. This is to
|
||||
/// inform other systems that records stored in this key
|
||||
/// may have changed.
|
||||
/// </summary>
|
||||
public sealed class RecordModifiedEvent : EntityEventArgs
|
||||
{
|
||||
public StationRecordKey Key { get; }
|
||||
|
||||
public RecordModifiedEvent(StationRecordKey key)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user