Merge remote-tracking branch 'space-wizards/master'

# Conflicts:
#	Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs
#	Content.Server/Explosion/EntitySystems/ExplosionSystem.cs
#	Resources/Maps/_Sunrise/Station/marathon.yml
#	Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml
#	Resources/Prototypes/Entities/Mobs/Player/silicon.yml
#	Resources/Prototypes/GameRules/events.yml
#	Resources/Textures/Mobs/Silicon/chassis.rsi/meta.json
This commit is contained in:
Vigers Ray 2025-08-25 03:48:48 +03:00
commit 5e89b697f6
86 changed files with 2307 additions and 949 deletions

View file

@ -15,6 +15,7 @@ namespace Content.Client.Administration.Managers
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IClientNetManager _netMgr = default!;
[Dependency] private readonly IClientConGroupController _conGroup = default!;
[Dependency] private readonly IClientConsoleHost _host = default!;
[Dependency] private readonly IResourceManager _res = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IUserInterfaceManager _userInterface = default!;
@ -86,12 +87,12 @@ namespace Content.Client.Administration.Managers
private void UpdateMessageRx(MsgUpdateAdminStatus message)
{
_availableCommands.Clear();
var host = IoCManager.Resolve<IClientConsoleHost>();
// Anything marked as Any we'll just add even if the server doesn't know about it.
foreach (var (command, instance) in host.AvailableCommands)
foreach (var (command, instance) in _host.AvailableCommands)
{
if (Attribute.GetCustomAttribute(instance.GetType(), typeof(AnyCommandAttribute)) == null) continue;
if (Attribute.GetCustomAttribute(instance.GetType(), typeof(AnyCommandAttribute)) == null)
continue;
_availableCommands.Add(command);
}

View file

@ -3,17 +3,13 @@ using Content.Shared.CCVar;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Audio;
using Robust.Shared.Log;
using Robust.Shared.Configuration;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using System.Linq;
using System.Numerics;
using Robust.Client.GameObjects;
using Robust.Shared.Audio.Effects;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
@ -31,6 +27,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
@ -65,18 +62,19 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
get => _overlayEnabled;
set
{
if (_overlayEnabled == value) return;
if (_overlayEnabled == value)
return;
_overlayEnabled = value;
var overlayManager = IoCManager.Resolve<IOverlayManager>();
if (_overlayEnabled)
{
_overlay = new AmbientSoundOverlay(EntityManager, this, EntityManager.System<EntityLookupSystem>());
overlayManager.AddOverlay(_overlay);
_overlayManager.AddOverlay(_overlay);
}
else
{
overlayManager.RemoveOverlay(_overlay!);
_overlayManager.RemoveOverlay(_overlay!);
_overlay = null;
}
}

View file

@ -3,11 +3,8 @@ using Content.Client.Gameplay;
using Content.Shared.Audio;
using Content.Shared.CCVar;
using Content.Shared.GameTicking;
using Content.Shared.Random;
using Content.Shared.Random.Rules;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Client.State;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Components;
@ -25,6 +22,7 @@ public sealed partial class ContentAudioSystem
{
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IRobustRandom _random = default!;
@ -61,7 +59,7 @@ public sealed partial class ContentAudioSystem
private void InitializeAmbientMusic()
{
Subs.CVar(_configManager, CCVars.AmbientMusicVolume, AmbienceCVarChanged, true);
_sawmill = IoCManager.Resolve<ILogManager>().GetSawmill("audio.ambience");
_sawmill = _logManager.GetSawmill("audio.ambience");
// Reset audio
_nextAudio = TimeSpan.MaxValue;

View file

@ -0,0 +1,30 @@
using Content.Shared.Changeling.Components;
using Content.Shared.Changeling.Systems;
using Robust.Client.GameObjects;
namespace Content.Client.Changeling.Systems;
public sealed class ChangelingIdentitySystem : SharedChangelingIdentitySystem
{
[Dependency] private readonly UserInterfaceSystem _ui = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ChangelingIdentityComponent, AfterAutoHandleStateEvent>(OnAfterAutoHandleState);
}
private void OnAfterAutoHandleState(Entity<ChangelingIdentityComponent> ent, ref AfterAutoHandleStateEvent args)
{
UpdateUi(ent);
}
public void UpdateUi(EntityUid uid)
{
if (_ui.TryGetOpenUi(uid, ChangelingTransformUiKey.Key, out var bui))
{
bui.Update();
}
}
}

View file

@ -2,7 +2,7 @@
using JetBrains.Annotations;
using Robust.Client.UserInterface;
namespace Content.Client.Changeling.Transform;
namespace Content.Client.Changeling.UI;
[UsedImplicitly]
public sealed partial class ChangelingTransformBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
@ -16,16 +16,16 @@ public sealed partial class ChangelingTransformBoundUserInterface(EntityUid owne
_window = this.CreateWindow<ChangelingTransformMenu>();
_window.OnIdentitySelect += SendIdentitySelect;
_window.Update(Owner);
}
protected override void UpdateState(BoundUserInterfaceState state)
public override void Update()
{
base.UpdateState(state);
if (state is not ChangelingTransformBoundUserInterfaceState current)
if (_window == null)
return;
_window?.UpdateState(current);
_window.Update(Owner);
}
public void SendIdentitySelect(NetEntity identityId)

View file

@ -1,11 +1,11 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Changeling.Systems;
using Content.Shared.Changeling.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Changeling.Transform;
namespace Content.Client.Changeling.UI;
[GenerateTypedNameReferences]
public sealed partial class ChangelingTransformMenu : RadialMenu
@ -19,13 +19,15 @@ public sealed partial class ChangelingTransformMenu : RadialMenu
IoCManager.InjectDependencies(this);
}
public void UpdateState(ChangelingTransformBoundUserInterfaceState state)
public void Update(EntityUid uid)
{
Main.DisposeAllChildren();
foreach (var identity in state.Identites)
{
var identityUid = _entity.GetEntity(identity);
if (!_entity.TryGetComponent<ChangelingIdentityComponent>(uid, out var identityComp))
return;
foreach (var identityUid in identityComp.ConsumedIdentities)
{
if (!_entity.TryGetComponent<MetaDataComponent>(identityUid, out var metadata))
continue;
@ -48,7 +50,7 @@ public sealed partial class ChangelingTransformMenu : RadialMenu
entView.SetEntity(identityUid);
button.OnButtonUp += _ =>
{
OnIdentitySelect?.Invoke(identity);
OnIdentitySelect?.Invoke(_entity.GetNetEntity(identityUid));
Close();
};
button.AddChild(entView);

View file

@ -30,6 +30,10 @@ namespace Content.Client.Lathe.UI
{
SendMessage(new LatheQueueRecipeMessage(recipe, amount));
};
_menu.QueueDeleteAction += index => SendMessage(new LatheDeleteRequestMessage(index));
_menu.QueueMoveUpAction += index => SendMessage(new LatheMoveRequestMessage(index, -1));
_menu.QueueMoveDownAction += index => SendMessage(new LatheMoveRequestMessage(index, 1));
_menu.DeleteFabricatingAction += () => SendMessage(new LatheAbortFabricationMessage());
}
protected override void UpdateState(BoundUserInterfaceState state)

View file

@ -1,6 +1,7 @@
<DefaultWindow
xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:ui="clr-namespace:Content.Client.Materials.UI"
Title="{Loc 'lathe-menu-title'}"
MinSize="550 450"
@ -110,6 +111,18 @@
HorizontalAlignment="Left"
Margin="130 0 0 0">
</Label>
<Button
Name="DeleteFabricating"
Margin="0"
Text="✖"
SetSize="38 32"
HorizontalAlignment="Right"
ToolTip="{Loc 'lathe-menu-delete-fabricating-tooltip'}">
<Button.StyleClasses>
<system:String>Caution</system:String>
<system:String>OpenLeft</system:String>
</Button.StyleClasses>
</Button>
</PanelContainer>
</BoxContainer>
<ScrollContainer VerticalExpand="True" HScrollEnabled="False">

View file

@ -26,6 +26,10 @@ public sealed partial class LatheMenu : DefaultWindow
public event Action<BaseButton.ButtonEventArgs>? OnServerListButtonPressed;
public event Action<string, int>? RecipeQueueAction;
public event Action<int>? QueueDeleteAction;
public event Action<int>? QueueMoveUpAction;
public event Action<int>? QueueMoveDownAction;
public event Action? DeleteFabricatingAction;
public List<ProtoId<LatheRecipePrototype>> Recipes = new();
@ -50,12 +54,21 @@ public sealed partial class LatheMenu : DefaultWindow
};
AmountLineEdit.OnTextChanged += _ =>
{
if (int.TryParse(AmountLineEdit.Text, out var amount))
{
if (amount > LatheSystem.MaxItemsPerRequest)
AmountLineEdit.Text = LatheSystem.MaxItemsPerRequest.ToString();
else if (amount < 0)
AmountLineEdit.Text = "0";
}
PopulateRecipes();
};
FilterOption.OnItemSelected += OnItemSelected;
ServerListButton.OnPressed += a => OnServerListButtonPressed?.Invoke(a);
DeleteFabricating.OnPressed += _ => DeleteFabricatingAction?.Invoke();
}
public void SetEntity(EntityUid uid)
@ -223,22 +236,27 @@ public sealed partial class LatheMenu : DefaultWindow
/// Populates the build queue list with all queued items
/// </summary>
/// <param name="queue"></param>
public void PopulateQueueList(IReadOnlyCollection<ProtoId<LatheRecipePrototype>> queue)
public void PopulateQueueList(IReadOnlyCollection<LatheRecipeBatch> queue)
{
QueueList.DisposeAllChildren();
var idx = 1;
foreach (var recipeProto in queue)
foreach (var batch in queue)
{
var recipe = _prototypeManager.Index(recipeProto);
var queuedRecipeBox = new BoxContainer();
queuedRecipeBox.Orientation = BoxContainer.LayoutOrientation.Horizontal;
var recipe = _prototypeManager.Index(batch.Recipe);
queuedRecipeBox.AddChild(GetRecipeDisplayControl(recipe));
var itemName = _lathe.GetRecipeName(batch.Recipe);
string displayText;
if (batch.ItemsRequested > 1)
displayText = Loc.GetString("lathe-menu-item-batch", ("index", idx), ("name", itemName), ("printed", batch.ItemsPrinted), ("total", batch.ItemsRequested));
else
displayText = Loc.GetString("lathe-menu-item-single", ("index", idx), ("name", itemName));
var queuedRecipeBox = new QueuedRecipeControl(displayText, idx - 1, GetRecipeDisplayControl(recipe));
queuedRecipeBox.OnDeletePressed += s => QueueDeleteAction?.Invoke(s);
queuedRecipeBox.OnMoveUpPressed += s => QueueMoveUpAction?.Invoke(s);
queuedRecipeBox.OnMoveDownPressed += s => QueueMoveDownAction?.Invoke(s);
var queuedRecipeLabel = new Label();
queuedRecipeLabel.Text = $"{idx}. {_lathe.GetRecipeName(recipe)}";
queuedRecipeBox.AddChild(queuedRecipeLabel);
QueueList.AddChild(queuedRecipeBox);
idx++;
}

View file

@ -0,0 +1,35 @@
<Control xmlns="https://spacestation14.io"
xmlns:system="clr-namespace:System;assembly=System.Runtime">
<BoxContainer Orientation="Horizontal">
<BoxContainer
Name="RecipeDisplayContainer"
Margin="0 0 4 0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
MinSize="32 32"
/>
<Label Name="RecipeName" HorizontalExpand="True" />
<Button
Name="MoveUp"
Margin="0"
Text="⏶"
StyleClasses="OpenRight"
ToolTip="{Loc 'lathe-menu-move-up-tooltip'}"/>
<Button
Name="MoveDown"
Margin="0"
Text="⏷"
StyleClasses="OpenBoth"
ToolTip="{Loc 'lathe-menu-move-down-tooltip'}"/>
<Button
Name="Delete"
Margin="0"
Text="✖"
ToolTip="{Loc 'lathe-menu-delete-item-tooltip'}">
<Button.StyleClasses>
<system:String>Caution</system:String>
<system:String>OpenLeft</system:String>
</Button.StyleClasses>
</Button>
</BoxContainer>
</Control>

View file

@ -0,0 +1,36 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Lathe.UI;
[GenerateTypedNameReferences]
public sealed partial class QueuedRecipeControl : Control
{
public Action<int>? OnDeletePressed;
public Action<int>? OnMoveUpPressed;
public Action<int>? OnMoveDownPressed;
public QueuedRecipeControl(string displayText, int index, Control displayControl)
{
RobustXamlLoader.Load(this);
RecipeName.Text = displayText;
RecipeDisplayContainer.AddChild(displayControl);
MoveUp.OnPressed += (_) =>
{
OnMoveUpPressed?.Invoke(index);
};
MoveDown.OnPressed += (_) =>
{
OnMoveDownPressed?.Invoke(index);
};
Delete.OnPressed += (_) =>
{
OnDeletePressed?.Invoke(index);
};
}
}

View file

@ -20,6 +20,7 @@ namespace Content.Client.NPC
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IResourceCache _cache = default!;
[Dependency] private readonly NPCSteeringSystem _steering = default!;
[Dependency] private readonly MapSystem _mapSystem = default!;
@ -30,17 +31,15 @@ namespace Content.Client.NPC
get => _modes;
set
{
var overlayManager = IoCManager.Resolve<IOverlayManager>();
if (value == PathfindingDebugMode.None)
{
Breadcrumbs.Clear();
Polys.Clear();
overlayManager.RemoveOverlay<PathfindingOverlay>();
_overlayManager.RemoveOverlay<PathfindingOverlay>();
}
else if (!overlayManager.HasOverlay<PathfindingOverlay>())
else if (!_overlayManager.HasOverlay<PathfindingOverlay>())
{
overlayManager.AddOverlay(new PathfindingOverlay(EntityManager, _eyeManager, _inputManager, _mapManager, _cache, this, _mapSystem, _transformSystem));
_overlayManager.AddOverlay(new PathfindingOverlay(EntityManager, _eyeManager, _inputManager, _mapManager, _cache, this, _mapSystem, _transformSystem));
}
if ((value & PathfindingDebugMode.Steering) != 0x0)

View file

@ -11,6 +11,8 @@ namespace Content.Client.Radiation.Overlays;
public sealed class RadiationDebugOverlay : Overlay
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IResourceCache _cache = default!;
private readonly SharedMapSystem _mapSystem;
private readonly RadiationSystem _radiation;
@ -24,8 +26,7 @@ public sealed class RadiationDebugOverlay : Overlay
_radiation = _entityManager.System<RadiationSystem>();
_mapSystem = _entityManager.System<SharedMapSystem>();
var cache = IoCManager.Resolve<IResourceCache>();
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 8);
_font = new VectorFont(_cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 8);
}
protected override void Draw(in OverlayDrawArgs args)

View file

@ -16,20 +16,20 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
get => _enableShuttlePosition;
set
{
if (_enableShuttlePosition == value) return;
if (_enableShuttlePosition == value)
return;
_enableShuttlePosition = value;
var overlayManager = IoCManager.Resolve<IOverlayManager>();
if (_enableShuttlePosition)
{
_overlay = new EmergencyShuttleOverlay(EntityManager.TransformQuery, XformSystem);
overlayManager.AddOverlay(_overlay);
_overlays.AddOverlay(_overlay);
RaiseNetworkEvent(new EmergencyShuttleRequestPositionMessage());
}
else
{
overlayManager.RemoveOverlay(_overlay!);
_overlays.RemoveOverlay(_overlay!);
_overlay = null;
}
}

View file

@ -42,6 +42,7 @@ public sealed partial class GunSystem : SharedGunSystem
[Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IStateManager _state = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
@ -75,11 +76,10 @@ public sealed partial class GunSystem : SharedGunSystem
return;
_spreadOverlay = value;
var overlayManager = IoCManager.Resolve<IOverlayManager>();
if (_spreadOverlay)
{
overlayManager.AddOverlay(new GunSpreadOverlay(
_overlayManager.AddOverlay(new GunSpreadOverlay(
EntityManager,
_eyeManager,
Timing,
@ -90,7 +90,7 @@ public sealed partial class GunSystem : SharedGunSystem
}
else
{
overlayManager.RemoveOverlay<GunSpreadOverlay>();
_overlayManager.RemoveOverlay<GunSpreadOverlay>();
}
}
}

View file

@ -0,0 +1,20 @@
using Content.IntegrationTests.Tests.Interaction;
using Content.Shared.Engineering.Systems;
namespace Content.IntegrationTests.Tests.Engineering;
[TestFixture]
[TestOf(typeof(InflatableSafeDisassemblySystem))]
public sealed class InflatablesDeflateTest : InteractionTest
{
[Test]
public async Task Test()
{
await SpawnTarget(InflatableWall);
await InteractUsing(Needle);
AssertDeleted();
await AssertEntityLookup(new EntitySpecifier(InflatableWallStack.Id, 1));
}
}

View file

@ -1,3 +1,6 @@
using Content.Shared.Stacks;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests.Interaction;
// This partial class contains various constant prototype IDs common to interaction tests.
@ -32,4 +35,9 @@ public abstract partial class InteractionTest
protected const string Manipulator1 = "MicroManipulatorStockPart";
protected const string Battery1 = "PowerCellSmall";
protected const string Battery4 = "PowerCellHyper";
// Inflatables & Needle used to pop them
protected static readonly EntProtoId InflatableWall = "InflatableWall";
protected static readonly EntProtoId Needle = "WeaponMeleeNeedle";
protected static readonly ProtoId<StackPrototype> InflatableWallStack = "InflatableWall";
}

View file

@ -6,7 +6,6 @@ using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Clothing.Systems;
using Content.Server.Electrocution;
using Content.Server.Explosion.EntitySystems;
using Content.Server.GhostKick;
@ -22,6 +21,7 @@ using Content.Server.Tabletop.Components;
using Content.Server.Terminator.Systems;
using Content.Shared.Administration;
using Content.Shared.Administration.Components;
using Content.Shared.Atmos.Components;
using Content.Shared.Body.Components;
using Content.Shared.Body.Part;
using Content.Shared.Clumsy;
@ -47,7 +47,6 @@ using Content.Shared.Nutrition.Components;
using Content.Shared.Popups;
using Content.Shared.Slippery;
using Content.Shared.Storage.Components;
using Content.Shared.Stunnable;
using Content.Shared.Tabletop.Components;
using Content.Shared.Tools.Systems;
using Content.Shared.Verbs;
@ -57,7 +56,6 @@ using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Timer = Robust.Shared.Timing.Timer;

View file

@ -1,7 +1,7 @@
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Anomaly.Components;
using Content.Shared.Anomaly.Effects.Components;
using Content.Shared.Atmos.Components;
using Robust.Shared.Map;
namespace Content.Server.Anomaly.Effects;

View file

@ -0,0 +1,5 @@
using Content.Shared.Changeling.Systems;
namespace Content.Server.Changeling.Systems;
public sealed class ChangelingIdentitySystem : SharedChangelingIdentitySystem;

View file

@ -1,6 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
@ -22,6 +21,7 @@ using Content.Server.Temperature.Systems;
using Content.Server.Traits.Assorted;
using Content.Server.Zombies;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Body.Components;
using Content.Shared.Coordinates.Helpers;
using Content.Shared.EntityEffects.EffectConditions;

View file

@ -5,6 +5,7 @@ using Content.Server.Atmos.Components;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NPC.Pathfinding;
using Content.Shared._RMC14.Explosion;
using Content.Shared.Atmos.Components;
using Content.Shared.Camera;
using Content.Shared.CCVar;
using Content.Shared.Damage;

View file

@ -75,6 +75,9 @@ namespace Content.Server.Lathe
SubscribeLocalEvent<LatheComponent, LatheQueueRecipeMessage>(OnLatheQueueRecipeMessage);
SubscribeLocalEvent<LatheComponent, LatheSyncRequestMessage>(OnLatheSyncRequestMessage);
SubscribeLocalEvent<LatheComponent, LatheDeleteRequestMessage>(OnLatheDeleteRequestMessage);
SubscribeLocalEvent<LatheComponent, LatheMoveRequestMessage>(OnLatheMoveRequestMessage);
SubscribeLocalEvent<LatheComponent, LatheAbortFabricationMessage>(OnLatheAbortFabricationMessage);
SubscribeLocalEvent<LatheComponent, BeforeActivatableUIOpenEvent>((u, c, _) => UpdateUserInterfaceState(u, c));
SubscribeLocalEvent<LatheComponent, MaterialAmountChangedEvent>(OnMaterialAmountChanged);
@ -168,23 +171,32 @@ namespace Content.Server.Lathe
return ev.Recipes.ToList();
}
public bool TryAddToQueue(EntityUid uid, LatheRecipePrototype recipe, LatheComponent? component = null)
public bool TryAddToQueue(EntityUid uid, LatheRecipePrototype recipe, int quantity, LatheComponent? component = null)
{
if (!Resolve(uid, ref component))
return false;
if (!CanProduce(uid, recipe, 1, component))
if (quantity <= 0)
return false;
quantity = int.Min(quantity, MaxItemsPerRequest);
if (!CanProduce(uid, recipe, quantity, component))
return false;
foreach (var (mat, amount) in recipe.Materials)
{
var adjustedAmount = recipe.ApplyMaterialDiscount
? (int) (-amount * component.MaterialUseMultiplier)
? (int)(-amount * component.MaterialUseMultiplier)
: -amount;
adjustedAmount *= quantity;
_materialStorage.TryChangeMaterialAmount(uid, mat, adjustedAmount);
}
component.Queue.Enqueue(recipe);
if (component.Queue.Last is { } node && node.ValueRef.Recipe == recipe.ID)
node.ValueRef.ItemsRequested += quantity;
else
component.Queue.AddLast(new LatheRecipeBatch(recipe.ID, 0, quantity));
return true;
}
@ -196,8 +208,11 @@ namespace Content.Server.Lathe
if (component.CurrentRecipe != null || component.Queue.Count <= 0 || !this.IsPowered(uid, EntityManager))
return false;
var recipeProto = component.Queue.Dequeue();
var recipe = _proto.Index(recipeProto);
var batch = component.Queue.First();
batch.ItemsPrinted++;
if (batch.ItemsPrinted >= batch.ItemsRequested || batch.ItemsPrinted < 0) // Rollover sanity check
component.Queue.RemoveFirst();
var recipe = _proto.Index(batch.Recipe);
var time = _reagentSpeed.ApplySpeed(uid, recipe.CompleteTime) * component.TimeMultiplier;
@ -273,8 +288,8 @@ namespace Content.Server.Lathe
return;
var producing = component.CurrentRecipe;
if (producing == null && component.Queue.TryPeek(out var next))
producing = next;
if (producing == null && component.Queue.First is { } node)
producing = node.Value.Recipe;
var state = new LatheUpdateState(GetAvailableRecipes(uid, component), component.Queue.ToArray(), producing);
_uiSys.SetUiState(uid, LatheUiKey.Key, state);
@ -351,12 +366,10 @@ namespace Content.Server.Lathe
{
if (!args.Powered)
{
RemComp<LatheProducingComponent>(uid);
UpdateRunningAppearance(uid, false);
AbortProduction(uid);
}
else if (component.CurrentRecipe != null)
else
{
EnsureComp<LatheProducingComponent>(uid);
TryStartProducing(uid, component);
}
}
@ -420,25 +433,46 @@ namespace Content.Server.Lathe
return GetAvailableRecipes(uid, component).Contains(recipe.ID);
}
public void AbortProduction(EntityUid uid, LatheComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
if (component.CurrentRecipe != null)
{
if (component.Queue.Count > 0)
{
// Batch abandoned while printing last item, need to create a one-item batch
var batch = component.Queue.First();
if (batch.Recipe != component.CurrentRecipe)
{
var newBatch = new LatheRecipeBatch(component.CurrentRecipe.Value, 0, 1);
component.Queue.AddFirst(newBatch);
}
else if (batch.ItemsPrinted > 0)
{
batch.ItemsPrinted--;
}
}
component.CurrentRecipe = null;
}
RemCompDeferred<LatheProducingComponent>(uid);
UpdateUserInterfaceState(uid, component);
UpdateRunningAppearance(uid, false);
}
#region UI Messages
private void OnLatheQueueRecipeMessage(EntityUid uid, LatheComponent component, LatheQueueRecipeMessage args)
{
if (_proto.TryIndex(args.ID, out LatheRecipePrototype? recipe))
{
var count = 0;
for (var i = 0; i < args.Quantity; i++)
{
if (TryAddToQueue(uid, recipe, component))
count++;
else
break;
}
if (count > 0)
if (TryAddToQueue(uid, recipe, args.Quantity, component))
{
_adminLogger.Add(LogType.Action,
LogImpact.Low,
$"{ToPrettyString(args.Actor):player} queued {count} {GetRecipeName(recipe)} at {ToPrettyString(uid):lathe}");
$"{ToPrettyString(args.Actor):player} queued {args.Quantity} {GetRecipeName(recipe)} at {ToPrettyString(uid):lathe}");
}
}
TryStartProducing(uid, component);
@ -449,6 +483,92 @@ namespace Content.Server.Lathe
{
UpdateUserInterfaceState(uid, component);
}
/// <summary>
/// Removes a batch from the batch queue by index.
/// If the index given does not exist or is outside of the bounds of the lathe's batch queue, nothing happens.
/// </summary>
/// <param name="uid">The lathe whose queue is being altered.</param>
/// <param name="component"></param>
/// <param name="args"></param>
public void OnLatheDeleteRequestMessage(EntityUid uid, LatheComponent component, ref LatheDeleteRequestMessage args)
{
if (args.Index < 0 || args.Index >= component.Queue.Count)
return;
var node = component.Queue.First;
for (int i = 0; i < args.Index; i++)
node = node?.Next;
if (node == null) // Shouldn't happen with checks above.
return;
var batch = node.Value;
_adminLogger.Add(LogType.Action,
LogImpact.Low,
$"{ToPrettyString(args.Actor):player} deleted a lathe job for ({batch.ItemsPrinted}/{batch.ItemsRequested}) {GetRecipeName(batch.Recipe)} at {ToPrettyString(uid):lathe}");
component.Queue.Remove(node);
UpdateUserInterfaceState(uid, component);
}
public void OnLatheMoveRequestMessage(EntityUid uid, LatheComponent component, ref LatheMoveRequestMessage args)
{
if (args.Change == 0 || args.Index < 0 || args.Index >= component.Queue.Count)
return;
// New index must be within the bounds of the batch.
var newIndex = args.Index + args.Change;
if (newIndex < 0 || newIndex >= component.Queue.Count)
return;
var node = component.Queue.First;
for (int i = 0; i < args.Index; i++)
node = node?.Next;
if (node == null) // Something went wrong.
return;
if (args.Change > 0)
{
var newRelativeNode = node.Next;
for (int i = 1; i < args.Change; i++) // 1-indexed: starting from Next
newRelativeNode = newRelativeNode?.Next;
if (newRelativeNode == null) // Something went wrong.
return;
component.Queue.Remove(node);
component.Queue.AddAfter(newRelativeNode, node);
}
else
{
var newRelativeNode = node.Previous;
for (int i = 1; i < -args.Change; i++) // 1-indexed: starting from Previous
newRelativeNode = newRelativeNode?.Previous;
if (newRelativeNode == null) // Something went wrong.
return;
component.Queue.Remove(node);
component.Queue.AddBefore(newRelativeNode, node);
}
UpdateUserInterfaceState(uid, component);
}
public void OnLatheAbortFabricationMessage(EntityUid uid, LatheComponent component, ref LatheAbortFabricationMessage args)
{
if (component.CurrentRecipe == null)
return;
_adminLogger.Add(LogType.Action,
LogImpact.Low,
$"{ToPrettyString(args.Actor):player} aborted printing {GetRecipeName(component.CurrentRecipe.Value)} at {ToPrettyString(uid):lathe}");
component.CurrentRecipe = null;
FinishProducing(uid, component);
}
#endregion
}
}

View file

@ -1,4 +1,3 @@
using Content.Server.Atmos.Components;
using Content.Server.Fluids.EntitySystems;
using Content.Server.Hands.Systems;
using Content.Server.NPC.Queries;
@ -6,13 +5,11 @@ using Content.Server.NPC.Queries.Considerations;
using Content.Server.NPC.Queries.Curves;
using Content.Server.NPC.Queries.Queries;
using Content.Server.Nutrition.Components;
using Content.Server.Nutrition.EntitySystems;
using Content.Server.Temperature.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Damage;
using Content.Shared.Examine;
using Content.Shared.Fluids.Components;
using Content.Shared.Hands.Components;
using Content.Shared.Inventory;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Systems;
@ -31,6 +28,7 @@ using Microsoft.Extensions.ObjectPool;
using Robust.Server.Containers;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using Content.Shared.Atmos.Components;
using System.Linq;
namespace Content.Server.NPC.Systems;

View file

@ -272,7 +272,7 @@ public sealed partial class PolymorphSystem : EntitySystem
if (configuration.TransferHumanoidAppearance)
{
_humanoid.CloneAppearance(uid, child);
_humanoid.CloneAppearance(child, uid);
}
if (_mindSystem.TryGetMind(uid, out var mindId, out var mind))

View file

@ -0,0 +1,44 @@
using Content.Server.Administration.Logs;
using Content.Server.GameTicking;
using Content.Shared.Database;
using Content.Shared.Trigger;
using Content.Shared.Trigger.Components.Effects;
namespace Content.Server.Trigger.Systems;
/// <summary>
/// Trigger system for game rules.
/// </summary>
public sealed class GameRuleTriggerSystem : EntitySystem
{
[Dependency] private readonly GameTicker _ticker = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AddGameRuleOnTriggerComponent, TriggerEvent>(AddRuleOnTrigger);
}
private void AddRuleOnTrigger(Entity<AddGameRuleOnTriggerComponent> ent, ref TriggerEvent args)
{
if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
return;
var rule = _ticker.AddGameRule(ent.Comp.GameRule);
_adminLogger.Add(LogType.EventStarted,
$"{ToPrettyString(args.User):entity} added a game rule [{ent.Comp.GameRule}]" +
$" via a trigger on {ToPrettyString(ent.Owner):entity}.");
if (ent.Comp.StartRule && _ticker.RunLevel == GameRunLevel.InRound)
{
_ticker.StartGameRule(rule);
_adminLogger.Add(LogType.EventStarted, $"{ToPrettyString(args.User):entity} started game rule [{ent.Comp.GameRule}].");
}
args.Handled = true;
}
}

View file

@ -5,6 +5,7 @@ using Content.Shared.Popups;
using Content.Shared.Tag;
using Content.Shared.Weapons.Melee.Events;
using Content.Shared.Throwing;
using Content.Shared.Weapons.Melee.Balloon;
using Robust.Shared.Audio.Systems;
namespace Content.Server.Weapons.Melee.Balloon;

View file

@ -1,6 +1,6 @@
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
using Content.Shared.Atmos.Components;
using Content.Shared.Xenoarchaeology.Artifact;
using Content.Shared.Xenoarchaeology.Artifact.XAE;
using Robust.Shared.Random;

View file

@ -1,11 +1,12 @@
using Content.Shared.Alert;
using Content.Shared.Damage;
using Robust.Shared.GameStates;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Prototypes;
namespace Content.Server.Atmos.Components
namespace Content.Shared.Atmos.Components
{
[RegisterComponent]
[RegisterComponent, NetworkedComponent]
public sealed partial class FlammableComponent : Component
{
[DataField]

View file

@ -8,7 +8,7 @@ namespace Content.Shared.Changeling.Components;
/// The storage component for Changelings, it handles the link between a changeling and its consumed identities
/// that exist on a paused map.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(raiseAfterAutoHandleState: true)]
public sealed partial class ChangelingIdentityComponent : Component
{
/// <summary>

View file

@ -32,7 +32,7 @@ public sealed class ChangelingDevourSystem : EntitySystem
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly ChangelingIdentitySystem _changelingIdentitySystem = default!;
[Dependency] private readonly SharedChangelingIdentitySystem _changelingIdentitySystem = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;

View file

@ -14,20 +14,8 @@ public sealed class ChangelingTransformIdentitySelectMessage(NetEntity targetIde
public readonly NetEntity TargetIdentity = targetIdentity;
}
// TODO: Replace with component states.
// We are already networking the ChangelingIdentityComponent, which contains all this information,
// so we can just read it from them from the component and update the UI in an AfterAuotHandleState subscription.
[Serializable, NetSerializable]
public sealed class ChangelingTransformBoundUserInterfaceState(List<NetEntity> identities) : BoundUserInterfaceState
{
/// <summary>
/// The uids of the cloned identities.
/// </summary>
public readonly List<NetEntity> Identites = identities;
}
[Serializable, NetSerializable]
public enum TransformUI : byte
public enum ChangelingTransformUiKey : byte
{
Key,
}

View file

@ -44,7 +44,7 @@ public sealed partial class ChangelingTransformSystem : EntitySystem
_actionsSystem.AddAction(ent, ref ent.Comp.ChangelingTransformActionEntity, ent.Comp.ChangelingTransformAction);
var userInterfaceComp = EnsureComp<UserInterfaceComponent>(ent);
_uiSystem.SetUi((ent, userInterfaceComp), TransformUI.Key, new InterfaceData(ChangelingBuiXmlGeneratedName));
_uiSystem.SetUi((ent, userInterfaceComp), ChangelingTransformUiKey.Key, new InterfaceData(ChangelingBuiXmlGeneratedName));
}
private void OnShutdown(Entity<ChangelingTransformComponent> ent, ref ComponentShutdown args)
@ -64,18 +64,9 @@ public sealed partial class ChangelingTransformSystem : EntitySystem
if (!TryComp<ChangelingIdentityComponent>(ent, out var userIdentity))
return;
if (!_uiSystem.IsUiOpen((ent, userInterfaceComp), TransformUI.Key, args.Performer))
if (!_uiSystem.IsUiOpen((ent, userInterfaceComp), ChangelingTransformUiKey.Key, args.Performer))
{
_uiSystem.OpenUi((ent, userInterfaceComp), TransformUI.Key, args.Performer);
var identityData = new List<NetEntity>();
foreach (var consumedIdentity in userIdentity.ConsumedIdentities)
{
identityData.Add(GetNetEntity(consumedIdentity));
}
_uiSystem.SetUiState((ent, userInterfaceComp), TransformUI.Key, new ChangelingTransformBoundUserInterfaceState(identityData));
_uiSystem.OpenUi((ent, userInterfaceComp), ChangelingTransformUiKey.Key, args.Performer);
} //TODO: Can add a Else here with TransformInto and CloseUI to make a quick switch,
// issue right now is that Radials cover the Action buttons so clicking the action closes the UI (due to clicking off a radial causing it to close, even with UI)
// but pressing the number does.
@ -108,7 +99,7 @@ public sealed partial class ChangelingTransformSystem : EntitySystem
else
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(ent.Owner):player} begun an attempt to transform into \"{Name(targetIdentity)}\"");
var result = _doAfterSystem.TryStartDoAfter(new DoAfterArgs(
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(
EntityManager,
ent,
ent.Comp.TransformWindup,
@ -127,7 +118,7 @@ public sealed partial class ChangelingTransformSystem : EntitySystem
private void OnTransformSelected(Entity<ChangelingTransformComponent> ent,
ref ChangelingTransformIdentitySelectMessage args)
{
_uiSystem.CloseUi(ent.Owner, TransformUI.Key, ent);
_uiSystem.CloseUi(ent.Owner, ChangelingTransformUiKey.Key, ent);
if (!TryGetEntity(args.TargetIdentity, out var targetIdentity))
return;

View file

@ -2,7 +2,6 @@
using Content.Shared.Changeling.Components;
using Content.Shared.Cloning;
using Content.Shared.Humanoid;
using Content.Shared.Mind.Components;
using Content.Shared.NameModifier.EntitySystems;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
@ -12,7 +11,7 @@ using Robust.Shared.Prototypes;
namespace Content.Shared.Changeling.Systems;
public sealed class ChangelingIdentitySystem : EntitySystem
public abstract class SharedChangelingIdentitySystem : EntitySystem
{
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
@ -32,22 +31,19 @@ public sealed class ChangelingIdentitySystem : EntitySystem
SubscribeLocalEvent<ChangelingIdentityComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<ChangelingIdentityComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<ChangelingIdentityComponent, MindAddedMessage>(OnMindAdded);
SubscribeLocalEvent<ChangelingIdentityComponent, MindRemovedMessage>(OnMindRemoved);
SubscribeLocalEvent<ChangelingIdentityComponent, PlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<ChangelingIdentityComponent, PlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<ChangelingStoredIdentityComponent, ComponentRemove>(OnStoredRemove);
}
private void OnMindAdded(Entity<ChangelingIdentityComponent> ent, ref MindAddedMessage args)
private void OnPlayerAttached(Entity<ChangelingIdentityComponent> ent, ref PlayerAttachedEvent args)
{
if (!TryComp<ActorComponent>(args.Container.Owner, out var actor))
return;
HandOverPvsOverride(actor.PlayerSession, ent.Comp);
HandOverPvsOverride(ent, args.Player);
}
private void OnMindRemoved(Entity<ChangelingIdentityComponent> ent, ref MindRemovedMessage args)
private void OnPlayerDetached(Entity<ChangelingIdentityComponent> ent, ref PlayerDetachedEvent args)
{
CleanupPvsOverride(ent, args.Container.Owner);
CleanupPvsOverride(ent, args.Player);
}
private void OnMapInit(Entity<ChangelingIdentityComponent> ent, ref MapInitEvent args)
@ -59,7 +55,8 @@ public sealed class ChangelingIdentitySystem : EntitySystem
private void OnShutdown(Entity<ChangelingIdentityComponent> ent, ref ComponentShutdown args)
{
CleanupPvsOverride(ent, ent.Owner);
if (TryComp<ActorComponent>(ent, out var actor))
CleanupPvsOverride(ent, actor.PlayerSession);
CleanupChangelingNullspaceIdentities(ent);
}
@ -107,66 +104,63 @@ public sealed class ChangelingIdentitySystem : EntitySystem
// Movercontrollers and mob collisions are currently being calculated even for paused entities.
// Spawning all of them in the same spot causes severe performance problems.
// Cryopods and Polymorph have the same problem.
var mob = Spawn(speciesPrototype.Prototype, new MapCoordinates(new Vector2(2 * _numberOfStoredIdentities++, 0), PausedMapId!.Value));
var clone = Spawn(speciesPrototype.Prototype, new MapCoordinates(new Vector2(2 * _numberOfStoredIdentities++, 0), PausedMapId!.Value));
var storedIdentity = EnsureComp<ChangelingStoredIdentityComponent>(mob);
var storedIdentity = EnsureComp<ChangelingStoredIdentityComponent>(clone);
storedIdentity.OriginalEntity = target; // TODO: network this once we have WeakEntityReference or the autonetworking source gen is fixed
if (TryComp<ActorComponent>(target, out var actor))
storedIdentity.OriginalSession = actor.PlayerSession;
_humanoidSystem.CloneAppearance(target, mob);
_cloningSystem.CloneComponents(target, mob, settings);
_humanoidSystem.CloneAppearance(target, clone);
_cloningSystem.CloneComponents(target, clone, settings);
var targetName = _nameMod.GetBaseName(target);
_metaSystem.SetEntityName(mob, targetName);
ent.Comp.ConsumedIdentities.Add(mob);
_metaSystem.SetEntityName(clone, targetName);
ent.Comp.ConsumedIdentities.Add(clone);
Dirty(ent);
HandlePvsOverride(ent, mob);
HandlePvsOverride(ent, clone);
return mob;
return clone;
}
/// <summary>
/// Simple helper to add a PVS override to a Nullspace Identity
/// Simple helper to add a PVS override to a nullspace identity.
/// </summary>
/// <param name="uid"></param>
/// <param name="target"></param>
private void HandlePvsOverride(EntityUid uid, EntityUid target)
/// <param name="uid">The actor that should get the override.</param>
/// <param name="identity">The identity stored in nullspace.</param>
private void HandlePvsOverride(EntityUid uid, EntityUid identity)
{
if (!TryComp<ActorComponent>(uid, out var actor))
return;
_pvsOverrideSystem.AddSessionOverride(target, actor.PlayerSession);
_pvsOverrideSystem.AddSessionOverride(identity, actor.PlayerSession);
}
/// <summary>
/// Cleanup all Pvs Overrides for the owner of the ChangelingIdentity
/// Cleanup all PVS overrides for the owner of the ChangelingIdentity
/// </summary>
/// <param name="ent">the Changeling itself</param>
/// <param name="entityUid">Who specifically to cleanup from, usually just the same owner, but in the case of a mindswap we want to clean up the victim</param>
private void CleanupPvsOverride(Entity<ChangelingIdentityComponent> ent, EntityUid entityUid)
/// <param name="ent">The changeling storing the identities.</param>
/// <param name="entityUid"The session you wish to remove the overrides from.</param>
private void CleanupPvsOverride(Entity<ChangelingIdentityComponent> ent, ICommonSession session)
{
if (!TryComp<ActorComponent>(entityUid, out var actor))
return;
foreach (var identity in ent.Comp.ConsumedIdentities)
{
_pvsOverrideSystem.RemoveSessionOverride(identity, actor.PlayerSession);
_pvsOverrideSystem.RemoveSessionOverride(identity, session);
}
}
/// <summary>
/// Inform another Session of the entities stored for Transformation
/// Inform another session of the entities stored for transformation.
/// </summary>
/// <param name="session">The Session you wish to inform</param>
/// <param name="comp">The Target storage of identities</param>
public void HandOverPvsOverride(ICommonSession session, ChangelingIdentityComponent comp)
/// <param name="ent">The changeling storing the identities.</param>
/// <param name="session">The session you wish to inform.</param>
public void HandOverPvsOverride(Entity<ChangelingIdentityComponent> ent, ICommonSession session)
{
foreach (var entity in comp.ConsumedIdentities)
foreach (var identity in ent.Comp.ConsumedIdentities)
{
_pvsOverrideSystem.AddSessionOverride(entity, session);
_pvsOverrideSystem.AddSessionOverride(identity, session);
}
}

View file

@ -0,0 +1,14 @@
using Content.Shared.Engineering.Systems;
using Content.Shared.Weapons.Melee.Balloon;
namespace Content.Shared.Engineering.Components;
/// <summary>
/// Implements logic to allow inflatable objects to be safely deflated by <see cref="BalloonPopperComponent"/> items.
/// </summary>
/// <remarks>
/// The owning entity must have <see cref="DisassembleOnAltVerbComponent"/> to implement the logic.
/// </remarks>
/// <seealso cref="InflatableSafeDisassemblySystem"/>
[RegisterComponent]
public sealed partial class InflatableSafeDisassemblyComponent : Component;

View file

@ -19,14 +19,12 @@ public sealed partial class DisassembleOnAltVerbSystem : EntitySystem
SubscribeLocalEvent<DisassembleOnAltVerbComponent, GetVerbsEvent<AlternativeVerb>>(AddDisassembleVerb);
SubscribeLocalEvent<DisassembleOnAltVerbComponent, DisassembleDoAfterEvent>(OnDisassembleDoAfter);
}
private void AddDisassembleVerb(Entity<DisassembleOnAltVerbComponent> entity, ref GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanInteract || !args.CanAccess || args.Hands == null)
return;
public void StartDisassembly(Entity<DisassembleOnAltVerbComponent> entity, EntityUid user)
{
// Doafter setup
var doAfterArgs = new DoAfterArgs(EntityManager,
args.User,
user,
entity.Comp.DisassembleTime,
new DisassembleDoAfterEvent(),
entity,
@ -35,12 +33,22 @@ public sealed partial class DisassembleOnAltVerbSystem : EntitySystem
BreakOnMove = true,
};
_doAfter.TryStartDoAfter(doAfterArgs);
}
private void AddDisassembleVerb(Entity<DisassembleOnAltVerbComponent> entity, ref GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanInteract || !args.CanAccess || args.Hands == null)
return;
var user = args.User;
// Actual verb stuff
AlternativeVerb verb = new()
{
Act = () =>
{
_doAfter.TryStartDoAfter(doAfterArgs);
StartDisassembly(entity, user);
},
Text = Loc.GetString("disassemble-system-verb-disassemble"),
Priority = 2

View file

@ -0,0 +1,39 @@
using Content.Shared.Engineering.Components;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Weapons.Melee.Balloon;
namespace Content.Shared.Engineering.Systems;
/// <summary>
/// Implements <see cref="InflatableSafeDisassemblyComponent"/>
/// </summary>
public sealed class InflatableSafeDisassemblySystem : EntitySystem
{
[Dependency] private readonly DisassembleOnAltVerbSystem _disassembleOnAltVerbSystem = null!;
[Dependency] private readonly SharedPopupSystem _popupSystem = null!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<InflatableSafeDisassemblyComponent, InteractUsingEvent>(InteractHandler);
}
private void InteractHandler(Entity<InflatableSafeDisassemblyComponent> ent, ref InteractUsingEvent args)
{
if (args.Handled)
return;
if (!HasComp<BalloonPopperComponent>(args.Used))
return;
_popupSystem.PopupPredicted(
Loc.GetString("inflatable-safe-disassembly", ("item", args.Used), ("target", ent.Owner)),
ent,
args.User);
_disassembleOnAltVerbSystem.StartDisassembly((ent, Comp<DisassembleOnAltVerbComponent>(ent)), args.User);
args.Handled = true;
}
}

View file

@ -26,10 +26,14 @@ namespace Content.Shared.Lathe
// Otherwise the material arbitrage test and/or LatheSystem.GetAllBaseRecipes needs to be updated
/// <summary>
/// The lathe's construction queue
/// The lathe's construction queue.
/// </summary>
/// <remarks>
/// This is a LinkedList to allow for constant time insertion/deletion (vs a List), and more efficient
/// moves (vs a Queue).
/// </remarks>
[DataField]
public Queue<ProtoId<LatheRecipePrototype>> Queue = new();
public LinkedList<LatheRecipeBatch> Queue = new();
/// <summary>
/// The sound that plays when the lathe is producing an item, if any
@ -97,6 +101,21 @@ namespace Content.Shared.Lathe
}
}
[Serializable]
public sealed partial class LatheRecipeBatch
{
public ProtoId<LatheRecipePrototype> Recipe;
public int ItemsPrinted;
public int ItemsRequested;
public LatheRecipeBatch(ProtoId<LatheRecipePrototype> recipe, int itemsPrinted, int itemsRequested)
{
Recipe = recipe;
ItemsPrinted = itemsPrinted;
ItemsRequested = itemsRequested;
}
}
/// <summary>
/// Event raised on a lathe when it starts producing a recipe.
/// </summary>

View file

@ -10,11 +10,11 @@ public sealed class LatheUpdateState : BoundUserInterfaceState
{
public List<ProtoId<LatheRecipePrototype>> Recipes;
public ProtoId<LatheRecipePrototype>[] Queue;
public LatheRecipeBatch[] Queue;
public ProtoId<LatheRecipePrototype>? CurrentlyProducing;
public LatheUpdateState(List<ProtoId<LatheRecipePrototype>> recipes, ProtoId<LatheRecipePrototype>[] queue, ProtoId<LatheRecipePrototype>? currentlyProducing = null)
public LatheUpdateState(List<ProtoId<LatheRecipePrototype>> recipes, LatheRecipeBatch[] queue, ProtoId<LatheRecipePrototype>? currentlyProducing = null)
{
Recipes = recipes;
Queue = queue;
@ -46,6 +46,33 @@ public sealed class LatheQueueRecipeMessage : BoundUserInterfaceMessage
}
}
/// <summary>
/// Sent to the server to remove a batch from the queue.
/// </summary>
[Serializable, NetSerializable]
public sealed class LatheDeleteRequestMessage(int index) : BoundUserInterfaceMessage
{
public int Index = index;
}
/// <summary>
/// Sent to the server to move the position of a batch in the queue.
/// </summary>
[Serializable, NetSerializable]
public sealed class LatheMoveRequestMessage(int index, int change) : BoundUserInterfaceMessage
{
public int Index = index;
public int Change = change;
}
/// <summary>
/// Sent to the server to stop producing the current item.
/// </summary>
[Serializable, NetSerializable]
public sealed class LatheAbortFabricationMessage() : BoundUserInterfaceMessage
{
}
[NetSerializable, Serializable]
public enum LatheUiKey
{

View file

@ -0,0 +1,81 @@
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
namespace Content.Shared.Lathe;
/// <summary>
/// Handles reading, writing, and validation for linked lists of prototypes.
/// </summary>
/// <typeparam name="T">The type of prototype this linked list represents</typeparam>
/// <remarks>
/// This is in the Content.Shared.Lathe namespace as there are no other LinkedList ProtoId instances.
/// </remarks>
[TypeSerializer]
public sealed class LinkedListSerializer<T> : ITypeSerializer<LinkedList<T>, SequenceDataNode>, ITypeCopier<LinkedList<T>> where T : class
{
public ValidationNode Validate(ISerializationManager serializationManager, SequenceDataNode node,
IDependencyCollection dependencies, ISerializationContext? context = null)
{
var list = new List<ValidationNode>();
foreach (var elem in node.Sequence)
{
list.Add(serializationManager.ValidateNode<T>(elem, context));
}
return new ValidatedSequenceNode(list);
}
public DataNode Write(ISerializationManager serializationManager, LinkedList<T> value,
IDependencyCollection dependencies,
bool alwaysWrite = false,
ISerializationContext? context = null)
{
var sequence = new SequenceDataNode();
foreach (var elem in value)
{
sequence.Add(serializationManager.WriteValue(elem, alwaysWrite, context));
}
return sequence;
}
LinkedList<T> ITypeReader<LinkedList<T>, SequenceDataNode>.Read(ISerializationManager serializationManager,
SequenceDataNode node,
IDependencyCollection dependencies,
SerializationHookContext hookCtx,
ISerializationContext? context, ISerializationManager.InstantiationDelegate<LinkedList<T>>? instanceProvider)
{
var list = instanceProvider != null ? instanceProvider() : new LinkedList<T>();
foreach (var dataNode in node.Sequence)
{
list.AddLast(serializationManager.Read<T>(dataNode, hookCtx, context));
}
return list;
}
public void CopyTo(
ISerializationManager serializationManager,
LinkedList<T> source,
ref LinkedList<T> target,
IDependencyCollection dependencies,
SerializationHookContext hookCtx,
ISerializationContext? context = null)
{
target.Clear();
using var enumerator = source.GetEnumerator();
while (enumerator.MoveNext())
{
var current = enumerator.Current;
target.AddLast(current);
}
}
}

View file

@ -22,6 +22,7 @@ public abstract class SharedLatheSystem : EntitySystem
[Dependency] private readonly EmagSystem _emag = default!;
public readonly Dictionary<string, List<LatheRecipePrototype>> InverseRecipes = new();
public const int MaxItemsPerRequest = 10_000;
public override void Initialize()
{
@ -86,6 +87,8 @@ public abstract class SharedLatheSystem : EntitySystem
return false;
if (!HasRecipe(uid, recipe, component))
return false;
if (amount <= 0)
return false;
foreach (var (material, needed) in recipe.Materials)
{

View file

@ -0,0 +1,26 @@
using Content.Shared.GameTicking.Components;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.Trigger.Components.Effects;
/// <summary>
/// Adds and starts a new game rule on a trigger.
/// The user is always logged alongside the game rule and this entity.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class AddGameRuleOnTriggerComponent : BaseXOnTriggerComponent
{
/// <summary>
/// The game rule that will be added. Entity requires <see cref="GameRuleComponent"/>.
/// </summary>
[DataField(required: true), AutoNetworkedField]
public EntProtoId<GameRuleComponent> GameRule;
/// <summary>
/// Whether to also start the game rule when adding it.
/// You almost always want this to be true.
/// </summary>
[DataField, AutoNetworkedField]
public bool StartRule = true;
}

View file

@ -2,7 +2,7 @@
using Robust.Shared.Audio;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Weapons.Melee.Balloon;
namespace Content.Shared.Weapons.Melee.Balloon;
/// <summary>
/// This is used for weapons that pop balloons on attack.

View file

@ -9,7 +9,6 @@ using Content.Shared.CombatMode;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Damage;
using Content.Shared.Examine;
using Content.Shared.Gravity;
using Content.Shared.Hands;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Hands.Components;
@ -34,7 +33,6 @@ using Robust.Shared.Containers;
using Robust.Shared.Input.Binding;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Player;
@ -65,7 +63,6 @@ public abstract partial class SharedGunSystem : EntitySystem
[Dependency] protected readonly SharedAudioSystem Audio = default!;
[Dependency] private readonly SharedCombatModeSystem _combatMode = default!;
[Dependency] protected readonly SharedContainerSystem Containers = default!;
[Dependency] private readonly SharedGravitySystem _gravity = default!;
[Dependency] protected readonly SharedPointLightSystem Lights = default!;
[Dependency] protected readonly SharedPopupSystem PopupSystem = default!;
[Dependency] protected readonly SharedPhysicsSystem Physics = default!;

View file

@ -1,27 +1,4 @@
Entries:
- author: metalgearsloth
changes:
- message: Tweak mob collision values in line with RMC14.
type: Tweak
id: 8367
time: '2025-04-27T14:47:18.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/36851
- author: Fildrance
changes:
- message: node scanner now dislplays live artifact info (while in range) after
linking to artifact
type: Tweak
id: 8368
time: '2025-04-27T15:11:13.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/36635
- author: ArtisticRoomba
changes:
- message: A new Mapping changelog has been added. You can find it in a different
"Mapping" tab under the changelog menu.
type: Add
id: 8369
time: '2025-04-27T20:15:17.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/34848
- author: ScarKy0
changes:
- message: Deliveries can now spawn as fragile-type! Deliver them intact to earn
@ -3948,3 +3925,26 @@
id: 8879
time: '2025-08-22T22:26:48.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/39834
- author: _miket, RedBookcase
changes:
- message: Added new Derelict Cyborg ghost roles, including the Derelict Engineer,
Janitor, Salvage, Medical, and Assault Cyborg!
type: Add
id: 8880
time: '2025-08-23T20:32:15.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/38159
- author: FungiFellow
changes:
- message: Added the Inflatable Module for borgs.
type: Add
id: 8881
time: '2025-08-23T22:15:28.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/35100
- author: whatston3
changes:
- message: Lathes now batch items into jobs, which can be moved around in priority
or deleted.
type: Add
id: 8882
time: '2025-08-24T15:02:47.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/38624

View file

@ -551,4 +551,11 @@
id: 67
time: '2025-08-18T18:22:15.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/39091
- author: M4rchy-S
changes:
- message: On Marathon, fixed the reagent grinder in perma not having power.
type: Fix
id: 68
time: '2025-08-23T22:48:39.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/39801
Order: 1

File diff suppressed because one or more lines are too long

View file

@ -251,9 +251,24 @@ ghost-role-information-syndicate-cyborg-assault-name = Syndicate Assault Cyborg
ghost-role-information-syndicate-cyborg-saboteur-name = Syndicate Saboteur Cyborg
ghost-role-information-syndicate-cyborg-description = The Syndicate needs reinforcements. You, a cold silicon killing machine, will help them.
ghost-role-information-derelict-cyborg-name = Derelict Cyborg
ghost-role-information-derelict-engineering-cyborg-name = Derelict Engineer Cyborg
ghost-role-information-derelict-engineering-cyborg-description = You are an engineer cyborg that got lost in space. After years of exposure to ion storms you find yourself near a space station.
ghost-role-information-derelict-cyborg-name = Derelict Generic Cyborg
ghost-role-information-derelict-cyborg-description = You are a regular cyborg that got lost in space. After years of exposure to ion storms you find yourself near a space station.
ghost-role-information-derelict-janitor-cyborg-name = Derelict Janitor Cyborg
ghost-role-information-derelict-janitor-cyborg-description = You are a janitor cyborg that got lost in space. After years of exposure to ion storms you find yourself near a space station.
ghost-role-information-derelict-medical-cyborg-name = Derelict Medical Cyborg
ghost-role-information-derelict-medical-cyborg-description = You are a medical cyborg that got lost in space. After years of exposure to ion storms you find yourself near a space station.
ghost-role-information-derelict-mining-cyborg-name = Derelict Salvage Cyborg
ghost-role-information-derelict-mining-cyborg-description = You are a salvage cyborg that got lost in space. After years of exposure to ion storms you find yourself near a space station.
ghost-role-information-derelict-syndicate-assault-cyborg-name = Derelict Syndicate Assault Cyborg
ghost-role-information-derelict-syndicate-assault-cyborg-description = You are an early model syndicate assault cyborg that got lost in space. After years of exposure to ion storms you find yourself near a space station.
ghost-role-information-security-name = Security
ghost-role-information-security-description = You are part of a security task force, but seem to have found yourself in a strange situation...

View file

@ -29,3 +29,9 @@ lathe-menu-silo-linked-message = Silo Linked
lathe-menu-fabricating-message = Fabricating...
lathe-menu-materials-title = Materials
lathe-menu-queue-title = Build Queue
lathe-menu-delete-fabricating-tooltip = Cancel printing the current item.
lathe-menu-delete-item-tooltip = Cancel printing this batch.
lathe-menu-move-up-tooltip = Move this batch ahead in the queue.
lathe-menu-move-down-tooltip = Move this batch back in the queue.
lathe-menu-item-single = {$index}. {$name}
lathe-menu-item-batch = {$index}. {$name} ({$printed}/{$total})

View file

@ -10,3 +10,5 @@ borg-slot-documents-empty = Books and papers
borg-slot-soap-empty = Soap
borg-slot-instruments-empty = Instruments
borg-slot-beakers-empty = Beakers
borg-slot-inflatable-door-empty = Inflatable Door
borg-slot-inflatable-wall-empty = Inflatable Wall

View file

@ -0,0 +1 @@
inflatable-safe-disassembly = You expertly use { THE($item) } to open the valve on { THE($target) }, and start deflating { OBJECT($target) } without causing damage.

View file

@ -248,6 +248,18 @@
- sprite: Mobs/Aliens/paradox_clone.rsi
state: preview
- type: entity
categories: [ HideSpawnMenu, Spawner ]
parent: SpawnPointGhostDerelictCyborg
id: SpawnPointGhostDerelictEngineeringCyborg
components:
- type: GhostRole
name: ghost-role-information-derelict-engineering-cyborg-name
description: ghost-role-information-derelict-engineering-cyborg-description
rules: ghost-role-information-silicon-rules
raffle:
settings: default
- type: entity
categories: [ HideSpawnMenu, Spawner ]
parent: BaseAntagSpawner
@ -266,6 +278,54 @@
- sprite: Mobs/Silicon/chassis.rsi
state: derelict_icon
- type: entity
categories: [ HideSpawnMenu, Spawner ]
parent: SpawnPointGhostDerelictCyborg
id: SpawnPointGhostDerelictJanitorCyborg
components:
- type: GhostRole
name: ghost-role-information-derelict-janitor-cyborg-name
description: ghost-role-information-derelict-janitor-cyborg-description
rules: ghost-role-information-silicon-rules
raffle:
settings: default
- type: entity
categories: [ HideSpawnMenu, Spawner ]
parent: SpawnPointGhostDerelictCyborg
id: SpawnPointGhostDerelictMedicalCyborg
components:
- type: GhostRole
name: ghost-role-information-derelict-medical-cyborg-name
description: ghost-role-information-derelict-medical-cyborg-description
rules: ghost-role-information-silicon-rules
raffle:
settings: default
- type: entity
categories: [ HideSpawnMenu, Spawner ]
parent: SpawnPointGhostDerelictCyborg
id: SpawnPointGhostDerelictMiningCyborg
components:
- type: GhostRole
name: ghost-role-information-derelict-mining-cyborg-name
description: ghost-role-information-derelict-mining-cyborg-description
rules: ghost-role-information-silicon-rules
raffle:
settings: default
- type: entity
categories: [ HideSpawnMenu, Spawner ]
parent: SpawnPointGhostDerelictCyborg
id: SpawnPointGhostDerelictSyndicateAssaultCyborg
components:
- type: GhostRole
name: ghost-role-information-derelict-syndicate-assault-cyborg-name
description: ghost-role-information-derelict-syndicate-assault-cyborg-description
rules: ghost-role-information-silicon-rules
raffle:
settings: default
- type: entity
categories: [ HideSpawnMenu, Spawner ]
parent: BaseAntagSpawner

View file

@ -413,6 +413,7 @@
- Syndicate
- type: ActiveRadio
channels:
- Binary
- Syndicate
- type: ShowSyndicateIcons
- type: MovementAlwaysTouching
@ -460,6 +461,19 @@
chance: 1
- type: ShowJobIcons
- type: entity
id: BaseBorgChassisSyndicateDerelict #For assault borg and maybe others in time
parent: BaseBorgChassisSyndicate
abstract: true
components:
- type: SiliconLawProvider
laws: SyndicateStatic #Non-subverted version so they can still be changed
- type: StartIonStormed
ionStormAmount: 3
- type: IonStormTarget
chance: 1
- type: ShowJobIcons
- type: entity
parent: BaseBorgChassisNotIonStormable
id: BaseXenoborgChassis

View file

@ -254,7 +254,7 @@
map: ["light"]
visible: false
- type: BorgChassis
maxModules: 5 # the sixth one broke lol
maxModules: 5 # One less module slot than the regular module to reflect this being a "broken" cyborg.
moduleWhitelist:
tags:
- BorgModuleGeneric
@ -267,3 +267,164 @@
interactFailureString: petting-failure-derelict-cyborg
interactSuccessSound:
path: /Audio/Ambience/Objects/periodic_beep.ogg
- type: entity
parent: BaseBorgChassisDerelict
id: EngineeringBorgChassisDerelict
name: derelict engineer cyborg
description: A man-machine hybrid that assists the engineering department. This one seems to have chunks of strange crystals pockmarking its surface.
components:
- type: Sprite
layers:
- state: engineer_derelict
- state: engineer_e_r
map: ["enum.BorgVisualLayers.Light"]
shader: unshaded
visible: false
- state: engineer_derelict_crystal #This layer and the layer below are duplicated in order to create a more mellow unshaded layer. See https://github.com/space-wizards/space-station-14/pull/37869 for more info on the method.
shader: unshaded
- state: engineer_derelict_crystal
shader: shaded
- state: engineer_l
shader: unshaded
map: ["light"]
visible: false
- type: BorgChassis
maxModules: 5 # One less module slot than the regular module to reflect this being a "broken" cyborg.
moduleWhitelist:
tags:
- BorgModuleGeneric
- BorgModuleEngineering
hasMindState: engineer_e
noMindState: engineer_e_r
- type: entity
parent: BaseBorgChassisDerelict
id: JanitorBorgChassisDerelict
name: derelict janitor cyborg
description: A man-machine hybrid that assists the service department. It's a bigger mess than anything it can clean up.
components:
- type: Sprite
layers:
- state: janitor_derelict
map: ["movement"]
- state: janitor_e_r
map: ["enum.BorgVisualLayers.Light"]
shader: unshaded
visible: false
- state: janitor_l
shader: unshaded
map: ["light"]
visible: false
- type: SpriteMovement
movementLayers:
movement:
state: janitor_moving_derelict
noMovementLayers:
movement:
state: janitor_derelict
- type: BorgChassis
maxModules: 5 # One less module slot than the regular module to reflect this being a "broken" cyborg.
moduleWhitelist:
tags:
- BorgModuleGeneric
- BorgModuleJanitor
hasMindState: janitor_e
noMindState: janitor_e_r
- type: entity
parent: BaseBorgChassisDerelict
id: MedicalBorgChassisDerelict
name: derelict medical cyborg
description: A man-machine hybrid that assists the medical department. This one's needles don't look very sanitary.
components:
- type: Sprite
layers:
- state: medical_derelict
map: ["movement"]
- state: medical_e_r
map: ["enum.BorgVisualLayers.Light"]
shader: unshaded
visible: false
- state: medical_l
shader: unshaded
map: ["light"]
visible: false
- type: SpriteMovement
movementLayers:
movement:
state: medical_moving_derelict
noMovementLayers:
movement:
state: medical_derelict
- type: BorgChassis
maxModules: 6 # One less module slot than the regular module to reflect this being a "broken" cyborg.
moduleWhitelist:
tags:
- BorgModuleGeneric
- BorgModuleMedical
hasMindState: medical_e
noMindState: medical_e_r
- type: entity
parent: BaseBorgChassisDerelict
id: MiningBorgChassisDerelict
name: derelict salvage cyborg
description: A man-machine hybrid that assists the cargo department. This one has seen the wrong side of a gibtonite chunk.
components:
- type: Sprite
layers:
- state: miner_derelict
map: ["movement"]
- state: miner_e_r
map: ["enum.BorgVisualLayers.Light"]
shader: unshaded
visible: false
- state: miner_l
shader: unshaded
map: ["light"]
visible: false
- type: SpriteMovement
movementLayers:
movement:
state: miner_moving_derelict
noMovementLayers:
movement:
state: miner_derelict
- type: BorgChassis
maxModules: 6 # One less module slot than the regular module to reflect this being a "broken" cyborg.
moduleWhitelist:
tags:
- BorgModuleGeneric
- BorgModuleCargo
hasMindState: miner_e
noMindState: miner_e_r
- type: entity
parent: BaseBorgChassisSyndicateDerelict
id: SyndicateAssaultBorgChassisDerelict
name: derelict syndicate assault cyborg
description: A lean, mean killing machine with access to a variety of deadly modules. This one is more rust-orange than blood-red.
components:
- type: Sprite
layers:
- state: synd_sec_derelict
- state: synd_sec_e
map: ["enum.BorgVisualLayers.Light"]
shader: unshaded
visible: false
- state: synd_sec_l
shader: unshaded
map: ["light"]
visible: false
- type: BorgChassis
maxModules: 3
moduleWhitelist: # Note - the Derelict Assault Borg does not have a traversal module. This is intentional as Assault Borgs have space traversal with their c20 and free space movement, and they can navigate to the station using the pinpointer.
tags:
- BorgModuleGeneric
- BorgModuleSyndicate
- BorgModuleSyndicateAssault
hasMindState: synd_sec_derelict_e
noMindState: synd_sec_derelict
- type: Construction
node: derelictcyborg

View file

@ -401,7 +401,7 @@
# borg_module:
# - BorgModuleOperative
# - BorgModuleL6C
# - BorgModuleEsword
# - BorgModuleDoubleEsword
# - type: ItemSlots
# slots:
# cell_slot:
@ -471,8 +471,44 @@
- PlayerBorgSyndicateSaboteurGhostRole
- type: entity
id: PlayerBorgDerelict
parent: EngineeringBorgChassisDerelict
id: PlayerEngineeringBorgDerelict
suffix: Battery, Module
components:
- type: ContainerFill
containers:
borg_brain:
- PositronicBrain
borg_module:
- BorgModuleTool
- BorgModuleFireExtinguisher
- BorgModuleConstruction
- BorgModuleRCD
- BorgModuleCable
- type: ItemSlots
slots:
cell_slot:
name: power-cell-slot-component-slot-name-default
startingItem: PowerCellHigh
- type: RandomMetadata
nameSegments: [NamesBorg]
- type: entity
parent: PlayerEngineeringBorgDerelict
id: PlayerEngineeringBorgDerelictGhostRole
suffix: Ghost role
components:
- type: GhostRole
name: ghost-role-information-derelict-engineering-cyborg-name
description: ghost-role-information-derelict-engineering-cyborg-description
rules: ghost-role-information-silicon-rules
raffle:
settings: default
- type: GhostTakeoverAvailable
- type: entity
parent: BorgChassisDerelict
id: PlayerBorgDerelict
suffix: Battery, Module
components:
- type: ContainerFill
@ -491,8 +527,8 @@
nameSegments: [NamesBorg]
- type: entity
id: PlayerBorgDerelictGhostRole
parent: PlayerBorgDerelict
id: PlayerBorgDerelictGhostRole
suffix: Ghost role
components:
- type: GhostRole
@ -502,3 +538,143 @@
raffle:
settings: default
- type: GhostTakeoverAvailable
- type: entity
parent: JanitorBorgChassisDerelict
id: PlayerJanitorBorgDerelict
suffix: Battery, Module
components:
- type: ContainerFill
containers:
borg_brain:
- PositronicBrain
borg_module:
- BorgModuleTool
- BorgModuleFireExtinguisher
- BorgModuleCleaning
- BorgModuleCustodial
- type: ItemSlots
slots:
cell_slot:
name: power-cell-slot-component-slot-name-default
startingItem: PowerCellHigh
- type: RandomMetadata
nameSegments: [NamesBorg]
- type: entity
parent: PlayerJanitorBorgDerelict
id: PlayerJanitorBorgDerelictGhostRole
suffix: Ghost role
components:
- type: GhostRole
name: ghost-role-information-derelict-janitor-cyborg-name
description: ghost-role-information-derelict-janitor-cyborg-description
rules: ghost-role-information-silicon-rules
raffle:
settings: default
- type: GhostTakeoverAvailable
- type: entity
parent: MedicalBorgChassisDerelict
id: PlayerMedicalBorgDerelict
suffix: Battery, Module
components:
- type: ContainerFill
containers:
borg_brain:
- PositronicBrain
borg_module:
- BorgModuleTool
- BorgModuleFireExtinguisher
- BorgModuleChemical
- BorgModuleTopicals
- BorgModuleRescue
- type: ItemSlots
slots:
cell_slot:
name: power-cell-slot-component-slot-name-default
startingItem: PowerCellHigh
- type: RandomMetadata
nameSegments: [NamesBorg]
- type: entity
parent: PlayerMedicalBorgDerelict
id: PlayerMedicalBorgDerelictGhostRole
suffix: Ghost role
components:
- type: GhostRole
name: ghost-role-information-derelict-medical-cyborg-name
description: ghost-role-information-derelict-medical-cyborg-description
rules: ghost-role-information-silicon-rules
raffle:
settings: default
- type: GhostTakeoverAvailable
- type: entity
parent: MiningBorgChassisDerelict
id: PlayerMiningBorgDerelict
suffix: Battery, Module
components:
- type: ContainerFill
containers:
borg_brain:
- PositronicBrain
borg_module:
- BorgModuleTool #No fire extinguisher, traversal is better
- BorgModuleMining
- BorgModuleTraversal
- BorgModuleAppraisal
- type: ItemSlots
slots:
cell_slot:
name: power-cell-slot-component-slot-name-default
startingItem: PowerCellHigh
- type: RandomMetadata
nameSegments: [NamesBorg]
- type: entity
parent: PlayerMiningBorgDerelict
id: PlayerMiningBorgDerelictGhostRole
suffix: Ghost role
components:
- type: GhostRole
name: ghost-role-information-derelict-mining-cyborg-name
description: ghost-role-information-derelict-mining-cyborg-description
rules: ghost-role-information-silicon-rules
raffle:
settings: default
- type: GhostTakeoverAvailable
- type: entity
parent: SyndicateAssaultBorgChassisDerelict
id: PlayerSyndicateAssaultBorgDerelict
suffix: Battery, Module
components:
- type: ContainerFill
containers:
borg_brain:
- PositronicBrain
borg_module:
- BorgModuleOperative
- BorgModuleC20r
- BorgModuleEsword
- type: ItemSlots
slots:
cell_slot:
name: power-cell-slot-component-slot-name-default
startingItem: PowerCellHyper
- type: RandomMetadata
nameSegments: [NamesDeathCommando]
- type: entity
parent: PlayerSyndicateAssaultBorgDerelict
id: PlayerBorgSyndicateDerelictGhostRole
suffix: Ghost role
components:
- type: GhostRole
name: ghost-role-information-derelict-syndicate-assault-cyborg-name
description: ghost-role-information-derelict-syndicate-assault-cyborg-description
rules: ghost-role-information-silicon-rules
raffle:
settings: default
- type: GhostTakeoverAvailable

View file

@ -33,6 +33,7 @@
- type: DisassembleOnAltVerb
prototypeToSpawn: InflatableWallStack1
disassembleTime: 3
- type: InflatableSafeDisassembly
- type: Airtight
- type: Transform
anchored: true
@ -81,5 +82,6 @@
- type: DisassembleOnAltVerb
prototypeToSpawn: InflatableDoorStack1
disassembleTime: 3
- type: InflatableSafeDisassembly
- type: Occluder
enabled: false

View file

@ -489,6 +489,36 @@
- type: BorgModuleIcon
icon: { sprite: Interface/Actions/actions_borg.rsi, state: tool-module }
- type: entity
id: BorgModuleInflatable
parent: [ BaseBorgModule, BaseProviderBorgModule ]
name: inflatable cyborg module
components:
- type: Sprite
layers:
- state: generic
- state: icon-inflatable
- type: ItemBorgModule
hands:
- item: InflatableDoorStack
hand:
emptyRepresentative: InflatableDoorStack
emptyLabel: borg-slot-inflatable-door-empty
whitelist:
tags:
- Inflatable
- item: InflatableWallStack
hand:
emptyRepresentative: InflatableWallStack
emptyLabel: borg-slot-inflatable-wall-empty
whitelist:
tags:
- Inflatable
- item: BoxInflatable
- item: WeaponMeleeNeedle
- type: BorgModuleIcon
icon: { sprite: Interface/Actions/actions_borg.rsi, state: inflatable-module }
# cargo modules
- type: entity
id: BorgModuleAppraisal
@ -1135,8 +1165,8 @@
#syndicate modules
- type: entity
id: BorgModuleSyndicateWeapon
parent: [ BaseBorgModule, BaseProviderBorgModule, BaseSyndicateContraband ]
id: BorgModuleSyndicateWeapon
name: weapon cyborg module
components:
- type: Sprite
@ -1171,8 +1201,8 @@
price: 2500
- type: entity
id: BorgModuleOperative
parent: [ BaseBorgModuleSyndicate, BaseProviderBorgModule, BaseSyndicateContraband ]
id: BorgModuleOperative
name: operative cyborg module
description: A module that comes with a crowbar, an Access Breaker and a syndicate pinpointer.
components:
@ -1190,42 +1220,76 @@
icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-operative-module }
- type: entity
id: BorgModuleEsword
parent: [ BaseBorgModuleSyndicate, BaseProviderBorgModule, BaseSyndicateContraband ]
id: BorgModuleEsword
name: energy sword cyborg module
description: A module that comes with a double energy sword.
description: A weapons module that comes with an energy sword.
components:
- type: Sprite
layers:
- state: syndicate
- state: icon-syndicate
- type: ItemBorgModule
hands:
- item: CyborgEnergySwordDouble
- item: PinpointerSyndicateNuclear
- type: BorgModuleIcon
icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-esword-module }
- type: Sprite
layers:
- state: syndicate
- state: icon-syndicate
- type: ItemBorgModule
hands:
- item: CyborgEnergySword
- item: PinpointerSyndicateNuclear
- type: BorgModuleIcon
icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-esword-module }
- type: entity
id: BorgModuleDoubleEsword
parent: [ BaseBorgModuleSyndicate, BaseProviderBorgModule, BaseSyndicateContraband ]
name: double energy sword cyborg module
description: A weapons module that comes with a double energy sword.
components:
- type: Sprite
layers:
- state: syndicate
- state: icon-syndicate
- type: ItemBorgModule
hands:
- item: CyborgEnergySwordDouble
- item: PinpointerSyndicateNuclear
- type: BorgModuleIcon
icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-desword-module }
- type: entity
id: BorgModuleL6C
parent: [ BaseBorgModuleSyndicateAssault, BaseProviderBorgModule, BaseSyndicateContraband ]
id: BorgModuleL6C
name: L6C ROW cyborg module
description: A module that comes with a L6C.
description: A weapons module that comes with a L6C.
components:
- type: Sprite
layers:
- state: syndicate
- state: icon-syndicate
- type: ItemBorgModule
hands:
- item: WeaponLightMachineGunL6C
- item: PinpointerSyndicateNuclear
- type: BorgModuleIcon
icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-l6c-module }
- type: Sprite
layers:
- state: syndicate
- state: icon-syndicate
- type: ItemBorgModule
hands:
- item: WeaponLightMachineGunL6C
- item: PinpointerSyndicateNuclear
- type: BorgModuleIcon
icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-l6c-module }
- type: entity
parent: [ BaseBorgModuleSyndicateAssault, BaseProviderBorgModule, BaseSyndicateContraband ]
id: BorgModuleC20r
name: C20-r ROW cyborg module
description: A weapons module that comes with a burst-fire C-20r.
components:
- type: Sprite
layers:
- state: syndicate
- state: icon-syndicate
- type: ItemBorgModule
hands:
- item: WeaponSubMachineGunC20rROW
- item: PinpointerSyndicateNuclear
- type: BorgModuleIcon
icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-c20r-module }
- type: entity
id: BorgModuleMartyr
parent: [ BaseBorgModule, BaseProviderBorgModule, BaseSyndicateContraband ]
id: BorgModuleMartyr
name: martyr cyborg module
description: "A module that comes with an explosive you probably don't want to handle yourself."
components:

View file

@ -5,21 +5,24 @@
description: A folded membrane which rapidly expands into a large cubical shape on activation.
suffix: Full
components:
- type: Stack
stackType: InflatableWall
count: 10
- type: Sprite
sprite: Objects/Misc/inflatable_wall.rsi
state: item_wall
- type: Item
sprite: Objects/Misc/inflatable_wall.rsi
size: Small
- type: SpawnAfterInteract
prototype: InflatableWall
doAfter: 1
removeOnInteract: true
- type: Clickable
- type: PhysicalComposition
- type: Stack
stackType: InflatableWall
count: 10
- type: Sprite
sprite: Objects/Misc/inflatable_wall.rsi
state: item_wall
- type: Item
sprite: Objects/Misc/inflatable_wall.rsi
size: Small
- type: SpawnAfterInteract
prototype: InflatableWall
doAfter: 1
removeOnInteract: true
- type: Clickable
- type: PhysicalComposition
- type: Tag
tags:
- Inflatable
# TODO: Add stack sprites + visuals.
- type: entity
@ -29,21 +32,24 @@
description: A folded membrane which rapidly expands into a large cubical shape on activation.
suffix: Full
components:
- type: Stack
stackType: InflatableDoor
count: 4
- type: Sprite
sprite: Objects/Misc/inflatable_door.rsi
state: item_door
- type: Item
sprite: Objects/Misc/inflatable_door.rsi
size: Small
- type: SpawnAfterInteract
prototype: InflatableDoor
doAfter: 1
removeOnInteract: true
- type: Clickable
- type: PhysicalComposition
- type: Stack
stackType: InflatableDoor
count: 4
- type: Sprite
sprite: Objects/Misc/inflatable_door.rsi
state: item_door
- type: Item
sprite: Objects/Misc/inflatable_door.rsi
size: Small
- type: SpawnAfterInteract
prototype: InflatableDoor
doAfter: 1
removeOnInteract: true
- type: Clickable
- type: PhysicalComposition
- type: Tag
tags:
- Inflatable
# TODO: Add stack sprites + visuals.
- type: entity
@ -51,27 +57,27 @@
id: InflatableWallStack5
suffix: 5
components:
- type: Sprite
state: item_wall
- type: Stack
count: 5
- type: Sprite
state: item_wall
- type: Stack
count: 5
- type: entity
parent: InflatableWallStack
id: InflatableWallStack1
suffix: 1
components:
- type: Sprite
state: item_wall
- type: Stack
count: 1
- type: Sprite
state: item_wall
- type: Stack
count: 1
- type: entity
parent: InflatableDoorStack
id: InflatableDoorStack1
suffix: 1
components:
- type: Sprite
state: item_door
- type: Stack
count: 1
- type: Sprite
state: item_door
- type: Stack
count: 1

View file

@ -143,6 +143,47 @@
- type: StaticPrice
price: 5000
- type: entity
name: C-20r ROW #I think ROW stands for Recharging Onboard Weapon so i'm following the L6C's example
id: WeaponSubMachineGunC20rROW
parent: BaseItem
description: A burst-fire C-20r submachine gun for use by cyborgs. Creates .35 caliber ammo on the fly from an internal ammo fabricator, which slowly self-charges.
components:
- type: Gun
minAngle: 2
maxAngle: 16
angleIncrease: 4
angleDecay: 16
fireRate: 8
burstFireRate: 8
selectedMode: Burst
availableModes:
- Burst
soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/c-20r.ogg
- type: Sprite
sprite: Objects/Weapons/Guns/SMGs/c20r.rsi
layers:
- state: base
map: ["enum.GunVisualLayers.Base"]
- state: mag-5
map: ["enum.GunVisualLayers.Mag"]
- type: Item
size: Huge
- type: ContainerContainer
containers:
ballistic-ammo: !type:Container
- type: ProjectileBatteryAmmoProvider
proto: CartridgePistol
fireCost: 100
- type: Battery
maxCharge: 3000
startingCharge: 3000
- type: BatterySelfRecharger
autoRecharge: true
autoRechargeRate: 25
- type: AmmoCounter
- type: entity
name: Drozd
parent: [BaseWeaponSubMachineGun, BaseSecurityContraband]

View file

@ -444,10 +444,17 @@
spread: 75
# Borgs
- type: entity
parent: EnergySword
id: CyborgEnergySword
suffix: For Borgs
description: A very loud & dangerous sword with a beam made of pure, concentrated plasma. Specially designed for syndicate cyborgs.
- type: entity
suffix: One-Handed, For Borgs
parent: EnergySwordDouble
id: CyborgEnergySwordDouble # why is this invalid if ID is BorgEnergySwordDouble
suffix: One-Handed, For Borgs
description: Syndicate Command Interns thought that having one blade on the energy sword was not enough. Specially designed for syndicate cyborgs.
components: # could add energy-draining like the L6C
- type: Wieldable

View file

@ -34,7 +34,8 @@
- id: ClosetSkeleton
- id: KingRatMigration
- id: RevenantSpawn
- id: DerelictCyborgSpawn
- !type:NestedSelector
tableId: DerelictBorgEventTable
- type: entityTable
id: ModerateAntagEventsTable
@ -48,6 +49,25 @@
- id: LoneOpsSpawn
- id: WizardSpawn
- id: AbductorsSpawn # Sunrise-Edit
- !type:NestedSelector
tableId: DerelictBorgEventTable
- type: entityTable
id: DerelictBorgEventTable #For Derelict Borg spawns
table: !type:GroupSelector
children:
- !type:GroupSelector # Standard NT Borgs
weight: 85
children:
- id: DerelictEngineerCyborgSpawn
- id: DerelictGenericCyborgSpawn
- id: DerelictJanitorCyborgSpawn
- id: DerelictMedicalCyborgSpawn
- id: DerelictMiningCyborgSpawn
- !type:GroupSelector # Other Borgs
weight: 15
children:
- id: DerelictSyndicateAssaultCyborgSpawn
- type: entity
id: BaseStationEvent
@ -556,7 +576,7 @@
earliestStart: 35
weight: 5.5
minimumPlayers: 20
duration: null # LoneOpsSpawn needs an infinite duration so that it inherits the NukeOpsRule things of an actually appropriate end scrreen (not always "Neutral outcome!") and... ending the game if the station is nuked.
duration: null # LoneOpsSpawn needs an infinite duration so that it inherits the NukeOpsRule things of an actually appropriate end screen (not always "Neutral outcome!") and... ending the game if the station is nuked.
- type: RuleGrids
- type: LoadMapRule
gridPath: /Maps/Shuttles/ShuttleEvent/striker.yml
@ -697,10 +717,10 @@
- type: entity
parent: BaseGameRule
id: DerelictCyborgSpawn
id: DerelictEngineerCyborgSpawn
components:
- type: StationEvent
weight: 5
weight: 2.5
earliestStart: 15
reoccurrenceDelay: 20
minimumPlayers: 4
@ -708,10 +728,115 @@
- type: SpaceSpawnRule
spawnDistance: 0
- type: AntagSpawner
prototype: PlayerBorgDerelict
prototype: PlayerEngineeringBorgDerelictGhostRole
- type: AntagSelection
definitions:
- spawnerPrototype: SpawnPointGhostDerelictEngineeringCyborg
min: 1
max: 1
pickPlayer: false
- type: entity
parent: BaseGameRule
id: DerelictGenericCyborgSpawn
components:
- type: StationEvent
weight: 2.5
earliestStart: 15
reoccurrenceDelay: 20
minimumPlayers: 4
duration: null
- type: SpaceSpawnRule
spawnDistance: 0
- type: AntagSpawner
prototype: PlayerBorgDerelictGhostRole
- type: AntagSelection
definitions:
- spawnerPrototype: SpawnPointGhostDerelictCyborg
min: 1
max: 1
pickPlayer: false
- type: entity
parent: BaseGameRule
id: DerelictJanitorCyborgSpawn
components:
- type: StationEvent
weight: 2.5
earliestStart: 15
reoccurrenceDelay: 20
minimumPlayers: 4
duration: null
- type: SpaceSpawnRule
spawnDistance: 0
- type: AntagSpawner
prototype: PlayerJanitorBorgDerelictGhostRole
- type: AntagSelection
definitions:
- spawnerPrototype: SpawnPointGhostDerelictJanitorCyborg
min: 1
max: 1
pickPlayer: false
- type: entity
parent: BaseGameRule
id: DerelictMedicalCyborgSpawn
components:
- type: StationEvent
weight: 2.5
earliestStart: 15
reoccurrenceDelay: 20
minimumPlayers: 4
duration: null
- type: SpaceSpawnRule
spawnDistance: 0
- type: AntagSpawner
prototype: PlayerMedicalBorgDerelictGhostRole
- type: AntagSelection
definitions:
- spawnerPrototype: SpawnPointGhostDerelictMedicalCyborg
min: 1
max: 1
pickPlayer: false
- type: entity
parent: BaseGameRule
id: DerelictMiningCyborgSpawn
components:
- type: StationEvent
weight: 2.5
earliestStart: 15
reoccurrenceDelay: 20
minimumPlayers: 4
duration: null
- type: SpaceSpawnRule
spawnDistance: 0
- type: AntagSpawner
prototype: PlayerMiningBorgDerelictGhostRole
- type: AntagSelection
definitions:
- spawnerPrototype: SpawnPointGhostDerelictMiningCyborg
min: 1
max: 1
pickPlayer: false
- type: entity
parent: BaseGameRule
id: DerelictSyndicateAssaultCyborgSpawn
components:
- type: StationEvent
weight: 2.5
earliestStart: 25
reoccurrenceDelay: 20
minimumPlayers: 15
duration: null
- type: SpaceSpawnRule
spawnDistance: 0
- type: AntagSpawner
prototype: PlayerBorgSyndicateDerelictGhostRole
- type: AntagSelection
definitions:
- spawnerPrototype: SpawnPointGhostDerelictSyndicateAssaultCyborg
min: 1
max: 1
pickPlayer: false

View file

@ -15,6 +15,7 @@
- BorgModuleTool
- BorgModuleCable
- BorgModuleFireExtinguisher
- BorgModuleInflatable
- type: latheRecipePack
id: BorgLimbsStatic

View file

@ -37,6 +37,11 @@
id: BorgModuleFireExtinguisher
result: BorgModuleFireExtinguisher
- type: latheRecipe
parent: BaseBorgModuleRecipe
id: BorgModuleInflatable
result: BorgModuleInflatable
# Cargo Modules
- type: latheRecipe

View file

@ -18,6 +18,7 @@
defaultModules:
- BorgModuleTool
- BorgModuleInflatable
- BorgModuleArtifact
- BorgModuleAnomaly

View file

@ -804,6 +804,9 @@
- type: Tag
id: Ingot
- type: Tag
id: Inflatable
- type: Tag
id: InstantDoAfters

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View file

@ -1,7 +1,7 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from vgstation at commit https://github.com/vgstation-coders/vgstation13/commit/cdbcb1e858b11f083994a7a269ed67ef5b452ce9, Module actions by Scarky0. chem, adv-chem, and adv-mining by mubururu_, xenoborg actions by Samuka-C (github), advclown by ThatGuyUSA",
"copyright": "Taken from vgstation at commit https://github.com/vgstation-coders/vgstation13/commit/cdbcb1e858b11f083994a7a269ed67ef5b452ce9, inflatable module by FungiFellow (GitHub), Module actions by Scarky0. chem, adv-chem, and adv-mining by mubururu_, xenoborg actions by Samuka-C (github), advclown by ThatGuyUSA. c20r and esword by RedBookcase on Github.",
"size": {
"x": 32,
"y": 32
@ -31,6 +31,9 @@
{
"name":"geiger-module"
},
{
"name":"inflatable-module"
},
{
"name":"rcd-module"
},
@ -109,12 +112,18 @@
{
"name":"syndicate-operative-module"
},
{
"name":"syndicate-desword-module"
},
{
"name":"syndicate-esword-module"
},
{
"name":"syndicate-l6c-module"
},
{
"name":"syndicate-c20r-module"
},
{
"name":"syndicate-martyr-module"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 782 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 782 B

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 824 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 924 B

View file

@ -1,9 +1,7 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from cev-eris at https://github.com/discordia-space/CEV-Eris/commit/740ff31a81313086cf16761f3677cf1e2ab46c93 and Taken from tgstation at https://github.com/tgstation/tgstation/blob/623290915c2292b56da11048deb62d758e1e3fb4/icons/obj/bloodpack.dmi, Blood pack redone by Ubaser",
"copyright": "Taken from https://github.com/tgstation/tgstation/blob/a3568da5634e756d0849480104afda402c6f1c3c/icons/obj/medical/stack_medical.dmi",
"copyright": "Tourniquet Sprite by PoorMansDreams, in-hand sprites of tourniquet, gauze, and bloodpack made by SeamLesss (github)",
"copyright": "Taken from cev-eris at https://github.com/discordia-space/CEV-Eris/commit/740ff31a81313086cf16761f3677cf1e2ab46c93 and Taken from tgstation at https://github.com/tgstation/tgstation/blob/623290915c2292b56da11048deb62d758e1e3fb4/icons/obj/bloodpack.dmi, Blood pack redone by Ubaser. Taken from https://github.com/tgstation/tgstation/blob/a3568da5634e756d0849480104afda402c6f1c3c/icons/obj/medical/stack_medical.dmi. Tourniquet Sprite by PoorMansDreams, in-hand sprites of tourniquet, gauze, and bloodpack made by SeamLesss (github).",
"size": {
"x": 32,
"y": 32

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View file

@ -1,7 +1,7 @@
{
"version": 1,
"license": "CC0-1.0",
"copyright": "Created by EmoGarbage404 (github) for Space Station 14. icon-construction.png created by deltanedas (github). syndicateborgbomb.png created by Mangohydra (github). layered inhands by mubururu_ (github), icon-chem.png & icon-mining-adv.png created by mubururu_ (github), Xenoborg modules sprites by Samuka-C (github)",
"copyright": "Created by EmoGarbage404 (github) for Space Station 14. icon-construction.png created by deltanedas (github). syndicateborgbomb.png created by Mangohydra (github). icon-chem.png & icon-mining-adv.png created by mubururu_ (github) icon-inflatable.png made by FungiFellow (GitHub), Xenoborg modules sprites by Samuka-C (github)",
"size": {
"x": 32,
"y": 32
@ -28,6 +28,9 @@
{
"name": "icon-cables"
},
{
"name": "icon-inflatable"
},
{
"name": "icon-chemist"
},
@ -244,4 +247,5 @@
"name": "NT"
}
]
}