mirror of
https://github.com/wega-team/ss14-wega.git
synced 2026-02-15 03:31:44 +01:00
Merge remote-tracking branch 'upstream/master' into update-sposnors
# Conflicts: # .github/workflows/publish.yml # Content.Packaging/ContentPackaging.cs # Content.Server/Chat/Managers/ChatManager.cs # Content.Server/GameTicking/GameTicker.StatusShell.cs # Resources/Audio/Weapons/Guns/Gunshots/license.txt # Resources/Prototypes/Roles/Jobs/Security/senior_officer.yml # Resources/Textures/Structures/Doors/Airlocks/Glass/syndicate.rsi/closed.png # Resources/Textures/Structures/Doors/Airlocks/Glass/syndicate.rsi/closing.png # Resources/Textures/Structures/Doors/Airlocks/Glass/syndicate.rsi/open.png # Resources/Textures/Structures/Doors/Airlocks/Glass/syndicate.rsi/opening.png # Resources/Textures/Structures/Doors/Airlocks/Standard/syndicate.rsi/closed.png # Resources/Textures/Structures/Doors/Airlocks/Standard/syndicate.rsi/closing.png # Resources/Textures/Structures/Doors/Airlocks/Standard/syndicate.rsi/open.png # Resources/Textures/Structures/Doors/Airlocks/Standard/syndicate.rsi/opening.png
This commit is contained in:
15
.github/workflows/publish.yml
vendored
15
.github/workflows/publish.yml
vendored
@@ -48,10 +48,17 @@ jobs:
|
||||
cd RobustToolbox
|
||||
git fetch --depth=1
|
||||
|
||||
- name: Package all
|
||||
run: |
|
||||
Tools/package_server_build.py -p win-x64 linux-x64 osx-x64 linux-arm64
|
||||
Tools/package_client_build.py
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build Packaging
|
||||
run: dotnet build Content.Packaging --configuration Release --no-restore /m
|
||||
|
||||
- name: Package server
|
||||
run: dotnet run --project Content.Packaging server --platform win-x64 --platform linux-x64 --platform osx-x64 --platform linux-arm64
|
||||
|
||||
- name: Package client
|
||||
run: dotnet run --project Content.Packaging client --no-wipe-release
|
||||
|
||||
- name: Update Build Info
|
||||
env:
|
||||
|
||||
12
.github/workflows/test-packaging.yml
vendored
12
.github/workflows/test-packaging.yml
vendored
@@ -70,11 +70,15 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Package client
|
||||
run: |
|
||||
Tools/package_server_build.py -p win-x64 linux-x64 osx-x64 linux-arm64
|
||||
Tools/package_client_build.py
|
||||
- name: Build Packaging
|
||||
run: dotnet build Content.Packaging --configuration Release --no-restore /m
|
||||
|
||||
- name: Package server
|
||||
run: dotnet run --project Content.Packaging server --platform win-x64 --platform linux-x64 --platform osx-x64 --platform linux-arm64
|
||||
|
||||
- name: Package client
|
||||
run: dotnet run --project Content.Packaging client --no-wipe-release
|
||||
|
||||
- name: Update Build Info
|
||||
env:
|
||||
FORK_ID: ${{ vars.FORK_ID }}
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace Content.Client.Administration.UI.CustomControls
|
||||
}
|
||||
else if (args.Event.Function == EngineKeyFunctions.UseSecondary && selectedPlayer.NetEntity != null)
|
||||
{
|
||||
_uiManager.GetUIController<VerbMenuUIController>().OpenVerbMenu(_entManager.GetEntity(selectedPlayer.NetEntity.Value));
|
||||
_uiManager.GetUIController<VerbMenuUIController>().OpenVerbMenu(selectedPlayer.NetEntity.Value, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,11 +14,13 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class PlayerTab : Control
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerMan = default!;
|
||||
|
||||
private const string ArrowUp = "↑";
|
||||
private const string ArrowDown = "↓";
|
||||
private readonly Color _altColor = Color.FromHex("#292B38");
|
||||
private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
|
||||
private IEntityManager _entManager;
|
||||
private readonly AdminSystem _adminSystem;
|
||||
private IReadOnlyList<PlayerInfo> _players = new List<PlayerInfo>();
|
||||
|
||||
@@ -30,7 +32,7 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
|
||||
|
||||
public PlayerTab()
|
||||
{
|
||||
_entManager = IoCManager.Resolve<IEntityManager>();
|
||||
IoCManager.InjectDependencies(this);
|
||||
_adminSystem = _entManager.System<AdminSystem>();
|
||||
RobustXamlLoader.Load(this);
|
||||
RefreshPlayerList(_adminSystem.PlayerList);
|
||||
@@ -95,13 +97,11 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
|
||||
foreach (var child in PlayerList.Children.ToArray())
|
||||
{
|
||||
if (child is PlayerTabEntry)
|
||||
child.Orphan();
|
||||
child.Dispose();
|
||||
}
|
||||
|
||||
_players = players;
|
||||
|
||||
var playerManager = IoCManager.Resolve<IPlayerManager>();
|
||||
PlayerCount.Text = $"Players: {playerManager.PlayerCount}";
|
||||
PlayerCount.Text = $"Players: {_playerMan.PlayerCount}";
|
||||
|
||||
var sortedPlayers = new List<PlayerInfo>(players);
|
||||
sortedPlayers.Sort(Compare);
|
||||
|
||||
43
Content.Client/Overlays/ShowSyndicateIconsSystem.cs
Normal file
43
Content.Client/Overlays/ShowSyndicateIconsSystem.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Content.Shared.Overlays;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
using Content.Shared.NukeOps;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Overlays;
|
||||
public sealed class ShowSyndicateIconsSystem : EquipmentHudSystem<ShowSyndicateIconsComponent>
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<NukeOperativeComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
|
||||
}
|
||||
|
||||
private void OnGetStatusIconsEvent(EntityUid uid, NukeOperativeComponent nukeOperativeComponent, ref GetStatusIconsEvent args)
|
||||
{
|
||||
if (!IsActive || args.InContainer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var healthIcons = SyndicateIcon(uid, nukeOperativeComponent);
|
||||
|
||||
args.StatusIcons.AddRange(healthIcons);
|
||||
}
|
||||
|
||||
private IReadOnlyList<StatusIconPrototype> SyndicateIcon(EntityUid uid, NukeOperativeComponent nukeOperativeComponent)
|
||||
{
|
||||
var result = new List<StatusIconPrototype>();
|
||||
|
||||
if (_prototype.TryIndex<StatusIconPrototype>(nukeOperativeComponent.SyndStatusIcon, out var syndicateicon))
|
||||
{
|
||||
result.Add(syndicateicon);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
namespace Content.Client.Roles;
|
||||
using Content.Shared.Roles;
|
||||
|
||||
public sealed class RoleSystem : EntitySystem
|
||||
namespace Content.Client.Roles;
|
||||
|
||||
public sealed class RoleSystem : SharedRoleSystem
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Items.Systems;
|
||||
using Content.Client.Message;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
@@ -23,6 +24,7 @@ namespace Content.Client.Storage.UI
|
||||
private readonly IEntityManager _entityManager;
|
||||
|
||||
private readonly SharedStorageSystem _storage;
|
||||
private readonly ItemSystem _item;
|
||||
|
||||
private readonly RichTextLabel _information;
|
||||
public readonly ContainerButton StorageContainerButton;
|
||||
@@ -34,6 +36,7 @@ namespace Content.Client.Storage.UI
|
||||
{
|
||||
_entityManager = entityManager;
|
||||
_storage = _entityManager.System<SharedStorageSystem>();
|
||||
_item = _entityManager.System<ItemSystem>();
|
||||
SetSize = new Vector2(240, 320);
|
||||
Title = Loc.GetString("comp-storage-window-title");
|
||||
RectClipContent = true;
|
||||
@@ -69,7 +72,7 @@ namespace Content.Client.Storage.UI
|
||||
_information.SetMessage(Loc.GetString("comp-storage-window-weight",
|
||||
("weight", 0),
|
||||
("maxWeight", 0),
|
||||
("size", SharedItemSystem.GetItemSizeLocale(ItemSize.Normal))));
|
||||
("size", _item.GetItemSizeLocale(SharedStorageSystem.DefaultStorageMaxItemSize))));
|
||||
|
||||
vBox.AddChild(_information);
|
||||
|
||||
@@ -117,14 +120,14 @@ namespace Content.Client.Storage.UI
|
||||
_information.SetMarkup(Loc.GetString("comp-storage-window-weight",
|
||||
("weight", _storage.GetCumulativeItemSizes(uid, uid.Comp)),
|
||||
("maxWeight", uid.Comp.MaxTotalWeight),
|
||||
("size", SharedItemSystem.GetItemSizeLocale(_storage.GetMaxItemSize((uid, uid.Comp))))));
|
||||
("size", _item.GetItemSizeLocale(_storage.GetMaxItemSize((uid, uid.Comp))))));
|
||||
}
|
||||
else
|
||||
{
|
||||
_information.SetMarkup(Loc.GetString("comp-storage-window-slots",
|
||||
("itemCount", uid.Comp.Container.ContainedEntities.Count),
|
||||
("maxCount", uid.Comp.MaxSlots),
|
||||
("size", SharedItemSystem.GetItemSizeLocale(_storage.GetMaxItemSize((uid, uid.Comp))))));
|
||||
("size", _item.GetItemSizeLocale(_storage.GetMaxItemSize((uid, uid.Comp))))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +170,7 @@ namespace Content.Client.Storage.UI
|
||||
{
|
||||
Align = Label.AlignMode.Right,
|
||||
Text = item?.Size != null
|
||||
? $"{SharedItemSystem.GetItemSizeWeight(item.Size)}"
|
||||
? $"{_item.GetItemSizeWeight(item.Size)}"
|
||||
: Loc.GetString("comp-storage-no-item-size")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ public sealed class AdminUIController : UIController, IOnStateEntered<GameplaySt
|
||||
if (function == EngineKeyFunctions.UIClick)
|
||||
_conHost.ExecuteCommand($"vv {entity}");
|
||||
else if (function == EngineKeyFunctions.UseSecondary)
|
||||
_verb.OpenVerbMenu(EntityManager.GetEntity(entity), true);
|
||||
_verb.OpenVerbMenu(entity, true);
|
||||
else
|
||||
return;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Verbs.UI
|
||||
{
|
||||
@@ -29,7 +30,7 @@ namespace Content.Client.Verbs.UI
|
||||
[UISystemDependency] private readonly CombatModeSystem _combatMode = default!;
|
||||
[UISystemDependency] private readonly VerbSystem _verbSystem = default!;
|
||||
|
||||
public EntityUid CurrentTarget;
|
||||
public NetEntity CurrentTarget;
|
||||
public SortedSet<Verb> CurrentVerbs = new();
|
||||
|
||||
/// <summary>
|
||||
@@ -64,8 +65,25 @@ namespace Content.Client.Verbs.UI
|
||||
/// </param>
|
||||
public void OpenVerbMenu(EntityUid target, bool force = false, ContextMenuPopup? popup=null)
|
||||
{
|
||||
if (_playerManager.LocalPlayer?.ControlledEntity is not {Valid: true} user ||
|
||||
_combatMode.IsInCombatMode(user))
|
||||
DebugTools.Assert(target.IsValid());
|
||||
OpenVerbMenu(EntityManager.GetNetEntity(target), force, popup);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open a verb menu and fill it with verbs applicable to the given target entity.
|
||||
/// </summary>
|
||||
/// <param name="target">Entity to get verbs on.</param>
|
||||
/// <param name="force">Used to force showing all verbs. Only works on networked entities if the user is an admin.</param>
|
||||
/// <param name="popup">
|
||||
/// If this is not null, verbs will be placed into the given popup instead.
|
||||
/// </param>
|
||||
public void OpenVerbMenu(NetEntity target, bool force = false, ContextMenuPopup? popup=null)
|
||||
{
|
||||
DebugTools.Assert(target.IsValid());
|
||||
if (_playerManager.LocalEntity is not {Valid: true} user)
|
||||
return;
|
||||
|
||||
if (!force && _combatMode.IsInCombatMode(user))
|
||||
return;
|
||||
|
||||
Close();
|
||||
@@ -82,7 +100,7 @@ namespace Content.Client.Verbs.UI
|
||||
|
||||
// Add indicator that some verbs may be missing.
|
||||
// I long for the day when verbs will all be predicted and this becomes unnecessary.
|
||||
if (!EntityManager.IsClientSide(target))
|
||||
if (!target.IsClientSide())
|
||||
{
|
||||
_context.AddElement(menu, new ContextMenuElement(Loc.GetString("verb-system-waiting-on-server-text")));
|
||||
}
|
||||
@@ -248,7 +266,7 @@ namespace Content.Client.Verbs.UI
|
||||
|
||||
private void HandleVerbsResponse(VerbsResponseEvent msg)
|
||||
{
|
||||
if (OpenMenu == null || !OpenMenu.Visible || CurrentTarget != EntityManager.GetEntity(msg.Entity))
|
||||
if (OpenMenu == null || !OpenMenu.Visible || CurrentTarget != msg.Entity)
|
||||
return;
|
||||
|
||||
AddServerVerbs(msg.Verbs, OpenMenu);
|
||||
|
||||
@@ -170,28 +170,27 @@ namespace Content.Client.Verbs
|
||||
/// </summary>
|
||||
public SortedSet<Verb> GetVerbs(EntityUid target, EntityUid user, Type type, bool force = false)
|
||||
{
|
||||
return GetVerbs(target, user, new List<Type>() { type }, force);
|
||||
return GetVerbs(GetNetEntity(target), user, new List<Type>() { type }, force);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ask the server to send back a list of server-side verbs, and for now return an incomplete list of verbs
|
||||
/// (only those defined locally).
|
||||
/// </summary>
|
||||
public SortedSet<Verb> GetVerbs(EntityUid target, EntityUid user, List<Type> verbTypes,
|
||||
public SortedSet<Verb> GetVerbs(NetEntity target, EntityUid user, List<Type> verbTypes,
|
||||
bool force = false)
|
||||
{
|
||||
if (!IsClientSide(target))
|
||||
{
|
||||
RaiseNetworkEvent(new RequestServerVerbsEvent(GetNetEntity(target), verbTypes, adminRequest: force));
|
||||
}
|
||||
if (!target.IsClientSide())
|
||||
RaiseNetworkEvent(new RequestServerVerbsEvent(target, verbTypes, adminRequest: force));
|
||||
|
||||
// Some admin menu interactions will try get verbs for entities that have not yet been sent to the player.
|
||||
if (!Exists(target))
|
||||
if (!TryGetEntity(target, out var local))
|
||||
return new();
|
||||
|
||||
return GetLocalVerbs(target, user, verbTypes, force);
|
||||
return GetLocalVerbs(local.Value, user, verbTypes, force);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Execute actions associated with the given verb.
|
||||
/// </summary>
|
||||
@@ -200,8 +199,18 @@ namespace Content.Client.Verbs
|
||||
/// </remarks>
|
||||
public void ExecuteVerb(EntityUid target, Verb verb)
|
||||
{
|
||||
var user = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
if (user == null)
|
||||
ExecuteVerb(GetNetEntity(target), verb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute actions associated with the given verb.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Unless this is a client-exclusive verb, this will also tell the server to run the same verb.
|
||||
/// </remarks>
|
||||
public void ExecuteVerb(NetEntity target, Verb verb)
|
||||
{
|
||||
if ( _playerManager.LocalEntity is not {} user)
|
||||
return;
|
||||
|
||||
// is this verb actually valid?
|
||||
@@ -209,16 +218,16 @@ namespace Content.Client.Verbs
|
||||
{
|
||||
// maybe send an informative pop-up message.
|
||||
if (!string.IsNullOrWhiteSpace(verb.Message))
|
||||
_popupSystem.PopupEntity(verb.Message, user.Value);
|
||||
_popupSystem.PopupEntity(verb.Message, user);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (verb.ClientExclusive || IsClientSide(target))
|
||||
if (verb.ClientExclusive || target.IsClientSide())
|
||||
// is this a client exclusive (gui) verb?
|
||||
ExecuteVerb(verb, user.Value, target);
|
||||
ExecuteVerb(verb, user, GetEntity(target));
|
||||
else
|
||||
EntityManager.RaisePredictiveEvent(new ExecuteVerbEvent(GetNetEntity(target), verb));
|
||||
EntityManager.RaisePredictiveEvent(new ExecuteVerbEvent(target, verb));
|
||||
}
|
||||
|
||||
private void HandleVerbResponse(VerbsResponseEvent msg)
|
||||
|
||||
@@ -26,6 +26,9 @@ namespace Content.IntegrationTests.Tests
|
||||
var server = pair.Server;
|
||||
|
||||
var protoManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var entMan = server.ResolveDependency<IEntityManager>();
|
||||
|
||||
var itemSys = entMan.System<SharedItemSystem>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
@@ -37,7 +40,9 @@ namespace Content.IntegrationTests.Tests
|
||||
!proto.TryGetComponent<ItemComponent>("Item", out var item))
|
||||
continue;
|
||||
|
||||
Assert.That(storage.MaxItemSize.Value, Is.LessThanOrEqualTo(item.Size), $"Found storage arbitrage on {proto.ID}");
|
||||
Assert.That(itemSys.GetSizePrototype(storage.MaxItemSize.Value).Weight,
|
||||
Is.LessThanOrEqualTo(itemSys.GetSizePrototype(item.Size).Weight),
|
||||
$"Found storage arbitrage on {proto.ID}");
|
||||
}
|
||||
});
|
||||
await pair.CleanReturnAsync();
|
||||
@@ -77,10 +82,16 @@ namespace Content.IntegrationTests.Tests
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entMan = server.ResolveDependency<IEntityManager>();
|
||||
var protoMan = server.ResolveDependency<IPrototypeManager>();
|
||||
var compFact = server.ResolveDependency<IComponentFactory>();
|
||||
var id = compFact.GetComponentName(typeof(StorageFillComponent));
|
||||
|
||||
var itemSys = entMan.System<SharedItemSystem>();
|
||||
|
||||
var allSizes = protoMan.EnumeratePrototypes<ItemSizePrototype>().ToList();
|
||||
allSizes.Sort();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
foreach (var proto in PoolManager.GetPrototypesWithComponent<StorageFillComponent>(server))
|
||||
@@ -97,14 +108,29 @@ namespace Content.IntegrationTests.Tests
|
||||
proto.TryGetComponent<ItemComponent>("Item", out var item);
|
||||
|
||||
var fill = (StorageFillComponent) proto.Components[id].Component;
|
||||
var size = GetFillSize(fill, false, protoMan);
|
||||
var maxSize = storage.MaxItemSize ??
|
||||
(item?.Size == null
|
||||
? SharedStorageSystem.DefaultStorageMaxItemSize
|
||||
: (ItemSize) Math.Max(0, (int) item.Size - 1));
|
||||
var size = GetFillSize(fill, false, protoMan, itemSys);
|
||||
|
||||
var maxSize = storage.MaxItemSize;
|
||||
if (storage.MaxItemSize == null)
|
||||
{
|
||||
if (item?.Size == null)
|
||||
{
|
||||
maxSize = SharedStorageSystem.DefaultStorageMaxItemSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
var curIndex = allSizes.IndexOf(protoMan.Index(item.Size));
|
||||
var index = Math.Max(0, curIndex - 1);
|
||||
maxSize = allSizes[index].ID;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxSize == null)
|
||||
continue;
|
||||
|
||||
if (storage.MaxSlots != null)
|
||||
{
|
||||
Assert.That(GetFillSize(fill, true, protoMan), Is.LessThanOrEqualTo(storage.MaxSlots),
|
||||
Assert.That(GetFillSize(fill, true, protoMan, itemSys), Is.LessThanOrEqualTo(storage.MaxSlots),
|
||||
$"{proto.ID} storage fill has too many items.");
|
||||
}
|
||||
else
|
||||
@@ -123,14 +149,14 @@ namespace Content.IntegrationTests.Tests
|
||||
if (!fillItem.TryGetComponent<ItemComponent>("Item", out var entryItem))
|
||||
continue;
|
||||
|
||||
Assert.That(entryItem.Size, Is.LessThanOrEqualTo(maxSize),
|
||||
Assert.That(protoMan.Index(entryItem.Size).Weight,
|
||||
Is.LessThanOrEqualTo(protoMan.Index(maxSize.Value).Weight),
|
||||
$"Entity {proto.ID} has storage-fill item, {entry.PrototypeId}, that is too large");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -139,10 +165,13 @@ namespace Content.IntegrationTests.Tests
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entMan = server.ResolveDependency<IEntityManager>();
|
||||
var protoMan = server.ResolveDependency<IPrototypeManager>();
|
||||
var compFact = server.ResolveDependency<IComponentFactory>();
|
||||
var id = compFact.GetComponentName(typeof(StorageFillComponent));
|
||||
|
||||
var itemSys = entMan.System<SharedItemSystem>();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
foreach (var proto in PoolManager.GetPrototypesWithComponent<StorageFillComponent>(server))
|
||||
@@ -157,7 +186,7 @@ namespace Content.IntegrationTests.Tests
|
||||
}
|
||||
|
||||
var fill = (StorageFillComponent) proto.Components[id].Component;
|
||||
var size = GetFillSize(fill, true, protoMan);
|
||||
var size = GetFillSize(fill, true, protoMan, itemSys);
|
||||
Assert.That(size, Is.LessThanOrEqualTo(entStorage.Capacity),
|
||||
$"{proto.ID} storage fill is too large.");
|
||||
}
|
||||
@@ -165,7 +194,7 @@ namespace Content.IntegrationTests.Tests
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
private int GetEntrySize(EntitySpawnEntry entry, bool getCount, IPrototypeManager protoMan)
|
||||
private int GetEntrySize(EntitySpawnEntry entry, bool getCount, IPrototypeManager protoMan, SharedItemSystem itemSystem)
|
||||
{
|
||||
if (entry.PrototypeId == null)
|
||||
return 0;
|
||||
@@ -179,20 +208,21 @@ namespace Content.IntegrationTests.Tests
|
||||
if (getCount)
|
||||
return entry.Amount;
|
||||
|
||||
|
||||
if (proto.TryGetComponent<ItemComponent>("Item", out var item))
|
||||
return SharedItemSystem.GetItemSizeWeight(item.Size) * entry.Amount;
|
||||
return itemSystem.GetItemSizeWeight(item.Size) * entry.Amount;
|
||||
|
||||
Assert.Fail($"Prototype is missing item comp: {entry.PrototypeId}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int GetFillSize(StorageFillComponent fill, bool getCount, IPrototypeManager protoMan)
|
||||
private int GetFillSize(StorageFillComponent fill, bool getCount, IPrototypeManager protoMan, SharedItemSystem itemSystem)
|
||||
{
|
||||
var totalSize = 0;
|
||||
var groups = new Dictionary<string, int>();
|
||||
foreach (var entry in fill.Contents)
|
||||
{
|
||||
var size = GetEntrySize(entry, getCount, protoMan);
|
||||
var size = GetEntrySize(entry, getCount, protoMan, itemSystem);
|
||||
|
||||
if (entry.GroupId == null)
|
||||
totalSize += size;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -15,7 +14,6 @@ using Robust.Shared.Timing;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SpriteComponent = Robust.Client.GameObjects.SpriteComponent;
|
||||
|
||||
namespace Content.MapRenderer.Painters
|
||||
{
|
||||
@@ -46,7 +44,7 @@ namespace Content.MapRenderer.Painters
|
||||
|
||||
await client.WaitPost(() =>
|
||||
{
|
||||
if (cEntityManager.TryGetComponent(cPlayerManager.LocalPlayer!.ControlledEntity!, out SpriteComponent? sprite))
|
||||
if (cEntityManager.TryGetComponent(cPlayerManager.LocalEntity, out SpriteComponent? sprite))
|
||||
{
|
||||
sprite.Visible = false;
|
||||
}
|
||||
@@ -62,7 +60,7 @@ namespace Content.MapRenderer.Painters
|
||||
|
||||
var tilePainter = new TilePainter(client, server);
|
||||
var entityPainter = new GridPainter(client, server);
|
||||
(EntityUid Uid, MapGridComponent Grid)[] grids = null!;
|
||||
Entity<MapGridComponent>[] grids = null!;
|
||||
var xformQuery = sEntityManager.GetEntityQuery<TransformComponent>();
|
||||
var xformSystem = sEntityManager.System<SharedTransformSystem>();
|
||||
|
||||
@@ -76,7 +74,7 @@ namespace Content.MapRenderer.Painters
|
||||
}
|
||||
|
||||
var mapId = sMapManager.GetAllMapIds().Last();
|
||||
grids = sMapManager.GetAllMapGrids(mapId).Select(o => (o.Owner, o)).ToArray();
|
||||
grids = sMapManager.GetAllGrids(mapId).ToArray();
|
||||
|
||||
foreach (var (uid, _) in grids)
|
||||
{
|
||||
|
||||
79
Content.Packaging/ClientPackaging.cs
Normal file
79
Content.Packaging/ClientPackaging.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO.Compression;
|
||||
using Robust.Packaging;
|
||||
using Robust.Packaging.AssetProcessing;
|
||||
using Robust.Packaging.AssetProcessing.Passes;
|
||||
using Robust.Packaging.Utility;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Packaging;
|
||||
|
||||
public static class ClientPackaging
|
||||
{
|
||||
/// <summary>
|
||||
/// Be advised this can be called from server packaging during a HybridACZ build.
|
||||
/// </summary>
|
||||
public static async Task PackageClient(bool skipBuild, IPackageLogger logger)
|
||||
{
|
||||
logger.Info("Building client...");
|
||||
|
||||
if (!skipBuild)
|
||||
{
|
||||
await ProcessHelpers.RunCheck(new ProcessStartInfo
|
||||
{
|
||||
FileName = "dotnet",
|
||||
ArgumentList =
|
||||
{
|
||||
"build",
|
||||
Path.Combine("Content.Client", "Content.Client.csproj"),
|
||||
"-c", "Release",
|
||||
"--nologo",
|
||||
"/v:m",
|
||||
"/t:Rebuild",
|
||||
"/p:FullRelease=true",
|
||||
"/m"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
logger.Info("Packaging client...");
|
||||
|
||||
var sw = RStopwatch.StartNew();
|
||||
{
|
||||
await using var zipFile =
|
||||
File.Open(Path.Combine("release", "SS14.Client.zip"), FileMode.Create, FileAccess.ReadWrite);
|
||||
using var zip = new ZipArchive(zipFile, ZipArchiveMode.Update);
|
||||
var writer = new AssetPassZipWriter(zip);
|
||||
|
||||
await WriteResources("", writer, logger, default);
|
||||
await writer.FinishedTask;
|
||||
}
|
||||
|
||||
logger.Info($"Finished packaging client in {sw.Elapsed}");
|
||||
}
|
||||
|
||||
public static async Task WriteResources(
|
||||
string contentDir,
|
||||
AssetPass pass,
|
||||
IPackageLogger logger,
|
||||
CancellationToken cancel)
|
||||
{
|
||||
var graph = new RobustClientAssetGraph();
|
||||
pass.Dependencies.Add(new AssetPassDependency(graph.Output.Name));
|
||||
|
||||
AssetGraph.CalculateGraph(graph.AllPasses.Append(pass).ToArray(), logger);
|
||||
|
||||
var inputPass = graph.Input;
|
||||
|
||||
await RobustSharedPackaging.WriteContentAssemblies(
|
||||
inputPass,
|
||||
contentDir,
|
||||
"Content.Client",
|
||||
new[] { "Content.Client", "Content.Shared", "Content.Shared.Database" },
|
||||
cancel: cancel);
|
||||
|
||||
await RobustClientPackaging.WriteClientResources(contentDir, pass, cancel);
|
||||
|
||||
inputPass.InjectFinished();
|
||||
}
|
||||
}
|
||||
139
Content.Packaging/CommandLineArgs.cs
Normal file
139
Content.Packaging/CommandLineArgs.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Content.Packaging;
|
||||
|
||||
public sealed class CommandLineArgs
|
||||
{
|
||||
// PJB forgib me
|
||||
|
||||
/// <summary>
|
||||
/// Generate client or server.
|
||||
/// </summary>
|
||||
public bool Client { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should we also build the relevant project.
|
||||
/// </summary>
|
||||
public bool SkipBuild { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should we wipe the release folder or ignore it.
|
||||
/// </summary>
|
||||
public bool WipeRelease { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Platforms for server packaging.
|
||||
/// </summary>
|
||||
public List<string>? Platforms { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Use HybridACZ for server packaging.
|
||||
/// </summary>
|
||||
public bool HybridAcz { get; set; }
|
||||
|
||||
// CommandLineArgs, 3rd of her name.
|
||||
public static bool TryParse(IReadOnlyList<string> args, [NotNullWhen(true)] out CommandLineArgs? parsed)
|
||||
{
|
||||
parsed = null;
|
||||
bool? client = null;
|
||||
var skipBuild = false;
|
||||
var wipeRelease = true;
|
||||
var hybridAcz = false;
|
||||
List<string>? platforms = null;
|
||||
|
||||
using var enumerator = args.GetEnumerator();
|
||||
var i = -1;
|
||||
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
i++;
|
||||
var arg = enumerator.Current;
|
||||
if (i == 0)
|
||||
{
|
||||
if (arg == "client")
|
||||
{
|
||||
client = true;
|
||||
}
|
||||
else if (arg == "server")
|
||||
{
|
||||
client = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg == "--skip-build")
|
||||
{
|
||||
skipBuild = true;
|
||||
}
|
||||
else if (arg == "--no-wipe-release")
|
||||
{
|
||||
wipeRelease = false;
|
||||
}
|
||||
else if (arg == "--hybrid-acz")
|
||||
{
|
||||
hybridAcz = true;
|
||||
}
|
||||
else if (arg == "--platform")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
Console.WriteLine("No platform provided");
|
||||
return false;
|
||||
}
|
||||
|
||||
platforms ??= new List<string>();
|
||||
platforms.Add(enumerator.Current);
|
||||
}
|
||||
else if (arg == "--help")
|
||||
{
|
||||
PrintHelp();
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Unknown argument: {0}", arg);
|
||||
}
|
||||
}
|
||||
|
||||
if (client == null)
|
||||
{
|
||||
Console.WriteLine("Client / server packaging unspecified.");
|
||||
return false;
|
||||
}
|
||||
|
||||
parsed = new CommandLineArgs(client.Value, skipBuild, wipeRelease, hybridAcz, platforms);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void PrintHelp()
|
||||
{
|
||||
Console.WriteLine(@"
|
||||
Usage: Content.Packaging [client/server] [options]
|
||||
|
||||
Options:
|
||||
--skip-build Should we skip building the project and use what's already there.
|
||||
--no-wipe-release Don't wipe the release folder before creating files.
|
||||
--hybrid-acz Use HybridACZ for server builds.
|
||||
--platform Platform for server builds. Default will output several x64 targets.
|
||||
");
|
||||
}
|
||||
|
||||
private CommandLineArgs(
|
||||
bool client,
|
||||
bool skipBuild,
|
||||
bool wipeRelease,
|
||||
bool hybridAcz,
|
||||
List<string>? platforms)
|
||||
{
|
||||
Client = client;
|
||||
SkipBuild = skipBuild;
|
||||
WipeRelease = wipeRelease;
|
||||
HybridAcz = hybridAcz;
|
||||
Platforms = platforms;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,9 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NVorbis" Version="0.10.1" PrivateAssets="compile" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Packaging\Robust.Packaging.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
using Robust.Packaging;
|
||||
using Robust.Packaging.AssetProcessing;
|
||||
|
||||
namespace Content.Packaging;
|
||||
|
||||
public static class ContentPackaging
|
||||
{
|
||||
public static async Task WriteResources(
|
||||
string contentDir,
|
||||
AssetPass pass,
|
||||
IPackageLogger logger,
|
||||
CancellationToken cancel)
|
||||
{
|
||||
var graph = new RobustClientAssetGraph();
|
||||
pass.Dependencies.Add(new AssetPassDependency(graph.Output.Name));
|
||||
|
||||
AssetGraph.CalculateGraph(graph.AllPasses.Append(pass).ToArray(), logger);
|
||||
|
||||
var inputPass = graph.Input;
|
||||
|
||||
await RobustClientPackaging.WriteContentAssemblies(
|
||||
inputPass,
|
||||
contentDir,
|
||||
"Content.Client",
|
||||
new[] { "Content.Client", "Content.Shared", "Content.Shared.Database", "Content.Corvax.Interfaces.Client", "Content.Corvax.Interfaces.Shared" }, // Corvax-Secrets: Add Corvax interfaces to Magic ACZ
|
||||
cancel);
|
||||
|
||||
await RobustClientPackaging.WriteClientResources(contentDir, inputPass, cancel);
|
||||
|
||||
inputPass.InjectFinished();
|
||||
}
|
||||
}
|
||||
@@ -1,68 +1,44 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO.Compression;
|
||||
using Content.Packaging;
|
||||
using Content.Packaging;
|
||||
using Robust.Packaging;
|
||||
using Robust.Packaging.AssetProcessing.Passes;
|
||||
using Robust.Packaging.Utility;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
IPackageLogger logger = new PackageLoggerConsole();
|
||||
|
||||
logger.Info("Clearing release/ directory");
|
||||
Directory.CreateDirectory("release");
|
||||
|
||||
var skipBuild = args.Contains("--skip-build");
|
||||
|
||||
if (!skipBuild)
|
||||
WipeBin();
|
||||
|
||||
await Build(skipBuild);
|
||||
|
||||
async Task Build(bool skipBuild)
|
||||
if (!CommandLineArgs.TryParse(args, out var parsed))
|
||||
{
|
||||
logger.Info("Building project...");
|
||||
|
||||
if (!skipBuild)
|
||||
{
|
||||
await ProcessHelpers.RunCheck(new ProcessStartInfo
|
||||
{
|
||||
FileName = "dotnet",
|
||||
ArgumentList =
|
||||
{
|
||||
"build",
|
||||
Path.Combine("Content.Client", "Content.Client.csproj"),
|
||||
"-c", "Release",
|
||||
"--nologo",
|
||||
"/v:m",
|
||||
"/t:Rebuild",
|
||||
"/p:FullRelease=true",
|
||||
"/m"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
logger.Info("Packaging client...");
|
||||
|
||||
var sw = RStopwatch.StartNew();
|
||||
|
||||
{
|
||||
using var zipFile =
|
||||
File.Open(Path.Combine("release", "SS14.Client.zip"), FileMode.Create, FileAccess.ReadWrite);
|
||||
using var zip = new ZipArchive(zipFile, ZipArchiveMode.Update);
|
||||
var writer = new AssetPassZipWriter(zip);
|
||||
|
||||
await ContentPackaging.WriteResources("", writer, logger, default);
|
||||
|
||||
await writer.FinishedTask;
|
||||
}
|
||||
|
||||
logger.Info($"Finished packaging in {sw.Elapsed}");
|
||||
logger.Error("Unable to parse args, aborting.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsed.WipeRelease)
|
||||
WipeRelease();
|
||||
|
||||
if (!parsed.SkipBuild)
|
||||
WipeBin();
|
||||
|
||||
if (parsed.Client)
|
||||
{
|
||||
await ClientPackaging.PackageClient(parsed.SkipBuild, logger);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ServerPackaging.PackageServer(parsed.SkipBuild, parsed.HybridAcz, logger, parsed.Platforms);
|
||||
}
|
||||
|
||||
void WipeBin()
|
||||
{
|
||||
logger.Info("Clearing old build artifacts (if any)...");
|
||||
|
||||
Directory.Delete("bin", recursive: true);
|
||||
if (Directory.Exists("bin"))
|
||||
Directory.Delete("bin", recursive: true);
|
||||
}
|
||||
|
||||
void WipeRelease()
|
||||
{
|
||||
if (Directory.Exists("release"))
|
||||
{
|
||||
logger.Info("Cleaning old release packages (release/)...");
|
||||
Directory.Delete("release", recursive: true);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory("release");
|
||||
}
|
||||
|
||||
226
Content.Packaging/ServerPackaging.cs
Normal file
226
Content.Packaging/ServerPackaging.cs
Normal file
@@ -0,0 +1,226 @@
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO.Compression;
|
||||
using Robust.Packaging;
|
||||
using Robust.Packaging.AssetProcessing;
|
||||
using Robust.Packaging.AssetProcessing.Passes;
|
||||
using Robust.Packaging.Utility;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
using YamlDotNet.Core;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Content.Packaging;
|
||||
|
||||
public static class ServerPackaging
|
||||
{
|
||||
private static readonly List<PlatformReg> Platforms = new()
|
||||
{
|
||||
new PlatformReg("win-x64", "Windows", true),
|
||||
new PlatformReg("linux-x64", "Linux", true),
|
||||
new PlatformReg("linux-arm64", "Linux", true),
|
||||
new PlatformReg("osx-x64", "MacOS", true),
|
||||
// Non-default platforms (i.e. for Watchdog Git)
|
||||
new PlatformReg("win-x86", "Windows", false),
|
||||
new PlatformReg("linux-x86", "Linux", false),
|
||||
new PlatformReg("linux-arm", "Linux", false),
|
||||
};
|
||||
|
||||
private static List<string> PlatformRids => Platforms
|
||||
.Select(o => o.Rid)
|
||||
.ToList();
|
||||
|
||||
private static List<string> PlatformRidsDefault => Platforms
|
||||
.Where(o => o.BuildByDefault)
|
||||
.Select(o => o.Rid)
|
||||
.ToList();
|
||||
|
||||
private static readonly List<string> ServerContentAssemblies = new()
|
||||
{
|
||||
"Content.Server.Database",
|
||||
"Content.Server",
|
||||
"Content.Shared",
|
||||
"Content.Shared.Database",
|
||||
};
|
||||
|
||||
private static readonly List<string> ServerExtraAssemblies = new()
|
||||
{
|
||||
// Python script had Npgsql. though we want Npgsql.dll as well soooo
|
||||
"Npgsql",
|
||||
"Microsoft",
|
||||
};
|
||||
|
||||
private static readonly List<string> ServerNotExtraAssemblies = new()
|
||||
{
|
||||
"Microsoft.CodeAnalysis",
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> BinSkipFolders = new()
|
||||
{
|
||||
// Roslyn localization files, screw em.
|
||||
"cs",
|
||||
"de",
|
||||
"es",
|
||||
"fr",
|
||||
"it",
|
||||
"ja",
|
||||
"ko",
|
||||
"pl",
|
||||
"pt-BR",
|
||||
"ru",
|
||||
"tr",
|
||||
"zh-Hans",
|
||||
"zh-Hant"
|
||||
};
|
||||
|
||||
public static async Task PackageServer(bool skipBuild, bool hybridAcz, IPackageLogger logger, List<string>? platforms = null)
|
||||
{
|
||||
if (platforms == null)
|
||||
{
|
||||
platforms ??= PlatformRidsDefault;
|
||||
}
|
||||
|
||||
if (hybridAcz)
|
||||
{
|
||||
// Hybrid ACZ involves a file "Content.Client.zip" in the server executable directory.
|
||||
// Rather than hosting the client ZIP on the watchdog or on a separate server,
|
||||
// Hybrid ACZ uses the ACZ hosting functionality to host it as part of the status host,
|
||||
// which means that features such as automatic UPnP forwarding still work properly.
|
||||
await ClientPackaging.PackageClient(skipBuild, logger);
|
||||
}
|
||||
|
||||
// Good variable naming right here.
|
||||
foreach (var platform in Platforms)
|
||||
{
|
||||
if (!platforms.Contains(platform.Rid))
|
||||
continue;
|
||||
|
||||
await BuildPlatform(platform, skipBuild, hybridAcz, logger);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task BuildPlatform(PlatformReg platform, bool skipBuild, bool hybridAcz, IPackageLogger logger)
|
||||
{
|
||||
logger.Info($"Building project for {platform}...");
|
||||
|
||||
if (!skipBuild)
|
||||
{
|
||||
await ProcessHelpers.RunCheck(new ProcessStartInfo
|
||||
{
|
||||
FileName = "dotnet",
|
||||
ArgumentList =
|
||||
{
|
||||
"build",
|
||||
Path.Combine("Content.Server", "Content.Server.csproj"),
|
||||
"-c", "Release",
|
||||
"--nologo",
|
||||
"/v:m",
|
||||
$"/p:TargetOs={platform.TargetOs}",
|
||||
"/t:Rebuild",
|
||||
"/p:FullRelease=true",
|
||||
"/m"
|
||||
}
|
||||
});
|
||||
|
||||
await PublishClientServer(platform.Rid, platform.TargetOs);
|
||||
}
|
||||
|
||||
logger.Info($"Packaging {platform.Rid} server...");
|
||||
|
||||
var sw = RStopwatch.StartNew();
|
||||
{
|
||||
await using var zipFile =
|
||||
File.Open(Path.Combine("release", $"SS14.Server_{platform.Rid}.zip"), FileMode.Create, FileAccess.ReadWrite);
|
||||
using var zip = new ZipArchive(zipFile, ZipArchiveMode.Update);
|
||||
var writer = new AssetPassZipWriter(zip);
|
||||
|
||||
await WriteServerResources(platform, "", writer, logger, hybridAcz, default);
|
||||
await writer.FinishedTask;
|
||||
}
|
||||
|
||||
logger.Info($"Finished packaging server in {sw.Elapsed}");
|
||||
}
|
||||
|
||||
private static async Task PublishClientServer(string runtime, string targetOs)
|
||||
{
|
||||
await ProcessHelpers.RunCheck(new ProcessStartInfo
|
||||
{
|
||||
FileName = "dotnet",
|
||||
ArgumentList =
|
||||
{
|
||||
"publish",
|
||||
"--runtime", runtime,
|
||||
"--no-self-contained",
|
||||
"-c", "Release",
|
||||
$"/p:TargetOs={targetOs}",
|
||||
"/p:FullRelease=True",
|
||||
"/m",
|
||||
"RobustToolbox/Robust.Server/Robust.Server.csproj"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task WriteServerResources(
|
||||
PlatformReg platform,
|
||||
string contentDir,
|
||||
AssetPass pass,
|
||||
IPackageLogger logger,
|
||||
bool hybridAcz,
|
||||
CancellationToken cancel)
|
||||
{
|
||||
var graph = new RobustClientAssetGraph();
|
||||
var passes = graph.AllPasses.ToList();
|
||||
|
||||
pass.Dependencies.Add(new AssetPassDependency(graph.Output.Name));
|
||||
passes.Add(pass);
|
||||
|
||||
AssetGraph.CalculateGraph(passes, logger);
|
||||
|
||||
var inputPass = graph.Input;
|
||||
var contentAssemblies = new List<string>(ServerContentAssemblies);
|
||||
|
||||
// Additional assemblies that need to be copied such as EFCore.
|
||||
var sourcePath = Path.Combine(contentDir, "bin", "Content.Server");
|
||||
|
||||
// Should this be an asset pass?
|
||||
// For future archaeologists I just want audio rework to work and need the audio pass so
|
||||
// just porting this as is from python.
|
||||
foreach (var fullPath in Directory.EnumerateFiles(sourcePath, "*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
var fileName = Path.GetFileNameWithoutExtension(fullPath);
|
||||
|
||||
if (!ServerNotExtraAssemblies.Any(o => fileName.StartsWith(o)) && ServerExtraAssemblies.Any(o => fileName.StartsWith(o)))
|
||||
{
|
||||
contentAssemblies.Add(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
await RobustSharedPackaging.DoResourceCopy(
|
||||
Path.Combine("RobustToolbox", "bin", "Server",
|
||||
platform.Rid,
|
||||
"publish"),
|
||||
inputPass,
|
||||
BinSkipFolders,
|
||||
cancel: cancel);
|
||||
|
||||
await RobustSharedPackaging.WriteContentAssemblies(
|
||||
inputPass,
|
||||
contentDir,
|
||||
"Content.Server",
|
||||
contentAssemblies,
|
||||
Path.Combine("Resources", "Assemblies"),
|
||||
cancel);
|
||||
|
||||
await RobustServerPackaging.WriteServerResources(contentDir, inputPass, cancel);
|
||||
|
||||
if (hybridAcz)
|
||||
{
|
||||
inputPass.InjectFileFromDisk("Content.Client.zip", Path.Combine("release", "SS14.Client.zip"));
|
||||
}
|
||||
|
||||
inputPass.InjectFinished();
|
||||
}
|
||||
|
||||
private readonly record struct PlatformReg(string Rid, string TargetOs, bool BuildByDefault);
|
||||
}
|
||||
@@ -20,6 +20,6 @@ public sealed class ContentMagicAczProvider : IMagicAczProvider
|
||||
{
|
||||
var contentDir = DefaultMagicAczProvider.FindContentRootPath(_deps);
|
||||
|
||||
await ContentPackaging.WriteResources(contentDir, pass, logger, cancel);
|
||||
await ClientPackaging.WriteResources(contentDir, pass, logger, cancel);
|
||||
}
|
||||
}
|
||||
|
||||
148
Content.Server/Anomaly/AnomalySynchronizerSystem.cs
Normal file
148
Content.Server/Anomaly/AnomalySynchronizerSystem.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
using Content.Server.Anomaly.Components;
|
||||
using Content.Server.DeviceLinking.Systems;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
|
||||
namespace Content.Server.Anomaly;
|
||||
|
||||
/// <summary>
|
||||
/// a device that allows you to translate anomaly activity into multitool signals.
|
||||
/// </summary>
|
||||
public sealed partial class AnomalySynchronizerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AnomalySystem _anomaly = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
|
||||
[Dependency] private readonly DeviceLinkSystem _signal = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly PowerReceiverSystem _power = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<AnomalySynchronizerComponent, InteractHandEvent>(OnInteractHand);
|
||||
SubscribeLocalEvent<AnomalySynchronizerComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
|
||||
SubscribeLocalEvent<AnomalyPulseEvent>(OnAnomalyPulse);
|
||||
SubscribeLocalEvent<AnomalySeverityChangedEvent>(OnAnomalySeverityChanged);
|
||||
SubscribeLocalEvent<AnomalyStabilityChangedEvent>(OnAnomalyStabilityChanged);
|
||||
}
|
||||
|
||||
private void OnPowerChanged(EntityUid uid, AnomalySynchronizerComponent component, ref PowerChangedEvent args)
|
||||
{
|
||||
if (args.Powered)
|
||||
return;
|
||||
|
||||
if (!TryComp<AnomalyComponent>(component.ConnectedAnomaly, out var anomaly))
|
||||
return;
|
||||
|
||||
_anomaly.DoAnomalyPulse(component.ConnectedAnomaly.Value, anomaly);
|
||||
DisconneсtFromAnomaly(uid, component, anomaly);
|
||||
}
|
||||
|
||||
private void OnInteractHand(EntityUid uid, AnomalySynchronizerComponent component, InteractHandEvent args)
|
||||
{
|
||||
if (!_power.IsPowered(uid))
|
||||
return;
|
||||
|
||||
foreach (var entity in _entityLookup.GetEntitiesInRange(uid, 0.15f)) //is the radius of one tile. It must not be set higher, otherwise the anomaly can be moved from tile to tile
|
||||
{
|
||||
if (!TryComp<AnomalyComponent>(entity, out var anomaly))
|
||||
continue;
|
||||
|
||||
|
||||
ConnectToAnomaly(uid, component, entity, anomaly);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ConnectToAnomaly(EntityUid uid, AnomalySynchronizerComponent component, EntityUid auid, AnomalyComponent anomaly)
|
||||
{
|
||||
if (component.ConnectedAnomaly == auid)
|
||||
return;
|
||||
|
||||
component.ConnectedAnomaly = auid;
|
||||
//move the anomaly to the center of the synchronizer, for aesthetics.
|
||||
var targetXform = _transform.GetWorldPosition(uid);
|
||||
_transform.SetWorldPosition(auid, targetXform);
|
||||
|
||||
_anomaly.DoAnomalyPulse(component.ConnectedAnomaly.Value, anomaly);
|
||||
_popup.PopupEntity(Loc.GetString("anomaly-sync-connected"), uid, PopupType.Medium);
|
||||
_audio.PlayPvs(component.ConnectedSound, uid);
|
||||
}
|
||||
|
||||
//TO DO: disconnection from the anomaly should also be triggered if the anomaly is far away from the synchronizer.
|
||||
//Currently only bluespace anomaly can do this, but for some reason it is the only one that cannot be connected to the synchronizer.
|
||||
private void DisconneсtFromAnomaly(EntityUid uid, AnomalySynchronizerComponent component, AnomalyComponent anomaly)
|
||||
{
|
||||
if (component.ConnectedAnomaly == null)
|
||||
return;
|
||||
|
||||
_anomaly.DoAnomalyPulse(component.ConnectedAnomaly.Value, anomaly);
|
||||
_popup.PopupEntity(Loc.GetString("anomaly-sync-disconnected"), uid, PopupType.Large);
|
||||
_audio.PlayPvs(component.ConnectedSound, uid);
|
||||
|
||||
component.ConnectedAnomaly = default!;
|
||||
}
|
||||
|
||||
private void OnAnomalyPulse(ref AnomalyPulseEvent args)
|
||||
{
|
||||
var query = EntityQueryEnumerator<AnomalySynchronizerComponent>();
|
||||
while (query.MoveNext(out var ent, out var component))
|
||||
{
|
||||
if (args.Anomaly != component.ConnectedAnomaly)
|
||||
continue;
|
||||
if (!_power.IsPowered(ent))
|
||||
continue;
|
||||
|
||||
_signal.InvokePort(ent, component.PulsePort);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAnomalySeverityChanged(ref AnomalySeverityChangedEvent args)
|
||||
{
|
||||
var query = EntityQueryEnumerator<AnomalySynchronizerComponent>();
|
||||
while (query.MoveNext(out var ent, out var component))
|
||||
{
|
||||
if (args.Anomaly != component.ConnectedAnomaly)
|
||||
continue;
|
||||
if (!_power.IsPowered(ent))
|
||||
continue;
|
||||
//The superscritical port is invoked not at the AnomalySupercriticalEvent,
|
||||
//but at the moment the growth animation starts. Otherwise, there is no point in this port.
|
||||
//ATTENTION! the console command supercriticalanomaly does not work here,
|
||||
//as it forcefully causes growth to start without increasing severity.
|
||||
if (args.Severity >= 1)
|
||||
_signal.InvokePort(ent, component.SupercritPort);
|
||||
}
|
||||
}
|
||||
private void OnAnomalyStabilityChanged(ref AnomalyStabilityChangedEvent args)
|
||||
{
|
||||
var query = EntityQueryEnumerator<AnomalySynchronizerComponent>();
|
||||
while (query.MoveNext(out var ent, out var component))
|
||||
{
|
||||
if (args.Anomaly != component.ConnectedAnomaly)
|
||||
continue;
|
||||
if (TryComp<ApcPowerReceiverComponent>(ent, out var apcPower) && !apcPower.Powered)
|
||||
continue;
|
||||
|
||||
if (args.Stability < 0.25f) //I couldn't find where these values are stored, so I hardcoded them. Tell me where these variables are stored and I'll fix it
|
||||
{
|
||||
_signal.InvokePort(ent, component.DecayingPort);
|
||||
}
|
||||
else if (args.Stability > 0.5f) //I couldn't find where these values are stored, so I hardcoded them. Tell me where these variables are stored and I'll fix it
|
||||
{
|
||||
_signal.InvokePort(ent, component.GrowingPort);
|
||||
}
|
||||
else
|
||||
{
|
||||
_signal.InvokePort(ent, component.StabilizePort);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Content.Shared.Anomaly;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.DeviceLinking;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Anomaly.Components;
|
||||
|
||||
/// <summary>
|
||||
/// a device that allows you to translate anomaly activity into multitool signals.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(AnomalySynchronizerSystem))]
|
||||
public sealed partial class AnomalySynchronizerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The uid of the anomaly to which the synchronizer is connected.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntityUid? ConnectedAnomaly;
|
||||
|
||||
|
||||
[DataField]
|
||||
public ProtoId<SourcePortPrototype> DecayingPort = "Decaying";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<SourcePortPrototype> StabilizePort = "Stabilize";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<SourcePortPrototype> GrowingPort = "Growing";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<SourcePortPrototype> PulsePort = "Pulse";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<SourcePortPrototype> SupercritPort = "Supercritical";
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public SoundSpecifier ConnectedSound = new SoundPathSpecifier("/Audio/Machines/anomaly_sync_connect.ogg");
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public SoundSpecifier DisconnectedSound = new SoundPathSpecifier("/Audio/Machines/anomaly_sync_connect.ogg");
|
||||
}
|
||||
@@ -21,27 +21,46 @@ public sealed class EntityAnomalySystem : EntitySystem
|
||||
{
|
||||
SubscribeLocalEvent<EntitySpawnAnomalyComponent, AnomalyPulseEvent>(OnPulse);
|
||||
SubscribeLocalEvent<EntitySpawnAnomalyComponent, AnomalySupercriticalEvent>(OnSupercritical);
|
||||
SubscribeLocalEvent<EntitySpawnAnomalyComponent, AnomalyStabilityChangedEvent>(OnStabilityChanged);
|
||||
}
|
||||
|
||||
private void OnPulse(EntityUid uid, EntitySpawnAnomalyComponent component, ref AnomalyPulseEvent args)
|
||||
{
|
||||
if (!component.SpawnOnPulse)
|
||||
return;
|
||||
|
||||
var range = component.SpawnRange * args.Stability;
|
||||
var amount = (int) (component.MaxSpawnAmount * args.Severity + 0.5f);
|
||||
|
||||
var xform = Transform(uid);
|
||||
SpawnMonstersOnOpenTiles(component, xform, amount, range, component.Spawns);
|
||||
SpawnEntitesOnOpenTiles(component, xform, amount, range, component.Spawns);
|
||||
}
|
||||
|
||||
private void OnSupercritical(EntityUid uid, EntitySpawnAnomalyComponent component, ref AnomalySupercriticalEvent args)
|
||||
{
|
||||
if (!component.SpawnOnSuperCritical)
|
||||
return;
|
||||
|
||||
var xform = Transform(uid);
|
||||
// A cluster of monsters
|
||||
SpawnMonstersOnOpenTiles(component, xform, component.MaxSpawnAmount, component.SpawnRange, component.Spawns);
|
||||
// A cluster of entities
|
||||
SpawnEntitesOnOpenTiles(component, xform, component.MaxSpawnAmount, component.SpawnRange, component.Spawns);
|
||||
// And so much meat (for the meat anomaly at least)
|
||||
SpawnMonstersOnOpenTiles(component, xform, component.MaxSpawnAmount, component.SpawnRange, component.SuperCriticalSpawns);
|
||||
SpawnEntitesOnOpenTiles(component, xform, component.MaxSpawnAmount, component.SpawnRange, component.SuperCriticalSpawns);
|
||||
}
|
||||
|
||||
private void SpawnMonstersOnOpenTiles(EntitySpawnAnomalyComponent component, TransformComponent xform, int amount, float radius, List<EntProtoId> spawns)
|
||||
private void OnStabilityChanged(EntityUid uid, EntitySpawnAnomalyComponent component, ref AnomalyStabilityChangedEvent args)
|
||||
{
|
||||
if (!component.SpawnOnStabilityChanged)
|
||||
return;
|
||||
|
||||
var range = component.SpawnRange * args.Stability;
|
||||
var amount = (int) (component.MaxSpawnAmount * args.Stability + 0.5f);
|
||||
|
||||
var xform = Transform(uid);
|
||||
SpawnEntitesOnOpenTiles(component, xform, amount, range, component.Spawns);
|
||||
}
|
||||
|
||||
private void SpawnEntitesOnOpenTiles(EntitySpawnAnomalyComponent component, TransformComponent xform, int amount, float radius, List<EntProtoId> spawns)
|
||||
{
|
||||
if (!component.Spawns.Any())
|
||||
return;
|
||||
|
||||
46
Content.Server/Atmos/Components/AirFilterComponent.cs
Normal file
46
Content.Server/Atmos/Components/AirFilterComponent.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
|
||||
namespace Content.Server.Atmos.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is basically a reverse scrubber but using <see cref="GetFilterAirEvent"/>.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(AirFilterSystem))]
|
||||
public sealed partial class AirFilterComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Gases that will be filtered out of internal air
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public HashSet<Gas> Gases = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gases that will be filtered out of internal air to maintain oxygen ratio.
|
||||
/// When oxygen is below <see cref="TargetOxygen"/>, these gases will be filtered instead of <see cref="Gases"/>.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public HashSet<Gas> OverflowGases = new();
|
||||
|
||||
/// <summary>
|
||||
/// Minimum oxygen fraction before it will start removing <see cref="OverflowGases"/>.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float TargetOxygen = 0.21f;
|
||||
|
||||
/// <summary>
|
||||
/// Gas to consider oxygen for <see cref="TargetOxygen"/> and <see cref="OverflowGases"/> logic.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For slime you might want to change this to be nitrogen, and overflowgases to remove oxygen.
|
||||
/// However theres still no real danger since standard atmos is mostly nitrogen so nitrogen tends to 100% anyway.
|
||||
/// </remarks>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public Gas Oxygen = Gas.Oxygen;
|
||||
|
||||
/// <summary>
|
||||
/// Fraction of target volume to transfer every second.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float TransferRate = 0.1f;
|
||||
}
|
||||
29
Content.Server/Atmos/Components/AirIntakeComponent.cs
Normal file
29
Content.Server/Atmos/Components/AirIntakeComponent.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
|
||||
namespace Content.Server.Atmos.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is basically a siphon vent for <see cref="GetFilterAirEvent"/>.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(AirFilterSystem))]
|
||||
public sealed partial class AirIntakeComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Target pressure change for a single atmos tick
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float TargetPressureChange = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// How strong the intake pump is, it will be able to replenish air from lower pressure areas.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float PumpPower = 2f;
|
||||
|
||||
/// <summary>
|
||||
/// Pressure to intake gases up to, maintains pressure of the air volume.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Pressure = Atmospherics.OneAtmosphere;
|
||||
}
|
||||
113
Content.Server/Atmos/EntitySystems/AirFilterSystem.cs
Normal file
113
Content.Server/Atmos/EntitySystems/AirFilterSystem.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// Handles gas filtering and intake for <see cref="AirIntakeComponent"/> and <see cref="AirFilterComponent"/>.
|
||||
/// </summary>
|
||||
public sealed class AirFilterSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<AirIntakeComponent, AtmosDeviceUpdateEvent>(OnIntakeUpdate);
|
||||
SubscribeLocalEvent<AirFilterComponent, AtmosDeviceUpdateEvent>(OnFilterUpdate);
|
||||
}
|
||||
|
||||
private void OnIntakeUpdate(EntityUid uid, AirIntakeComponent intake, AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!GetAir(uid, out var air))
|
||||
return;
|
||||
|
||||
// if the volume is filled there is nothing to do
|
||||
if (air.Pressure >= intake.Pressure)
|
||||
return;
|
||||
|
||||
var environment = _atmosphere.GetContainingMixture(uid, true, true);
|
||||
// nothing to intake from
|
||||
if (environment == null)
|
||||
return;
|
||||
|
||||
// absolute maximum pressure change
|
||||
var pressureDelta = args.dt * intake.TargetPressureChange;
|
||||
pressureDelta = MathF.Min(pressureDelta, intake.Pressure - air.Pressure);
|
||||
if (pressureDelta <= 0)
|
||||
return;
|
||||
|
||||
// how many moles to transfer to change internal pressure by pressureDelta
|
||||
// ignores temperature difference because lazy
|
||||
var transferMoles = pressureDelta * air.Volume / (environment.Temperature * Atmospherics.R);
|
||||
_atmosphere.Merge(air, environment.Remove(transferMoles));
|
||||
}
|
||||
|
||||
private void OnFilterUpdate(EntityUid uid, AirFilterComponent filter, AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!GetAir(uid, out var air))
|
||||
return;
|
||||
|
||||
var ratio = MathF.Min(1f, args.dt * filter.TransferRate);
|
||||
var removed = air.RemoveRatio(ratio);
|
||||
// nothing left to remove from the volume
|
||||
if (MathHelper.CloseToPercent(removed.TotalMoles, 0f))
|
||||
return;
|
||||
|
||||
// when oxygen gets too low start removing overflow gases (nitrogen) to maintain oxygen ratio
|
||||
var oxygen = air.GetMoles(filter.Oxygen) / air.TotalMoles;
|
||||
var gases = oxygen >= filter.TargetOxygen ? filter.Gases : filter.OverflowGases;
|
||||
|
||||
var coordinates = Transform(uid).MapPosition;
|
||||
GasMixture? destination = null;
|
||||
if (_map.TryFindGridAt(coordinates, out _, out var grid))
|
||||
{
|
||||
var tile = grid.GetTileRef(coordinates);
|
||||
destination = _atmosphere.GetTileMixture(tile.GridUid, null, tile.GridIndices, true);
|
||||
}
|
||||
|
||||
if (destination != null)
|
||||
{
|
||||
_atmosphere.ScrubInto(removed, destination, gases);
|
||||
}
|
||||
else
|
||||
{
|
||||
// filtering into space/planet so just discard them
|
||||
foreach (var gas in gases)
|
||||
{
|
||||
removed.SetMoles(gas, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
_atmosphere.Merge(air, removed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses <see cref="GetFilterAirEvent"/> to get an internal volume of air on an entity.
|
||||
/// Used for both filter and intake.
|
||||
/// </summary>
|
||||
public bool GetAir(EntityUid uid, [NotNullWhen(true)] out GasMixture? air)
|
||||
{
|
||||
air = null;
|
||||
|
||||
var ev = new GetFilterAirEvent();
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
air = ev.Air;
|
||||
return air != null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a reference to an entity's air volume to filter.
|
||||
/// Do not create a new mixture as this will be modified when filtering and intaking air.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct GetFilterAirEvent(GasMixture? Air = null);
|
||||
@@ -33,8 +33,15 @@ public sealed partial class StationCargoBountyDatabaseComponent : Component
|
||||
public float MinBountyTime = 600f;
|
||||
|
||||
/// <summary>
|
||||
/// The maxmium amount of time the bounty lasts before being removed.
|
||||
/// The maximum amount of time the bounty lasts before being removed.
|
||||
/// </summary>
|
||||
[DataField("maxBountyTime"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float MaxBountyTime = 905f;
|
||||
|
||||
/// <summary>
|
||||
/// A list of bounty IDs that have been checked this tick.
|
||||
/// Used to prevent multiplying bounty prices.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public HashSet<int> CheckedBounties = new();
|
||||
}
|
||||
|
||||
@@ -89,18 +89,24 @@ public sealed partial class CargoSystem
|
||||
if (!_container.TryGetContainingContainer(uid, out var container) || container.ID != LabelSystem.ContainerName)
|
||||
return;
|
||||
|
||||
if (_station.GetOwningStation(uid) is not { } station)
|
||||
if (_station.GetOwningStation(uid) is not { } station || !TryComp<StationCargoBountyDatabaseComponent>(station, out var database))
|
||||
return;
|
||||
|
||||
if (!TryGetBountyFromId(station, component.Id, out var bounty))
|
||||
if (database.CheckedBounties.Contains(component.Id))
|
||||
return;
|
||||
|
||||
if (!_protoMan.TryIndex<CargoBountyPrototype>(bounty.Value.Bounty, out var bountyProtoype) ||!IsBountyComplete(container.Owner, bountyProtoype))
|
||||
if (!TryGetBountyFromId(station, component.Id, out var bounty, database))
|
||||
return;
|
||||
|
||||
if (!_protoMan.TryIndex<CargoBountyPrototype>(bounty.Value.Bounty, out var bountyPrototype) ||
|
||||
!IsBountyComplete(container.Owner, bountyPrototype))
|
||||
return;
|
||||
|
||||
database.CheckedBounties.Add(component.Id);
|
||||
args.Handled = true;
|
||||
|
||||
component.Calculating = true;
|
||||
args.Price = bountyProtoype.Reward - _pricing.GetPrice(container.Owner);
|
||||
args.Price = bountyPrototype.Reward - _pricing.GetPrice(container.Owner);
|
||||
component.Calculating = false;
|
||||
}
|
||||
|
||||
@@ -329,6 +335,7 @@ public sealed partial class CargoSystem
|
||||
var query = EntityQueryEnumerator<StationCargoBountyDatabaseComponent>();
|
||||
while (query.MoveNext(out var uid, out var bountyDatabase))
|
||||
{
|
||||
bountyDatabase.CheckedBounties.Clear();
|
||||
var bounties = new ValueList<CargoBountyData>(bountyDatabase.Bounties);
|
||||
foreach (var bounty in bounties)
|
||||
{
|
||||
|
||||
34
Content.Server/Chat/ChatUser.cs
Normal file
34
Content.Server/Chat/ChatUser.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Content.Shared.Chat;
|
||||
|
||||
namespace Content.Server.Chat;
|
||||
|
||||
public sealed class ChatUser
|
||||
{
|
||||
/// <summary>
|
||||
/// The unique key associated with this chat user, starting from 1 and incremented.
|
||||
/// Used when the server sends <see cref="MsgChatMessage"/>.
|
||||
/// Used on the client to delete messages sent by this user when receiving
|
||||
/// <see cref="MsgDeleteChatMessagesBy"/>.
|
||||
/// </summary>
|
||||
public readonly int Key;
|
||||
|
||||
/// <summary>
|
||||
/// All entities that this chat user was attached to while sending chat messages.
|
||||
/// Sent to the client to delete messages sent by those entities when receiving
|
||||
/// <see cref="MsgDeleteChatMessagesBy"/>.
|
||||
/// </summary>
|
||||
public readonly HashSet<NetEntity> Entities = new();
|
||||
|
||||
public ChatUser(int key)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
|
||||
public void AddEntity(NetEntity entity)
|
||||
{
|
||||
if (!entity.Valid)
|
||||
return;
|
||||
|
||||
Entities.Add(entity);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Content.Corvax.Interfaces.Server;
|
||||
@@ -51,8 +52,7 @@ namespace Content.Server.Chat.Managers
|
||||
private bool _oocEnabled = true;
|
||||
private bool _adminOocEnabled = true;
|
||||
|
||||
public Dictionary<ICommonSession, int> SenderKeys { get; } = new();
|
||||
public Dictionary<ICommonSession, HashSet<NetEntity>> SenderEntities { get; } = new();
|
||||
private readonly Dictionary<NetUserId, ChatUser> _players = new();
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
@@ -82,13 +82,26 @@ namespace Content.Server.Chat.Managers
|
||||
|
||||
public void DeleteMessagesBy(ICommonSession player)
|
||||
{
|
||||
var key = SenderKeys.GetValueOrDefault(player);
|
||||
var entities = SenderEntities.GetValueOrDefault(player) ?? new HashSet<NetEntity>();
|
||||
var msg = new MsgDeleteChatMessagesBy { Key = key, Entities = entities };
|
||||
if (!_players.TryGetValue(player.UserId, out var user))
|
||||
return;
|
||||
|
||||
var msg = new MsgDeleteChatMessagesBy { Key = user.Key, Entities = user.Entities };
|
||||
_netManager.ServerSendToAll(msg);
|
||||
}
|
||||
|
||||
[return: NotNullIfNotNull(nameof(author))]
|
||||
public ChatUser? EnsurePlayer(NetUserId? author)
|
||||
{
|
||||
if (author == null)
|
||||
return null;
|
||||
|
||||
ref var user = ref CollectionsMarshal.GetValueRefOrAddDefault(_players, author.Value, out var exists);
|
||||
if (!exists || user == null)
|
||||
user = new ChatUser(_players.Count);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
#region Server Announcements
|
||||
|
||||
public void DispatchServerAnnouncement(string message, Color? colorOverride = null)
|
||||
@@ -217,10 +230,6 @@ namespace Content.Server.Chat.Managers
|
||||
wrappedMessage = Loc.GetString("chat-manager-send-ooc-patron-wrap-message", ("patronColor", patronColor),("playerName", player.Name), ("message", FormattedMessage.EscapeText(message)));
|
||||
}
|
||||
|
||||
ref var key = ref CollectionsMarshal.GetValueRefOrAddDefault(SenderKeys, player, out var exists);
|
||||
if (!exists)
|
||||
key = SenderKeys.Count;
|
||||
|
||||
// Corvax-Sponsors-Start
|
||||
if (_sponsorsManager != null && _sponsorsManager.TryGetOocColor(player.UserId, out var oocColor))
|
||||
{
|
||||
@@ -229,7 +238,7 @@ namespace Content.Server.Chat.Managers
|
||||
// Corvax-Sponsors-End
|
||||
|
||||
//TODO: player.Name color, this will need to change the structure of the MsgChatMessage
|
||||
ChatMessageToAll(ChatChannel.OOC, message, wrappedMessage, EntityUid.Invalid, hideChat: false, recordReplay: true, colorOverride: colorOverride, senderKey: key);
|
||||
ChatMessageToAll(ChatChannel.OOC, message, wrappedMessage, EntityUid.Invalid, hideChat: false, recordReplay: true, colorOverride: colorOverride, author: player.UserId);
|
||||
_mommiLink.SendOOCMessage(player.Name, message);
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"OOC from {player:Player}: {message}");
|
||||
}
|
||||
@@ -247,10 +256,6 @@ namespace Content.Server.Chat.Managers
|
||||
("adminChannelName", Loc.GetString("chat-manager-admin-channel-name")),
|
||||
("playerName", player.Name), ("message", FormattedMessage.EscapeText(message)));
|
||||
|
||||
ref var key = ref CollectionsMarshal.GetValueRefOrAddDefault(SenderKeys, player, out var exists);
|
||||
if (!exists)
|
||||
key = SenderKeys.Count;
|
||||
|
||||
foreach (var client in clients)
|
||||
{
|
||||
var isSource = client != player.ConnectedClient;
|
||||
@@ -261,7 +266,8 @@ namespace Content.Server.Chat.Managers
|
||||
false,
|
||||
client,
|
||||
audioPath: isSource ? _netConfigManager.GetClientCVar(client, CCVars.AdminChatSoundPath) : default,
|
||||
audioVolume: isSource ? _netConfigManager.GetClientCVar(client, CCVars.AdminChatSoundVolume) : default, senderKey: key);
|
||||
audioVolume: isSource ? _netConfigManager.GetClientCVar(client, CCVars.AdminChatSoundVolume) : default,
|
||||
author: player.UserId);
|
||||
}
|
||||
|
||||
_adminLogger.Add(LogType.Chat, $"Admin chat from {player:Player}: {message}");
|
||||
@@ -271,9 +277,13 @@ namespace Content.Server.Chat.Managers
|
||||
|
||||
#region Utility
|
||||
|
||||
public void ChatMessageToOne(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, INetChannel client, Color? colorOverride = null, bool recordReplay = false, string? audioPath = null, float audioVolume = 0, int? senderKey = null)
|
||||
public void ChatMessageToOne(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, INetChannel client, Color? colorOverride = null, bool recordReplay = false, string? audioPath = null, float audioVolume = 0, NetUserId? author = null)
|
||||
{
|
||||
var msg = new ChatMessage(channel, message, wrappedMessage, _entityManager.GetNetEntity(source), senderKey, hideChat, colorOverride, audioPath, audioVolume);
|
||||
var user = author == null ? null : EnsurePlayer(author);
|
||||
var netSource = _entityManager.GetNetEntity(source);
|
||||
user?.AddEntity(netSource);
|
||||
|
||||
var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
|
||||
_netManager.ServerSendMessage(new MsgChatMessage() { Message = msg }, client);
|
||||
|
||||
if (!recordReplay)
|
||||
@@ -286,12 +296,16 @@ namespace Content.Server.Chat.Managers
|
||||
}
|
||||
}
|
||||
|
||||
public void ChatMessageToMany(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, bool recordReplay, IEnumerable<INetChannel> clients, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0)
|
||||
=> ChatMessageToMany(channel, message, wrappedMessage, source, hideChat, recordReplay, clients.ToList(), colorOverride, audioPath, audioVolume);
|
||||
public void ChatMessageToMany(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, bool recordReplay, IEnumerable<INetChannel> clients, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0, NetUserId? author = null)
|
||||
=> ChatMessageToMany(channel, message, wrappedMessage, source, hideChat, recordReplay, clients.ToList(), colorOverride, audioPath, audioVolume, author);
|
||||
|
||||
public void ChatMessageToMany(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, bool recordReplay, List<INetChannel> clients, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0)
|
||||
public void ChatMessageToMany(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, bool recordReplay, List<INetChannel> clients, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0, NetUserId? author = null)
|
||||
{
|
||||
var msg = new ChatMessage(channel, message, wrappedMessage, _entityManager.GetNetEntity(source), null, hideChat, colorOverride, audioPath, audioVolume);
|
||||
var user = author == null ? null : EnsurePlayer(author);
|
||||
var netSource = _entityManager.GetNetEntity(source);
|
||||
user?.AddEntity(netSource);
|
||||
|
||||
var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
|
||||
_netManager.ServerSendToMany(new MsgChatMessage() { Message = msg }, clients);
|
||||
|
||||
if (!recordReplay)
|
||||
@@ -319,9 +333,13 @@ namespace Content.Server.Chat.Managers
|
||||
ChatMessageToMany(channel, message, wrappedMessage, source, hideChat, recordReplay, clients, colorOverride, audioPath, audioVolume);
|
||||
}
|
||||
|
||||
public void ChatMessageToAll(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, bool recordReplay, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0, int? senderKey = null)
|
||||
public void ChatMessageToAll(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, bool recordReplay, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0, NetUserId? author = null)
|
||||
{
|
||||
var msg = new ChatMessage(channel, message, wrappedMessage, _entityManager.GetNetEntity(source), senderKey, hideChat, colorOverride, audioPath, audioVolume);
|
||||
var user = author == null ? null : EnsurePlayer(author);
|
||||
var netSource = _entityManager.GetNetEntity(source);
|
||||
user?.AddEntity(netSource);
|
||||
|
||||
var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
|
||||
_netManager.ServerSendToAll(new MsgChatMessage() { Message = msg });
|
||||
|
||||
if (!recordReplay)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Chat;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
@@ -6,17 +7,6 @@ namespace Content.Server.Chat.Managers
|
||||
{
|
||||
public interface IChatManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Keys identifying messages sent by a specific player, used when sending
|
||||
/// <see cref="MsgChatMessage"/>
|
||||
/// </summary>
|
||||
Dictionary<ICommonSession, int> SenderKeys { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Tracks which entities a player was attached to while sending messages.
|
||||
/// </summary>
|
||||
Dictionary<ICommonSession, HashSet<NetEntity>> SenderEntities { get; }
|
||||
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
@@ -36,17 +26,20 @@ namespace Content.Server.Chat.Managers
|
||||
void SendAdminAlert(EntityUid player, string message);
|
||||
|
||||
void ChatMessageToOne(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat,
|
||||
INetChannel client, Color? colorOverride = null, bool recordReplay = false, string? audioPath = null, float audioVolume = 0, int? senderKey = null);
|
||||
INetChannel client, Color? colorOverride = null, bool recordReplay = false, string? audioPath = null, float audioVolume = 0, NetUserId? author = null);
|
||||
|
||||
void ChatMessageToMany(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, bool recordReplay,
|
||||
IEnumerable<INetChannel> clients, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0);
|
||||
IEnumerable<INetChannel> clients, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0, NetUserId? author = null);
|
||||
|
||||
void ChatMessageToManyFiltered(Filter filter, ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, bool recordReplay, Color? colorOverride, string? audioPath = null, float audioVolume = 0);
|
||||
|
||||
void ChatMessageToAll(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, bool recordReplay, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0, int? senderKey = null);
|
||||
void ChatMessageToAll(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, bool recordReplay, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0, NetUserId? author = null);
|
||||
|
||||
bool MessageCharacterLimit(ICommonSession player, string message);
|
||||
|
||||
void DeleteMessagesBy(ICommonSession player);
|
||||
|
||||
[return: NotNullIfNotNull(nameof(author))]
|
||||
ChatUser? EnsurePlayer(NetUserId? author);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Content.Server.Speech.EntitySystems;
|
||||
using Content.Server.Speech.Components;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Speech.Components;
|
||||
using Content.Server.Speech.EntitySystems;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.ActionBlocker;
|
||||
@@ -20,7 +20,6 @@ using Content.Shared.Interaction;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Players;
|
||||
using Content.Shared.Radio;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
@@ -195,8 +194,18 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
if (!CanSendInGame(message, shell, player))
|
||||
return;
|
||||
|
||||
// this method is a disaster
|
||||
// every second i have to spend working with this code is fucking agony
|
||||
// scientists have to wonder how any of this was merged
|
||||
// coding any game admin feature that involves chat code is pure torture
|
||||
// changing even 10 lines of code feels like waterboarding myself
|
||||
// and i dont feel like vibe checking 50 code paths
|
||||
// so we set this here
|
||||
// todo free me from chat code
|
||||
if (player != null)
|
||||
_chatManager.SenderEntities.GetOrNew(player).Add(GetNetEntity(source));
|
||||
{
|
||||
_chatManager.EnsurePlayer(player.UserId).AddEntity(GetNetEntity(source));
|
||||
}
|
||||
|
||||
if (desiredType == InGameICChatType.Speak && message.StartsWith(LocalPrefix))
|
||||
{
|
||||
@@ -529,7 +538,8 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
string? nameOverride,
|
||||
bool hideLog = false,
|
||||
bool checkEmote = true,
|
||||
bool ignoreActionBlocker = false
|
||||
bool ignoreActionBlocker = false,
|
||||
NetUserId? author = null
|
||||
)
|
||||
{
|
||||
if (!_actionBlocker.CanEmote(source) && !ignoreActionBlocker)
|
||||
@@ -547,7 +557,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
|
||||
if (checkEmote)
|
||||
TryEmoteChatInput(source, action);
|
||||
SendInVoiceRange(ChatChannel.Emotes, action, wrappedMessage, source, range);
|
||||
SendInVoiceRange(ChatChannel.Emotes, action, wrappedMessage, source, range, author);
|
||||
if (!hideLog)
|
||||
if (name != Name(source))
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {ToPrettyString(source):user} as {name}: {action}");
|
||||
@@ -574,9 +584,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
("entityName", name),
|
||||
("message", FormattedMessage.EscapeText(message)));
|
||||
|
||||
_chatManager.SenderEntities.GetOrNew(player).Add(GetNetEntity(source));
|
||||
|
||||
SendInVoiceRange(ChatChannel.LOOC, message, wrappedMessage, source, hideChat ? ChatTransmitRange.HideChat : ChatTransmitRange.Normal);
|
||||
SendInVoiceRange(ChatChannel.LOOC, message, wrappedMessage, source, hideChat ? ChatTransmitRange.HideChat : ChatTransmitRange.Normal, player.UserId);
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"LOOC from {player:Player}: {message}");
|
||||
}
|
||||
|
||||
@@ -602,9 +610,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Dead chat from {player:Player}: {message}");
|
||||
}
|
||||
|
||||
_chatManager.SenderEntities.GetOrNew(player).Add(GetNetEntity(source));
|
||||
|
||||
_chatManager.ChatMessageToMany(ChatChannel.Dead, message, wrappedMessage, source, hideChat, true, clients.ToList());
|
||||
_chatManager.ChatMessageToMany(ChatChannel.Dead, message, wrappedMessage, source, hideChat, true, clients.ToList(), author: player.UserId);
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -658,7 +664,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
/// <summary>
|
||||
/// Sends a chat message to the given players in range of the source entity.
|
||||
/// </summary>
|
||||
private void SendInVoiceRange(ChatChannel channel, string message, string wrappedMessage, EntityUid source, ChatTransmitRange range)
|
||||
private void SendInVoiceRange(ChatChannel channel, string message, string wrappedMessage, EntityUid source, ChatTransmitRange range, NetUserId? author = null)
|
||||
{
|
||||
foreach (var (session, data) in GetRecipients(source, VoiceRange))
|
||||
{
|
||||
@@ -666,7 +672,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
if (entRange == MessageRangeCheckResult.Disallowed)
|
||||
continue;
|
||||
var entHideChat = entRange == MessageRangeCheckResult.HideChat;
|
||||
_chatManager.ChatMessageToOne(channel, message, wrappedMessage, source, entHideChat, session.ConnectedClient);
|
||||
_chatManager.ChatMessageToOne(channel, message, wrappedMessage, source, entHideChat, session.ConnectedClient, author: author);
|
||||
}
|
||||
|
||||
_replay.RecordServerMessage(new ChatMessage(channel, message, wrappedMessage, GetNetEntity(source), null, MessageRangeHideChatForReplay(range)));
|
||||
|
||||
@@ -13,10 +13,10 @@ namespace Content.Server.Chemistry.ReagentEffects
|
||||
{
|
||||
/// How many units of thirst to add each time we vomit
|
||||
[DataField("thirstAmount")]
|
||||
public float ThirstAmount = -40f;
|
||||
public float ThirstAmount = -8f;
|
||||
/// How many units of hunger to add each time we vomit
|
||||
[DataField("hungerAmount")]
|
||||
public float HungerAmount = -40f;
|
||||
public float HungerAmount = -8f;
|
||||
|
||||
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
=> Loc.GetString("reagent-effect-guidebook-chem-vomit", ("chance", Probability));
|
||||
|
||||
38
Content.Server/Clothing/Systems/SkatesSystem.cs
Normal file
38
Content.Server/Clothing/Systems/SkatesSystem.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Server.Damage.Systems;
|
||||
|
||||
namespace Content.Server.Clothing;
|
||||
|
||||
public sealed class SkatesSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly MovementSpeedModifierSystem _move = default!;
|
||||
[Dependency] private readonly DamageOnHighSpeedImpactSystem _impact = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SkatesComponent, GotEquippedEvent>(OnGotEquipped);
|
||||
SubscribeLocalEvent<SkatesComponent, GotUnequippedEvent>(OnGotUnequipped);
|
||||
}
|
||||
|
||||
public void OnGotUnequipped(EntityUid uid, SkatesComponent component, GotUnequippedEvent args)
|
||||
{
|
||||
if (args.Slot == "shoes")
|
||||
{
|
||||
_move.ChangeFriction(args.Equipee, 20f, null, 20f);
|
||||
_impact.ChangeCollide(args.Equipee, 20f, 1f, 2f);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGotEquipped(EntityUid uid, SkatesComponent component, GotEquippedEvent args)
|
||||
{
|
||||
if (args.Slot == "shoes")
|
||||
{
|
||||
_move.ChangeFriction(args.Equipee, 5f, 5f, 20f);
|
||||
_impact.ChangeCollide(args.Equipee, 4f, 1f, 2f);
|
||||
}
|
||||
}
|
||||
}
|
||||
83
Content.Server/Construction/Conditions/MinSolution.cs
Normal file
83
Content.Server/Construction/Conditions/MinSolution.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Construction.Conditions;
|
||||
|
||||
/// <summary>
|
||||
/// Requires that a certain solution has a minimum amount of a reagent to proceed.
|
||||
/// </summary>
|
||||
[DataDefinition]
|
||||
public sealed partial class MinSolution : IGraphCondition
|
||||
{
|
||||
/// <summary>
|
||||
/// The solution that needs to have the reagent.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public string Solution = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The reagent that needs to be present.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public ReagentId Reagent = new();
|
||||
|
||||
/// <summary>
|
||||
/// How much of the reagent must be present.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public FixedPoint2 Quantity = 1;
|
||||
|
||||
public bool Condition(EntityUid uid, IEntityManager entMan)
|
||||
{
|
||||
var containerSys = entMan.System<SolutionContainerSystem>();
|
||||
if (!containerSys.TryGetSolution(uid, Solution, out var solution))
|
||||
return false;
|
||||
|
||||
solution.TryGetReagentQuantity(Reagent, out var quantity);
|
||||
return quantity >= Quantity;
|
||||
}
|
||||
|
||||
public bool DoExamine(ExaminedEvent args)
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
var uid = args.Examined;
|
||||
|
||||
var containerSys = entMan.System<SolutionContainerSystem>();
|
||||
if (!containerSys.TryGetSolution(uid, Solution, out var solution))
|
||||
return false;
|
||||
|
||||
solution.TryGetReagentQuantity(Reagent, out var quantity);
|
||||
|
||||
// already has enough so dont show examine
|
||||
if (quantity >= Quantity)
|
||||
return false;
|
||||
|
||||
args.PushMarkup(Loc.GetString("construction-examine-condition-min-solution",
|
||||
("quantity", Quantity - quantity), ("reagent", Name())) + "\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
public IEnumerable<ConstructionGuideEntry> GenerateGuideEntry()
|
||||
{
|
||||
yield return new ConstructionGuideEntry()
|
||||
{
|
||||
Localization = "construction-guide-condition-min-solution",
|
||||
Arguments = new (string, object)[]
|
||||
{
|
||||
("quantity", Quantity),
|
||||
("reagent", Name())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private string Name()
|
||||
{
|
||||
var protoMan = IoCManager.Resolve<IPrototypeManager>();
|
||||
var proto = protoMan.Index<ReagentPrototype>(Reagent.Prototype);
|
||||
return proto.LocalizedName;
|
||||
}
|
||||
}
|
||||
52
Content.Server/Construction/Conditions/SolutionEmpty.cs
Normal file
52
Content.Server/Construction/Conditions/SolutionEmpty.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Examine;
|
||||
|
||||
namespace Content.Server.Construction.Conditions;
|
||||
|
||||
/// <summary>
|
||||
/// Requires that a certain solution be empty to proceed.
|
||||
/// </summary>
|
||||
[DataDefinition]
|
||||
public sealed partial class SolutionEmpty : IGraphCondition
|
||||
{
|
||||
/// <summary>
|
||||
/// The solution that needs to be empty.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string Solution;
|
||||
|
||||
public bool Condition(EntityUid uid, IEntityManager entMan)
|
||||
{
|
||||
var containerSys = entMan.System<SolutionContainerSystem>();
|
||||
if (!containerSys.TryGetSolution(uid, Solution, out var solution))
|
||||
return false;
|
||||
|
||||
return solution.Volume == 0;
|
||||
}
|
||||
|
||||
public bool DoExamine(ExaminedEvent args)
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
var uid = args.Examined;
|
||||
|
||||
var containerSys = entMan.System<SolutionContainerSystem>();
|
||||
if (!containerSys.TryGetSolution(uid, Solution, out var solution))
|
||||
return false;
|
||||
|
||||
// already empty so dont show examine
|
||||
if (solution.Volume == 0)
|
||||
return false;
|
||||
|
||||
args.PushMarkup(Loc.GetString("construction-examine-condition-solution-empty"));
|
||||
return true;
|
||||
}
|
||||
|
||||
public IEnumerable<ConstructionGuideEntry> GenerateGuideEntry()
|
||||
{
|
||||
yield return new ConstructionGuideEntry()
|
||||
{
|
||||
Localization = "construction-guide-condition-solution-empty"
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -53,4 +53,15 @@ public sealed class DamageOnHighSpeedImpactSystem : EntitySystem
|
||||
_audio.PlayPvs(component.SoundHit, uid, AudioParams.Default.WithVariation(0.125f).WithVolume(-0.125f));
|
||||
_color.RaiseEffect(Color.Red, new List<EntityUid>() { uid }, Filter.Pvs(uid, entityManager: EntityManager));
|
||||
}
|
||||
|
||||
public void ChangeCollide(EntityUid uid, float minimumSpeed, float stunSeconds, float damageCooldown, DamageOnHighSpeedImpactComponent? collide = null)
|
||||
{
|
||||
if (!Resolve(uid, ref collide, false))
|
||||
return;
|
||||
|
||||
collide.MinimumSpeed = minimumSpeed;
|
||||
collide.StunSeconds = stunSeconds;
|
||||
collide.DamageCooldown = damageCooldown;
|
||||
Dirty(uid, collide);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Content.Server.DeviceNetwork
|
||||
{
|
||||
public sealed class NetworkPayload : Dictionary<string, object>
|
||||
public sealed class NetworkPayload : Dictionary<string, object?>
|
||||
{
|
||||
/// <summary>
|
||||
/// Tries to get a value from the payload and checks if that value is of type T.
|
||||
|
||||
@@ -200,7 +200,7 @@ namespace Content.Server.GameTicking
|
||||
_mind.SetUserId(newMind, data.UserId);
|
||||
|
||||
var jobPrototype = _prototypeManager.Index<JobPrototype>(jobId);
|
||||
var job = new JobComponent { PrototypeId = jobId };
|
||||
var job = new JobComponent { Prototype = jobId };
|
||||
_roles.MindAddRole(newMind, job, silent: silent);
|
||||
var jobName = _jobs.MindTryGetJobName(newMind);
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace Content.Server.GameTicking
|
||||
// Corvax-Queue-End
|
||||
|
||||
jObject["name"] = _baseServer.ServerName;
|
||||
jObject["map"] = _gameMapManager.GetSelectedMap()?.MapName;
|
||||
jObject["players"] = players; // Corvax-Queue
|
||||
jObject["soft_max_players"] = _cfg.GetCVar(CCVars.SoftMaxPlayers);
|
||||
jObject["run_level"] = (int) _runLevel;
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for tagging a mob as a nuke operative.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class NukeOperativeComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Path to antagonist alert sound.
|
||||
/// </summary>
|
||||
[DataField("greetSoundNotification")]
|
||||
public SoundSpecifier GreetSoundNotification = new SoundPathSpecifier("/Audio/Ambience/Antag/nukeops_start.ogg");
|
||||
}
|
||||
@@ -8,6 +8,7 @@ using Content.Server.Preferences.Managers;
|
||||
using Content.Server.Spawners.Components;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.NPC.Systems;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Mind;
|
||||
@@ -40,6 +41,7 @@ public sealed class PiratesRuleSystem : GameRuleSystem<PiratesRuleComponent>
|
||||
[Dependency] private readonly PricingSystem _pricingSystem = default!;
|
||||
[Dependency] private readonly MapLoaderSystem _map = default!;
|
||||
[Dependency] private readonly NamingSystem _namingSystem = default!;
|
||||
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
@@ -222,6 +224,9 @@ public sealed class PiratesRuleSystem : GameRuleSystem<PiratesRuleComponent>
|
||||
var profile = _prefs.GetPreferences(session.UserId).SelectedCharacter as HumanoidCharacterProfile;
|
||||
_stationSpawningSystem.EquipStartingGear(mob, pirateGear, profile);
|
||||
|
||||
_npcFaction.RemoveFaction(mob, "NanoTrasen", false);
|
||||
_npcFaction.AddFaction(mob, "Syndicate");
|
||||
|
||||
pirates.Pirates.Add(newMind);
|
||||
|
||||
// Notificate every player about a pirate antagonist role with sound
|
||||
|
||||
@@ -9,24 +9,26 @@ namespace Content.Server.Guardian
|
||||
/// <summary>
|
||||
/// The guardian host entity
|
||||
/// </summary>
|
||||
public EntityUid Host;
|
||||
[DataField]
|
||||
public EntityUid? Host;
|
||||
|
||||
/// <summary>
|
||||
/// Percentage of damage reflected from the guardian to the host
|
||||
/// </summary>
|
||||
[DataField("damageShare")]
|
||||
[DataField]
|
||||
public float DamageShare { get; set; } = 0.65f;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum distance the guardian can travel before it's forced to recall, use YAML to set
|
||||
/// </summary>
|
||||
[DataField("distanceAllowed")]
|
||||
[DataField]
|
||||
public float DistanceAllowed { get; set; } = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// If the guardian is currently manifested
|
||||
/// </summary>
|
||||
public bool GuardianLoose = false;
|
||||
[DataField]
|
||||
public bool GuardianLoose;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Guardian
|
||||
{
|
||||
@@ -16,6 +15,7 @@ namespace Content.Server.Guardian
|
||||
/// <remarks>
|
||||
/// Can be null if the component is added at any time.
|
||||
/// </remarks>
|
||||
[DataField]
|
||||
public EntityUid? HostedGuardian;
|
||||
|
||||
/// <summary>
|
||||
@@ -23,9 +23,9 @@ namespace Content.Server.Guardian
|
||||
/// </summary>
|
||||
[ViewVariables] public ContainerSlot GuardianContainer = default!;
|
||||
|
||||
[DataField("action", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string Action = "ActionToggleGuardian";
|
||||
[DataField]
|
||||
public EntProtoId Action = "ActionToggleGuardian";
|
||||
|
||||
[DataField("actionEntity")] public EntityUid? ActionEntity;
|
||||
[DataField] public EntityUid? ActionEntity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,10 +41,11 @@ namespace Content.Server.Guardian
|
||||
SubscribeLocalEvent<GuardianCreatorComponent, ExaminedEvent>(OnCreatorExamine);
|
||||
SubscribeLocalEvent<GuardianCreatorComponent, GuardianCreatorDoAfterEvent>(OnDoAfter);
|
||||
|
||||
SubscribeLocalEvent<GuardianComponent, ComponentShutdown>(OnGuardianShutdown);
|
||||
SubscribeLocalEvent<GuardianComponent, MoveEvent>(OnGuardianMove);
|
||||
SubscribeLocalEvent<GuardianComponent, DamageChangedEvent>(OnGuardianDamaged);
|
||||
SubscribeLocalEvent<GuardianComponent, PlayerAttachedEvent>(OnGuardianPlayer);
|
||||
SubscribeLocalEvent<GuardianComponent, PlayerDetachedEvent>(OnGuardianUnplayer);
|
||||
SubscribeLocalEvent<GuardianComponent, PlayerAttachedEvent>(OnGuardianPlayerAttached);
|
||||
SubscribeLocalEvent<GuardianComponent, PlayerDetachedEvent>(OnGuardianPlayerDetached);
|
||||
|
||||
SubscribeLocalEvent<GuardianHostComponent, ComponentInit>(OnHostInit);
|
||||
SubscribeLocalEvent<GuardianHostComponent, MoveEvent>(OnHostMove);
|
||||
@@ -56,6 +57,21 @@ namespace Content.Server.Guardian
|
||||
SubscribeLocalEvent<GuardianComponent, AttackAttemptEvent>(OnGuardianAttackAttempt);
|
||||
}
|
||||
|
||||
private void OnGuardianShutdown(EntityUid uid, GuardianComponent component, ComponentShutdown args)
|
||||
{
|
||||
var host = component.Host;
|
||||
component.Host = null;
|
||||
|
||||
if (!TryComp(host, out GuardianHostComponent? hostComponent))
|
||||
return;
|
||||
|
||||
hostComponent.GuardianContainer.Remove(uid);
|
||||
hostComponent.HostedGuardian = null;
|
||||
Dirty(host.Value, hostComponent);
|
||||
QueueDel(hostComponent.ActionEntity);
|
||||
hostComponent.ActionEntity = null;
|
||||
}
|
||||
|
||||
private void OnPerformAction(EntityUid uid, GuardianHostComponent component, GuardianToggleActionEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
@@ -67,24 +83,29 @@ namespace Content.Server.Guardian
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnGuardianUnplayer(EntityUid uid, GuardianComponent component, PlayerDetachedEvent args)
|
||||
private void OnGuardianPlayerDetached(EntityUid uid, GuardianComponent component, PlayerDetachedEvent args)
|
||||
{
|
||||
var host = component.Host;
|
||||
|
||||
if (!TryComp<GuardianHostComponent>(host, out var hostComponent) || LifeStage(host) >= EntityLifeStage.MapInitialized)
|
||||
if (!TryComp<GuardianHostComponent>(host, out var hostComponent) || TerminatingOrDeleted(host.Value))
|
||||
{
|
||||
QueueDel(uid);
|
||||
return;
|
||||
}
|
||||
|
||||
RetractGuardian(host, hostComponent, uid, component);
|
||||
RetractGuardian(host.Value, hostComponent, uid, component);
|
||||
}
|
||||
|
||||
private void OnGuardianPlayer(EntityUid uid, GuardianComponent component, PlayerAttachedEvent args)
|
||||
private void OnGuardianPlayerAttached(EntityUid uid, GuardianComponent component, PlayerAttachedEvent args)
|
||||
{
|
||||
var host = component.Host;
|
||||
|
||||
if (!HasComp<GuardianHostComponent>(host))
|
||||
{
|
||||
QueueDel(uid);
|
||||
return;
|
||||
}
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("guardian-available"), host, host);
|
||||
_popupSystem.PopupEntity(Loc.GetString("guardian-available"), host.Value, host.Value);
|
||||
}
|
||||
|
||||
private void OnHostInit(EntityUid uid, GuardianHostComponent component, ComponentInit args)
|
||||
@@ -95,14 +116,16 @@ namespace Content.Server.Guardian
|
||||
|
||||
private void OnHostShutdown(EntityUid uid, GuardianHostComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (component.HostedGuardian == null)
|
||||
if (component.HostedGuardian is not {} guardian)
|
||||
return;
|
||||
|
||||
if (HasComp<HandsComponent>(component.HostedGuardian.Value))
|
||||
// Ensure held items are dropped before deleting guardian.
|
||||
if (HasComp<HandsComponent>(guardian))
|
||||
_bodySystem.GibBody(component.HostedGuardian.Value);
|
||||
|
||||
EntityManager.QueueDeleteEntity(component.HostedGuardian.Value);
|
||||
_actionSystem.RemoveAction(uid, component.ActionEntity);
|
||||
QueueDel(guardian);
|
||||
QueueDel(component.ActionEntity);
|
||||
component.ActionEntity = null;
|
||||
}
|
||||
|
||||
private void OnGuardianAttackAttempt(EntityUid uid, GuardianComponent component, AttackAttemptEvent args)
|
||||
@@ -117,7 +140,7 @@ namespace Content.Server.Guardian
|
||||
|
||||
public void ToggleGuardian(EntityUid user, GuardianHostComponent hostComponent)
|
||||
{
|
||||
if (hostComponent.HostedGuardian == null || !TryComp<GuardianComponent>(hostComponent.HostedGuardian, out var guardianComponent))
|
||||
if (!TryComp<GuardianComponent>(hostComponent.HostedGuardian, out var guardianComponent))
|
||||
return;
|
||||
|
||||
if (guardianComponent.GuardianLoose)
|
||||
@@ -134,7 +157,7 @@ namespace Content.Server.Guardian
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
//args.Handled = true;
|
||||
args.Handled = true;
|
||||
UseCreator(args.User, args.User, uid, component);
|
||||
}
|
||||
|
||||
@@ -143,7 +166,7 @@ namespace Content.Server.Guardian
|
||||
if (args.Handled || args.Target == null || !args.CanReach)
|
||||
return;
|
||||
|
||||
//args.Handled = true;
|
||||
args.Handled = true;
|
||||
UseCreator(args.User, args.Target.Value, uid, component);
|
||||
}
|
||||
private void UseCreator(EntityUid user, EntityUid target, EntityUid injector, GuardianCreatorComponent component)
|
||||
@@ -194,6 +217,7 @@ namespace Content.Server.Guardian
|
||||
if (TryComp<GuardianComponent>(guardian, out var guardianComp))
|
||||
{
|
||||
guardianComp.Host = args.Args.Target.Value;
|
||||
// TODO this should be a data field, not a hardcoded path
|
||||
_audio.Play("/Audio/Effects/guardian_inject.ogg", Filter.Pvs(args.Args.Target.Value), args.Args.Target.Value, true);
|
||||
_popupSystem.PopupEntity(Loc.GetString("guardian-created"), args.Args.Target.Value, args.Args.Target.Value);
|
||||
// Exhaust the activator
|
||||
@@ -201,8 +225,8 @@ namespace Content.Server.Guardian
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS("guardian", $"Tried to spawn a guardian that doesn't have {nameof(GuardianComponent)}");
|
||||
EntityManager.QueueDeleteEntity(guardian);
|
||||
Log.Error($"Tried to spawn a guardian that doesn't have {nameof(GuardianComponent)}");
|
||||
QueueDel(guardian);
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
@@ -219,13 +243,14 @@ namespace Content.Server.Guardian
|
||||
if (args.NewMobState == MobState.Critical)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("guardian-host-critical-warn"), component.HostedGuardian.Value, component.HostedGuardian.Value);
|
||||
// TODO this should be a data field, not a hardcoded path
|
||||
_audio.Play("/Audio/Effects/guardian_warn.ogg", Filter.Pvs(component.HostedGuardian.Value), component.HostedGuardian.Value, true);
|
||||
}
|
||||
else if (args.NewMobState == MobState.Dead)
|
||||
{
|
||||
//TODO: Replace WithVariation with datafield
|
||||
_audio.Play("/Audio/Voice/Human/malescream_guardian.ogg", Filter.Pvs(uid), uid, true, AudioHelpers.WithVariation(0.20f));
|
||||
EntityManager.RemoveComponent<GuardianHostComponent>(uid);
|
||||
RemComp<GuardianHostComponent>(uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,11 +259,11 @@ namespace Content.Server.Guardian
|
||||
/// </summary>
|
||||
private void OnGuardianDamaged(EntityUid uid, GuardianComponent component, DamageChangedEvent args)
|
||||
{
|
||||
if (args.DamageDelta == null)
|
||||
if (args.DamageDelta == null || component.Host == null || component.DamageShare > 0)
|
||||
return;
|
||||
|
||||
_damageSystem.TryChangeDamage(component.Host, args.DamageDelta * component.DamageShare, origin: args.Origin);
|
||||
_popupSystem.PopupEntity(Loc.GetString("guardian-entity-taking-damage"), component.Host, component.Host);
|
||||
_popupSystem.PopupEntity(Loc.GetString("guardian-entity-taking-damage"), component.Host.Value, component.Host.Value);
|
||||
|
||||
}
|
||||
|
||||
@@ -256,8 +281,7 @@ namespace Content.Server.Guardian
|
||||
/// </summary>
|
||||
private void OnHostMove(EntityUid uid, GuardianHostComponent component, ref MoveEvent args)
|
||||
{
|
||||
if (component.HostedGuardian == null ||
|
||||
!TryComp(component.HostedGuardian, out GuardianComponent? guardianComponent) ||
|
||||
if (!TryComp(component.HostedGuardian, out GuardianComponent? guardianComponent) ||
|
||||
!guardianComponent.GuardianLoose)
|
||||
{
|
||||
return;
|
||||
@@ -271,10 +295,10 @@ namespace Content.Server.Guardian
|
||||
/// </summary>
|
||||
private void OnGuardianMove(EntityUid uid, GuardianComponent component, ref MoveEvent args)
|
||||
{
|
||||
if (!component.GuardianLoose)
|
||||
if (!component.GuardianLoose || component.Host == null)
|
||||
return;
|
||||
|
||||
CheckGuardianMove(component.Host, uid, guardianComponent: component);
|
||||
CheckGuardianMove(component.Host.Value, uid, guardianComponent: component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -288,6 +312,9 @@ namespace Content.Server.Guardian
|
||||
TransformComponent? hostXform = null,
|
||||
TransformComponent? guardianXform = null)
|
||||
{
|
||||
if (TerminatingOrDeleted(guardianUid) || TerminatingOrDeleted(hostUid))
|
||||
return;
|
||||
|
||||
if (!Resolve(hostUid, ref hostComponent, ref hostXform) ||
|
||||
!Resolve(guardianUid, ref guardianComponent, ref guardianXform))
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Content.Shared.DeviceLinking;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -37,6 +38,9 @@ namespace Content.Server.Kitchen.Components
|
||||
[ViewVariables]
|
||||
public bool Broken;
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public ProtoId<SinkPortPrototype> OnPort = "On";
|
||||
|
||||
/// <summary>
|
||||
/// This is a fixed offset of 5.
|
||||
/// The cook times for all recipes should be divisible by 5,with a minimum of 1 second.
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Construction;
|
||||
using Content.Server.DeviceLinking.Events;
|
||||
using Content.Server.DeviceLinking.Systems;
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.Kitchen.Components;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Temperature.Components;
|
||||
using Content.Server.Temperature.Systems;
|
||||
using Content.Shared.Body.Components;
|
||||
@@ -33,7 +37,9 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
{
|
||||
[Dependency] private readonly BodySystem _bodySystem = default!;
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
[Dependency] private readonly DeviceLinkSystem _deviceLink = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly PowerReceiverSystem _power = default!;
|
||||
[Dependency] private readonly RecipeManager _recipeManager = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
@@ -49,6 +55,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<MicrowaveComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<MicrowaveComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<MicrowaveComponent, SolutionChangedEvent>(OnSolutionChange);
|
||||
SubscribeLocalEvent<MicrowaveComponent, InteractUsingEvent>(OnInteractUsing, after: new[] { typeof(AnchorableSystem) });
|
||||
SubscribeLocalEvent<MicrowaveComponent, BreakageEventArgs>(OnBreak);
|
||||
@@ -57,6 +64,8 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
SubscribeLocalEvent<MicrowaveComponent, RefreshPartsEvent>(OnRefreshParts);
|
||||
SubscribeLocalEvent<MicrowaveComponent, UpgradeExamineEvent>(OnUpgradeExamine);
|
||||
|
||||
SubscribeLocalEvent<MicrowaveComponent, SignalReceivedEvent>(OnSignalReceived);
|
||||
|
||||
SubscribeLocalEvent<MicrowaveComponent, MicrowaveStartCookMessage>((u, c, m) => Wzhzhzh(u, c, m.Session.AttachedEntity));
|
||||
SubscribeLocalEvent<MicrowaveComponent, MicrowaveEjectMessage>(OnEjectMessage);
|
||||
SubscribeLocalEvent<MicrowaveComponent, MicrowaveEjectSolidIndexedMessage>(OnEjectIndex);
|
||||
@@ -172,9 +181,15 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, MicrowaveComponent component, ComponentInit ags)
|
||||
private void OnInit(Entity<MicrowaveComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
component.Storage = _container.EnsureContainer<Container>(uid, "microwave_entity_container");
|
||||
// this really does have to be in ComponentInit
|
||||
ent.Comp.Storage = _container.EnsureContainer<Container>(ent, "microwave_entity_container");
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<MicrowaveComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
_deviceLink.EnsureSinkPorts(ent, ent.Comp.OnPort);
|
||||
}
|
||||
|
||||
private void OnSuicide(EntityUid uid, MicrowaveComponent component, SuicideEvent args)
|
||||
@@ -277,6 +292,17 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
args.AddPercentageUpgrade("microwave-component-upgrade-cook-time", component.CookTimeMultiplier);
|
||||
}
|
||||
|
||||
private void OnSignalReceived(Entity<MicrowaveComponent> ent, ref SignalReceivedEvent args)
|
||||
{
|
||||
if (args.Port != ent.Comp.OnPort)
|
||||
return;
|
||||
|
||||
if (ent.Comp.Broken || !_power.IsPowered(ent))
|
||||
return;
|
||||
|
||||
Wzhzhzh(ent.Owner, ent.Comp, null);
|
||||
}
|
||||
|
||||
public void UpdateUserInterfaceState(EntityUid uid, MicrowaveComponent component)
|
||||
{
|
||||
var ui = _userInterface.GetUiOrNull(uid, MicrowaveUiKey.Key);
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace Content.Server.Mech.Components;
|
||||
public sealed partial class MechAirComponent : Component
|
||||
{
|
||||
//TODO: this doesn't support a tank implant for mechs or anything like that
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public GasMixture Air = new (GasMixVolume);
|
||||
|
||||
public const float GasMixVolume = 70f;
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
using Content.Shared.Atmos;
|
||||
|
||||
namespace Content.Server.Mech.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is basically a reverse scrubber for MechAir
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class MechAirFilterComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Gases that will be filtered out of internal air
|
||||
/// </summary>
|
||||
[DataField("gases", required: true)]
|
||||
public HashSet<Gas> Gases = new();
|
||||
|
||||
/// <summary>
|
||||
/// Target volume to transfer every second.
|
||||
/// </summary>
|
||||
[DataField("transferRate")]
|
||||
public float TransferRate = MechAirComponent.GasMixVolume * 0.1f;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using Content.Shared.Atmos;
|
||||
|
||||
namespace Content.Server.Mech.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is basically a siphon vent for mech but not using pump vent component because MechAir bad
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class MechAirIntakeComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Target pressure change for a single atmos tick
|
||||
/// </summary>
|
||||
[DataField("targetPressureChange")]
|
||||
public float TargetPressureChange = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// How strong the intake pump is, it will be able to replenish air from lower pressure areas.
|
||||
/// </summary>
|
||||
[DataField("pumpPower")]
|
||||
public float PumpPower = 2f;
|
||||
|
||||
/// <summary>
|
||||
/// Pressure to intake gases up to, maintains MechAir pressure.
|
||||
/// </summary>
|
||||
[DataField("pressure")]
|
||||
public float Pressure = Atmospherics.OneAtmosphere;
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.Mech.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Mech.Components;
|
||||
|
||||
namespace Content.Server.Mech.Systems;
|
||||
|
||||
// TODO: this could be reused for gasmask or something if MechAir wasnt a thing
|
||||
public sealed partial class MechSystem
|
||||
{
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private void InitializeFiltering()
|
||||
{
|
||||
SubscribeLocalEvent<MechAirIntakeComponent, AtmosDeviceUpdateEvent>(OnIntakeUpdate);
|
||||
SubscribeLocalEvent<MechAirFilterComponent, AtmosDeviceUpdateEvent>(OnFilterUpdate);
|
||||
}
|
||||
|
||||
private void OnIntakeUpdate(EntityUid uid, MechAirIntakeComponent intake, AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!TryComp<MechComponent>(uid, out var mech) || !mech.Airtight || !TryComp<MechAirComponent>(uid, out var mechAir))
|
||||
return;
|
||||
|
||||
// if the mech is filled there is nothing to do
|
||||
if (mechAir.Air.Pressure >= intake.Pressure)
|
||||
return;
|
||||
|
||||
var environment = _atmosphere.GetContainingMixture(uid, true, true);
|
||||
// nothing to intake from
|
||||
if (environment == null)
|
||||
return;
|
||||
|
||||
// absolute maximum pressure change
|
||||
var pressureDelta = args.dt * intake.TargetPressureChange;
|
||||
pressureDelta = MathF.Min(pressureDelta, intake.Pressure - mechAir.Air.Pressure);
|
||||
if (pressureDelta <= 0)
|
||||
return;
|
||||
|
||||
// how many moles to transfer to change internal pressure by pressureDelta
|
||||
// ignores temperature difference because lazy
|
||||
var transferMoles = pressureDelta * mechAir.Air.Volume / (environment.Temperature * Atmospherics.R);
|
||||
_atmosphere.Merge(mechAir.Air, environment.Remove(transferMoles));
|
||||
}
|
||||
|
||||
private void OnFilterUpdate(EntityUid uid, MechAirFilterComponent filter, AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!TryComp<MechComponent>(uid, out var mech) || !mech.Airtight || !TryComp<MechAirComponent>(uid, out var mechAir))
|
||||
return;
|
||||
|
||||
var ratio = MathF.Min(1f, args.dt * filter.TransferRate / mechAir.Air.Volume);
|
||||
var removed = mechAir.Air.RemoveRatio(ratio);
|
||||
// nothing left to remove from the mech
|
||||
if (MathHelper.CloseToPercent(removed.TotalMoles, 0f))
|
||||
return;
|
||||
|
||||
|
||||
var coordinates = Transform(uid).MapPosition;
|
||||
GasMixture? destination = null;
|
||||
if (_map.TryFindGridAt(coordinates, out var gridId, out var grid))
|
||||
{
|
||||
var tile = _mapSystem.GetTileRef(gridId, grid, coordinates);
|
||||
destination = _atmosphere.GetTileMixture(tile.GridUid, null, tile.GridIndices, true);
|
||||
}
|
||||
|
||||
if (destination != null)
|
||||
{
|
||||
_atmosphere.ScrubInto(removed, destination, filter.Gases);
|
||||
}
|
||||
else
|
||||
{
|
||||
// filtering into space/planet so just discard them
|
||||
foreach (var gas in filter.Gases)
|
||||
{
|
||||
removed.SetMoles(gas, 0f);
|
||||
}
|
||||
}
|
||||
_atmosphere.Merge(mechAir.Air, removed);
|
||||
}
|
||||
}
|
||||
@@ -47,8 +47,6 @@ public sealed partial class MechSystem : SharedMechSystem
|
||||
|
||||
_sawmill = Logger.GetSawmill("mech");
|
||||
|
||||
InitializeFiltering();
|
||||
|
||||
SubscribeLocalEvent<MechComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
SubscribeLocalEvent<MechComponent, EntInsertedIntoContainerMessage>(OnInsertBattery);
|
||||
SubscribeLocalEvent<MechComponent, MapInitEvent>(OnMapInit);
|
||||
@@ -69,6 +67,8 @@ public sealed partial class MechSystem : SharedMechSystem
|
||||
SubscribeLocalEvent<MechPilotComponent, ExhaleLocationEvent>(OnExhale);
|
||||
SubscribeLocalEvent<MechPilotComponent, AtmosExposedGetAirEvent>(OnExpose);
|
||||
|
||||
SubscribeLocalEvent<MechAirComponent, GetFilterAirEvent>(OnGetFilterAir);
|
||||
|
||||
#region Equipment UI message relays
|
||||
SubscribeLocalEvent<MechComponent, MechGrabberEjectMessage>(ReceiveEquipmentUiMesssages);
|
||||
SubscribeLocalEvent<MechComponent, MechSoundboardPlayMessage>(ReceiveEquipmentUiMesssages);
|
||||
@@ -423,5 +423,17 @@ public sealed partial class MechSystem : SharedMechSystem
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnGetFilterAir(EntityUid uid, MechAirComponent comp, ref GetFilterAirEvent args)
|
||||
{
|
||||
if (args.Air != null)
|
||||
return;
|
||||
|
||||
// only airtight mechs get internal air
|
||||
if (!TryComp<MechComponent>(uid, out var mech) || !mech.Airtight)
|
||||
return;
|
||||
|
||||
args.Air = comp.Air;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -9,6 +9,12 @@ public sealed partial class SpeakOperator : HTNOperator
|
||||
[DataField("speech", required: true)]
|
||||
public string Speech = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to hide message from chat window and logs.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Hidden;
|
||||
|
||||
public override void Initialize(IEntitySystemManager sysManager)
|
||||
{
|
||||
base.Initialize(sysManager);
|
||||
@@ -19,7 +25,7 @@ public sealed partial class SpeakOperator : HTNOperator
|
||||
{
|
||||
var speaker = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
|
||||
|
||||
_chat.TrySendInGameICMessage(speaker, Loc.GetString(Speech), InGameICChatType.Speak, false);
|
||||
_chat.TrySendInGameICMessage(speaker, Loc.GetString(Speech), InGameICChatType.Speak, hideChat: Hidden, hideLog: Hidden);
|
||||
return base.Update(blackboard, frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
using Content.Server.Ninja.Events;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Electrocution;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Ninja.Components;
|
||||
using Content.Shared.Ninja.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Ninja.Systems;
|
||||
@@ -17,11 +20,13 @@ namespace Content.Server.Ninja.Systems;
|
||||
public sealed class StunProviderSystem : SharedStunProviderSystem
|
||||
{
|
||||
[Dependency] private readonly BatterySystem _battery = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedElectrocutionSystem _electrocution = default!;
|
||||
[Dependency] private readonly SharedNinjaGlovesSystem _gloves = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedStunSystem _stun = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -55,8 +60,9 @@ public sealed class StunProviderSystem : SharedStunProviderSystem
|
||||
|
||||
_audio.PlayPvs(comp.Sound, target);
|
||||
|
||||
// not holding hands with target so insuls don't matter
|
||||
_electrocution.TryDoElectrocution(target, uid, comp.StunDamage, comp.StunTime, false, ignoreInsulation: true);
|
||||
_damageable.TryChangeDamage(target, comp.StunDamage, false, true, null, origin: uid);
|
||||
_stun.TryParalyze(target, comp.StunTime, refresh: false);
|
||||
|
||||
// short cooldown to prevent instant stunlocking
|
||||
comp.NextStun = _timing.CurTime + comp.Cooldown;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Server.Objectives.Components;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
|
||||
@@ -25,7 +24,7 @@ public sealed class NotJobRequirementSystem : EntitySystem
|
||||
if (!TryComp<JobComponent>(args.MindId, out var job))
|
||||
return;
|
||||
|
||||
if (job.PrototypeId == comp.Job)
|
||||
if (job.Prototype == comp.Job)
|
||||
args.Cancelled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +107,7 @@ namespace Content.Server.Paper
|
||||
paperComp.Mode = PaperAction.Write;
|
||||
_uiSystem.TryOpen(uid, PaperUiKey.Key, actor.PlayerSession);
|
||||
UpdateUserInterface(uid, paperComp, actor.PlayerSession);
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ using Content.Server.Mind;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Roles.Jobs;
|
||||
|
||||
@@ -48,6 +47,6 @@ public sealed class JobSystem : SharedJobSystem
|
||||
if (MindHasJobWithId(mindId, jobPrototypeId))
|
||||
return;
|
||||
|
||||
_roles.MindAddRole(mindId, new JobComponent { PrototypeId = jobPrototypeId });
|
||||
_roles.MindAddRole(mindId, new JobComponent { Prototype = jobPrototypeId });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Silicons.Borgs;
|
||||
|
||||
@@ -89,18 +88,19 @@ public sealed partial class BorgSystem
|
||||
if (!TryComp<BorgChassisComponent>(chassis, out var chassisComp))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
if (chassisComp.SelectedModule == uid)
|
||||
{
|
||||
UnselectModule(chassis, chassisComp);
|
||||
return;
|
||||
}
|
||||
var selected = chassisComp.SelectedModule;
|
||||
|
||||
SelectModule(chassis, uid, chassisComp, component);
|
||||
args.Handled = true;
|
||||
UnselectModule(chassis, chassisComp);
|
||||
|
||||
if (selected != uid)
|
||||
{
|
||||
SelectModule(chassis, uid, chassisComp, component);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects a module, enablind the borg to use its provided abilities.
|
||||
/// Selects a module, enabling the borg to use its provided abilities.
|
||||
/// </summary>
|
||||
public void SelectModule(EntityUid chassis,
|
||||
EntityUid moduleUid,
|
||||
|
||||
@@ -39,7 +39,7 @@ public sealed class SpawnPointSystem : EntitySystem
|
||||
|
||||
if (_gameTicker.RunLevel != GameRunLevel.InRound &&
|
||||
spawnPoint.SpawnType == SpawnPointType.Job &&
|
||||
(args.Job == null || spawnPoint.Job?.ID == args.Job.PrototypeId))
|
||||
(args.Job == null || spawnPoint.Job?.ID == args.Job.Prototype))
|
||||
{
|
||||
possiblePositions.Add(xform.Coordinates);
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
|
||||
EntityUid? station,
|
||||
EntityUid? entity = null)
|
||||
{
|
||||
_prototypeManager.TryIndex(job?.PrototypeId ?? string.Empty, out JobPrototype? prototype);
|
||||
_prototypeManager.TryIndex(job?.Prototype ?? string.Empty, out JobPrototype? prototype);
|
||||
|
||||
// If we're not spawning a humanoid, we're gonna exit early without doing all the humanoid stuff.
|
||||
if (prototype?.JobEntity != null)
|
||||
@@ -161,7 +161,7 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
|
||||
|
||||
private void DoJobSpecials(JobComponent? job, EntityUid entity)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(job?.PrototypeId ?? string.Empty, out JobPrototype? prototype))
|
||||
if (!_prototypeManager.TryIndex(job?.Prototype ?? string.Empty, out JobPrototype? prototype))
|
||||
return;
|
||||
|
||||
foreach (var jobSpecial in prototype.Special)
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Toilet;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Storage.Components
|
||||
{
|
||||
@@ -19,7 +20,7 @@ namespace Content.Server.Storage.Components
|
||||
/// Max item size that can be fitted into secret stash.
|
||||
/// </summary>
|
||||
[DataField("maxItemSize")]
|
||||
public ItemSize MaxItemSize = ItemSize.Small;
|
||||
public ProtoId<ItemSizePrototype> MaxItemSize = "Small";
|
||||
|
||||
/// <summary>
|
||||
/// IC secret stash name. For example "the toilet cistern".
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Content.Server.Storage.EntitySystems
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
[Dependency] private readonly SharedItemSystem _item = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -66,7 +67,7 @@ namespace Content.Server.Storage.EntitySystems
|
||||
}
|
||||
|
||||
// check if item is too big to fit into secret stash
|
||||
if (item.Size > component.MaxItemSize)
|
||||
if (_item.GetSizePrototype(item.Size) > _item.GetSizePrototype(component.MaxItemSize))
|
||||
{
|
||||
var msg = Loc.GetString("comp-secret-stash-action-hide-item-too-big",
|
||||
("item", itemToHideUid), ("stash", GetSecretPartName(uid, component)));
|
||||
|
||||
@@ -39,11 +39,11 @@ public sealed partial class BuyerDepartmentCondition : ListingCondition
|
||||
var jobs = ent.System<SharedJobSystem>();
|
||||
jobs.MindTryGetJob(mindId, out var job, out _);
|
||||
|
||||
if (Blacklist != null && job?.PrototypeId != null)
|
||||
if (Blacklist != null && job?.Prototype != null)
|
||||
{
|
||||
foreach (var department in prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
|
||||
{
|
||||
if (department.Roles.Contains(job.PrototypeId) && Blacklist.Contains(department.ID))
|
||||
if (department.Roles.Contains(job.Prototype) && Blacklist.Contains(department.ID))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -52,11 +52,11 @@ public sealed partial class BuyerDepartmentCondition : ListingCondition
|
||||
{
|
||||
var found = false;
|
||||
|
||||
if (job?.PrototypeId != null)
|
||||
if (job?.Prototype != null)
|
||||
{
|
||||
foreach (var department in prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
|
||||
{
|
||||
if (department.Roles.Contains(job.PrototypeId) && Whitelist.Contains(department.ID))
|
||||
if (department.Roles.Contains(job.Prototype) && Whitelist.Contains(department.ID))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
|
||||
@@ -38,13 +38,13 @@ public sealed partial class BuyerJobCondition : ListingCondition
|
||||
|
||||
if (Blacklist != null)
|
||||
{
|
||||
if (job?.PrototypeId != null && Blacklist.Contains(job.PrototypeId))
|
||||
if (job?.Prototype != null && Blacklist.Contains(job.Prototype))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Whitelist != null)
|
||||
{
|
||||
if (job?.PrototypeId == null || !Whitelist.Contains(job.PrototypeId))
|
||||
if (job?.Prototype == null || !Whitelist.Contains(job.Prototype))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@ public sealed class StatValuesCommand : IConsoleCommand
|
||||
private StatValuesEuiMessage GetItem()
|
||||
{
|
||||
var values = new List<string[]>();
|
||||
var itemSystem = _entManager.System<ItemSystem>();
|
||||
var metaQuery = _entManager.GetEntityQuery<MetaDataComponent>();
|
||||
var itemQuery = _entManager.GetEntityQuery<ItemComponent>();
|
||||
var items = new HashSet<string>(1024);
|
||||
@@ -149,7 +150,7 @@ public sealed class StatValuesCommand : IConsoleCommand
|
||||
values.Add(new[]
|
||||
{
|
||||
id,
|
||||
$"{SharedItemSystem.GetItemSizeLocale(itemComp.Size)}",
|
||||
$"{itemSystem.GetItemSizeLocale(itemComp.Size)}",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ public sealed class EnergySwordSystem : EntitySystem
|
||||
{
|
||||
if (TryComp(uid, out ItemComponent? item))
|
||||
{
|
||||
_item.SetSize(uid, ItemSize.Small, item);
|
||||
_item.SetSize(uid, "Small", item);
|
||||
}
|
||||
|
||||
if (TryComp<DisarmMalusComponent>(uid, out var malus))
|
||||
@@ -125,7 +125,7 @@ public sealed class EnergySwordSystem : EntitySystem
|
||||
{
|
||||
if (TryComp(uid, out ItemComponent? item))
|
||||
{
|
||||
_item.SetSize(uid, ItemSize.Huge, item);
|
||||
_item.SetSize(uid, "Huge", item);
|
||||
}
|
||||
|
||||
if (comp.IsSharp)
|
||||
|
||||
@@ -226,7 +226,7 @@ namespace Content.Server.Zombies
|
||||
}
|
||||
}
|
||||
|
||||
if (_mobState.IsIncapacitated(entity, mobState) && !HasComp<ZombieComponent>(entity))
|
||||
if (_mobState.IsIncapacitated(entity, mobState) && !HasComp<ZombieComponent>(entity) && !HasComp<ZombieImmuneComponent>(entity))
|
||||
{
|
||||
ZombifyEntity(entity);
|
||||
args.BonusDamage = -args.BaseDamage;
|
||||
|
||||
@@ -243,16 +243,17 @@ public sealed partial class AnomalyComponent : Component
|
||||
/// <summary>
|
||||
/// Event raised at regular intervals on an anomaly to do whatever its effect is.
|
||||
/// </summary>
|
||||
/// <param name="Anomaly">The anomaly pulsing</param>
|
||||
/// <param name="Stability"></param>
|
||||
/// <param name="Severity"></param>
|
||||
[ByRefEvent]
|
||||
public readonly record struct AnomalyPulseEvent(float Stability, float Severity);
|
||||
public readonly record struct AnomalyPulseEvent(EntityUid Anomaly, float Stability, float Severity);
|
||||
|
||||
/// <summary>
|
||||
/// Event raised on an anomaly when it reaches a supercritical point.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct AnomalySupercriticalEvent;
|
||||
public readonly record struct AnomalySupercriticalEvent(EntityUid Anomaly);
|
||||
|
||||
/// <summary>
|
||||
/// Event broadcast after an anomaly goes supercritical
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Shared.Anomaly.Effects.Components;
|
||||
|
||||
@@ -36,8 +33,21 @@ public sealed partial class EntitySpawnAnomalyComponent : Component
|
||||
public float SpawnRange = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// The tile that is spawned by the anomaly's effect
|
||||
/// Whether or not anomaly spawns entities on Pulse
|
||||
/// </summary>
|
||||
[DataField("floorTileId", customTypeSerializer: typeof(PrototypeIdSerializer<ContentTileDefinition>)), ViewVariables(VVAccess.ReadWrite)]
|
||||
public string FloorTileId = "FloorFlesh";
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool SpawnOnPulse = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not anomaly spawns entities on SuperCritical
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool SpawnOnSuperCritical = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not anomaly spawns entities on StabilityChanged
|
||||
/// The idea was to spawn entities either on Pulse/Supercritical OR StabilityChanged
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool SpawnOnStabilityChanged = false;
|
||||
}
|
||||
|
||||
@@ -109,9 +109,9 @@ public abstract class SharedAnomalySystem : EntitySystem
|
||||
var pulse = EnsureComp<AnomalyPulsingComponent>(uid);
|
||||
pulse.EndTime = Timing.CurTime + pulse.PulseDuration;
|
||||
Appearance.SetData(uid, AnomalyVisuals.IsPulsing, true);
|
||||
|
||||
var ev = new AnomalyPulseEvent(component.Stability, component.Severity);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
|
||||
var ev = new AnomalyPulseEvent(uid, component.Stability, component.Severity);
|
||||
RaiseLocalEvent(uid, ref ev, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -154,8 +154,8 @@ public abstract class SharedAnomalySystem : EntitySystem
|
||||
if (_net.IsServer)
|
||||
_sawmill.Info($"Raising supercritical event. Entity: {ToPrettyString(uid)}");
|
||||
|
||||
var ev = new AnomalySupercriticalEvent();
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
var ev = new AnomalySupercriticalEvent(uid);
|
||||
RaiseLocalEvent(uid, ref ev, true);
|
||||
|
||||
EndAnomaly(uid, component, true);
|
||||
}
|
||||
|
||||
9
Content.Shared/Clothing/Components/SkatesComponent.cs
Normal file
9
Content.Shared/Clothing/Components/SkatesComponent.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Clothing;
|
||||
|
||||
[RegisterComponent]
|
||||
[NetworkedComponent]
|
||||
public sealed partial class SkatesComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -182,7 +182,9 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
//TODO: Actually shows all items/clothing/etc.
|
||||
private void HandleExamined(EntityUid uid, HandsComponent handsComp, ExaminedEvent args)
|
||||
{
|
||||
var held = EnumerateHeld(uid, handsComp).ToList();
|
||||
var held = EnumerateHeld(uid, handsComp)
|
||||
.Where(x => !HasComp<HandVirtualItemComponent>(x)).ToList();
|
||||
|
||||
if (!held.Any())
|
||||
{
|
||||
args.PushText(Loc.GetString("comp-hands-examine-empty",
|
||||
@@ -191,7 +193,6 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
}
|
||||
|
||||
var heldList = ContentLocalizationManager.FormatList(held
|
||||
.Where(x => !HasComp<HandVirtualItemComponent>(x))
|
||||
.Select(x => Loc.GetString("comp-hands-examine-wrapper",
|
||||
("item", Identity.Entity(x, EntityManager)))).ToList());
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Inventory;
|
||||
@@ -23,12 +24,16 @@ public abstract partial class InventorySystem
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly SharedItemSystem _item = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly INetManager _netMan = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
[ValidatePrototypeId<ItemSizePrototype>]
|
||||
private const string PocketableItemSize = "Small";
|
||||
|
||||
private void InitializeEquip()
|
||||
{
|
||||
//these events ensure that the client also gets its proper events raised when getting its containerstate updated
|
||||
@@ -264,7 +269,9 @@ public abstract partial class InventorySystem
|
||||
if (slotDefinition.DependsOn != null && !TryGetSlotEntity(target, slotDefinition.DependsOn, out _, inventory))
|
||||
return false;
|
||||
|
||||
var fittingInPocket = slotDefinition.SlotFlags.HasFlag(SlotFlags.POCKET) && item is { Size: <= ItemSize.Small };
|
||||
var fittingInPocket = slotDefinition.SlotFlags.HasFlag(SlotFlags.POCKET) &&
|
||||
item != null &&
|
||||
_item.GetSizePrototype(item.Size) <= _item.GetSizePrototype(PocketableItemSize);
|
||||
if (clothing == null && !fittingInPocket
|
||||
|| clothing != null && !clothing.Slots.HasFlag(slotDefinition.SlotFlags) && !fittingInPocket)
|
||||
{
|
||||
|
||||
@@ -42,6 +42,7 @@ public partial class InventorySystem
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowSecurityIconsComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHungerIconsComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowThirstIconsComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowSyndicateIconsComponent>>(RelayInventoryEvent);
|
||||
|
||||
SubscribeLocalEvent<InventoryComponent, GetVerbsEvent<EquipmentVerb>>(OnGetEquipmentVerbs);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Hands.Components;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Item;
|
||||
@@ -15,7 +16,7 @@ public sealed partial class ItemComponent : Component
|
||||
{
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[Access(typeof(SharedItemSystem))]
|
||||
public ItemSize Size = ItemSize.Small;
|
||||
public ProtoId<ItemSizePrototype> Size = "Small";
|
||||
|
||||
[Access(typeof(SharedItemSystem))]
|
||||
[DataField]
|
||||
@@ -38,10 +39,10 @@ public sealed partial class ItemComponent : Component
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ItemComponentState : ComponentState
|
||||
{
|
||||
public ItemSize Size { get; }
|
||||
public ProtoId<ItemSizePrototype> Size { get; }
|
||||
public string? HeldPrefix { get; }
|
||||
|
||||
public ItemComponentState(ItemSize size, string? heldPrefix)
|
||||
public ItemComponentState(ProtoId<ItemSizePrototype> size, string? heldPrefix)
|
||||
{
|
||||
Size = size;
|
||||
HeldPrefix = heldPrefix;
|
||||
@@ -64,40 +65,3 @@ public sealed class VisualsChangedEvent : EntityEventArgs
|
||||
ContainerId = containerId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstracted sizes for items.
|
||||
/// Used to determine what can fit into inventories.
|
||||
/// </summary>
|
||||
public enum ItemSize
|
||||
{
|
||||
/// <summary>
|
||||
/// Items that can be held completely in one's hand.
|
||||
/// </summary>
|
||||
Tiny = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Items that can fit inside of a standard pocket.
|
||||
/// </summary>
|
||||
Small = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Items that can fit inside of a standard bag.
|
||||
/// </summary>
|
||||
Normal = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Items that are too large to fit inside of standard bags, but can worn in exterior slots or placed in custom containers.
|
||||
/// </summary>
|
||||
Large = 8,
|
||||
|
||||
/// <summary>
|
||||
/// Items that are too large to place inside of any kind of container.
|
||||
/// </summary>
|
||||
Huge = 16,
|
||||
|
||||
/// <summary>
|
||||
/// Picture furry gf
|
||||
/// </summary>
|
||||
Ginormous = 32
|
||||
}
|
||||
|
||||
53
Content.Shared/Item/ItemSizePrototype.cs
Normal file
53
Content.Shared/Item/ItemSizePrototype.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Item;
|
||||
|
||||
/// <summary>
|
||||
/// This is a prototype for a category of an item's size.
|
||||
/// </summary>
|
||||
[Prototype("itemSize")]
|
||||
public sealed partial class ItemSizePrototype : IPrototype, IComparable<ItemSizePrototype>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of space in a bag an item of this size takes.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public readonly int Weight = 1;
|
||||
|
||||
/// <summary>
|
||||
/// A player-facing name used to describe this size.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public readonly LocId Name;
|
||||
|
||||
public int CompareTo(ItemSizePrototype? other)
|
||||
{
|
||||
if (other is not { } otherItemSize)
|
||||
return 0;
|
||||
return Weight.CompareTo(otherItemSize.Weight);
|
||||
}
|
||||
|
||||
public static bool operator <(ItemSizePrototype a, ItemSizePrototype b)
|
||||
{
|
||||
return a.Weight < b.Weight;
|
||||
}
|
||||
|
||||
public static bool operator >(ItemSizePrototype a, ItemSizePrototype b)
|
||||
{
|
||||
return a.Weight > b.Weight;
|
||||
}
|
||||
|
||||
public static bool operator <=(ItemSizePrototype a, ItemSizePrototype b)
|
||||
{
|
||||
return a.Weight <= b.Weight;
|
||||
}
|
||||
|
||||
public static bool operator >=(ItemSizePrototype a, ItemSizePrototype b)
|
||||
{
|
||||
return a.Weight >= b.Weight;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Shared.Item;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Weapons.Melee.ItemToggle;
|
||||
|
||||
@@ -21,11 +22,11 @@ public sealed partial class ItemToggleComponent : Component
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("offSize")]
|
||||
public ItemSize OffSize = ItemSize.Small;
|
||||
public ProtoId<ItemSizePrototype> OffSize = "Small";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("onSize")]
|
||||
public ItemSize OnSize = ItemSize.Huge;
|
||||
public ProtoId<ItemSizePrototype> OnSize = "Huge";
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
|
||||
@@ -6,12 +6,14 @@ using Content.Shared.Examine;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Item;
|
||||
|
||||
public abstract class SharedItemSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] protected readonly SharedContainerSystem Container = default!;
|
||||
|
||||
@@ -30,7 +32,7 @@ public abstract class SharedItemSystem : EntitySystem
|
||||
|
||||
#region Public API
|
||||
|
||||
public void SetSize(EntityUid uid, ItemSize size, ItemComponent? component = null)
|
||||
public void SetSize(EntityUid uid, ProtoId<ItemSizePrototype> size, ItemComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false))
|
||||
return;
|
||||
@@ -128,6 +130,11 @@ public abstract class SharedItemSystem : EntitySystem
|
||||
("size", GetItemSizeLocale(component.Size))));
|
||||
}
|
||||
|
||||
public ItemSizePrototype GetSizePrototype(ProtoId<ItemSizePrototype> id)
|
||||
{
|
||||
return _prototype.Index(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies any entity that is holding or wearing this item that they may need to update their sprite.
|
||||
/// </summary>
|
||||
@@ -140,14 +147,14 @@ public abstract class SharedItemSystem : EntitySystem
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static string GetItemSizeLocale(ItemSize size)
|
||||
public string GetItemSizeLocale(ProtoId<ItemSizePrototype> size)
|
||||
{
|
||||
return Robust.Shared.Localization.Loc.GetString($"item-component-size-{size.ToString()}");
|
||||
return Loc.GetString(GetSizePrototype(size).Name);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static int GetItemSizeWeight(ItemSize size)
|
||||
public int GetItemSizeWeight(ProtoId<ItemSizePrototype> size)
|
||||
{
|
||||
return (int) size;
|
||||
return GetSizePrototype(size).Weight;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.DragDrop;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Components;
|
||||
@@ -35,6 +36,7 @@ public abstract class SharedMechSystem : EntitySystem
|
||||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly SharedMoverController _mover = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
@@ -45,6 +47,8 @@ public abstract class SharedMechSystem : EntitySystem
|
||||
SubscribeLocalEvent<MechComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<MechComponent, DestructionEventArgs>(OnDestruction);
|
||||
SubscribeLocalEvent<MechComponent, GetAdditionalAccessEvent>(OnGetAdditionalAccess);
|
||||
SubscribeLocalEvent<MechComponent, DragDropTargetEvent>(OnDragDrop);
|
||||
SubscribeLocalEvent<MechComponent, CanDropTargetEvent>(OnCanDragDrop);
|
||||
|
||||
SubscribeLocalEvent<MechPilotComponent, GetMeleeWeaponEvent>(OnGetMeleeWeapon);
|
||||
SubscribeLocalEvent<MechPilotComponent, CanAttackFromContainerEvent>(OnCanAttackFromContainer);
|
||||
@@ -420,6 +424,29 @@ public abstract class SharedMechSystem : EntitySystem
|
||||
_appearance.SetData(uid, MechVisuals.Open, IsEmpty(component), appearance);
|
||||
_appearance.SetData(uid, MechVisuals.Broken, component.Broken, appearance);
|
||||
}
|
||||
|
||||
private void OnDragDrop(EntityUid uid, MechComponent component, ref DragDropTargetEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
|
||||
var doAfterEventArgs = new DoAfterArgs(EntityManager, args.Dragged, component.EntryDelay, new MechEntryEvent(), uid, target: uid)
|
||||
{
|
||||
BreakOnUserMove = true,
|
||||
};
|
||||
|
||||
_doAfter.TryStartDoAfter(doAfterEventArgs);
|
||||
}
|
||||
|
||||
private void OnCanDragDrop(EntityUid uid, MechComponent component, ref CanDropTargetEvent args)
|
||||
{
|
||||
args.Handled = true;
|
||||
|
||||
args.CanDrop |= !component.Broken && CanInsert(uid, args.Dragged, component);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Movement.Components;
|
||||
|
||||
[NetworkedComponent, RegisterComponent]
|
||||
[AutoGenerateComponentState]
|
||||
[Access(typeof(FrictionContactsSystem))]
|
||||
public sealed partial class FrictionContactsComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Modified mob friction while on FrictionContactsComponent
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[AutoNetworkedField]
|
||||
public float MobFriction = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Modified mob friction without input while on FrictionContactsComponent
|
||||
/// </summary>
|
||||
[AutoNetworkedField]
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float MobFrictionNoInput = 0.05f;
|
||||
|
||||
/// <summary>
|
||||
/// Modified mob acceleration while on FrictionContactsComponent
|
||||
/// </summary>
|
||||
[AutoNetworkedField]
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float MobAcceleration = 2.0f;
|
||||
}
|
||||
@@ -87,19 +87,19 @@ namespace Content.Shared.Movement.Components
|
||||
/// <summary>
|
||||
/// The acceleration applied to mobs when moving.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField]
|
||||
[AutoNetworkedField, ViewVariables(VVAccess.ReadWrite), DataField]
|
||||
public float Acceleration = DefaultAcceleration;
|
||||
|
||||
/// <summary>
|
||||
/// The negative velocity applied for friction.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField]
|
||||
[AutoNetworkedField, ViewVariables(VVAccess.ReadWrite), DataField]
|
||||
public float Friction = DefaultFriction;
|
||||
|
||||
/// <summary>
|
||||
/// The negative velocity applied for friction.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField]
|
||||
[AutoNetworkedField, ViewVariables(VVAccess.ReadWrite), DataField]
|
||||
public float? FrictionNoInput;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
|
||||
|
||||
100
Content.Shared/Movement/Systems/FrictionContactsSystem.cs
Normal file
100
Content.Shared/Movement/Systems/FrictionContactsSystem.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using Content.Shared.Movement.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
|
||||
namespace Content.Shared.Movement.Systems;
|
||||
|
||||
public sealed class FrictionContactsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly MovementSpeedModifierSystem _speedModifierSystem = default!;
|
||||
|
||||
// Comment copied from "original" SlowContactsSystem.cs
|
||||
// TODO full-game-save
|
||||
// Either these need to be processed before a map is saved, or slowed/slowing entities need to update on init.
|
||||
private HashSet<EntityUid> _toUpdate = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<FrictionContactsComponent, StartCollideEvent>(OnEntityEnter);
|
||||
SubscribeLocalEvent<FrictionContactsComponent, EndCollideEvent>(OnEntityExit);
|
||||
SubscribeLocalEvent<FrictionContactsComponent, ComponentShutdown>(OnShutdown);
|
||||
|
||||
UpdatesAfter.Add(typeof(SharedPhysicsSystem));
|
||||
}
|
||||
|
||||
private void OnEntityEnter(EntityUid uid, FrictionContactsComponent component, ref StartCollideEvent args)
|
||||
{
|
||||
var otherUid = args.OtherEntity;
|
||||
|
||||
if (!HasComp(otherUid, typeof(MovementSpeedModifierComponent)))
|
||||
return;
|
||||
|
||||
_toUpdate.Add(otherUid);
|
||||
}
|
||||
|
||||
private void OnEntityExit(EntityUid uid, FrictionContactsComponent component, ref EndCollideEvent args)
|
||||
{
|
||||
var otherUid = args.OtherEntity;
|
||||
|
||||
if (!HasComp(otherUid, typeof(MovementSpeedModifierComponent)))
|
||||
return;
|
||||
|
||||
_toUpdate.Add(otherUid);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, FrictionContactsComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (!TryComp(uid, out PhysicsComponent? phys))
|
||||
return;
|
||||
|
||||
_toUpdate.UnionWith(_physics.GetContactingEntities(uid, phys));
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
foreach (var uid in _toUpdate)
|
||||
{
|
||||
ApplyFrictionChange(uid);
|
||||
}
|
||||
|
||||
_toUpdate.Clear();
|
||||
}
|
||||
|
||||
private void ApplyFrictionChange(EntityUid uid)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent<PhysicsComponent>(uid, out var physicsComponent))
|
||||
return;
|
||||
|
||||
if (!TryComp(uid, out MovementSpeedModifierComponent? speedModifier))
|
||||
return;
|
||||
|
||||
FrictionContactsComponent? frictionComponent = TouchesFrictionContactsComponent(uid, physicsComponent);
|
||||
|
||||
if (frictionComponent == null)
|
||||
{
|
||||
_speedModifierSystem.ChangeFriction(uid, MovementSpeedModifierComponent.DefaultFriction, null, MovementSpeedModifierComponent.DefaultAcceleration, speedModifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
_speedModifierSystem.ChangeFriction(uid, frictionComponent.MobFriction, frictionComponent.MobFrictionNoInput, frictionComponent.MobAcceleration, speedModifier);
|
||||
}
|
||||
}
|
||||
|
||||
private FrictionContactsComponent? TouchesFrictionContactsComponent(EntityUid uid, PhysicsComponent physicsComponent)
|
||||
{
|
||||
foreach (var ent in _physics.GetContactingEntities(uid, physicsComponent))
|
||||
{
|
||||
if (!TryComp(ent, out FrictionContactsComponent? frictionContacts))
|
||||
continue;
|
||||
|
||||
return frictionContacts;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ namespace Content.Shared.Movement.Systems
|
||||
|
||||
move.WalkSpeedModifier = ev.WalkSpeedModifier;
|
||||
move.SprintSpeedModifier = ev.SprintSpeedModifier;
|
||||
Dirty(move);
|
||||
Dirty(uid, move);
|
||||
}
|
||||
|
||||
public void ChangeBaseSpeed(EntityUid uid, float baseWalkSpeed, float baseSprintSpeed, float acceleration, MovementSpeedModifierComponent? move = null)
|
||||
@@ -36,7 +36,19 @@ namespace Content.Shared.Movement.Systems
|
||||
move.BaseWalkSpeed = baseWalkSpeed;
|
||||
move.BaseSprintSpeed = baseSprintSpeed;
|
||||
move.Acceleration = acceleration;
|
||||
Dirty(move);
|
||||
Dirty(uid, move);
|
||||
}
|
||||
|
||||
// We might want to create separate RefreshMovementFrictionModifiersEvent and RefreshMovementFrictionModifiers function that will call it
|
||||
public void ChangeFriction(EntityUid uid, float friction, float? frictionNoInput, float acceleration, MovementSpeedModifierComponent? move = null)
|
||||
{
|
||||
if (!Resolve(uid, ref move, false))
|
||||
return;
|
||||
|
||||
move.Friction = friction;
|
||||
move.FrictionNoInput = frictionNoInput;
|
||||
move.Acceleration = acceleration;
|
||||
Dirty(uid, move);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Ninja.Systems;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio;
|
||||
@@ -17,49 +18,55 @@ public sealed partial class StunProviderComponent : Component
|
||||
/// The powercell entity to take power from.
|
||||
/// Determines whether stunning is possible.
|
||||
/// </summary>
|
||||
[DataField("batteryUid"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
public EntityUid? BatteryUid;
|
||||
|
||||
/// <summary>
|
||||
/// Sound played when stunning someone.
|
||||
/// </summary>
|
||||
[DataField("sound"), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public SoundSpecifier Sound = new SoundCollectionSpecifier("sparks");
|
||||
|
||||
/// <summary>
|
||||
/// Joules required in the battery to stun someone. Defaults to 10 uses on a small battery.
|
||||
/// </summary>
|
||||
[DataField("stunCharge"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float StunCharge = 36.0f;
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float StunCharge = 36f;
|
||||
|
||||
/// <summary>
|
||||
/// Shock damage dealt when stunning someone
|
||||
/// Damage dealt when stunning someone
|
||||
/// </summary>
|
||||
[DataField("stunDamage"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public int StunDamage = 5;
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageSpecifier StunDamage = new()
|
||||
{
|
||||
DamageDict = new()
|
||||
{
|
||||
{ "Shock", 5 }
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Time that someone is stunned for, stacks if done multiple times.
|
||||
/// </summary>
|
||||
[DataField("stunTime"), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan StunTime = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
/// How long stunning is disabled after stunning something.
|
||||
/// </summary>
|
||||
[DataField("cooldown"), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan Cooldown = TimeSpan.FromSeconds(2);
|
||||
|
||||
/// <summary>
|
||||
/// Locale string to popup when there is no power
|
||||
/// </summary>
|
||||
[DataField("noPowerPopup", required: true), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
|
||||
public string NoPowerPopup = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Whitelist for what counts as a mob.
|
||||
/// </summary>
|
||||
[DataField("whitelist")]
|
||||
[DataField]
|
||||
public EntityWhitelist Whitelist = new()
|
||||
{
|
||||
Components = new[] {"Stamina"}
|
||||
@@ -69,6 +76,6 @@ public sealed partial class StunProviderComponent : Component
|
||||
/// When someone can next be stunned.
|
||||
/// Essentially a UseDelay unique to this component.
|
||||
/// </summary>
|
||||
[DataField("nextStun", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan NextStun = TimeSpan.Zero;
|
||||
}
|
||||
|
||||
27
Content.Shared/NukeOps/NukeOperativeComponent.cs
Normal file
27
Content.Shared/NukeOps/NukeOperativeComponent.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Robust.Shared.Audio;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.NukeOps;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for tagging a mob as a nuke operative.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class NukeOperativeComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Path to antagonist alert sound.
|
||||
/// </summary>
|
||||
[DataField("greetSoundNotification")]
|
||||
public SoundSpecifier GreetSoundNotification = new SoundPathSpecifier("/Audio/Ambience/Antag/nukeops_start.ogg");
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[DataField("syndStatusIcon", customTypeSerializer: typeof(PrototypeIdSerializer<StatusIconPrototype>))]
|
||||
public string SyndStatusIcon = "SyndicateFaction";
|
||||
}
|
||||
9
Content.Shared/Overlays/ShowSyndicateIconsComponent.cs
Normal file
9
Content.Shared/Overlays/ShowSyndicateIconsComponent.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Overlays;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class ShowSyndicateIconsComponent : Component {}
|
||||
@@ -26,8 +26,8 @@ public sealed class ItemPlacerSystem : EntitySystem
|
||||
if (comp.Whitelist != null && !comp.Whitelist.IsValid(args.OtherEntity))
|
||||
return;
|
||||
|
||||
if (TryComp<CollisionWakeComponent>(uid, out var wakeComp))
|
||||
_wake.SetEnabled(uid, false, wakeComp);
|
||||
if (TryComp<CollisionWakeComponent>(args.OtherEntity, out var wakeComp))
|
||||
_wake.SetEnabled(args.OtherEntity, false, wakeComp);
|
||||
|
||||
var count = comp.PlacedEntities.Count;
|
||||
if (comp.MaxEntities == 0 || count < comp.MaxEntities)
|
||||
@@ -47,8 +47,8 @@ public sealed class ItemPlacerSystem : EntitySystem
|
||||
|
||||
private void OnEndCollide(EntityUid uid, ItemPlacerComponent comp, ref EndCollideEvent args)
|
||||
{
|
||||
if (TryComp<CollisionWakeComponent>(uid, out var wakeComp))
|
||||
_wake.SetEnabled(uid, true, wakeComp);
|
||||
if (TryComp<CollisionWakeComponent>(args.OtherEntity, out var wakeComp))
|
||||
_wake.SetEnabled(args.OtherEntity, true, wakeComp);
|
||||
|
||||
comp.PlacedEntities.Remove(args.OtherEntity);
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Roles.Jobs;
|
||||
|
||||
/// <summary>
|
||||
/// Added to mind entities to hold the data for the player's current job.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class JobComponent : Component
|
||||
{
|
||||
[DataField("prototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<JobPrototype>))]
|
||||
public string? PrototypeId;
|
||||
[DataField(required: true), AutoNetworkedField]
|
||||
public ProtoId<JobPrototype>? Prototype;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ public abstract class SharedJobSystem : EntitySystem
|
||||
|
||||
public bool MindHasJobWithId(EntityUid? mindId, string prototypeId)
|
||||
{
|
||||
return CompOrNull<JobComponent>(mindId)?.PrototypeId == prototypeId;
|
||||
return CompOrNull<JobComponent>(mindId)?.Prototype == prototypeId;
|
||||
}
|
||||
|
||||
public bool MindTryGetJob(
|
||||
@@ -95,8 +95,8 @@ public abstract class SharedJobSystem : EntitySystem
|
||||
prototype = null;
|
||||
|
||||
return TryComp(mindId, out comp) &&
|
||||
comp.PrototypeId != null &&
|
||||
_prototypes.TryIndex(comp.PrototypeId, out prototype);
|
||||
comp.Prototype != null &&
|
||||
_prototypes.TryIndex(comp.Prototype, out prototype);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -25,7 +25,7 @@ public abstract class SharedRoleSystem : EntitySystem
|
||||
{
|
||||
var name = "game-ticker-unknown-role";
|
||||
string? playTimeTracker = null;
|
||||
if (component.PrototypeId != null && _prototypes.TryIndex(component.PrototypeId, out JobPrototype? job))
|
||||
if (component.Prototype != null && _prototypes.TryIndex(component.Prototype, out JobPrototype? job))
|
||||
{
|
||||
name = job.Name;
|
||||
playTimeTracker = job.PlayTimeTracker;
|
||||
|
||||
@@ -368,13 +368,9 @@ public abstract class SharedEntityStorageSystem : EntitySystem
|
||||
if (toAdd == container)
|
||||
return false;
|
||||
|
||||
if (TryComp<PhysicsComponent>(toAdd, out var phys))
|
||||
{
|
||||
var aabb = _physics.GetWorldAABB(toAdd, body: phys);
|
||||
|
||||
if (component.MaxSize < aabb.Size.X || component.MaxSize < aabb.Size.Y)
|
||||
return false;
|
||||
}
|
||||
var aabb = _lookup.GetAABBNoContainer(toAdd, Vector2.Zero, 0);
|
||||
if (component.MaxSize < aabb.Size.X || component.MaxSize < aabb.Size.Y)
|
||||
return false;
|
||||
|
||||
return Insert(toAdd, container, component);
|
||||
}
|
||||
|
||||
@@ -19,21 +19,23 @@ using Content.Shared.Timing;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Shared.Storage.EntitySystems;
|
||||
|
||||
public abstract class SharedStorageSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] protected readonly IRobustRandom Random = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _entityLookupSystem = default!;
|
||||
[Dependency] protected readonly SharedEntityStorageSystem EntityStorage = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly SharedItemSystem _item = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _sharedHandsSystem = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _sharedInteractionSystem = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] protected readonly SharedAudioSystem Audio = default!;
|
||||
@@ -46,7 +48,8 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
private EntityQuery<StackComponent> _stackQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
public const ItemSize DefaultStorageMaxItemSize = ItemSize.Normal;
|
||||
[ValidatePrototypeId<ItemSizePrototype>]
|
||||
public const string DefaultStorageMaxItemSize = "Normal";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
@@ -474,14 +477,15 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
|
||||
if (!_stackQuery.TryGetComponent(insertEnt, out var stack) || !HasSpaceInStacks(uid, stack.StackTypeId))
|
||||
{
|
||||
if (item.Size > GetMaxItemSize((uid, storageComp)))
|
||||
var maxSize = _item.GetSizePrototype(GetMaxItemSize((uid, storageComp)));
|
||||
if (_item.GetSizePrototype(item.Size) > maxSize)
|
||||
{
|
||||
reason = "comp-storage-too-big";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TryComp<StorageComponent>(insertEnt, out var insertStorage)
|
||||
&& GetMaxItemSize((insertEnt, insertStorage)) >= GetMaxItemSize((uid, storageComp)))
|
||||
&& _item.GetSizePrototype(GetMaxItemSize((insertEnt, insertStorage))) >= maxSize)
|
||||
{
|
||||
reason = "comp-storage-too-big";
|
||||
return false;
|
||||
@@ -495,7 +499,7 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (SharedItemSystem.GetItemSizeWeight(item.Size) + GetCumulativeItemSizes(uid, storageComp) > storageComp.MaxTotalWeight)
|
||||
else if (_item.GetItemSizeWeight(item.Size) + GetCumulativeItemSizes(uid, storageComp) > storageComp.MaxTotalWeight)
|
||||
{
|
||||
reason = "comp-storage-insufficient-capacity";
|
||||
return false;
|
||||
@@ -643,7 +647,7 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
/// <returns>true if inserted, false otherwise</returns>
|
||||
public bool PlayerInsertEntityInWorld(Entity<StorageComponent?> uid, EntityUid player, EntityUid toInsert)
|
||||
{
|
||||
if (!Resolve(uid, ref uid.Comp) || !_sharedInteractionSystem.InRangeUnobstructed(player, uid))
|
||||
if (!Resolve(uid, ref uid.Comp) || !_interactionSystem.InRangeUnobstructed(player, uid))
|
||||
return false;
|
||||
|
||||
if (!Insert(uid, toInsert, out _, user: player, uid.Comp))
|
||||
@@ -706,13 +710,13 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
{
|
||||
if (!_itemQuery.TryGetComponent(item, out var itemComp))
|
||||
continue;
|
||||
sum += SharedItemSystem.GetItemSizeWeight(itemComp.Size);
|
||||
sum += _item.GetItemSizeWeight(itemComp.Size);
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
public ItemSize GetMaxItemSize(Entity<StorageComponent?> uid)
|
||||
public ProtoId<ItemSizePrototype> GetMaxItemSize(Entity<StorageComponent?> uid)
|
||||
{
|
||||
if (!Resolve(uid, ref uid.Comp))
|
||||
return DefaultStorageMaxItemSize;
|
||||
@@ -723,12 +727,14 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
|
||||
if (!_itemQuery.TryGetComponent(uid, out var item))
|
||||
return DefaultStorageMaxItemSize;
|
||||
var size = _item.GetSizePrototype(item.Size);
|
||||
|
||||
// if there is no max item size specified, the value used
|
||||
// is one below the item size of the storage entity, clamped at ItemSize.Tiny
|
||||
var sizes = Enum.GetValues<ItemSize>().ToList();
|
||||
var currentSizeIndex = sizes.IndexOf(item.Size);
|
||||
return sizes[Math.Max(currentSizeIndex - 1, 0)];
|
||||
var sizes = _prototype.EnumeratePrototypes<ItemSizePrototype>().ToList();
|
||||
sizes.Sort();
|
||||
var currentSizeIndex = sizes.IndexOf(size);
|
||||
return sizes[Math.Max(currentSizeIndex - 1, 0)].ID;
|
||||
}
|
||||
|
||||
public FixedPoint2 GetStorageFillPercentage(Entity<StorageComponent?> uid)
|
||||
|
||||
@@ -5,6 +5,7 @@ using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Storage
|
||||
@@ -34,7 +35,7 @@ namespace Content.Shared.Storage
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
[Access(typeof(SharedStorageSystem))]
|
||||
public ItemSize? MaxItemSize;
|
||||
public ProtoId<ItemSizePrototype>? MaxItemSize;
|
||||
|
||||
/// <summary>
|
||||
/// The max number of entities that can be inserted into this storage.
|
||||
|
||||
@@ -24,16 +24,17 @@ namespace Content.Shared.Verbs
|
||||
if (user == null)
|
||||
return;
|
||||
|
||||
var target = GetEntity(args.Target);
|
||||
if (!TryGetEntity(args.Target, out var target))
|
||||
return;
|
||||
|
||||
// It is possible that client-side prediction can cause this event to be raised after the target entity has
|
||||
// been deleted. So we need to check that the entity still exists.
|
||||
if (Deleted(target) || Deleted(user))
|
||||
if (Deleted(user))
|
||||
return;
|
||||
|
||||
// Get the list of verbs. This effectively also checks that the requested verb is in fact a valid verb that
|
||||
// the user can perform.
|
||||
var verbs = GetLocalVerbs(target, user.Value, args.RequestedVerb.GetType());
|
||||
var verbs = GetLocalVerbs(target.Value, user.Value, args.RequestedVerb.GetType());
|
||||
|
||||
// Note that GetLocalVerbs might waste time checking & preparing unrelated verbs even though we know
|
||||
// precisely which one we want to run. However, MOST entities will only have 1 or 2 verbs of a given type.
|
||||
@@ -41,7 +42,7 @@ namespace Content.Shared.Verbs
|
||||
|
||||
// Find the requested verb.
|
||||
if (verbs.TryGetValue(args.RequestedVerb, out var verb))
|
||||
ExecuteVerb(verb, user.Value, target);
|
||||
ExecuteVerb(verb, user.Value, target.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
114
Resources/Audio/Animals/attributions.yml
Normal file
114
Resources/Audio/Animals/attributions.yml
Normal file
@@ -0,0 +1,114 @@
|
||||
- files: ["monkey_scream.ogg"]
|
||||
license: "CC-BY-NC-SA-3.0"
|
||||
copyright: "Taken from goonstation"
|
||||
source: "https://github.com/goonstation/goonstation/blob/4059e4be90832b02b1228b1bee3db342094e4f1e/sound/voice/screams/monkey_scream.ogg"
|
||||
|
||||
- files: ["cat_meow.ogg"]
|
||||
license: "CC-BY-3.0"
|
||||
copyright: "Modified from 'Meow 4.wav' by freesound user 'TRNGLE. The original audio was trimmed, split to mono, and converted from WAV to OGG format"
|
||||
source: "https://freesound.org/people/TRNGLE/sounds/368006/"
|
||||
|
||||
- files: ["cat_hiss.ogg"]
|
||||
license: "CC0-1.0"
|
||||
copyright: "Modified from 'Meow 4.wav' by freesound user 'TRNGLE'. The original audio was trimmed, split to mono, and converted from WAV to OGG format"
|
||||
source: "https://freesound.org/people/Zabuhailo/sounds/146963/"
|
||||
|
||||
- files: ["small_dog_bark_happy.ogg"]
|
||||
license: "CC0-1.0"
|
||||
copyright: "Modified from 'catHisses1.wav' by freesound user 'Zabuhailo. ' The original audio was trimmed and converted from WAV to OGG format"
|
||||
source: "https://freesound.org/people/MisterTood/sounds/9032/"
|
||||
|
||||
- files: ["duck_quack_happy.ogg"]
|
||||
license: "CC0-1.0"
|
||||
copyright: "Modified from 'Duck Quack - Sound Effect (HD).mp3' by freesound user 'Tabby+Gus.' The original audio was trimmed, looped, split to mono, and converted from MP3 to OGG format"
|
||||
source: "https://freesound.org/people/Tabby+Gus./sounds/515408/"
|
||||
|
||||
- files: ["chicken_cluck_happy.ogg"]
|
||||
license: "CC0-1.0"
|
||||
copyright: "Modified from 'Chicken Single Alarm Call' by freesound user 'Rudmer_Rotteveel'. The original audio was trimmed and converted from WAV to OGG format"
|
||||
source: "https://freesound.org/people/Rudmer_Rotteveel/sounds/316920/"
|
||||
|
||||
- files: ["ferret_happy.ogg"]
|
||||
license: "CC-BY-3.0"
|
||||
copyright: "Modified from 'Ferret' by freesound user 'J.Zazvurek'. The original audio was trimmed and converted from WAV to OGG format"
|
||||
source: "https://freesound.org/people/J.Zazvurek/sounds/155115/"
|
||||
|
||||
- files: ["mouse_squeak.ogg"]
|
||||
license: "CC0-1.0"
|
||||
copyright: "Modified from 'Cartoon - Bat / Mouse Squeak' by freesound user 'Breviceps'. The original audio was converted from WAV to OGG format"
|
||||
source: "https://freesound.org/people/Breviceps/sounds/445958/"
|
||||
|
||||
- files: ["sloth_squeak.ogg"]
|
||||
license: "CC0-1.0"
|
||||
copyright: "Modified from 'squeakfinal.wav' by freesound user 'Higgs01'. The original audio was converted from WAV to OFF format AND converted from stereo to mono"
|
||||
source: "https://freesound.org/people/Higgs01/sounds/428114/"
|
||||
|
||||
- files: ["parrot_raught.ogg"]
|
||||
license: "CC-BY-3.0"
|
||||
copyright: "Modified and used from 'Parrot 1 raught 2.wav' by freesound user 'Coral_Island_Studios'. The original audio was converted from WAV to OGG format"
|
||||
source: "https://freesound.org/people/Coral_Island_Studios/sounds/432588/"
|
||||
|
||||
- files: ["fox_squeak.ogg"]
|
||||
license: "CC0-1.0"
|
||||
copyright: "Modified from 'Video Game Squeak' by freesound user 'Breviceps'. The original audio was converted from WAV to OGG format"
|
||||
source: "https://freesound.org/people/Breviceps/sounds/468442/"
|
||||
|
||||
- files: ["frog_ribbit.ogg"]
|
||||
license: "CC0-1.0"
|
||||
copyright: "Modified from 'Video Game Squeak' by freesound user 'Breviceps'. The original audio was converted from WAV to OGG format"
|
||||
source: "https://freesound.org/people/egomassive/sounds/536759/"
|
||||
|
||||
- files: ["goose_honk.ogg"]
|
||||
license: "CC0-1.0"
|
||||
copyright: "Modified from ' Bird Honk - 1' by freesound user 'SpaceJoe'. The originial audio was converted from WAV to OGG format"
|
||||
source: "https://freesound.org/people/SpaceJoe/sounds/510940/"
|
||||
|
||||
- files: ["snake_his.ogg"]
|
||||
license: "CC-BY-4.0"
|
||||
copyright: "Modified from 'hissing snake sound effect' by freesound user 'Garuda1982'. The original audio was convertred from WAV to OGG format"
|
||||
source: "https://freesound.org/people/Garuda1982/sounds/541656/"
|
||||
|
||||
- files: ["pig_oink.ogg"]
|
||||
license: "CC0-1.0"
|
||||
copyright: "Modified from 'Pig Oink.wav' by freesound user 'qubodup'. The original audio was converted from WAV to OGG format"
|
||||
source: "https://freesound.org/people/qubodup/sounds/442906/"
|
||||
|
||||
- files: ["cerberus.ogg"]
|
||||
license: "CC0-1.0"
|
||||
copyright: "Modified from 'DogBARKING.wav' by freesound user 'ahill86'. The original audio was converted from WAV to OGG format, echoed, amplified and pitched down"
|
||||
source: "https://freesound.org/people/ahill86/sounds/207124/"
|
||||
|
||||
- files: ["space_dragon_roar.ogg"]
|
||||
license: "CC-BY-3.0"
|
||||
copyright: "Taken from tgstation"
|
||||
source: "https://github.com/tgstation/tgstation/commit/d4f678a1772007ff8d7eddd21cf7218c8e07bfc0"
|
||||
|
||||
- files: ["penguin_squawk.ogg"]
|
||||
license: "CC-BY-3.0"
|
||||
copyright: "Audio is recorded/created by youtube user 'ProSounds. The original audio was trimmed and converted from MP3 to OGG format"
|
||||
source: "https://youtu.be/Anr35RbBL3Y"
|
||||
|
||||
- files: ["raccoon_squeak.ogg"]
|
||||
license: "CC-BY-3.0"
|
||||
copyright: "Audio is recorded by youtube user 'jnargus'"
|
||||
source: "https://youtu.be/BGjFP1CP7E0"
|
||||
|
||||
- files: ["goat_bah.ogg"]
|
||||
license: "CC-BY-3.0"
|
||||
copyright: "Audio is created by youtube user 'Winry Marini'"
|
||||
source: "https://youtu.be/QIhwzsk5bww"
|
||||
|
||||
- files: ["lizard_happy.ogg"]
|
||||
license: "CC-BY-3.0"
|
||||
copyright: "Audio created by youtube user 'Nagaty Studio'"
|
||||
source: "https://youtu.be/I7CX0AS8RNI"
|
||||
|
||||
- files: ["bear.ogg"]
|
||||
license: "CC-BY-3.0"
|
||||
copyright: "Audio is recorded by 'Nagaty Studio'. The original audio was reverbed"
|
||||
source: "https://www.youtube.com/watch?v=pz6eZbESlU8"
|
||||
|
||||
- files: ["kangaroo_grunt.ogg"]
|
||||
license: "CC-BY-4.0"
|
||||
copyright: "Audio is recorded/created by Pfranzen 'FreeSound.org'. The original audio was trimmed and renamed"
|
||||
source: "https://freesound.org/people/pfranzen/sounds/322744/"
|
||||
@@ -1,53 +0,0 @@
|
||||
monkey_scream.ogg from
|
||||
https://github.com/goonstation/goonstation/blob/4059e4be90832b02b1228b1bee3db342094e4f1e/sound/voice/screams/monkey_scream.ogg
|
||||
licensed under CC BY-NC-SA 3.0
|
||||
|
||||
The following sounds were used from freesound:
|
||||
|
||||
cat_meow.ogg: modified from "Meow 4.wav" by freesound user "TRNGLE" (https://freesound.org/people/TRNGLE/sounds/368006/) licensed under CCBY 3.0. The original audio was trimmed, split to mono, and converted from WAV to OGG format.
|
||||
|
||||
cat_hiss.ogg: modified from "catHisses1.wav" by freesound user "Zabuhailo" (https://freesound.org/people/Zabuhailo/sounds/146963/) licensed under CC0 1.0 (public domain). The original audio was trimmed and converted from WAV to OGG format.
|
||||
|
||||
small_dog_bark_happy.ogg: modified from "Dog bark2.wav" by freesound user "MisterTood" (https://freesound.org/people/MisterTood/sounds/9032/) licensed under CC0 1.0 (public domain). The original audio was trimmed and converted from WAV to OGG format.
|
||||
|
||||
duck_quack_happy.ogg: modified from "Duck Quack - Sound Effect (HD).mp3" by freesound user "Tabby+Gus." (https://freesound.org/people/Tabby+Gus./sounds/515408/) licensed under CC0 1.0 (public domain). The original audio was trimmed, looped, split to mono, and converted from MP3 to OGG format.
|
||||
|
||||
chicken_cluck_happy.ogg: modified from "Chicken Single Alarm Call" by freesound user "Rudmer_Rotteveel" (https://freesound.org/people/Rudmer_Rotteveel/sounds/316920/) licensed under CC0 1.0 (public domain). The original audio was trimmed and converted from WAV to OGG format.
|
||||
|
||||
ferret_happy.ogg: modified from "Ferret" by freesound user "J.Zazvurek" (https://freesound.org/people/J.Zazvurek/sounds/155115/) licensed under CC BY 3.0. The original audio was trimmed and converted from WAV to OGG format.
|
||||
|
||||
mouse_squeak.ogg: modified from "Cartoon - Bat / Mouse Squeak" by freesound user "Breviceps" (https://freesound.org/people/Breviceps/sounds/445958/) licensed under CC0 1.0 (public domain). The original audio was converted from WAV to OGG format.
|
||||
|
||||
sloth_squeak.ogg: modified from "squeakfinal.wav" by freesound user "Higgs01" (https://freesound.org/people/Higgs01/sounds/428114/) licensed under CC0 1.0 (Public domain dedication). The original audio was converted from WAV to OFF format AND converted from stereo to mono.
|
||||
|
||||
parrot_raught.ogg: modified and used from "Parrot 1 raught 2.wav" by freesound user "Coral_Island_Studios" (https://freesound.org/people/Coral_Island_Studios/sounds/432588/) licensed under CC BY 3.0. The original audio was converted from WAV to OGG format.
|
||||
|
||||
fox_squeak.ogg: modified from "Video Game Squeak" by freesound user "Breviceps" (https://freesound.org/people/Breviceps/sounds/468442/) licensed under CC0 1.0. The original audio was converted from WAV to OGG format
|
||||
|
||||
frog_ribbit.ogg: original audio created by freesound user "egomassive" (https://freesound.org/people/egomassive/sounds/536759/). licensed under licensed under CC0 1.0
|
||||
|
||||
goose_honk.ogg: modified from " Bird Honk - 1" by freesound user "SpaceJoe" (https://freesound.org/people/SpaceJoe/sounds/510940/) licensed under CC0 1.0. The originial audio was converted from WAV to OGG format.
|
||||
|
||||
snake_hiss.ogg: modified from "hissing snake sound effect" by freesound user "Garuda1982" (https://freesound.org/people/Garuda1982/sounds/541656/) licensed under CC BY 4.0. The original audio was convertred from WAV to OGG format.
|
||||
|
||||
pig_oink.ogg: modified from "Pig Oink.wav" by freesound user "qubodup" (https://freesound.org/people/qubodup/sounds/442906/) licensed under CC0 1.0 (public domain). The original audio was converted from WAV to OGG format.
|
||||
|
||||
cerberus.ogg: modified from "DogBARKING.wav" by freesound user "ahill86" (https://freesound.org/people/ahill86/sounds/207124/) licensed under CCO 1.0 (Public domain dedication). The original audio was converted from WAV to OGG format, echoed, amplified and pitched down.
|
||||
|
||||
The following sounds are taken from TGstation github (licensed under CC by 3.0):
|
||||
|
||||
space_dragon_roar.ogg: taken at https://github.com/tgstation/tgstation/commit/d4f678a1772007ff8d7eddd21cf7218c8e07bfc0
|
||||
|
||||
The following sounds were used from youtube:
|
||||
|
||||
penguin_squawk.ogg: audio is recorded/created by youtube user "ProSounds" (https://youtu.be/Anr35RbBL3Y) licensed under CC BY 3.0. The original audio was trimmed and converted from MP3 to OGG format.
|
||||
|
||||
raccoon_squeak.ogg: audio is recorded by youtube user "jnargus" (https://youtu.be/BGjFP1CP7E0) licensed under CC by 3.0
|
||||
|
||||
goat_bah.ogg: audio is created by youtube user "Winry Marini" (https://youtu.be/QIhwzsk5bww) licensed under CC BY 3.0
|
||||
|
||||
lizard_happy.ogg: audio created by youtube user "Nagaty Studio" (https://youtu.be/I7CX0AS8RNI) licensed under CC by 3.0.
|
||||
|
||||
bear.ogg: audio is recorded by "Nagaty Studio" (https://www.youtube.com/watch?v=pz6eZbESlU8) licensed under CC by 3.0. The original audio was reverbed.
|
||||
|
||||
kangaroo_grunt.ogg: audio is recorded/created by Pfranzen "FreeSound.org" (https://freesound.org/people/pfranzen/sounds/322744/) licensed under CC BY 4.0. The original audio was trimmed and renamed.
|
||||
@@ -32,3 +32,13 @@
|
||||
license: "CC-BY-SA-3.0"
|
||||
copyright: "Taken and modified from tgstation (clownstep 1 and 2) by brainfood1183 (github)"
|
||||
source: "https://github.com/tgstation/tgstation/tree/f8ee37afc00bce1ad421615eaa0e4cbddd5eea90/sound/effects"
|
||||
|
||||
- files:
|
||||
- bells1.ogg
|
||||
- bells2.ogg
|
||||
- bells3.ogg
|
||||
- bells4.ogg
|
||||
- bells5.ogg
|
||||
license: "CC-BY-SA-4.0"
|
||||
copyright: "Taken and modified from el1n freesound.org"
|
||||
source: "https://freesound.org/people/el1n/sounds/442746/"
|
||||
|
||||
BIN
Resources/Audio/Effects/Footsteps/bells1.ogg
Normal file
BIN
Resources/Audio/Effects/Footsteps/bells1.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Effects/Footsteps/bells2.ogg
Normal file
BIN
Resources/Audio/Effects/Footsteps/bells2.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Effects/Footsteps/bells3.ogg
Normal file
BIN
Resources/Audio/Effects/Footsteps/bells3.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Effects/Footsteps/bells4.ogg
Normal file
BIN
Resources/Audio/Effects/Footsteps/bells4.ogg
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user