mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-02-14 23:14:45 +01:00
upstream remote merge
This commit is contained in:
@@ -25,7 +25,7 @@ namespace Content.Client.Access.UI
|
||||
public void SetAccessLevels(IPrototypeManager protoManager, List<ProtoId<AccessLevelPrototype>> accessLevels)
|
||||
{
|
||||
_accessButtons.Clear();
|
||||
AccessLevelGrid.DisposeAllChildren();
|
||||
AccessLevelGrid.RemoveAllChildren();
|
||||
|
||||
foreach (var access in accessLevels)
|
||||
{
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Content.Client.Access.UI
|
||||
|
||||
public void SetAllowedIcons(string currentJobIconId)
|
||||
{
|
||||
IconGrid.DisposeAllChildren();
|
||||
IconGrid.RemoveAllChildren();
|
||||
|
||||
var jobIconButtonGroup = new ButtonGroup();
|
||||
var i = 0;
|
||||
|
||||
@@ -99,8 +99,8 @@ public sealed partial class GroupedAccessLevelChecklist : BoxContainer
|
||||
|
||||
private bool TryRebuildAccessGroupControls()
|
||||
{
|
||||
AccessGroupList.DisposeAllChildren();
|
||||
AccessLevelChecklist.DisposeAllChildren();
|
||||
AccessGroupList.RemoveAllChildren();
|
||||
AccessLevelChecklist.RemoveAllChildren();
|
||||
|
||||
// No access level prototypes were assigned to any of the access level groups.
|
||||
// Either the turret controller has no assigned access levels or their names were invalid.
|
||||
@@ -165,7 +165,7 @@ public sealed partial class GroupedAccessLevelChecklist : BoxContainer
|
||||
/// </summary>
|
||||
public void RebuildAccessLevelsControls()
|
||||
{
|
||||
AccessLevelChecklist.DisposeAllChildren();
|
||||
AccessLevelChecklist.RemoveAllChildren();
|
||||
_accessLevelEntries.Clear();
|
||||
|
||||
// No access level prototypes were assigned to any of the access level groups
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace Content.Client.Actions
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IResourceManager _resources = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
|
||||
public event Action<EntityUid>? OnActionAdded;
|
||||
public event Action<EntityUid>? OnActionRemoved;
|
||||
@@ -286,8 +287,27 @@ namespace Content.Client.Actions
|
||||
continue;
|
||||
}
|
||||
|
||||
if (assignmentNode is SequenceDataNode sequenceAssignments)
|
||||
{
|
||||
try
|
||||
{
|
||||
var nodeAssignments = _serialization.Read<List<(byte Hotbar, byte Slot)>>(sequenceAssignments, notNullableOverride: true);
|
||||
|
||||
foreach (var index in nodeAssignments)
|
||||
{
|
||||
assignments.Add(new SlotAssignment(index.Hotbar, index.Slot, actionId));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Failed to parse action assignments: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
AddActionDirect((user, actions), actionId);
|
||||
}
|
||||
|
||||
AssignSlot?.Invoke(assignments);
|
||||
}
|
||||
|
||||
private void OnWorldTargetAttempt(Entity<WorldTargetActionComponent> ent, ref ActionTargetAttemptEvent args)
|
||||
@@ -309,10 +329,10 @@ namespace Content.Client.Actions
|
||||
// this is the actual entity-world targeting magic
|
||||
EntityUid? targetEnt = null;
|
||||
if (TryComp<EntityTargetActionComponent>(ent, out var entity) &&
|
||||
args.Input.EntityUid != null &&
|
||||
ValidateEntityTarget(user, args.Input.EntityUid, (uid, entity)))
|
||||
args.Input.EntityUid is { Valid: true } entityUid &&
|
||||
ValidateEntityTarget(user, entityUid, (uid, entity)))
|
||||
{
|
||||
targetEnt = args.Input.EntityUid;
|
||||
targetEnt = entityUid;
|
||||
}
|
||||
|
||||
if (action.ClientExclusive)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc admin-camera-window-title-placeholder}"
|
||||
SetSize="425 550"
|
||||
MinSize="200 225"
|
||||
Name="Window">
|
||||
MinSize="200 225">
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace Content.Client.Administration.UI.ManageSolutions
|
||||
/// </summary>
|
||||
public void UpdateReagents()
|
||||
{
|
||||
ReagentList.DisposeAllChildren();
|
||||
ReagentList.RemoveAllChildren();
|
||||
|
||||
if (_selectedSolution == null || _solutions == null)
|
||||
return;
|
||||
@@ -92,7 +92,7 @@ namespace Content.Client.Administration.UI.ManageSolutions
|
||||
/// <param name="solution">The selected solution.</param>
|
||||
private void UpdateVolumeBox(Solution solution)
|
||||
{
|
||||
VolumeBox.DisposeAllChildren();
|
||||
VolumeBox.RemoveAllChildren();
|
||||
|
||||
var volumeLabel = new Label();
|
||||
volumeLabel.HorizontalExpand = true;
|
||||
@@ -131,7 +131,7 @@ namespace Content.Client.Administration.UI.ManageSolutions
|
||||
/// <param name="solution">The selected solution.</param>
|
||||
private void UpdateThermalBox(Solution solution)
|
||||
{
|
||||
ThermalBox.DisposeAllChildren();
|
||||
ThermalBox.RemoveAllChildren();
|
||||
var heatCap = solution.GetHeatCapacity(null);
|
||||
var specificHeatLabel = new Label();
|
||||
specificHeatLabel.HorizontalExpand = true;
|
||||
|
||||
@@ -206,7 +206,7 @@ namespace Content.Client.Cargo.UI
|
||||
if (!_orderConsoleQuery.TryComp(_owner, out var orderConsole))
|
||||
return;
|
||||
|
||||
Requests.DisposeAllChildren();
|
||||
Requests.RemoveAllChildren();
|
||||
|
||||
foreach (var order in orders)
|
||||
{
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Content.Client.Cargo.UI
|
||||
|
||||
public void SetOrders(SpriteSystem sprites, IPrototypeManager protoManager, List<CargoOrderData> orders)
|
||||
{
|
||||
Orders.DisposeAllChildren();
|
||||
Orders.RemoveAllChildren();
|
||||
|
||||
foreach (var order in orders)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Client.CrewManifest.UI;
|
||||
using Content.Client.CrewManifest.UI;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
@@ -21,7 +21,6 @@ public sealed partial class CrewManifestUiFragment : BoxContainer
|
||||
|
||||
public void UpdateState(string stationName, CrewManifestEntries? entries)
|
||||
{
|
||||
CrewManifestListing.DisposeAllChildren();
|
||||
CrewManifestListing.RemoveAllChildren();
|
||||
|
||||
StationNameContainer.Visible = entries != null;
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace Content.Client.Changelog
|
||||
// Changelog is not kept in memory so load it again.
|
||||
var changelogs = await _changelog.LoadChangelog();
|
||||
|
||||
Tabs.DisposeAllChildren();
|
||||
Tabs.RemoveAllChildren();
|
||||
|
||||
var i = 0;
|
||||
foreach (var changelog in changelogs)
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace Content.Client.ContextMenu.UI
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
RootMenu.MenuBody.DisposeAllChildren();
|
||||
RootMenu.MenuBody.RemoveAllChildren();
|
||||
CancelOpen?.Cancel();
|
||||
CancelClose?.Cancel();
|
||||
OnContextClosed?.Invoke();
|
||||
|
||||
@@ -293,7 +293,7 @@ namespace Content.Client.ContextMenu.UI
|
||||
var element = new EntityMenuElement(entity);
|
||||
element.SubMenu = new ContextMenuPopup(_context, element);
|
||||
element.SubMenu.OnPopupOpen += () => _verb.OpenVerbMenu(entity, popup: element.SubMenu);
|
||||
element.SubMenu.OnPopupHide += element.SubMenu.MenuBody.DisposeAllChildren;
|
||||
element.SubMenu.OnPopupHide += element.SubMenu.MenuBody.RemoveAllChildren;
|
||||
_context.AddElement(menu, element);
|
||||
Elements.TryAdd(entity, element);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Content.Client.Crayon.UI
|
||||
private void RefreshList()
|
||||
{
|
||||
// Clear
|
||||
Grids.DisposeAllChildren();
|
||||
Grids.RemoveAllChildren();
|
||||
|
||||
if (_decals == null || _allDecals == null)
|
||||
return;
|
||||
|
||||
@@ -18,7 +18,6 @@ public sealed partial class CrewManifestUi : DefaultWindow
|
||||
|
||||
public void Populate(string name, CrewManifestEntries? entries)
|
||||
{
|
||||
CrewManifestListing.DisposeAllChildren();
|
||||
CrewManifestListing.RemoveAllChildren();
|
||||
|
||||
StationNameContainer.Visible = entries != null;
|
||||
|
||||
@@ -72,7 +72,7 @@ public sealed partial class GatewayWindow : FancyWindow,
|
||||
_isUnlockPending = _nextUnlock >= _timing.CurTime;
|
||||
_isCooldownPending = _nextReady >= _timing.CurTime;
|
||||
|
||||
Container.DisposeAllChildren();
|
||||
Container.RemoveAllChildren();
|
||||
|
||||
if (_destinations.Count == 0)
|
||||
{
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace Content.Client.HealthAnalyzer.UI
|
||||
AlertsContainer.Visible = showAlerts;
|
||||
|
||||
if (showAlerts)
|
||||
AlertsContainer.DisposeAllChildren();
|
||||
AlertsContainer.RemoveAllChildren();
|
||||
|
||||
if (msg.Unrevivable == true)
|
||||
AlertsContainer.AddChild(new RichTextLabel
|
||||
|
||||
@@ -416,7 +416,7 @@ public sealed partial class MarkingPicker : Control
|
||||
|
||||
var stateNames = GetMarkingStateNames(prototype);
|
||||
_currentMarkingColors.Clear();
|
||||
CMarkingColors.DisposeAllChildren();
|
||||
CMarkingColors.RemoveAllChildren();
|
||||
List<ColorSelectorSliders> colorSliders = new();
|
||||
for (int i = 0; i < prototype.Sprites.Count; i++)
|
||||
{
|
||||
|
||||
@@ -226,7 +226,6 @@ public sealed partial class SingleMarkingPicker : BoxContainer
|
||||
|
||||
var marking = _markings[Slot];
|
||||
|
||||
ColorSelectorContainer.DisposeAllChildren();
|
||||
ColorSelectorContainer.RemoveAllChildren();
|
||||
|
||||
if (marking.MarkingColors.Count != proto.Sprites.Count)
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
using Content.Shared.IdentityManagement;
|
||||
|
||||
namespace Content.Client.IdentityManagement;
|
||||
|
||||
public sealed class IdentitySystem : SharedIdentitySystem
|
||||
{
|
||||
}
|
||||
@@ -80,7 +80,7 @@ namespace Content.Client.Lobby.UI
|
||||
public void ReloadCharacterPickers()
|
||||
{
|
||||
_createNewCharacterButton.Orphan();
|
||||
Characters.DisposeAllChildren();
|
||||
Characters.RemoveAllChildren();
|
||||
|
||||
var numberOfFullSlots = 0;
|
||||
var characterButtonsGroup = new ButtonGroup();
|
||||
|
||||
@@ -505,7 +505,7 @@ namespace Content.Client.Lobby.UI
|
||||
/// </summary>
|
||||
public void RefreshTraits()
|
||||
{
|
||||
TraitsList.DisposeAllChildren();
|
||||
TraitsList.RemoveAllChildren();
|
||||
|
||||
var traits = _prototypeManager.EnumeratePrototypes<TraitPrototype>().OrderBy(t => Loc.GetString(t.Name)).ToList();
|
||||
TabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab"));
|
||||
@@ -650,7 +650,7 @@ namespace Content.Client.Lobby.UI
|
||||
|
||||
public void RefreshAntags()
|
||||
{
|
||||
AntagList.DisposeAllChildren();
|
||||
AntagList.RemoveAllChildren();
|
||||
var items = new[]
|
||||
{
|
||||
("humanoid-profile-editor-antag-preference-yes-button", 0),
|
||||
@@ -845,7 +845,7 @@ namespace Content.Client.Lobby.UI
|
||||
/// </summary>
|
||||
public void RefreshJobs()
|
||||
{
|
||||
JobList.DisposeAllChildren();
|
||||
JobList.RemoveAllChildren();
|
||||
_jobCategories.Clear();
|
||||
_jobPriorities.Clear();
|
||||
var firstCategory = true;
|
||||
|
||||
@@ -43,7 +43,7 @@ public sealed partial class LoadoutGroupContainer : BoxContainer
|
||||
{
|
||||
var protoMan = collection.Resolve<IPrototypeManager>();
|
||||
var loadoutSystem = collection.Resolve<IEntityManager>().System<LoadoutSystem>();
|
||||
RestrictionsContainer.DisposeAllChildren();
|
||||
RestrictionsContainer.RemoveAllChildren();
|
||||
|
||||
if (_groupProto.MinLimit > 0)
|
||||
{
|
||||
@@ -72,7 +72,7 @@ public sealed partial class LoadoutGroupContainer : BoxContainer
|
||||
});
|
||||
}
|
||||
|
||||
LoadoutsContainer.DisposeAllChildren();
|
||||
LoadoutsContainer.RemoveAllChildren();
|
||||
|
||||
// Corvax-Loadouts-Start
|
||||
var groupLoadouts = _groupProto.Loadouts;
|
||||
|
||||
@@ -43,7 +43,7 @@ public sealed partial class LobbyCharacterPreviewPanel : Control
|
||||
|
||||
_previewDummy = uid;
|
||||
|
||||
ViewBox.DisposeAllChildren();
|
||||
ViewBox.RemoveAllChildren();
|
||||
var spriteView = new SpriteView
|
||||
{
|
||||
OverrideDirection = Direction.South,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
@@ -37,7 +37,7 @@ public sealed partial class MappingPrototypeList : Control
|
||||
{
|
||||
_prototypes.Clear();
|
||||
|
||||
PrototypeList.DisposeAllChildren();
|
||||
PrototypeList.RemoveAllChildren();
|
||||
|
||||
_prototypes.AddRange(prototypes);
|
||||
|
||||
@@ -99,7 +99,7 @@ public sealed partial class MappingPrototypeList : Control
|
||||
public void Search(List<MappingPrototype> prototypes)
|
||||
{
|
||||
_search.Clear();
|
||||
SearchList.DisposeAllChildren();
|
||||
SearchList.RemoveAllChildren();
|
||||
_lastIndices = (0, -1);
|
||||
|
||||
_search.AddRange(prototypes);
|
||||
|
||||
@@ -861,7 +861,7 @@ public sealed class MappingState : GameplayStateBase
|
||||
}
|
||||
else
|
||||
{
|
||||
button.ChildrenPrototypes.DisposeAllChildren();
|
||||
button.ChildrenPrototypes.RemoveAllChildren();
|
||||
button.CollapseButton.Label.Text = "▶";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,8 +53,8 @@ public sealed partial class PowerMonitoringWindow
|
||||
// Selection action
|
||||
windowEntry.Button.OnButtonUp += args =>
|
||||
{
|
||||
windowEntry.SourcesContainer.DisposeAllChildren();
|
||||
windowEntry.LoadsContainer.DisposeAllChildren();
|
||||
windowEntry.SourcesContainer.RemoveAllChildren();
|
||||
windowEntry.LoadsContainer.RemoveAllChildren();
|
||||
ButtonAction(windowEntry, masterContainer);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public sealed partial class OfferingWindow : FancyWindow,
|
||||
|
||||
public void ClearOptions()
|
||||
{
|
||||
Container.DisposeAllChildren();
|
||||
Container.RemoveAllChildren();
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
|
||||
@@ -67,8 +67,8 @@ public sealed partial class DockingScreen : BoxContainer
|
||||
{
|
||||
DockingControl.BuildDocks(shuttle);
|
||||
var currentDock = DockingControl.ViewedDock;
|
||||
// DockedWith.DisposeAllChildren();
|
||||
DockPorts.DisposeAllChildren();
|
||||
// DockedWith.RemoveAllChildren();
|
||||
DockPorts.RemoveAllChildren();
|
||||
_ourDockButtons.Clear();
|
||||
|
||||
if (shuttle == null)
|
||||
|
||||
@@ -59,7 +59,7 @@ public sealed partial class EmergencyConsoleWindow : FancyWindow,
|
||||
// TODO: Loc and cvar for this.
|
||||
_earlyLaunchTime = scc.EarlyLaunchTime;
|
||||
|
||||
AuthorizationsContainer.DisposeAllChildren();
|
||||
AuthorizationsContainer.RemoveAllChildren();
|
||||
var remainingAuths = scc.AuthorizationsRequired - scc.Authorizations.Count;
|
||||
AuthorizationCount.Text = Loc.GetString("emergency-shuttle-ui-remaining", ("remaining", remainingAuths));
|
||||
|
||||
|
||||
@@ -237,7 +237,7 @@ public sealed partial class MapScreen : BoxContainer
|
||||
private void ClearMapObjects()
|
||||
{
|
||||
_mapObjectControls.Clear();
|
||||
HyperspaceDestinations.DisposeAllChildren();
|
||||
HyperspaceDestinations.RemoveAllChildren();
|
||||
_pendingMapObjects.Clear();
|
||||
_mapObjects.Clear();
|
||||
_mapHeadings.Clear();
|
||||
|
||||
@@ -25,9 +25,9 @@ namespace Content.Client.Strip
|
||||
|
||||
public void ClearButtons()
|
||||
{
|
||||
InventoryContainer.DisposeAllChildren();
|
||||
HandsContainer.DisposeAllChildren();
|
||||
SnareContainer.DisposeAllChildren();
|
||||
InventoryContainer.RemoveAllChildren();
|
||||
HandsContainer.RemoveAllChildren();
|
||||
SnareContainer.RemoveAllChildren();
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
|
||||
@@ -29,7 +29,7 @@ public sealed partial class ThiefBackpackMenu : FancyWindow
|
||||
|
||||
public void UpdateState(ThiefBackpackBoundUserInterfaceState state)
|
||||
{
|
||||
SetsGrid.DisposeAllChildren();
|
||||
SetsGrid.RemoveAllChildren();
|
||||
var selectedNumber = 0;
|
||||
foreach (var (set, info) in state.Sets)
|
||||
{
|
||||
|
||||
@@ -66,7 +66,8 @@ namespace Content.Client.UserInterface.Controls
|
||||
Viewport.StretchMode = filterMode switch
|
||||
{
|
||||
"nearest" => ScalingViewportStretchMode.Nearest,
|
||||
"bilinear" => ScalingViewportStretchMode.Bilinear
|
||||
"bilinear" => ScalingViewportStretchMode.Bilinear,
|
||||
_ => ScalingViewportStretchMode.Nearest
|
||||
};
|
||||
Viewport.IgnoreDimension = verticalFit ? ScalingViewportIgnoreDimension.Horizontal : ScalingViewportIgnoreDimension.None;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
@@ -19,7 +19,7 @@ namespace Content.Client.UserInterface.Controls
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
DisposeAllChildren();
|
||||
RemoveAllChildren();
|
||||
}
|
||||
|
||||
public void AddEntry(float amount, Color color, string? tooltip = null)
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Content.Client.UserInterface
|
||||
|
||||
public void UpdateValues(List<string> headers, List<string[]> values)
|
||||
{
|
||||
Values.DisposeAllChildren();
|
||||
Values.RemoveAllChildren();
|
||||
Values.Columns = headers.Count;
|
||||
|
||||
for (var i = 0; i < headers.Count; i++)
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls
|
||||
|
||||
public void Populate()
|
||||
{
|
||||
ButtonContainer.DisposeAllChildren();
|
||||
ButtonContainer.RemoveAllChildren();
|
||||
AddButtons();
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
|
||||
public void ClearEntries()
|
||||
{
|
||||
NoRolesMessage.Visible = true;
|
||||
EntryContainer.DisposeAllChildren();
|
||||
EntryContainer.RemoveAllChildren();
|
||||
_collapsibleBoxes.Clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using Content.Client.UserInterface.Systems.Inventory.Controls;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
@@ -74,7 +74,7 @@ public sealed class HandsContainer : ItemSlotUIContainer<HandButton>
|
||||
public void Clear()
|
||||
{
|
||||
ClearButtons();
|
||||
_grid.DisposeAllChildren();
|
||||
_grid.RemoveAllChildren();
|
||||
}
|
||||
|
||||
public IEnumerable<HandButton> GetButtons()
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace Content.Client.Verbs.UI
|
||||
Close();
|
||||
|
||||
var menu = popup ?? _context.RootMenu;
|
||||
menu.MenuBody.DisposeAllChildren();
|
||||
menu.MenuBody.RemoveAllChildren();
|
||||
|
||||
CurrentTarget = target;
|
||||
CurrentVerbs = _verbSystem.GetVerbs(target, user, Verb.VerbTypes, out ExtraCategories, force);
|
||||
@@ -207,7 +207,7 @@ namespace Content.Client.Verbs.UI
|
||||
/// </summary>
|
||||
public void AddServerVerbs(List<Verb>? verbs, ContextMenuPopup popup)
|
||||
{
|
||||
popup.MenuBody.DisposeAllChildren();
|
||||
popup.MenuBody.RemoveAllChildren();
|
||||
|
||||
// Verbs may be null if the server does not think we can see the target entity. This **should** not happen.
|
||||
if (verbs == null)
|
||||
|
||||
@@ -271,7 +271,7 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
// We consider only non-audio entities, as some entities will just play sounds when they spawn.
|
||||
int Count(IEntityManager ent) => ent.EntityCount - ent.Count<AudioComponent>();
|
||||
IEnumerable<EntityUid> Entities(IEntityManager entMan) => entMan.GetEntities().Where(entMan.HasComponent<AudioComponent>);
|
||||
IEnumerable<EntityUid> Entities(IEntityManager entMan) => entMan.GetEntities().Where(e => !entMan.HasComponent<AudioComponent>(e));
|
||||
|
||||
await Assert.MultipleAsync(async () =>
|
||||
{
|
||||
@@ -311,8 +311,8 @@ namespace Content.IntegrationTests.Tests
|
||||
// Check that the number of entities has gone back to the original value.
|
||||
Assert.That(Count(server.EntMan), Is.EqualTo(count), $"Server prototype {protoId} failed on deletion: count didn't reset properly\n" +
|
||||
BuildDiffString(serverEntities, Entities(server.EntMan), server.EntMan));
|
||||
Assert.That(client.EntMan.EntityCount, Is.EqualTo(clientCount), $"Client prototype {protoId} failed on deletion: count didn't reset properly:\n" +
|
||||
$"Expected {clientCount} and found {client.EntMan.EntityCount}.\n" +
|
||||
Assert.That(Count(client.EntMan), Is.EqualTo(clientCount), $"Client prototype {protoId} failed on deletion: count didn't reset properly:\n" +
|
||||
$"Expected {clientCount} and found {Count(client.EntMan)}.\n" +
|
||||
$"Server count was {count}.\n" +
|
||||
BuildDiffString(clientEntities, Entities(client.EntMan), client.EntMan));
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Emp;
|
||||
using Content.Server.IdentityManagement;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Clothing.EntitySystems;
|
||||
using Content.Shared.Emp;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.IdentityManagement.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Prototypes;
|
||||
|
||||
@@ -269,31 +269,4 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
|
||||
mob = user;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the new identity's name has a criminal record attached to it, and gives the entity the icon that
|
||||
/// belongs to the status if it does.
|
||||
/// </summary>
|
||||
public void CheckNewIdentity(EntityUid uid)
|
||||
{
|
||||
var name = Identity.Name(uid, EntityManager);
|
||||
var xform = Transform(uid);
|
||||
|
||||
// TODO use the entity's station? Not the station of the map that it happens to currently be on?
|
||||
var station = _station.GetStationInMap(xform.MapID);
|
||||
|
||||
if (station != null && _records.GetRecordByName(station.Value, name) is { } id)
|
||||
{
|
||||
if (_records.TryGetRecord<CriminalRecord>(new StationRecordKey(id, station.Value),
|
||||
out var record))
|
||||
{
|
||||
if (record.Status != SecurityStatus.None)
|
||||
{
|
||||
_criminalRecords.SetCriminalIcon(name, record.Status, uid);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
RemComp<CriminalRecordComponent>(uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Content.Shared.Delivery;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
using Content.Server.StationRecords;
|
||||
using Content.Shared.EntityTable;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
|
||||
@@ -30,6 +30,10 @@ public sealed class DeviceNetworkJammerSystem : SharedDeviceNetworkJammerSystem
|
||||
if (!_jammer.GetJammableNetworks((uid, jammerComp)).Contains(ev.NetworkId))
|
||||
continue;
|
||||
|
||||
if (jammerComp.FrequenciesExcluded != null &&
|
||||
jammerComp.FrequenciesExcluded.Contains(ev.Frequency))
|
||||
continue;
|
||||
|
||||
if (_transform.InRange(jammerXform.Coordinates, ev.SenderTransform.Coordinates, jammerComp.Range)
|
||||
|| _transform.InRange(jammerXform.Coordinates, xform.Comp.Coordinates, jammerComp.Range))
|
||||
{
|
||||
|
||||
@@ -349,7 +349,7 @@ namespace Content.Server.DeviceNetwork.Systems
|
||||
if (connection.Owner == packet.Sender)
|
||||
continue;
|
||||
|
||||
BeforePacketSentEvent beforeEv = new(packet.Sender, xform, senderPos, connection.NetIdEnum.ToString());
|
||||
BeforePacketSentEvent beforeEv = new(packet.Sender, xform, senderPos, connection.NetIdEnum.ToString(), packet.Frequency);
|
||||
RaiseLocalEvent(connection.Owner, beforeEv, false);
|
||||
|
||||
if (!beforeEv.Cancelled)
|
||||
|
||||
@@ -63,16 +63,6 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
||||
|
||||
public const int MaxExplosionAudioRange = 30;
|
||||
|
||||
/// <summary>
|
||||
/// The "default" explosion prototype.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Generally components should specify an explosion prototype via a yaml datafield, so that the yaml-linter can
|
||||
/// find errors. However some components, like rogue arrows, or some commands like the admin-smite need to have
|
||||
/// a "default" option specified outside of yaml data-fields. Hence this const string.
|
||||
/// </remarks>
|
||||
public static readonly ProtoId<ExplosionPrototype> DefaultExplosionPrototypeId = "Default";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -222,10 +212,8 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
||||
return r0 * (MathF.Sqrt(12 * totalIntensity / v0 - 3) / 6 + 0.5f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an explosions, centered on some entity.
|
||||
/// </summary>
|
||||
public void QueueExplosion(EntityUid uid,
|
||||
/// <inheritdoc />
|
||||
public override void QueueExplosion(EntityUid uid,
|
||||
string typeId,
|
||||
float totalIntensity,
|
||||
float slope,
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
using Content.Server.Access.Systems;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.CriminalRecords.Systems;
|
||||
using Content.Server.Humanoid;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.IdentityManagement.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects.Components.Localization;
|
||||
|
||||
namespace Content.Server.IdentityManagement;
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for updating the identity of an entity on init or clothing equip/unequip.
|
||||
/// </summary>
|
||||
public sealed class IdentitySystem : SharedIdentitySystem
|
||||
{
|
||||
[Dependency] private readonly IdCardSystem _idCard = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLog = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
|
||||
[Dependency] private readonly CriminalRecordsConsoleSystem _criminalRecordsConsole = default!;
|
||||
[Dependency] private readonly GrammarSystem _grammarSystem = default!;
|
||||
|
||||
private HashSet<EntityUid> _queuedIdentityUpdates = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<IdentityComponent, DidEquipEvent>((uid, _, _) => QueueIdentityUpdate(uid));
|
||||
SubscribeLocalEvent<IdentityComponent, DidEquipHandEvent>((uid, _, _) => QueueIdentityUpdate(uid));
|
||||
SubscribeLocalEvent<IdentityComponent, DidUnequipEvent>((uid, _, _) => QueueIdentityUpdate(uid));
|
||||
SubscribeLocalEvent<IdentityComponent, DidUnequipHandEvent>((uid, _, _) => QueueIdentityUpdate(uid));
|
||||
SubscribeLocalEvent<IdentityComponent, WearerMaskToggledEvent>((uid, _, _) => QueueIdentityUpdate(uid));
|
||||
SubscribeLocalEvent<IdentityComponent, EntityRenamedEvent>((uid, _, _) => QueueIdentityUpdate(uid));
|
||||
SubscribeLocalEvent<IdentityComponent, MapInitEvent>(OnMapInit);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
foreach (var ent in _queuedIdentityUpdates)
|
||||
{
|
||||
if (!TryComp<IdentityComponent>(ent, out var identity))
|
||||
continue;
|
||||
|
||||
UpdateIdentityInfo(ent, identity);
|
||||
}
|
||||
|
||||
_queuedIdentityUpdates.Clear();
|
||||
}
|
||||
|
||||
// This is where the magic happens
|
||||
private void OnMapInit(EntityUid uid, IdentityComponent component, MapInitEvent args)
|
||||
{
|
||||
var ident = Spawn(null, Transform(uid).Coordinates);
|
||||
|
||||
_metaData.SetEntityName(ident, "identity");
|
||||
QueueIdentityUpdate(uid);
|
||||
_container.Insert(ident, component.IdentityEntitySlot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queues an identity update to the start of the next tick.
|
||||
/// </summary>
|
||||
public override void QueueIdentityUpdate(EntityUid uid)
|
||||
{
|
||||
_queuedIdentityUpdates.Add(uid);
|
||||
}
|
||||
|
||||
#region Private API
|
||||
|
||||
/// <summary>
|
||||
/// Updates the metadata name for the id(entity) from the current state of the character.
|
||||
/// </summary>
|
||||
private void UpdateIdentityInfo(EntityUid uid, IdentityComponent identity)
|
||||
{
|
||||
if (identity.IdentityEntitySlot.ContainedEntity is not { } ident)
|
||||
return;
|
||||
|
||||
var representation = GetIdentityRepresentation(uid);
|
||||
var name = GetIdentityName(uid, representation);
|
||||
|
||||
// Clone the old entity's grammar to the identity entity, for loc purposes.
|
||||
if (TryComp<GrammarComponent>(uid, out var grammar))
|
||||
{
|
||||
var identityGrammar = EnsureComp<GrammarComponent>(ident);
|
||||
identityGrammar.Attributes.Clear();
|
||||
|
||||
foreach (var (k, v) in grammar.Attributes)
|
||||
{
|
||||
identityGrammar.Attributes.Add(k, v);
|
||||
}
|
||||
|
||||
// If presumed name is null and we're using that, we set proper noun to be false ("the old woman")
|
||||
if (name != representation.TrueName && representation.PresumedName == null)
|
||||
_grammarSystem.SetProperNoun((ident, identityGrammar), false);
|
||||
|
||||
Dirty(ident, identityGrammar);
|
||||
}
|
||||
|
||||
if (name == Name(ident))
|
||||
return;
|
||||
|
||||
_metaData.SetEntityName(ident, name);
|
||||
|
||||
_adminLog.Add(LogType.Identity, LogImpact.Medium, $"{ToPrettyString(uid)} changed identity to {name}");
|
||||
var identityChangedEvent = new IdentityChangedEvent(uid, ident);
|
||||
RaiseLocalEvent(uid, ref identityChangedEvent);
|
||||
SetIdentityCriminalIcon(uid);
|
||||
}
|
||||
|
||||
private string GetIdentityName(EntityUid target, IdentityRepresentation representation)
|
||||
{
|
||||
var ev = new SeeIdentityAttemptEvent();
|
||||
|
||||
RaiseLocalEvent(target, ev);
|
||||
return representation.ToStringKnown(!ev.Cancelled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When the identity of a person is changed, searches the criminal records to see if the name of the new identity
|
||||
/// has a record. If the new name has a criminal status attached to it, the person will get the criminal status
|
||||
/// until they change identity again.
|
||||
/// </summary>
|
||||
private void SetIdentityCriminalIcon(EntityUid uid)
|
||||
{
|
||||
_criminalRecordsConsole.CheckNewIdentity(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an 'identity representation' of an entity, with their true name being the entity name
|
||||
/// and their 'presumed name' and 'presumed job' being the name/job on their ID card, if they have one.
|
||||
/// </summary>
|
||||
private IdentityRepresentation GetIdentityRepresentation(EntityUid target,
|
||||
InventoryComponent? inventory=null,
|
||||
HumanoidAppearanceComponent? appearance=null)
|
||||
{
|
||||
int age = 18;
|
||||
Gender gender = Gender.Epicene;
|
||||
string species = SharedHumanoidAppearanceSystem.DefaultSpecies;
|
||||
|
||||
// Always use their actual age and gender, since that can't really be changed by an ID.
|
||||
if (Resolve(target, ref appearance, false))
|
||||
{
|
||||
gender = appearance.Gender;
|
||||
age = appearance.Age;
|
||||
species = appearance.Species;
|
||||
}
|
||||
|
||||
var ageString = _humanoid.GetAgeRepresentation(species, age);
|
||||
var trueName = Name(target);
|
||||
if (!Resolve(target, ref inventory, false))
|
||||
return new(trueName, gender, ageString, string.Empty);
|
||||
|
||||
string? presumedJob = null;
|
||||
string? presumedName = null;
|
||||
|
||||
// Get their name and job from their ID for their presumed name.
|
||||
if (_idCard.TryFindIdCard(target, out var id))
|
||||
{
|
||||
presumedName = string.IsNullOrWhiteSpace(id.Comp.FullName) ? null : id.Comp.FullName;
|
||||
presumedJob = id.Comp.LocalizedJobTitle?.ToLowerInvariant();
|
||||
}
|
||||
|
||||
// If it didn't find a job, that's fine.
|
||||
return new(trueName, gender, ageString, presumedName, presumedJob);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -72,6 +72,15 @@ public sealed class JammerSystem : SharedJammerSystem
|
||||
EnsureComp<DeviceNetworkJammerComponent>(ent, out var jammingComp);
|
||||
_jammer.SetRange((ent, jammingComp), GetCurrentRange(ent));
|
||||
_jammer.AddJammableNetwork((ent, jammingComp), DeviceNetworkComponent.DeviceNetIdDefaults.Wireless.ToString());
|
||||
|
||||
// Add excluded frequencies using the system method
|
||||
if (ent.Comp.FrequenciesExcluded != null)
|
||||
{
|
||||
foreach (var freq in ent.Comp.FrequenciesExcluded)
|
||||
{
|
||||
_jammer.AddExcludedFrequency((ent, jammingComp), (uint)freq);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -96,19 +105,23 @@ public sealed class JammerSystem : SharedJammerSystem
|
||||
|
||||
private void OnRadioSendAttempt(ref RadioSendAttemptEvent args)
|
||||
{
|
||||
if (ShouldCancelSend(args.RadioSource))
|
||||
if (ShouldCancelSend(args.RadioSource, args.Channel.Frequency))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldCancelSend(EntityUid sourceUid)
|
||||
private bool ShouldCancelSend(EntityUid sourceUid, int frequency)
|
||||
{
|
||||
var source = Transform(sourceUid).Coordinates;
|
||||
var query = EntityQueryEnumerator<ActiveRadioJammerComponent, RadioJammerComponent, TransformComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out _, out var jam, out var transform))
|
||||
{
|
||||
// Check if this jammer excludes the frequency
|
||||
if (jam.FrequenciesExcluded != null && jam.FrequenciesExcluded.Contains(frequency))
|
||||
continue;
|
||||
|
||||
if (_transform.InRange(source, transform.Coordinates, GetCurrentRange((uid, jam))))
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Content.Server.Access.Systems;
|
||||
using Content.Server.Humanoid;
|
||||
using Content.Server.IdentityManagement;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.PDA;
|
||||
using Content.Server.Station.Components;
|
||||
@@ -11,6 +10,7 @@ using Content.Shared.Clothing;
|
||||
using Content.Shared.DetailExaminable;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
|
||||
@@ -35,7 +35,8 @@ public sealed class ImmovableRodRule : StationEventSystem<ImmovableRodRuleCompon
|
||||
var speed = RobustRandom.NextFloat(rod.MinSpeed, rod.MaxSpeed);
|
||||
var angle = RobustRandom.NextAngle();
|
||||
var direction = angle.ToVec();
|
||||
var spawnCoords = targetCoords.ToMap(EntityManager, _transform).Offset(-direction * speed * despawn.Lifetime / 2);
|
||||
var mapCoords = _transform.ToMapCoordinates(targetCoords);
|
||||
var spawnCoords = mapCoords.Offset(-direction * speed * despawn.Lifetime / 2);
|
||||
var ent = Spawn(protoName, spawnCoords);
|
||||
_gun.ShootProjectile(ent, direction, Vector2.Zero, uid, speed: speed);
|
||||
}
|
||||
|
||||
@@ -215,26 +215,6 @@ public sealed class StationRecordsSystem : SharedStationRecordsSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <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="key">Station and 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>(StationRecordKey key, [NotNullWhen(true)] out T? entry, StationRecordsComponent? records = null)
|
||||
{
|
||||
entry = default;
|
||||
|
||||
if (!Resolve(key.OriginStation, ref records))
|
||||
return false;
|
||||
|
||||
return records.Records.TryGetRecordEntry(key.Id, out entry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a random record from the station's record entries.
|
||||
/// </summary>
|
||||
@@ -257,26 +237,6 @@ public sealed class StationRecordsSystem : SharedStationRecordsSystem
|
||||
return ent.Comp.Records.TryGetRecordEntry(key, out entry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an id if a record with the same name exists.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Linear search so O(n) time complexity.
|
||||
/// </remarks>
|
||||
public uint? GetRecordByName(EntityUid station, string name, StationRecordsComponent? records = null)
|
||||
{
|
||||
if (!Resolve(station, ref records, false))
|
||||
return null;
|
||||
|
||||
foreach (var (id, record) in GetRecordsOfType<GeneralStationRecord>(station, records))
|
||||
{
|
||||
if (record.Name == name)
|
||||
return id;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the name for a record, or an empty string if it has no record.
|
||||
/// </summary>
|
||||
@@ -288,21 +248,6 @@ public sealed class StationRecordsSystem : SharedStationRecordsSystem
|
||||
return record.Name;
|
||||
}
|
||||
|
||||
/// <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<(uint, T)> GetRecordsOfType<T>(EntityUid station, StationRecordsComponent? records = null)
|
||||
{
|
||||
if (!Resolve(station, ref records))
|
||||
return Array.Empty<(uint, T)>();
|
||||
|
||||
return records.Records.GetRecordsOfType<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new record entry to a station's record set.
|
||||
/// </summary>
|
||||
|
||||
@@ -6,7 +6,6 @@ using Content.Server.Chat.Managers;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Ghost.Roles.Components;
|
||||
using Content.Server.Humanoid;
|
||||
using Content.Server.IdentityManagement;
|
||||
using Content.Server.Inventory;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.NPC;
|
||||
@@ -40,6 +39,7 @@ using Content.Shared.Prying.Components;
|
||||
using Content.Shared.Traits.Assorted;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Content.Shared.Ghost.Roles.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -840,7 +840,7 @@ public abstract partial class SharedActionsSystem : EntitySystem
|
||||
|
||||
if (!_actionsQuery.Resolve(performer, ref performer.Comp, false))
|
||||
{
|
||||
DebugTools.Assert(performer == null || TerminatingOrDeleted(performer));
|
||||
DebugTools.Assert(TerminatingOrDeleted(performer));
|
||||
ent.Comp.AttachedEntity = null;
|
||||
// TODO: should this delete the action since it's now orphaned?
|
||||
return;
|
||||
|
||||
@@ -14,7 +14,6 @@ namespace Content.Shared.Clothing;
|
||||
public sealed class SharedMagbootsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly ItemToggleSystem _toggle = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly SharedGravitySystem _gravity = default!;
|
||||
|
||||
@@ -9,12 +9,12 @@ namespace Content.Shared.Coordinates.Helpers
|
||||
public static EntityCoordinates SnapToGrid(this EntityCoordinates coordinates, IEntityManager? entMan = null, IMapManager? mapManager = null)
|
||||
{
|
||||
IoCManager.Resolve(ref entMan, ref mapManager);
|
||||
var xformSys = entMan.System<SharedTransformSystem>();
|
||||
|
||||
var gridId = coordinates.GetGridUid(entMan);
|
||||
var gridId = xformSys.GetGrid(coordinates.EntityId);
|
||||
|
||||
if (gridId == null)
|
||||
{
|
||||
var xformSys = entMan.System<SharedTransformSystem>();
|
||||
var mapPos = xformSys.ToMapCoordinates(coordinates);
|
||||
var mapX = (int)Math.Floor(mapPos.X) + 0.5f;
|
||||
var mapY = (int)Math.Floor(mapPos.Y) + 0.5f;
|
||||
@@ -24,11 +24,11 @@ namespace Content.Shared.Coordinates.Helpers
|
||||
|
||||
var grid = entMan.GetComponent<MapGridComponent>(gridId.Value);
|
||||
var tileSize = grid.TileSize;
|
||||
var localPos = coordinates.WithEntityId(gridId.Value).Position;
|
||||
var localPos = xformSys.WithEntityId(coordinates, gridId.Value).Position;
|
||||
var x = (int)Math.Floor(localPos.X / tileSize) + tileSize / 2f;
|
||||
var y = (int)Math.Floor(localPos.Y / tileSize) + tileSize / 2f;
|
||||
var gridPos = new EntityCoordinates(gridId.Value, new Vector2(x, y));
|
||||
return gridPos.WithEntityId(coordinates.EntityId);
|
||||
return xformSys.WithEntityId(gridPos, coordinates.EntityId);
|
||||
}
|
||||
|
||||
public static EntityCoordinates SnapToGrid(this EntityCoordinates coordinates, MapGridComponent grid)
|
||||
|
||||
@@ -1,6 +1,44 @@
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Security;
|
||||
using Content.Shared.Security.Components;
|
||||
using Content.Shared.Station;
|
||||
using Content.Shared.StationRecords;
|
||||
|
||||
namespace Content.Shared.CriminalRecords.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Station records aren't predicted, just exists for access.
|
||||
/// </summary>
|
||||
public abstract class SharedCriminalRecordsConsoleSystem : EntitySystem;
|
||||
public abstract class SharedCriminalRecordsConsoleSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedCriminalRecordsSystem _criminalRecords = default!;
|
||||
[Dependency] private readonly SharedStationRecordsSystem _records = default!;
|
||||
[Dependency] private readonly SharedStationSystem _station = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the new identity's name has a criminal record attached to it, and gives the entity the icon that
|
||||
/// belongs to the status if it does.
|
||||
/// </summary>
|
||||
public void CheckNewIdentity(EntityUid uid)
|
||||
{
|
||||
var name = Identity.Name(uid, EntityManager);
|
||||
var xform = Transform(uid);
|
||||
|
||||
// TODO use the entity's station? Not the station of the map that it happens to currently be on?
|
||||
var station = _station.GetStationInMap(xform.MapID);
|
||||
|
||||
if (station != null && _records.GetRecordByName(station.Value, name) is { } id)
|
||||
{
|
||||
if (_records.TryGetRecord<CriminalRecord>(new StationRecordKey(id, station.Value),
|
||||
out var record))
|
||||
{
|
||||
if (record.Status != SecurityStatus.None)
|
||||
{
|
||||
_criminalRecords.SetCriminalIcon(name, record.Status, uid);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
RemComp<CriminalRecordComponent>(uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@ public sealed class DamagePopupSystem : EntitySystem
|
||||
_ => "Invalid type",
|
||||
};
|
||||
|
||||
_popupSystem.PopupPredicted(msg, ent.Owner, args.Origin);
|
||||
// Turn this back into (msg, ent.Owner, args.Origin) when shooting gets predicted.
|
||||
_popupSystem.PopupPredicted(msg, ent.Owner, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,4 +23,10 @@ public sealed partial class DeviceNetworkJammerComponent : Component
|
||||
[DataField, AutoNetworkedField]
|
||||
public HashSet<string> JammableNetworks = [];
|
||||
|
||||
/// <summary>
|
||||
/// Device networks frequencies that wont be jammed.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public HashSet<uint> FrequenciesExcluded = [];
|
||||
|
||||
}
|
||||
|
||||
@@ -25,11 +25,17 @@ public sealed class BeforePacketSentEvent : CancellableEntityEventArgs
|
||||
/// </summary>
|
||||
public readonly string NetworkId;
|
||||
|
||||
public BeforePacketSentEvent(EntityUid sender, TransformComponent xform, Vector2 senderPosition, string networkId)
|
||||
/// <summary>
|
||||
/// The frequency the packet is sent on.
|
||||
/// </summary>
|
||||
public readonly uint Frequency;
|
||||
|
||||
public BeforePacketSentEvent(EntityUid sender, TransformComponent xform, Vector2 senderPosition, string networkId, uint frequency)
|
||||
{
|
||||
Sender = sender;
|
||||
SenderTransform = xform;
|
||||
SenderPosition = senderPosition;
|
||||
NetworkId = networkId;
|
||||
Frequency = frequency;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,4 +60,34 @@ public abstract class SharedDeviceNetworkJammerSystem : EntitySystem
|
||||
ent.Comp.JammableNetworks.Clear();
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables this entity to stop packets with the specified frequency from being jammmed.
|
||||
/// </summary>
|
||||
public void AddExcludedFrequency(Entity<DeviceNetworkJammerComponent> ent, uint frequency)
|
||||
{
|
||||
if (ent.Comp.FrequenciesExcluded.Add(frequency))
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops this entity to stop packets with the specified frequency from being jammmed.
|
||||
/// </summary>
|
||||
public void RemoveExcludedFrequency(Entity<DeviceNetworkJammerComponent> ent, uint frequency)
|
||||
{
|
||||
if (ent.Comp.FrequenciesExcluded.Remove(frequency))
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops this entity to stop packets with any frequency from being jammmed.
|
||||
/// </summary>
|
||||
public void ClearExcludedFrequency(Entity<DeviceNetworkJammerComponent> ent)
|
||||
{
|
||||
if (ent.Comp.FrequenciesExcluded.Count == 0)
|
||||
return;
|
||||
|
||||
ent.Comp.FrequenciesExcluded.Clear();
|
||||
Dirty(ent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,26 @@
|
||||
using Content.Shared.Armor;
|
||||
using Content.Shared.Explosion.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Explosion.EntitySystems;
|
||||
|
||||
// TODO some sort of struct like DamageSpecifier but for explosions.
|
||||
/// <summary>
|
||||
/// Lets code in shared trigger explosions and handles explosion resistance examining.
|
||||
/// All processing is still done clientside.
|
||||
/// </summary>
|
||||
public abstract class SharedExplosionSystem : EntitySystem
|
||||
{
|
||||
/// <summary>
|
||||
/// The "default" explosion prototype.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Generally components should specify an explosion prototype via a yaml datafield, so that the yaml-linter can
|
||||
/// find errors. However some components, like rogue arrows, or some commands like the admin-smite need to have
|
||||
/// a "default" option specified outside of yaml data-fields. Hence this const string.
|
||||
/// </remarks>
|
||||
public static readonly ProtoId<ExplosionPrototype> DefaultExplosionPrototypeId = "Default";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -37,4 +49,24 @@ public abstract class SharedExplosionSystem : EntitySystem
|
||||
public virtual void TriggerExplosive(EntityUid uid, ExplosiveComponent? explosive = null, bool delete = true, float? totalIntensity = null, float? radius = null, EntityUid? user = null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an explosion centered on some entity. Bypasses needing <see cref="ExplosiveComponent"/>.
|
||||
/// </summary>
|
||||
/// <param name="uid">Where the explosion happens.</param>
|
||||
/// <param name="typeId">A ProtoId of type <see cref="ExplosionPrototype"/>.</param>
|
||||
/// <param name="user">The entity which caused the explosion.</param>
|
||||
/// <param name="addLog">Whether to add an admin log about this explosion. Includes user.</param>
|
||||
public virtual void QueueExplosion(EntityUid uid,
|
||||
string typeId,
|
||||
float totalIntensity,
|
||||
float slope,
|
||||
float maxTileIntensity,
|
||||
float tileBreakScale = 1f,
|
||||
int maxTileBreak = int.MaxValue,
|
||||
bool canCreateVacuum = true,
|
||||
EntityUid? user = null,
|
||||
bool addLog = true)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public abstract class SharedHumanoidAppearanceSystem : EntitySystem
|
||||
[Dependency] private readonly ISerializationManager _serManager = default!;
|
||||
[Dependency] private readonly MarkingManager _markingManager = default!;
|
||||
[Dependency] private readonly GrammarSystem _grammarSystem = default!;
|
||||
[Dependency] private readonly SharedIdentitySystem _identity = default!;
|
||||
[Dependency] private readonly IdentitySystem _identity = default!;
|
||||
private ISharedSponsorsManager? _sponsors;
|
||||
|
||||
public static readonly ProtoId<SpeciesPrototype> DefaultSpecies = "Human";
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using Content.Shared.Inventory;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.IdentityManagement.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class IdentityBlockerComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Enabled = true;
|
||||
|
||||
/// <summary>
|
||||
@@ -16,6 +17,8 @@ public sealed partial class IdentityBlockerComponent : Component
|
||||
public IdentityBlockerCoverage Coverage = IdentityBlockerCoverage.FULL;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
[Serializable, NetSerializable]
|
||||
public enum IdentityBlockerCoverage
|
||||
{
|
||||
NONE = 0,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.IdentityManagement.Components;
|
||||
|
||||
@@ -10,7 +11,7 @@ namespace Content.Shared.IdentityManagement.Components;
|
||||
/// <remarks>
|
||||
/// This is a <see cref="ContainerSlot"/> and not just a datum entity because we do sort of care that it gets deleted and sent with the user.
|
||||
/// </remarks>
|
||||
[RegisterComponent]
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class IdentityComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
|
||||
241
Content.Shared/IdentityManagement/IdentitySystem.cs
Normal file
241
Content.Shared/IdentityManagement/IdentitySystem.cs
Normal file
@@ -0,0 +1,241 @@
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.CriminalRecords.Systems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.IdentityManagement.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects.Components.Localization;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.IdentityManagement;
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for updating the identity of an entity on init or clothing equip/unequip.
|
||||
/// </summary>
|
||||
public sealed class IdentitySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly GrammarSystem _grammarSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLog = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly SharedCriminalRecordsConsoleSystem _criminalRecordsConsole = default!;
|
||||
[Dependency] private readonly SharedHumanoidAppearanceSystem _humanoid = default!;
|
||||
[Dependency] private readonly SharedIdCardSystem _idCard = default!;
|
||||
|
||||
// The name of the container holding the identity entity
|
||||
private const string SlotName = "identity";
|
||||
|
||||
// Recycled hashset for tracking identities each tick that need to update
|
||||
private readonly HashSet<EntityUid> _queuedIdentityUpdates = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<IdentityBlockerComponent, SeeIdentityAttemptEvent>(OnSeeIdentity);
|
||||
SubscribeLocalEvent<IdentityBlockerComponent, InventoryRelayedEvent<SeeIdentityAttemptEvent>>(OnRelaySeeIdentity);
|
||||
SubscribeLocalEvent<IdentityBlockerComponent, ItemMaskToggledEvent>(OnMaskToggled);
|
||||
|
||||
SubscribeLocalEvent<IdentityComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<IdentityComponent, ComponentInit>(OnComponentInit);
|
||||
|
||||
SubscribeLocalEvent<IdentityComponent, DidEquipEvent>((uid, _, _) => QueueIdentityUpdate(uid));
|
||||
SubscribeLocalEvent<IdentityComponent, DidEquipHandEvent>((uid, _, _) => QueueIdentityUpdate(uid));
|
||||
SubscribeLocalEvent<IdentityComponent, DidUnequipEvent>((uid, _, _) => QueueIdentityUpdate(uid));
|
||||
SubscribeLocalEvent<IdentityComponent, DidUnequipHandEvent>((uid, _, _) => QueueIdentityUpdate(uid));
|
||||
SubscribeLocalEvent<IdentityComponent, WearerMaskToggledEvent>((uid, _, _) => QueueIdentityUpdate(uid));
|
||||
SubscribeLocalEvent<IdentityComponent, EntityRenamedEvent>((uid, _, _) => QueueIdentityUpdate(uid));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates through all identities that need to be updated.
|
||||
/// </summary>
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
foreach (var ent in _queuedIdentityUpdates)
|
||||
{
|
||||
if (!TryComp<IdentityComponent>(ent, out var identity))
|
||||
continue;
|
||||
|
||||
UpdateIdentityInfo((ent, identity));
|
||||
}
|
||||
|
||||
_queuedIdentityUpdates.Clear();
|
||||
}
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
// Creates an identity entity, and store it in the identity container
|
||||
private void OnMapInit(Entity<IdentityComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
var ident = Spawn(null, Transform(ent).Coordinates);
|
||||
|
||||
_metaData.SetEntityName(ident, "identity");
|
||||
QueueIdentityUpdate(ent);
|
||||
_container.Insert(ident, ent.Comp.IdentityEntitySlot);
|
||||
}
|
||||
|
||||
private void OnComponentInit(Entity<IdentityComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
ent.Comp.IdentityEntitySlot = _container.EnsureContainer<ContainerSlot>(ent, SlotName);
|
||||
}
|
||||
|
||||
// Adds an identity blocker's coverage, and cancels the event if coverage is complete.
|
||||
private void OnSeeIdentity(Entity<IdentityBlockerComponent> ent, ref SeeIdentityAttemptEvent args)
|
||||
{
|
||||
if (ent.Comp.Enabled)
|
||||
{
|
||||
args.TotalCoverage |= ent.Comp.Coverage;
|
||||
if (args.TotalCoverage == IdentityBlockerCoverage.FULL)
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRelaySeeIdentity(Entity<IdentityBlockerComponent> ent, ref InventoryRelayedEvent<SeeIdentityAttemptEvent> args)
|
||||
{
|
||||
OnSeeIdentity(ent, ref args.Args);
|
||||
}
|
||||
|
||||
// Toggles if a mask is hiding the identity.
|
||||
private void OnMaskToggled(Entity<IdentityBlockerComponent> ent, ref ItemMaskToggledEvent args)
|
||||
{
|
||||
ent.Comp.Enabled = !args.Mask.Comp.IsToggled;
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Queues an identity update to the start of the next tick.
|
||||
/// </summary>
|
||||
public void QueueIdentityUpdate(EntityUid uid)
|
||||
{
|
||||
if (_timing.ApplyingState)
|
||||
return;
|
||||
|
||||
_queuedIdentityUpdates.Add(uid);
|
||||
}
|
||||
#region Private API
|
||||
|
||||
/// <summary>
|
||||
/// Updates the metadata name for the id(entity) from the current state of the character.
|
||||
/// </summary>
|
||||
private void UpdateIdentityInfo(Entity<IdentityComponent> ent)
|
||||
{
|
||||
if (ent.Comp.IdentityEntitySlot.ContainedEntity is not { } ident)
|
||||
return;
|
||||
|
||||
var representation = GetIdentityRepresentation(ent.Owner);
|
||||
var name = GetIdentityName(ent, representation);
|
||||
|
||||
// Clone the old entity's grammar to the identity entity, for loc purposes.
|
||||
if (TryComp<GrammarComponent>(ent, out var grammar))
|
||||
{
|
||||
var identityGrammar = EnsureComp<GrammarComponent>(ident);
|
||||
identityGrammar.Attributes.Clear();
|
||||
|
||||
foreach (var (k, v) in grammar.Attributes)
|
||||
{
|
||||
identityGrammar.Attributes.Add(k, v);
|
||||
}
|
||||
|
||||
// If presumed name is null and we're using that, we set proper noun to be false ("the old woman")
|
||||
if (name != representation.TrueName && representation.PresumedName == null)
|
||||
_grammarSystem.SetProperNoun((ident, identityGrammar), false);
|
||||
|
||||
Dirty(ident, identityGrammar);
|
||||
}
|
||||
|
||||
if (name == Name(ident))
|
||||
return;
|
||||
|
||||
_metaData.SetEntityName(ident, name);
|
||||
|
||||
_adminLog.Add(LogType.Identity, LogImpact.Medium, $"{ToPrettyString(ent)} changed identity to {name}");
|
||||
var identityChangedEvent = new IdentityChangedEvent(ent, ident);
|
||||
RaiseLocalEvent(ent, ref identityChangedEvent);
|
||||
SetIdentityCriminalIcon(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When the identity of a person is changed, searches the criminal records to see if the name of the new identity
|
||||
/// has a record. If the new name has a criminal status attached to it, the person will get the criminal status
|
||||
/// until they change identity again.
|
||||
/// </summary>
|
||||
private void SetIdentityCriminalIcon(EntityUid uid)
|
||||
{
|
||||
_criminalRecordsConsole.CheckNewIdentity(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get an entity's name. Cancelled if the entity has full coverage from <see cref="IdentityBlockerComponent"/>.
|
||||
/// </summary>
|
||||
/// <param name="target">The entity being targeted.</param>
|
||||
/// <param name="representation">The data structure containing an entity's identities.</param>
|
||||
/// <returns>
|
||||
/// An entity's real name if <see cref="SeeIdentityAttemptEvent"/> isn't cancelled,
|
||||
/// or a hidden identity such as a fake ID or fully hidden identity like "middle-aged man".
|
||||
/// </returns>
|
||||
private string GetIdentityName(EntityUid target, IdentityRepresentation representation)
|
||||
{
|
||||
var ev = new SeeIdentityAttemptEvent();
|
||||
|
||||
RaiseLocalEvent(target, ev);
|
||||
return representation.ToStringKnown(!ev.Cancelled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an 'identity representation' of an entity, with their true name being the entity name
|
||||
/// and their 'presumed name' and 'presumed job' being the name/job on their ID card, if they have one.
|
||||
/// </summary>
|
||||
private IdentityRepresentation GetIdentityRepresentation(Entity<InventoryComponent?, HumanoidAppearanceComponent?> target)
|
||||
{
|
||||
var age = 18;
|
||||
var gender = Gender.Epicene;
|
||||
var species = SharedHumanoidAppearanceSystem.DefaultSpecies;
|
||||
|
||||
// Always use their actual age and gender, since that can't really be changed by an ID.
|
||||
if (Resolve(target, ref target.Comp2, false))
|
||||
{
|
||||
gender = target.Comp2.Gender;
|
||||
age = target.Comp2.Age;
|
||||
species = target.Comp2.Species;
|
||||
}
|
||||
|
||||
var ageString = _humanoid.GetAgeRepresentation(species, age);
|
||||
var trueName = Name(target);
|
||||
if (!Resolve(target, ref target.Comp1, false))
|
||||
return new(trueName, gender, ageString, string.Empty);
|
||||
|
||||
string? presumedJob = null;
|
||||
string? presumedName = null;
|
||||
|
||||
// Get their name and job from their ID for their presumed name.
|
||||
if (_idCard.TryFindIdCard(target, out var id))
|
||||
{
|
||||
presumedName = string.IsNullOrWhiteSpace(id.Comp.FullName) ? null : id.Comp.FullName;
|
||||
presumedJob = id.Comp.LocalizedJobTitle?.ToLowerInvariant();
|
||||
}
|
||||
|
||||
// If it didn't find a job, that's fine.
|
||||
return new(trueName, gender, ageString, presumedName, presumedJob);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets called whenever an entity changes their identity.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct IdentityChangedEvent(EntityUid CharacterEntity, EntityUid IdentityEntity);
|
||||
@@ -1,52 +0,0 @@
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.IdentityManagement.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Shared.IdentityManagement;
|
||||
|
||||
public abstract class SharedIdentitySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
private static string SlotName = "identity";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<IdentityComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<IdentityBlockerComponent, SeeIdentityAttemptEvent>(OnSeeIdentity);
|
||||
SubscribeLocalEvent<IdentityBlockerComponent, InventoryRelayedEvent<SeeIdentityAttemptEvent>>((e, c, ev) => OnSeeIdentity(e, c, ev.Args));
|
||||
SubscribeLocalEvent<IdentityBlockerComponent, ItemMaskToggledEvent>(OnMaskToggled);
|
||||
}
|
||||
|
||||
private void OnSeeIdentity(EntityUid uid, IdentityBlockerComponent component, SeeIdentityAttemptEvent args)
|
||||
{
|
||||
if (component.Enabled)
|
||||
{
|
||||
args.TotalCoverage |= component.Coverage;
|
||||
if(args.TotalCoverage == IdentityBlockerCoverage.FULL)
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnComponentInit(EntityUid uid, IdentityComponent component, ComponentInit args)
|
||||
{
|
||||
component.IdentityEntitySlot = _container.EnsureContainer<ContainerSlot>(uid, SlotName);
|
||||
}
|
||||
|
||||
private void OnMaskToggled(Entity<IdentityBlockerComponent> ent, ref ItemMaskToggledEvent args)
|
||||
{
|
||||
ent.Comp.Enabled = !args.Mask.Comp.IsToggled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queues an identity update to the start of the next tick.
|
||||
/// </summary>
|
||||
public virtual void QueueIdentityUpdate(EntityUid uid) { }
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets called whenever an entity changes their identity.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct IdentityChangedEvent(EntityUid CharacterEntity, EntityUid IdentityEntity);
|
||||
@@ -4,7 +4,7 @@ using Robust.Shared.GameStates;
|
||||
namespace Content.Shared.Radio.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Prevents all radio in range from sending messages
|
||||
/// Prevents all non whitelisted radios from sending messages
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(SharedJammerSystem))]
|
||||
|
||||
@@ -46,6 +46,12 @@ public sealed partial class RadioJammerComponent : Component
|
||||
[DataField(required: true), ViewVariables(VVAccess.ReadOnly)]
|
||||
public RadioJamSetting[] Settings;
|
||||
|
||||
/// <summary>
|
||||
/// Frequencies that are NOT jammed by this jammer.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public HashSet<int> FrequenciesExcluded = [];
|
||||
|
||||
/// <summary>
|
||||
/// Index of the currently selected setting.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
using Content.Shared.Random;
|
||||
using Content.Shared.EntityTable.EntitySelectors;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.RatKing;
|
||||
namespace Content.Shared.RatKing.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for entities that can be
|
||||
/// rummaged through by the rat king to get loot.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedRatKingSystem))]
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[AutoGenerateComponentState]
|
||||
public sealed partial class RatKingRummageableComponent : Component
|
||||
public sealed partial class RummageableComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether or not this entity has been rummaged through already.
|
||||
@@ -28,11 +27,10 @@ public sealed partial class RatKingRummageableComponent : Component
|
||||
public float RummageDuration = 3f;
|
||||
|
||||
/// <summary>
|
||||
/// A weighted random entity prototype containing the different loot that rummaging can provide.
|
||||
/// The entity table to select loot from.
|
||||
/// </summary>
|
||||
[DataField("rummageLoot", customTypeSerializer: typeof(PrototypeIdSerializer<WeightedRandomEntityPrototype>)), ViewVariables(VVAccess.ReadWrite)]
|
||||
[AutoNetworkedField]
|
||||
public string RummageLoot = "RatKingLoot";
|
||||
[DataField(required: true)]
|
||||
public EntityTableSelector Table = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Sound played on rummage completion.
|
||||
11
Content.Shared/RatKing/Components/RummagerComponent.cs
Normal file
11
Content.Shared/RatKing/Components/RummagerComponent.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.RatKing.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for entities that can rummage through entities
|
||||
/// with the <see cref="RummageableComponent"/>
|
||||
/// </summary>
|
||||
///
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class RummagerComponent : Component;
|
||||
@@ -1,26 +1,15 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.Components;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Random;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Network;
|
||||
using Content.Shared.Actions.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.RatKing;
|
||||
|
||||
public abstract class SharedRatKingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
|
||||
[Dependency] protected readonly IRobustRandom Random = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _action = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
@@ -28,11 +17,7 @@ public abstract class SharedRatKingSystem : EntitySystem
|
||||
SubscribeLocalEvent<RatKingComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<RatKingComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<RatKingComponent, RatKingOrderActionEvent>(OnOrderAction);
|
||||
|
||||
SubscribeLocalEvent<RatKingServantComponent, ComponentShutdown>(OnServantShutdown);
|
||||
|
||||
SubscribeLocalEvent<RatKingRummageableComponent, GetVerbsEvent<AlternativeVerb>>(OnGetVerb);
|
||||
SubscribeLocalEvent<RatKingRummageableComponent, RatKingRummageDoAfterEvent>(OnDoAfterComplete);
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, RatKingComponent component, ComponentStartup args)
|
||||
@@ -105,43 +90,6 @@ public abstract class SharedRatKingSystem : EntitySystem
|
||||
_action.StartUseDelay(component.ActionOrderLooseEntity);
|
||||
}
|
||||
|
||||
private void OnGetVerb(EntityUid uid, RatKingRummageableComponent component, GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!HasComp<RatKingComponent>(args.User) || component.Looted)
|
||||
return;
|
||||
|
||||
args.Verbs.Add(new AlternativeVerb
|
||||
{
|
||||
Text = Loc.GetString("rat-king-rummage-text"),
|
||||
Priority = 0,
|
||||
Act = () =>
|
||||
{
|
||||
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, component.RummageDuration,
|
||||
new RatKingRummageDoAfterEvent(), uid, uid)
|
||||
{
|
||||
BlockDuplicate = true,
|
||||
BreakOnDamage = true,
|
||||
BreakOnMove = true,
|
||||
DistanceThreshold = 2f
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void OnDoAfterComplete(EntityUid uid, RatKingRummageableComponent component, RatKingRummageDoAfterEvent args)
|
||||
{
|
||||
if (args.Cancelled || component.Looted)
|
||||
return;
|
||||
|
||||
component.Looted = true;
|
||||
Dirty(uid, component);
|
||||
_audio.PlayPredicted(component.Sound, uid, args.User);
|
||||
|
||||
var spawn = PrototypeManager.Index<WeightedRandomEntityPrototype>(component.RummageLoot).Pick(Random);
|
||||
if (_net.IsServer)
|
||||
Spawn(spawn, Transform(uid).Coordinates);
|
||||
}
|
||||
|
||||
public void UpdateAllServants(EntityUid uid, RatKingComponent component)
|
||||
{
|
||||
foreach (var servant in component.Servants)
|
||||
@@ -160,9 +108,3 @@ public abstract class SharedRatKingSystem : EntitySystem
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class RatKingRummageDoAfterEvent : SimpleDoAfterEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
82
Content.Shared/RatKing/Systems/RummagerSystem.cs
Normal file
82
Content.Shared/RatKing/Systems/RummagerSystem.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.EntityTable;
|
||||
using Content.Shared.RatKing.Components;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.RatKing.Systems;
|
||||
|
||||
public sealed class RummagerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly EntityTableSystem _entityTable = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RummageableComponent, GetVerbsEvent<AlternativeVerb>>(OnGetVerb);
|
||||
SubscribeLocalEvent<RummageableComponent, RummageDoAfterEvent>(OnDoAfterComplete);
|
||||
}
|
||||
|
||||
private void OnGetVerb(Entity<RummageableComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!HasComp<RummagerComponent>(args.User) || ent.Comp.Looted)
|
||||
return;
|
||||
|
||||
var user = args.User;
|
||||
|
||||
args.Verbs.Add(new AlternativeVerb
|
||||
{
|
||||
Text = Loc.GetString("rat-king-rummage-text"),
|
||||
Priority = 0,
|
||||
Act = () =>
|
||||
{
|
||||
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager,
|
||||
user,
|
||||
ent.Comp.RummageDuration,
|
||||
new RummageDoAfterEvent(),
|
||||
ent,
|
||||
ent)
|
||||
{
|
||||
BlockDuplicate = true,
|
||||
BreakOnDamage = true,
|
||||
BreakOnMove = true,
|
||||
DistanceThreshold = 2f
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void OnDoAfterComplete(Entity<RummageableComponent> ent, ref RummageDoAfterEvent args)
|
||||
{
|
||||
if (args.Cancelled || ent.Comp.Looted)
|
||||
return;
|
||||
|
||||
ent.Comp.Looted = true;
|
||||
Dirty(ent, ent.Comp);
|
||||
_audio.PlayPredicted(ent.Comp.Sound, ent, args.User);
|
||||
|
||||
if (_net.IsClient)
|
||||
return;
|
||||
|
||||
var spawns = _entityTable.GetSpawns(ent.Comp.Table);
|
||||
var coordinates = Transform(ent).Coordinates;
|
||||
|
||||
foreach (var spawn in spawns)
|
||||
{
|
||||
Spawn(spawn, coordinates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DoAfter event for rummaging through a container with RummageableComponent.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class RummageDoAfterEvent : SimpleDoAfterEvent;
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Content.Shared.StationRecords;
|
||||
|
||||
public abstract class SharedStationRecordsSystem : EntitySystem
|
||||
@@ -40,4 +42,60 @@ public abstract class SharedStationRecordsSystem : EntitySystem
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <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="key">Station and 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. Always false on client.</returns>
|
||||
public bool TryGetRecord<T>(StationRecordKey key, [NotNullWhen(true)] out T? entry, StationRecordsComponent? records = null)
|
||||
{
|
||||
entry = default;
|
||||
|
||||
if (!Resolve(key.OriginStation, ref records))
|
||||
return false;
|
||||
|
||||
return records.Records.TryGetRecordEntry(key.Id, 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. Always empty on client.</returns>
|
||||
public IEnumerable<(uint, T)> GetRecordsOfType<T>(EntityUid station, StationRecordsComponent? records = null)
|
||||
{
|
||||
if (!Resolve(station, ref records))
|
||||
return Array.Empty<(uint, T)>();
|
||||
|
||||
return records.Records.GetRecordsOfType<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an id if a record with the same name exists.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Linear search so O(n) time complexity.
|
||||
/// </remarks>
|
||||
/// <returns>Returns a station record id. Always null on client.</returns>
|
||||
public uint? GetRecordByName(EntityUid station, string name, StationRecordsComponent? records = null)
|
||||
{
|
||||
if (!Resolve(station, ref records, false))
|
||||
return null;
|
||||
|
||||
foreach (var (id, record) in GetRecordsOfType<GeneralStationRecord>(station, records))
|
||||
{
|
||||
if (record.Name == name)
|
||||
return id;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.StationRecords;
|
||||
namespace Content.Shared.StationRecords;
|
||||
|
||||
/// <summary>
|
||||
/// Set of station records for a single station. StationRecordsComponent stores these.
|
||||
@@ -1,8 +1,6 @@
|
||||
using Content.Server.StationRecords.Systems;
|
||||
namespace Content.Shared.StationRecords;
|
||||
|
||||
namespace Content.Server.StationRecords;
|
||||
|
||||
[Access(typeof(StationRecordsSystem))]
|
||||
[Access(typeof(SharedStationRecordsSystem))]
|
||||
[RegisterComponent]
|
||||
public sealed partial class StationRecordsComponent : Component
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
using Content.Shared.Movement.Pulling.Systems;
|
||||
@@ -6,7 +6,6 @@ using Content.Shared.Popups;
|
||||
using Content.Shared.Projectiles;
|
||||
using Content.Shared.Teleportation.Components;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
@@ -19,8 +18,10 @@ using Robust.Shared.Utility;
|
||||
namespace Content.Shared.Teleportation.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// This handles teleporting entities through portals, and creating new linked portals.
|
||||
/// This handles teleporting entities from a portal to a linked portal, or to a random nearby destination.
|
||||
/// Uses <see cref="LinkedEntitySystem"/> to get linked portals.
|
||||
/// </summary>
|
||||
/// <seealso cref="PortalComponent"/>
|
||||
public abstract class SharedPortalSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
@@ -39,12 +40,13 @@ public abstract class SharedPortalSystem : EntitySystem
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<PortalComponent, GetVerbsEvent<AlternativeVerb>>(OnGetVerbs);
|
||||
|
||||
SubscribeLocalEvent<PortalComponent, StartCollideEvent>(OnCollide);
|
||||
SubscribeLocalEvent<PortalComponent, EndCollideEvent>(OnEndCollide);
|
||||
SubscribeLocalEvent<PortalComponent, GetVerbsEvent<AlternativeVerb>>(OnGetVerbs);
|
||||
}
|
||||
|
||||
private void OnGetVerbs(EntityUid uid, PortalComponent component, GetVerbsEvent<AlternativeVerb> args)
|
||||
private void OnGetVerbs(Entity<PortalComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
// Traversal altverb for ghosts to use that bypasses normal functionality
|
||||
if (!args.CanAccess || !HasComp<GhostComponent>(args.User))
|
||||
@@ -52,8 +54,9 @@ public abstract class SharedPortalSystem : EntitySystem
|
||||
|
||||
// Don't use the verb with unlinked or with multi-output portals
|
||||
// (this is only intended to be useful for ghosts to see where a linked portal leads)
|
||||
var disabled = !TryComp<LinkedEntityComponent>(uid, out var link) || link.LinkedEntities.Count != 1;
|
||||
var disabled = !TryComp<LinkedEntityComponent>(ent, out var link) || link.LinkedEntities.Count != 1;
|
||||
|
||||
var subject = args.User;
|
||||
args.Verbs.Add(new AlternativeVerb
|
||||
{
|
||||
Priority = 11,
|
||||
@@ -62,8 +65,13 @@ public abstract class SharedPortalSystem : EntitySystem
|
||||
if (link == null || disabled)
|
||||
return;
|
||||
|
||||
var ent = link.LinkedEntities.First();
|
||||
TeleportEntity(uid, args.User, Transform(ent).Coordinates, ent, false);
|
||||
// check prediction
|
||||
if (_netMan.IsClient && !CanPredictTeleport((ent, link)))
|
||||
return;
|
||||
|
||||
var destination = link.LinkedEntities.First();
|
||||
|
||||
TeleportEntity(ent, subject, Transform(destination).Coordinates, destination, false);
|
||||
},
|
||||
Disabled = disabled,
|
||||
Text = Loc.GetString("portal-component-ghost-traverse"),
|
||||
@@ -74,14 +82,7 @@ public abstract class SharedPortalSystem : EntitySystem
|
||||
});
|
||||
}
|
||||
|
||||
private bool ShouldCollide(string ourId, string otherId, Fixture our, Fixture other)
|
||||
{
|
||||
// most non-hard fixtures shouldn't pass through portals, but projectiles are non-hard as well
|
||||
// and they should still pass through
|
||||
return ourId == PortalFixture && (other.Hard || otherId == ProjectileFixture);
|
||||
}
|
||||
|
||||
private void OnCollide(EntityUid uid, PortalComponent component, ref StartCollideEvent args)
|
||||
private void OnCollide(Entity<PortalComponent> ent, ref StartCollideEvent args)
|
||||
{
|
||||
if (!ShouldCollide(args.OurFixtureId, args.OtherFixtureId, args.OurFixture, args.OtherFixture))
|
||||
return;
|
||||
@@ -92,7 +93,7 @@ public abstract class SharedPortalSystem : EntitySystem
|
||||
if (Transform(subject).Anchored)
|
||||
return;
|
||||
|
||||
// break pulls before portal enter so we dont break shit
|
||||
// break pulls before portal enter so we don't break shit
|
||||
if (TryComp<PullableComponent>(subject, out var pullable) && pullable.BeingPulled)
|
||||
{
|
||||
_pulling.TryStopPull(subject, pullable);
|
||||
@@ -110,33 +111,27 @@ public abstract class SharedPortalSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryComp<LinkedEntityComponent>(uid, out var link))
|
||||
if (TryComp<LinkedEntityComponent>(ent, out var link))
|
||||
{
|
||||
if (link.LinkedEntities.Count == 0)
|
||||
return;
|
||||
|
||||
// client can't predict outside of simple portal-to-portal interactions due to randomness involved
|
||||
// --also can't predict if the target doesn't exist on the client / is outside of PVS
|
||||
if (_netMan.IsClient)
|
||||
{
|
||||
var first = link.LinkedEntities.First();
|
||||
var exists = Exists(first);
|
||||
if (link.LinkedEntities.Count != 1 || !exists || (exists && Transform(first).MapID == MapId.Nullspace))
|
||||
return;
|
||||
}
|
||||
// check prediction
|
||||
if (_netMan.IsClient && !CanPredictTeleport((ent, link)))
|
||||
return;
|
||||
|
||||
// pick a target and teleport there
|
||||
var target = _random.Pick(link.LinkedEntities);
|
||||
|
||||
if (HasComp<PortalComponent>(target))
|
||||
{
|
||||
// if target is a portal, signal that they shouldn't be immediately portaled back
|
||||
// if target is a portal, signal that they shouldn't be immediately teleported back
|
||||
var timeout = EnsureComp<PortalTimeoutComponent>(subject);
|
||||
timeout.EnteredPortal = uid;
|
||||
timeout.EnteredPortal = ent;
|
||||
Dirty(subject, timeout);
|
||||
}
|
||||
|
||||
TeleportEntity(uid, subject, Transform(target).Coordinates, target);
|
||||
TeleportEntity(ent, subject, Transform(target).Coordinates, target);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -144,49 +139,86 @@ public abstract class SharedPortalSystem : EntitySystem
|
||||
return;
|
||||
|
||||
// no linked entity--teleport randomly
|
||||
if (component.RandomTeleport)
|
||||
TeleportRandomly(uid, subject, component);
|
||||
if (ent.Comp.RandomTeleport)
|
||||
TeleportRandomly(ent, subject);
|
||||
}
|
||||
|
||||
private void OnEndCollide(EntityUid uid, PortalComponent component, ref EndCollideEvent args)
|
||||
private void OnEndCollide(Entity<PortalComponent> ent, ref EndCollideEvent args)
|
||||
{
|
||||
if (!ShouldCollide(args.OurFixtureId, args.OtherFixtureId, args.OurFixture, args.OtherFixture))
|
||||
return;
|
||||
|
||||
var subject = args.OtherEntity;
|
||||
|
||||
// if they came from (not us), remove the timeout
|
||||
if (TryComp<PortalTimeoutComponent>(subject, out var timeout) && timeout.EnteredPortal != uid)
|
||||
// if they came from a different portal, remove the timeout
|
||||
if (TryComp<PortalTimeoutComponent>(subject, out var timeout) && timeout.EnteredPortal != ent)
|
||||
{
|
||||
RemCompDeferred<PortalTimeoutComponent>(subject);
|
||||
}
|
||||
}
|
||||
|
||||
private void TeleportEntity(EntityUid portal, EntityUid subject, EntityCoordinates target, EntityUid? targetEntity = null, bool playSound = true,
|
||||
PortalComponent? portalComponent = null)
|
||||
/// <summary>
|
||||
/// Checks if the colliding fixtures are the ones we want.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// False if our fixture is not a portal fixture.
|
||||
/// False if other fixture is not hard, but makes an exception for projectiles.
|
||||
/// </returns>
|
||||
private bool ShouldCollide(string ourId, string otherId, Fixture our, Fixture other)
|
||||
{
|
||||
if (!Resolve(portal, ref portalComponent))
|
||||
return;
|
||||
return ourId == PortalFixture && (other.Hard || otherId == ProjectileFixture);
|
||||
}
|
||||
|
||||
var ourCoords = Transform(portal).Coordinates;
|
||||
/// <summary>
|
||||
/// Checks if the client is able to predict the teleport.
|
||||
/// Client can't predict outside 1-to-1 portal-to-portal interactions due to randomness involved.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// False if the linked entity count isn't 1.
|
||||
/// False if the linked entity doesn't exist on the client / is outside PVS.
|
||||
/// </returns>
|
||||
private bool CanPredictTeleport(Entity<LinkedEntityComponent> portal)
|
||||
{
|
||||
var first = portal.Comp.LinkedEntities.First();
|
||||
var exists = Exists(first);
|
||||
|
||||
if (!exists ||
|
||||
portal.Comp.LinkedEntities.Count != 1 || // 0 and >1 use RNG
|
||||
exists && Transform(first).MapID == MapId.Nullspace) // The linked entity is most likely outside PVS
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles teleporting a subject from the portal entity to a coordinate.
|
||||
/// Also deletes invalid portals.
|
||||
/// </summary>
|
||||
/// <param name="ent"> The portal being collided with. </param>
|
||||
/// <param name="subject"> The entity getting teleported. </param>
|
||||
/// <param name="target"> The location to teleport to. </param>
|
||||
/// <param name="targetEntity"> The portal on the other side of the teleport. </param>
|
||||
private void TeleportEntity(Entity<PortalComponent> ent, EntityUid subject, EntityCoordinates target, EntityUid? targetEntity = null, bool playSound = true)
|
||||
{
|
||||
var ourCoords = Transform(ent).Coordinates;
|
||||
var onSameMap = _transform.GetMapId(ourCoords) == _transform.GetMapId(target);
|
||||
var distanceInvalid = portalComponent.MaxTeleportRadius != null
|
||||
var distanceInvalid = ent.Comp.MaxTeleportRadius != null
|
||||
&& ourCoords.TryDistance(EntityManager, target, out var distance)
|
||||
&& distance > portalComponent.MaxTeleportRadius;
|
||||
&& distance > ent.Comp.MaxTeleportRadius;
|
||||
|
||||
if (!onSameMap && !portalComponent.CanTeleportToOtherMaps || distanceInvalid)
|
||||
// Early out if this is an invalid configuration
|
||||
if (!onSameMap && !ent.Comp.CanTeleportToOtherMaps || distanceInvalid)
|
||||
{
|
||||
if (!_netMan.IsServer)
|
||||
if (_netMan.IsClient)
|
||||
return;
|
||||
|
||||
// Early out if this is an invalid configuration
|
||||
_popup.PopupCoordinates(Loc.GetString("portal-component-invalid-configuration-fizzle"),
|
||||
ourCoords, Filter.Pvs(ourCoords, entityMan: EntityManager), true);
|
||||
|
||||
_popup.PopupCoordinates(Loc.GetString("portal-component-invalid-configuration-fizzle"),
|
||||
target, Filter.Pvs(target, entityMan: EntityManager), true);
|
||||
|
||||
QueueDel(portal);
|
||||
QueueDel(ent);
|
||||
|
||||
if (targetEntity != null)
|
||||
QueueDel(targetEntity.Value);
|
||||
@@ -194,8 +226,8 @@ public abstract class SharedPortalSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
var arrivalSound = CompOrNull<PortalComponent>(targetEntity)?.ArrivalSound ?? portalComponent.ArrivalSound;
|
||||
var departureSound = portalComponent.DepartureSound;
|
||||
var arrivalSound = CompOrNull<PortalComponent>(targetEntity)?.ArrivalSound ?? ent.Comp.ArrivalSound;
|
||||
var departureSound = ent.Comp.DepartureSound;
|
||||
|
||||
// Some special cased stuff: projectiles should stop ignoring shooter when they enter a portal, to avoid
|
||||
// stacking 500 bullets in between 2 portals and instakilling people--you'll just hit yourself instead
|
||||
@@ -205,36 +237,41 @@ public abstract class SharedPortalSystem : EntitySystem
|
||||
projectile.IgnoreShooter = false;
|
||||
}
|
||||
|
||||
LogTeleport(portal, subject, Transform(subject).Coordinates, target);
|
||||
LogTeleport(ent, subject, Transform(subject).Coordinates, target);
|
||||
|
||||
_transform.SetCoordinates(subject, target);
|
||||
|
||||
if (!playSound)
|
||||
return;
|
||||
|
||||
_audio.PlayPredicted(departureSound, portal, subject);
|
||||
_audio.PlayPredicted(departureSound, ent, subject);
|
||||
_audio.PlayPredicted(arrivalSound, subject, subject);
|
||||
}
|
||||
|
||||
private void TeleportRandomly(EntityUid portal, EntityUid subject, PortalComponent? component = null)
|
||||
/// <summary>
|
||||
/// Finds a random coordinate within the portal's radius and teleports the subject there.
|
||||
/// Attempts to not put the subject inside a static entity (e.g. wall).
|
||||
/// </summary>
|
||||
/// <param name="ent"> The portal being collided with. </param>
|
||||
/// <param name="subject"> The entity getting teleported. </param>
|
||||
private void TeleportRandomly(Entity<PortalComponent> ent, EntityUid subject)
|
||||
{
|
||||
if (!Resolve(portal, ref component))
|
||||
return;
|
||||
|
||||
var xform = Transform(portal);
|
||||
var xform = Transform(ent);
|
||||
var coords = xform.Coordinates;
|
||||
var newCoords = coords.Offset(_random.NextVector2(component.MaxRandomRadius));
|
||||
var newCoords = coords.Offset(_random.NextVector2(ent.Comp.MaxRandomRadius));
|
||||
for (var i = 0; i < MaxRandomTeleportAttempts; i++)
|
||||
{
|
||||
var randVector = _random.NextVector2(component.MaxRandomRadius);
|
||||
var randVector = _random.NextVector2(ent.Comp.MaxRandomRadius);
|
||||
newCoords = coords.Offset(randVector);
|
||||
if (!_lookup.AnyEntitiesIntersecting(_transform.ToMapCoordinates(newCoords), LookupFlags.Static))
|
||||
{
|
||||
// newCoords is not a wall
|
||||
break;
|
||||
}
|
||||
// after "MaxRandomTeleportAttempts" attempts, end up in the walls
|
||||
}
|
||||
|
||||
TeleportEntity(portal, subject, newCoords);
|
||||
TeleportEntity(ent, subject, newCoords);
|
||||
}
|
||||
|
||||
protected virtual void LogTeleport(EntityUid portal, EntityUid subject, EntityCoordinates source,
|
||||
|
||||
@@ -5,7 +5,6 @@ using Content.Shared.CCVar;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Friction;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Projectiles;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map;
|
||||
@@ -30,7 +29,6 @@ public sealed class ThrowingSystem : EntitySystem
|
||||
private float _airDamping;
|
||||
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly SharedGravitySystem _gravity = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly ThrownItemSystem _thrownSystem = default!;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Explosion.Components;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Trigger.Components.Effects;
|
||||
@@ -7,8 +8,6 @@ namespace Content.Shared.Trigger.Components.Effects;
|
||||
/// TargetUser will only work of the user has ExplosiveComponent as well.
|
||||
/// The User will be logged in the admin logs.
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// TODO: Allow this to work without an ExplosiveComponent on the user via QueueExplosion.
|
||||
/// </summary>
|
||||
/// <seealso cref="ExplosionOnTriggerComponent"/>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class ExplodeOnTriggerComponent : BaseXOnTriggerComponent;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using Content.Shared.Explosion;
|
||||
using Content.Shared.Explosion.Components;
|
||||
using Content.Shared.Explosion.EntitySystems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Trigger.Components.Effects;
|
||||
|
||||
// TODO some sort of struct like DamageSpecifier but for explosions.
|
||||
/// <summary>
|
||||
/// Will explode the entity using this component's explosion specifications.
|
||||
/// If TargetUser is true, they'll explode instead.
|
||||
/// The User will be logged in the admin logs.
|
||||
/// </summary>
|
||||
/// <seealso cref="ExplodeOnTriggerComponent"/>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class ExplosionOnTriggerComponent : BaseXOnTriggerComponent
|
||||
{
|
||||
/// <inheritdoc cref="ExplosiveComponent.ExplosionType"/>
|
||||
[DataField, AutoNetworkedField]
|
||||
public ProtoId<ExplosionPrototype> ExplosionType = SharedExplosionSystem.DefaultExplosionPrototypeId;
|
||||
|
||||
/// <inheritdoc cref="ExplosiveComponent.MaxIntensity"/>
|
||||
[DataField, AutoNetworkedField]
|
||||
public float MaxTileIntensity = 4;
|
||||
|
||||
/// <inheritdoc cref="ExplosiveComponent.IntensitySlope"/>
|
||||
[DataField, AutoNetworkedField]
|
||||
public float IntensitySlope = 1;
|
||||
|
||||
/// <inheritdoc cref="ExplosiveComponent.TotalIntensity"/>
|
||||
[DataField, AutoNetworkedField]
|
||||
public float TotalIntensity = 10;
|
||||
|
||||
/// <inheritdoc cref="ExplosiveComponent.TileBreakScale"/>
|
||||
[DataField, AutoNetworkedField]
|
||||
public float TileBreakScale = 1f;
|
||||
|
||||
/// <inheritdoc cref="ExplosiveComponent.MaxTileBreak"/>
|
||||
[DataField, AutoNetworkedField]
|
||||
public int MaxTileBreak = int.MaxValue;
|
||||
|
||||
/// <inheritdoc cref="ExplosiveComponent.CanCreateVacuum"/>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool CanCreateVacuum = true;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Content.Shared.Weather;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Trigger.Components.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// Changes the current weather when triggered.
|
||||
/// If TargetUser is true then it will change the weather at the user's map instead of the entitys map.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class WeatherOnTriggerComponent : BaseXOnTriggerComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Weather type. Null to clear the weather.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public ProtoId<WeatherPrototype>? Weather;
|
||||
|
||||
/// <summary>
|
||||
/// How long the weather should last. Null for forever.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public TimeSpan? Duration;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Content.Shared.GameTicking;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Trigger.Components.Triggers;
|
||||
|
||||
/// <summary>
|
||||
/// A trigger which occurs on <see cref="PlayerSpawnCompleteEvent"/>.
|
||||
/// </summary>
|
||||
/// <remarks>This does not work with <see cref="TraitSystem"/>, as it would add this component while the event is getting raised.</remarks>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class TriggerOnPlayerSpawnCompleteComponent : BaseTriggerOnXComponent;
|
||||
@@ -13,7 +13,7 @@ public sealed class DnaScrambleOnTriggerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly SharedHumanoidAppearanceSystem _humanoidAppearance = default!;
|
||||
[Dependency] private readonly SharedIdentitySystem _identity = default!;
|
||||
[Dependency] private readonly IdentitySystem _identity = default!;
|
||||
[Dependency] private readonly SharedForensicsSystem _forensics = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
|
||||
@@ -11,10 +11,11 @@ public sealed class ExplodeOnTriggerSystem : EntitySystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ExplodeOnTriggerComponent, TriggerEvent>(OnTrigger);
|
||||
SubscribeLocalEvent<ExplodeOnTriggerComponent, TriggerEvent>(OnExplodeTrigger);
|
||||
SubscribeLocalEvent<ExplosionOnTriggerComponent, TriggerEvent>(OnQueueExplosionTrigger);
|
||||
}
|
||||
|
||||
private void OnTrigger(Entity<ExplodeOnTriggerComponent> ent, ref TriggerEvent args)
|
||||
private void OnExplodeTrigger(Entity<ExplodeOnTriggerComponent> ent, ref TriggerEvent args)
|
||||
{
|
||||
if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
|
||||
return;
|
||||
@@ -27,4 +28,27 @@ public sealed class ExplodeOnTriggerSystem : EntitySystem
|
||||
_explosion.TriggerExplosive(target.Value, user: args.User);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnQueueExplosionTrigger(Entity<ExplosionOnTriggerComponent> ent, ref TriggerEvent args)
|
||||
{
|
||||
var (uid, comp) = ent;
|
||||
if (args.Key != null && !comp.KeysIn.Contains(args.Key))
|
||||
return;
|
||||
|
||||
var target = comp.TargetUser ? args.User : uid;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
_explosion.QueueExplosion(target.Value,
|
||||
comp.ExplosionType,
|
||||
comp.TotalIntensity,
|
||||
comp.IntensitySlope,
|
||||
comp.MaxTileIntensity,
|
||||
comp.TileBreakScale,
|
||||
comp.MaxTileBreak,
|
||||
comp.CanCreateVacuum,
|
||||
args.User);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Trigger.Components.Effects;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Trigger.Components.Effects;
|
||||
using Content.Shared.Trigger.Components.Triggers;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -9,6 +10,7 @@ public sealed partial class TriggerSystem
|
||||
private void InitializeSpawn()
|
||||
{
|
||||
SubscribeLocalEvent<TriggerOnSpawnComponent, MapInitEvent>(OnSpawnInit);
|
||||
SubscribeLocalEvent<TriggerOnPlayerSpawnCompleteComponent, PlayerSpawnCompleteEvent>(OnPlayerSpawn);
|
||||
|
||||
SubscribeLocalEvent<SpawnOnTriggerComponent, TriggerEvent>(HandleSpawnOnTrigger);
|
||||
SubscribeLocalEvent<SpawnEntityTableOnTriggerComponent, TriggerEvent>(HandleSpawnTableOnTrigger);
|
||||
@@ -20,6 +22,11 @@ public sealed partial class TriggerSystem
|
||||
Trigger(ent.Owner, null, ent.Comp.KeyOut);
|
||||
}
|
||||
|
||||
private void OnPlayerSpawn(Entity<TriggerOnPlayerSpawnCompleteComponent> ent, ref PlayerSpawnCompleteEvent args)
|
||||
{
|
||||
Trigger(ent.Owner, null, ent.Comp.KeyOut);
|
||||
}
|
||||
|
||||
private void HandleSpawnOnTrigger(Entity<SpawnOnTriggerComponent> ent, ref TriggerEvent args)
|
||||
{
|
||||
if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
|
||||
|
||||
44
Content.Shared/Trigger/Systems/WeatherTriggerSystem.cs
Normal file
44
Content.Shared/Trigger/Systems/WeatherTriggerSystem.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Content.Shared.Trigger.Components.Effects;
|
||||
using Content.Shared.Weather;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Trigger.Systems;
|
||||
|
||||
public sealed class WeatherTriggerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly SharedWeatherSystem _weather = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<WeatherOnTriggerComponent, TriggerEvent>(OnTrigger);
|
||||
}
|
||||
|
||||
private void OnTrigger(Entity<WeatherOnTriggerComponent> ent, ref TriggerEvent args)
|
||||
{
|
||||
if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
|
||||
return;
|
||||
|
||||
var target = ent.Comp.TargetUser ? args.User : ent.Owner;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
var xform = Transform(target.Value);
|
||||
|
||||
if (ent.Comp.Weather == null) //Clear weather if nothing is set
|
||||
{
|
||||
_weather.SetWeather(xform.MapID, null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
var endTime = ent.Comp.Duration == null ? null : ent.Comp.Duration + _timing.CurTime;
|
||||
|
||||
if (_prototypeManager.Resolve(ent.Comp.Weather, out var weatherPrototype))
|
||||
_weather.SetWeather(xform.MapID, weatherPrototype, endTime);
|
||||
}
|
||||
}
|
||||
@@ -1,48 +1,4 @@
|
||||
Entries:
|
||||
- author: qwerltaz
|
||||
changes:
|
||||
- message: Snipping the panic wire in an air alarm now forces panic mode until the
|
||||
wire is mended.
|
||||
type: Tweak
|
||||
id: 8485
|
||||
time: '2025-05-14T20:39:47.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/36439
|
||||
- author: IProduceWidgets
|
||||
changes:
|
||||
- message: more wizard name variety
|
||||
type: Add
|
||||
id: 8486
|
||||
time: '2025-05-14T21:06:56.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/36437
|
||||
- author: archee1
|
||||
changes:
|
||||
- message: Bike horns of all varieties have had their textures updated slightly.
|
||||
type: Tweak
|
||||
id: 8487
|
||||
time: '2025-05-14T22:05:45.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/37413
|
||||
- author: slarticodefast
|
||||
changes:
|
||||
- message: Fixed paradox clones not copying the mute, snoring and frontal lisp traits.
|
||||
type: Fix
|
||||
id: 8488
|
||||
time: '2025-05-15T01:45:48.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/37467
|
||||
- author: kosticia
|
||||
changes:
|
||||
- message: Hamsters, mice, butterflies, mothroaches, bees, bats, snails, rats, ticks
|
||||
and cockroaches now don't leave organs on gibbing.
|
||||
type: Fix
|
||||
id: 8489
|
||||
time: '2025-05-15T03:45:50.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/37080
|
||||
- author: EmoGarbage404
|
||||
changes:
|
||||
- message: Goliath tentacles no longer miss if you stand still.
|
||||
type: Fix
|
||||
id: 8490
|
||||
time: '2025-05-15T03:55:05.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/37168
|
||||
- author: K-Dynamic
|
||||
changes:
|
||||
- message: Industrial advanced welders may be found in welding supplies lockers.
|
||||
@@ -3954,3 +3910,48 @@
|
||||
id: 8995
|
||||
time: '2025-09-22T06:40:14.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/40491
|
||||
- author: hoshizora-sayo
|
||||
changes:
|
||||
- message: Fixed fire helmets alone giving you full temperature protection
|
||||
type: Fix
|
||||
id: 8996
|
||||
time: '2025-09-23T17:02:50.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/40481
|
||||
- author: aada
|
||||
changes:
|
||||
- message: Circuit tiles and faux tiles have been moved to the cutter machine.
|
||||
type: Tweak
|
||||
id: 8997
|
||||
time: '2025-09-24T00:12:45.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/37982
|
||||
- author: Kittygyat
|
||||
changes:
|
||||
- message: Added 4 diagnostic huds to the engi-vend
|
||||
type: Add
|
||||
id: 8998
|
||||
time: '2025-09-24T04:19:38.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/40461
|
||||
- author: RedBookcase
|
||||
changes:
|
||||
- message: Rechargers can now charge power cells again.
|
||||
type: Tweak
|
||||
id: 8999
|
||||
time: '2025-09-24T20:33:45.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/38138
|
||||
- author: Nox38, BurgerMoth
|
||||
changes:
|
||||
- message: Added descriptions to .20 ammo boxes, magazines, and cartridges.
|
||||
type: Add
|
||||
- message: Changed the descriptions of the Lecter, Estoc DMR, and M90GL.
|
||||
type: Tweak
|
||||
id: 9000
|
||||
time: '2025-09-24T21:48:34.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/36496
|
||||
- author: Keer-Sar
|
||||
changes:
|
||||
- message: Added two new markings for lizard snouts, "Lizard Visage (Round)" & "Lizard
|
||||
Visage (Sharp)."
|
||||
type: Add
|
||||
id: 9001
|
||||
time: '2025-09-24T23:13:14.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/35294
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
comp-kitchen-spike-begin-hook-self = You begin dragging yourself onto { THE($hook) }!
|
||||
comp-kitchen-spike-begin-hook-self = You begin dragging yourself onto { THE($hook) }!
|
||||
comp-kitchen-spike-begin-hook-self-other = { CAPITALIZE(THE($victim)) } begins dragging { REFLEXIVE($victim) } onto { THE($hook) }!
|
||||
|
||||
comp-kitchen-spike-begin-hook-other-self = You begin dragging { CAPITALIZE(THE($victim)) } onto { THE($hook) }!
|
||||
comp-kitchen-spike-begin-hook-other = { CAPITALIZE(THE($user)) } begins dragging { CAPITALIZE(THE($victim)) } onto { THE($hook) }!a
|
||||
comp-kitchen-spike-begin-hook-other = { CAPITALIZE(THE($user)) } begins dragging { CAPITALIZE(THE($victim)) } onto { THE($hook) }!
|
||||
|
||||
comp-kitchen-spike-hook-self = You threw yourself on { THE($hook) }!
|
||||
comp-kitchen-spike-hook-self-other = { CAPITALIZE(THE($victim)) } threw { REFLEXIVE($victim) } on { THE($hook) }!
|
||||
|
||||
@@ -4,7 +4,6 @@ lathe-category-clothing = Clothing
|
||||
lathe-category-lights = Lights
|
||||
lathe-category-machines = Machines
|
||||
lathe-category-parts = Parts
|
||||
lathe-category-tiles = Tiles
|
||||
lathe-category-tools = Tools
|
||||
lathe-category-weapons = Weapons
|
||||
|
||||
@@ -24,13 +23,16 @@ lathe-category-service = Service
|
||||
lathe-category-supply = Supply
|
||||
|
||||
# Cutter
|
||||
lathe-category-concrete = Concrete
|
||||
lathe-category-dark = Dark
|
||||
lathe-category-maints = Maints
|
||||
lathe-category-steel = Steel
|
||||
lathe-category-white = White
|
||||
lathe-category-wood = Wood
|
||||
lathe-category-tiles = Tiles
|
||||
lathe-category-circuit-tile = Circuit
|
||||
lathe-category-concrete-tile = Concrete
|
||||
lathe-category-dark-tile = Dark
|
||||
lathe-category-faux-tile = Faux
|
||||
lathe-category-maints-tile = Maints
|
||||
lathe-category-marble = Marble
|
||||
lathe-category-steel-tile = Steel
|
||||
lathe-category-white-tile = White
|
||||
lathe-category-wood-tile = Wood
|
||||
|
||||
# Science
|
||||
lathe-category-mechs = Mechs
|
||||
|
||||
@@ -117,3 +117,9 @@ marking-LizardChestFin = Lizard Fin
|
||||
marking-LizardSnoutSplotch = Lizard Snout (Splotch)
|
||||
marking-LizardSnoutSplotch-snout_splotch_primary = Muzzle
|
||||
marking-LizardSnoutSplotch-snout_splotch_secondary = Snoot
|
||||
|
||||
marking-LizardSnoutVisageSharp = Lizard Visage (Sharp)
|
||||
marking-LizardSnoutVisageSharp-visage_sharp = Lizard Visage (Sharp)
|
||||
|
||||
marking-LizardSnoutVisageRound = Lizard Visage (Round)
|
||||
marking-LizardSnoutVisageRound-visage_round = Lizard Visage (Round)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
id: EngiVendInventory
|
||||
startingInventory:
|
||||
ClothingEyesGlassesMeson: 4
|
||||
ClothingEyesHudDiagnostic: 4
|
||||
ClothingHeadHatWelding: 6
|
||||
CrowbarYellow: 8
|
||||
Multitool: 4
|
||||
|
||||
@@ -273,8 +273,8 @@
|
||||
quickEquip: true
|
||||
- type: IngestionBlocker
|
||||
- type: TemperatureProtection
|
||||
heatingCoefficient: 0.01
|
||||
coolingCoefficient: 0.2
|
||||
heatingCoefficient: 0.5
|
||||
coolingCoefficient: 0.8
|
||||
- type: FireProtection
|
||||
reduction: 0.15 # not fully sealed so less protection
|
||||
- type: IdentityBlocker
|
||||
@@ -301,8 +301,8 @@
|
||||
quickEquip: true
|
||||
- type: IngestionBlocker
|
||||
- type: TemperatureProtection
|
||||
heatingCoefficient: 0.01
|
||||
coolingCoefficient: 0.2
|
||||
heatingCoefficient: 0.3
|
||||
coolingCoefficient: 0.8
|
||||
- type: FireProtection
|
||||
reduction: 0.2
|
||||
- type: PressureProtection
|
||||
|
||||
@@ -206,7 +206,7 @@
|
||||
delay: 3
|
||||
transferAmount: 1
|
||||
solution: puddle
|
||||
utensil: None
|
||||
utensil: Spoon
|
||||
- type: ExaminableSolution
|
||||
solution: puddle
|
||||
locVolume: "examinable-solution-on-examine-volume-puddle"
|
||||
|
||||
@@ -213,6 +213,24 @@
|
||||
- sprite: Mobs/Customization/reptilian_parts.rsi
|
||||
state: snout_splotch_secondary
|
||||
|
||||
- type: marking
|
||||
id: LizardSnoutVisageRound
|
||||
bodyPart: Snout
|
||||
markingCategory: Snout
|
||||
speciesRestriction: [Reptilian]
|
||||
sprites:
|
||||
- sprite: Mobs/Customization/reptilian_parts.rsi
|
||||
state: visage_round
|
||||
|
||||
- type: marking
|
||||
id: LizardSnoutVisageSharp
|
||||
bodyPart: Snout
|
||||
markingCategory: Snout
|
||||
speciesRestriction: [Reptilian]
|
||||
sprites:
|
||||
- sprite: Mobs/Customization/reptilian_parts.rsi
|
||||
state: visage_sharp
|
||||
|
||||
- type: marking
|
||||
id: LizardChestTiger
|
||||
bodyPart: Chest
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
- CannotSuicide
|
||||
- FootstepSound
|
||||
- type: NoSlip
|
||||
- type: Rummager
|
||||
- type: RatKing
|
||||
hungerPerArmyUse: 25
|
||||
hungerPerDomainUse: 50
|
||||
@@ -308,12 +309,16 @@
|
||||
sprite: Mobs/Effects/onfire.rsi
|
||||
normalState: Mouse_burning
|
||||
|
||||
- type: weightedRandomEntity
|
||||
- type: entityTable
|
||||
id: RatKingLoot
|
||||
weights:
|
||||
RandomSpawner100: 66 #garbage
|
||||
FoodCheese: 28 #food
|
||||
IngotGold1: 5 #loot
|
||||
table: !type:GroupSelector
|
||||
children:
|
||||
- id: RandomSpawner100
|
||||
weight: 66
|
||||
- id: FoodCheese
|
||||
weight: 28
|
||||
- id: IngotGold1
|
||||
weight: 5
|
||||
|
||||
- type: entity
|
||||
parent: BaseAction
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
- type: entity
|
||||
- type: entity
|
||||
abstract: true
|
||||
save: false
|
||||
parent: [BaseMobSpeciesOrganic]
|
||||
@@ -172,9 +172,7 @@
|
||||
- type: entity
|
||||
parent: [BaseSpeciesDummy]
|
||||
id: MobVulpkaninDummy
|
||||
name: Vulpkanin Dummy
|
||||
categories: [ HideSpawnMenu ]
|
||||
description: A dummy Vulpkanin meant to be used in character setup.
|
||||
components:
|
||||
- type: HumanoidAppearance
|
||||
species: Vulpkanin
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
- type: FlavorProfile
|
||||
flavors:
|
||||
- sweet
|
||||
- type: Food
|
||||
- type: Sprite
|
||||
sprite: Objects/Consumable/Food/Baked/cake.rsi
|
||||
- type: SolutionContainerManager
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
abstract: true
|
||||
description: Goes great with robust coffee.
|
||||
components:
|
||||
- type: Food
|
||||
- type: Tag
|
||||
tags:
|
||||
- Donut
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
tags:
|
||||
- Egg
|
||||
- Meat
|
||||
- type: Food
|
||||
- type: Edible
|
||||
trash:
|
||||
- Eggshells
|
||||
- type: Sprite
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# When adding new food also add to random spawner located in Resources\Prototypes\Entities\Markers\Spawners\Random\Food_Drinks\food_snack.yml
|
||||
# When adding new food also add to random spawner located in Resources\Prototypes\Entities\Markers\Spawners\Random\Food_Drinks\food_snack.yml
|
||||
# Base
|
||||
|
||||
- type: entity
|
||||
@@ -63,7 +63,7 @@
|
||||
- type: entity
|
||||
parent: FoodFrozenBase
|
||||
id: FoodFrozenCornuto
|
||||
name: strawberry ice-cream sandwich
|
||||
name: cornuto
|
||||
description: A Neapolitan vanilla and chocolate ice-cream cone. It menaces with a sprinkling of caramelized nuts.
|
||||
components:
|
||||
- type: Item
|
||||
|
||||
@@ -15,10 +15,16 @@
|
||||
tags:
|
||||
- Bot # for funny bot moments
|
||||
blacklist:
|
||||
components:
|
||||
components: # TODO: This blacklist should be cut down by a lot once Chameleon Projector code is less buggy. See #40510
|
||||
- ChameleonDisguise # no becoming kleiner
|
||||
- Door # no faking doors
|
||||
- MindContainer # no
|
||||
- Pda # PDAs currently make you invisible /!\
|
||||
- SubFloorHide # no hiding under the floor
|
||||
tags:
|
||||
- Catwalk # Catwalks make you invisible
|
||||
- Wall # Walls make you invisible
|
||||
- Window # Windows make you invisible
|
||||
disguiseProto: ChameleonDisguise
|
||||
- type: StaticPrice
|
||||
price: 5000
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user