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:
Morb0
2023-11-08 03:00:51 +03:00
382 changed files with 131797 additions and 132634 deletions

View File

@@ -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:

View File

@@ -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 }}

View File

@@ -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);
}
}

View File

@@ -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);

View 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;
}
}

View File

@@ -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
{
}

View File

@@ -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")
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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)

View File

@@ -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;

View File

@@ -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)
{

View 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();
}
}

View 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;
}
}

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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");
}

View 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);
}

View File

@@ -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);
}
}

View 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);
}
}
}
}

View File

@@ -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");
}

View File

@@ -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;

View 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;
}

View 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;
}

View 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);

View File

@@ -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();
}

View File

@@ -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)
{

View 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);
}
}

View File

@@ -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)

View File

@@ -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);
}
}

View File

@@ -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)));

View File

@@ -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));

View 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);
}
}
}

View 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;
}
}

View 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"
};
}
}

View File

@@ -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);
}
}

View File

@@ -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.

View File

@@ -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);

View File

@@ -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;

View File

@@ -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");
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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))
{

View File

@@ -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.

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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 });
}
}

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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)

View File

@@ -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".

View File

@@ -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)));

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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)}",
});
}

View File

@@ -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)

View File

@@ -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;

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -0,0 +1,9 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Clothing;
[RegisterComponent]
[NetworkedComponent]
public sealed partial class SkatesComponent : Component
{
}

View File

@@ -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());

View File

@@ -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)
{

View File

@@ -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);
}

View File

@@ -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
}

View 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;
}
}

View File

@@ -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]

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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]

View 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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View 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";
}

View File

@@ -0,0 +1,9 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Overlays;
/// <summary>
///
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class ShowSyndicateIconsComponent : Component {}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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)

View File

@@ -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.

View File

@@ -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>

View 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/"

View File

@@ -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.

View File

@@ -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/"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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