Merge branch 'space-wizards:master' into master

This commit is contained in:
Morbo 2021-12-29 15:27:56 +03:00 • committed by GitHub
commit 62fca1fe07
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
223 changed files with 3754 additions and 131 deletions

View file

@ -299,6 +299,7 @@ namespace Content.Client.Entry
"IncreaseDamageOnWield",
"TabletopGame",
"LitOnPowered",
"Foldable",
"TriggerOnSignalReceived",
"ToggleDoorOnTrigger",
"DeviceNetworkComponent",
@ -307,6 +308,8 @@ namespace Content.Client.Entry
"ExtensionCableReceiver",
"ExtensionCableProvider",
"ApcNetworkConnection",
"SuitSensor",
"CrewMonitoringConsole",
"ApcNetSwitch",
"HandLabeler",
"Label",
@ -314,6 +317,9 @@ namespace Content.Client.Entry
"NukeCodePaper",
"GhostRadio",
"Armor",
"Guardian",
"GuardianCreator",
"GuardianHost",
"Udder",
"PneumaticCannon",
"Spreader",

View file

@ -3,29 +3,38 @@ using System.Globalization;
using Content.Client.Lobby;
using Content.Client.Viewport;
using Content.Shared.CCVar;
using Content.Shared.Info;
using Robust.Client.Console;
using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Utility;
namespace Content.Client.Info;
public sealed class RulesManager
public sealed class RulesManager : SharedRulesManager
{
[Dependency] private readonly IResourceManager _resource = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly INetManager _netManager = default!;
public void Initialize()
{
_netManager.RegisterNetMessage<ShowRulesPopupMessage>(OnShowRulesPopupMessage);
_stateManager.OnStateChanged += OnStateChanged;
}
private void OnShowRulesPopupMessage(ShowRulesPopupMessage message)
{
ShowRules(message.PopupTime);
}
private void OnStateChanged(StateChangedEventArgs args)
{
if (args.NewState is not (GameScreen or LobbyState))

View file

@ -0,0 +1,44 @@
using Content.Shared.Medical.CrewMonitoring;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
namespace Content.Client.Medical.CrewMonitoring
{
public class CrewMonitoringBoundUserInterface : BoundUserInterface
{
private CrewMonitoringWindow? _menu;
public CrewMonitoringBoundUserInterface([NotNull] ClientUserInterfaceComponent owner, [NotNull] object uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
_menu = new CrewMonitoringWindow();
_menu.OpenCentered();
_menu.OnClose += Close;
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
switch (state)
{
case CrewMonitoringState st:
_menu?.ShowSensors(st.Sensors);
break;
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
_menu?.Dispose();
}
}
}

View file

@ -0,0 +1,23 @@
<SS14Window xmlns="https://spacestation14.io"
Title="{Loc 'crew-monitoring-user-interface-title'}"
MinSize="450 400">
<ScrollContainer HorizontalExpand="True"
VerticalExpand="True">
<GridContainer Name="SensorsTable"
HorizontalExpand="True"
VerticalExpand="True"
HSeparationOverride="5"
VSeparationOverride="20"
Columns="3">
<!-- Table header -->
<Label Text="{Loc 'crew-monitoring-user-interface-name'}"
StyleClasses="LabelHeading"/>
<Label Text="{Loc 'crew-monitoring-user-interface-status'}"
StyleClasses="LabelHeading"/>
<Label Text="{Loc 'crew-monitoring-user-interface-location'}"
StyleClasses="LabelHeading"/>
<!-- Table rows are filled by code -->
</GridContainer>
</ScrollContainer>
</SS14Window>

View file

@ -0,0 +1,87 @@
using System.Collections.Generic;
using Content.Shared.Medical.SuitSensor;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Localization;
namespace Content.Client.Medical.CrewMonitoring
{
[GenerateTypedNameReferences]
public partial class CrewMonitoringWindow : SS14Window
{
private List<Control> _rowsContent = new();
public CrewMonitoringWindow()
{
RobustXamlLoader.Load(this);
}
public void ShowSensors(List<SuitSensorStatus> stSensors)
{
ClearAllSensors();
// add a row for each sensor
foreach (var sensor in stSensors)
{
// add users name and job
// format: UserName (Job)
var nameLabel = new Label()
{
Text = $"{sensor.Name} ({sensor.Job})"
};
SensorsTable.AddChild(nameLabel);
_rowsContent.Add(nameLabel);
// add users status and damage
// format: IsAlive (TotalDamage)
var statusText = Loc.GetString(sensor.IsAlive ?
"crew-monitoring-user-interface-alive" :
"crew-monitoring-user-interface-dead");
if (sensor.TotalDamage != null)
{
statusText += $" ({sensor.TotalDamage})";
}
var statusLabel = new Label()
{
Text = statusText
};
SensorsTable.AddChild(statusLabel);
_rowsContent.Add(statusLabel);
// add users positions
// format: (x, y)
string posText;
if (sensor.Coordinates != null)
{
// todo: add locations names (kitchen, bridge, etc)
var pos = sensor.Coordinates.Value.Position;
var x = (int) pos.X;
var y = (int) pos.Y;
posText = $"({x}, {y})";
}
else
{
posText = Loc.GetString("crew-monitoring-user-interface-no-info");
}
var posLabel = new Label()
{
Text = posText
};
SensorsTable.AddChild(posLabel);
_rowsContent.Add(posLabel);
}
}
private void ClearAllSensors()
{
foreach (var child in _rowsContent)
{
SensorsTable.RemoveChild(child);
}
_rowsContent.Clear();
}
}
}

View file

@ -23,6 +23,10 @@ namespace Content.Client.Morgue.Visualizers
{
sprite.LayerSetVisible(BodyBagVisualLayers.Label, labelVal);
}
else
{
sprite.LayerSetVisible(BodyBagVisualLayers.Label, false);
}
}
}

View file

@ -10,6 +10,9 @@ namespace Content.Client.Storage.Visualizers
[UsedImplicitly]
public sealed class StorageVisualizer : AppearanceVisualizer
{
/// <summary>
/// Sets the base sprite to this layer. Exists to make the inheritance tree less boilerplate-y.
/// </summary>
[DataField("state")]
private string? _stateBase;
[DataField("state_open")]
@ -41,9 +44,24 @@ namespace Content.Client.Storage.Visualizers
}
component.TryGetData(StorageVisuals.Open, out bool open);
var state = open ? _stateOpen ?? $"{_stateBase}_open" : _stateClosed ?? $"{_stateBase}_door";
sprite.LayerSetState(StorageVisualLayers.Door, state);
if (sprite.LayerMapTryGet(StorageVisualLayers.Door, out _))
{
sprite.LayerSetVisible(StorageVisualLayers.Door, true);
if (open && _stateOpen != null)
{
sprite.LayerSetState(StorageVisualLayers.Door, _stateOpen);
}
else if (!open && _stateClosed != null)
{
sprite.LayerSetState(StorageVisualLayers.Door, _stateClosed);
}
else
{
sprite.LayerSetVisible(StorageVisualLayers.Door, false);
}
}
if (component.TryGetData(StorageVisuals.CanLock, out bool canLock) && canLock)
{

View file

@ -0,0 +1,36 @@
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Client.Visualizer;
public sealed class FoldableVisualizer : AppearanceVisualizer
{
[DataField("key")]
private string _key = default!;
public override void OnChangeData(AppearanceComponent appearance)
{
base.OnChangeData(appearance);
var entManager = IoCManager.Resolve<IEntityManager>();
if (!entManager.TryGetComponent(appearance.Owner, out SpriteComponent? sprite)) return;
if (appearance.TryGetData("FoldedState", out bool folded) && folded)
{
sprite.LayerSetState(FoldableVisualLayers.Base, $"{_key}_folded");
}
else
{
sprite.LayerSetState(FoldableVisualLayers.Base, $"{_key}");
}
}
public enum FoldableVisualLayers : byte
{
Base,
}
}

View file

@ -0,0 +1,29 @@
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Client.Visualizer
{
[UsedImplicitly]
public sealed class RollerbedVisualizer : AppearanceVisualizer
{
[DataField("key")]
private string _key = default!;
public override void OnChangeData(AppearanceComponent appearance)
{
base.OnChangeData(appearance);
var entManager = IoCManager.Resolve<IEntityManager>();
if (!entManager.TryGetComponent(appearance.Owner, out SpriteComponent? sprite)) return;
if (appearance.TryGetData("StrapState", out bool strapped) && strapped)
{
sprite.LayerSetState(0, $"{_key}_buckled");
}
}
}
}

View file

@ -101,22 +101,14 @@ namespace Content.Server.Access.Systems
return true;
// check inventory slot?
if (EntityManager.TryGetComponent(uid, out InventoryComponent? inventoryComponent) &&
inventoryComponent.HasSlot(EquipmentSlotDefines.Slots.IDCARD) &&
inventoryComponent.TryGetSlotItem(EquipmentSlotDefines.Slots.IDCARD, out ItemComponent? item) &&
TryGetIdCard(item.Owner, out idCard))
{
return true;
}
return false;
return TryGetIdCardSlot(uid, out idCard);
}
/// <summary>
/// Attempt to get an id card component from an entity, either by getting it directly from the entity, or by
/// getting the contained id from a <see cref="PDAComponent"/>.
/// </summary>
private bool TryGetIdCard(EntityUid uid, [NotNullWhen(true)] out IdCardComponent? idCard)
public bool TryGetIdCard(EntityUid uid, [NotNullWhen(true)] out IdCardComponent? idCard)
{
if (EntityManager.TryGetComponent(uid, out idCard))
return true;
@ -129,5 +121,17 @@ namespace Content.Server.Access.Systems
return false;
}
/// <summary>
/// Try get id card from mobs ID inventory slot
/// </summary>
public bool TryGetIdCardSlot(EntityUid uid, [NotNullWhen(true)] out IdCardComponent? idCard)
{
idCard = null;
return EntityManager.TryGetComponent(uid, out InventoryComponent? inventoryComponent) &&
inventoryComponent.HasSlot(EquipmentSlotDefines.Slots.IDCARD) &&
inventoryComponent.TryGetSlotItem(EquipmentSlotDefines.Slots.IDCARD, out ItemComponent? item) &&
TryGetIdCard(item.Owner, out idCard);
}
}
}

View file

@ -0,0 +1,38 @@
using Content.Server.Guardian;
using Content.Shared.Actions.Behaviors;
using Content.Shared.Cooldown;
using Content.Shared.Popups;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Actions.Actions
{
/// <summary>
/// Manifests the guardian saved in the action, using the system
/// </summary>
[UsedImplicitly]
[DataDefinition]
public class ToggleGuardianAction : IInstantAction
{
[DataField("cooldown")] public float Cooldown { get; [UsedImplicitly] private set; }
public void DoInstantAction(InstantActionEventArgs args)
{
var entManager = IoCManager.Resolve<IEntityManager>();
if (entManager.TryGetComponent(args.Performer, out GuardianHostComponent? hostComponent) &&
hostComponent.HostedGuardian != null)
{
EntitySystem.Get<GuardianSystem>().ToggleGuardian(hostComponent);
args.PerformerActions?.Cooldown(args.ActionType, Cooldowns.SecondsFromNow(Cooldown));
}
else
{
args.Performer.PopupMessage(Loc.GetString("guardian-missing-invalid-action"));
}
}
}
}

View file

@ -133,7 +133,7 @@ namespace Content.Server.Buckle.Components
break;
}
ownTransform.LocalPosition = Vector2.Zero + BuckleOffset;
ownTransform.LocalPosition = strap.BuckleOffset;
}
public bool CanBuckle(EntityUid user, EntityUid to, [NotNullWhen(true)] out StrapComponent? strap)
@ -317,10 +317,17 @@ namespace Content.Server.Buckle.Components
BuckledTo = null;
if (_entMan.GetComponent<TransformComponent>(Owner).Parent == _entMan.GetComponent<TransformComponent>(oldBuckledTo.Owner))
var entManager = IoCManager.Resolve<IEntityManager>();
var xform = entManager.GetComponent<TransformComponent>(Owner);
var oldBuckledXform = entManager.GetComponent<TransformComponent>(oldBuckledTo.Owner);
if (xform.ParentUid == oldBuckledXform.Owner)
{
_entMan.GetComponent<TransformComponent>(Owner).AttachParentToContainerOrGrid();
_entMan.GetComponent<TransformComponent>(Owner).WorldRotation = _entMan.GetComponent<TransformComponent>(oldBuckledTo.Owner).WorldRotation;
xform.AttachParentToContainerOrGrid();
xform.WorldRotation = oldBuckledXform.WorldRotation;
if (oldBuckledTo.UnbuckleOffset != Vector2.Zero)
xform.Coordinates = oldBuckledXform.Coordinates.Offset(oldBuckledTo.UnbuckleOffset);
}
Appearance?.SetData(BuckleVisuals.Buckled, false);
@ -403,22 +410,5 @@ namespace Content.Server.Buckle.Components
return new BuckleComponentState(Buckled, drawDepth, LastEntityBuckledTo, DontCollide);
}
public void Update(PhysicsComponent physics)
{
if (!DontCollide)
return;
physics.WakeBody();
if (!IsOnStrapEntityThisFrame && DontCollide)
{
DontCollide = false;
TryUnbuckle(Owner);
Dirty();
}
IsOnStrapEntityThisFrame = false;
}
}
}

View file

@ -9,6 +9,7 @@ using Content.Shared.Sound;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
@ -21,8 +22,6 @@ namespace Content.Server.Buckle.Components
{
[ComponentDependency] public readonly SpriteComponent? SpriteComponent = null;
[Dependency] private readonly IEntityManager _entMan = default!;
private readonly HashSet<EntityUid> _buckledEntities = new();
/// <summary>
@ -37,6 +36,50 @@ namespace Content.Server.Buckle.Components
[ViewVariables] [DataField("size")] private int _size = 100;
private int _occupiedSize;
/// <summary>
/// The buckled entity will be offset by this amount from the center of the strap object.
/// If this offset it too big, it will be clamped to <see cref="MaxBuckleDistance"/>
/// </summary>
[DataField("buckleOffset", required: false)]
private Vector2 _buckleOffset = Vector2.Zero;
private bool _enabled = true;
/// <summary>
/// If disabled, nothing can be buckled on this object, and it will unbuckle anything that's already buckled
/// </summary>
public bool Enabled
{
get => _enabled;
set
{
_enabled = value;
if (_enabled == value) return;
RemoveAll();
}
}
/// <summary>
/// The distance above which a buckled entity will be automatically unbuckled.
/// Don't change it unless you really have to
/// </summary>
[DataField("maxBuckleDistance", required: false)]
public float MaxBuckleDistance = 0.1f;
/// <summary>
/// You can specify the offset the entity will have after unbuckling.
/// </summary>
[DataField("unbuckleOffset", required: false)]
public Vector2 UnbuckleOffset = Vector2.Zero;
/// <summary>
/// Gets and clamps the buckle offset to MaxBuckleDistance
/// </summary>
public Vector2 BuckleOffset => Vector2.Clamp(
_buckleOffset,
Vector2.One * -MaxBuckleDistance,
Vector2.One * MaxBuckleDistance);
/// <summary>
/// The entity that is currently buckled here, synced from <see cref="BuckleComponent.BuckledTo"/>
/// </summary>
@ -96,6 +139,8 @@ namespace Content.Server.Buckle.Components
/// <returns>True if added, false otherwise</returns>
public bool TryAdd(BuckleComponent buckle, bool force = false)
{
if (!Enabled) return false;
if (!force && !HasSpace(buckle))
{
return false;
@ -110,6 +155,12 @@ namespace Content.Server.Buckle.Components
buckle.Appearance?.SetData(StrapVisuals.RotationAngle, _rotation);
// Update the visuals of the strap object
if (IoCManager.Resolve<IEntityManager>().TryGetComponent<AppearanceComponent>(Owner, out var appearance))
{
appearance.SetData("StrapState", true);
}
#pragma warning disable 618
SendMessage(new StrapMessage(buckle.Owner, Owner));
#pragma warning restore 618
@ -126,6 +177,11 @@ namespace Content.Server.Buckle.Components
{
if (_buckledEntities.Remove(buckle.Owner))
{
if (IoCManager.Resolve<IEntityManager>().TryGetComponent<AppearanceComponent>(Owner, out var appearance))
{
appearance.SetData("StrapState", false);
}
_occupiedSize -= buckle.Size;
#pragma warning disable 618
SendMessage(new UnStrapMessage(buckle.Owner, Owner));
@ -147,9 +203,11 @@ namespace Content.Server.Buckle.Components
private void RemoveAll()
{
var entManager = IoCManager.Resolve<IEntityManager>();
foreach (var entity in _buckledEntities.ToArray())
{
if (_entMan.TryGetComponent<BuckleComponent?>(entity, out var buckle))
if (entManager.TryGetComponent<BuckleComponent>(entity, out var buckle))
{
buckle.TryUnbuckle(entity, true);
}
@ -166,7 +224,9 @@ namespace Content.Server.Buckle.Components
bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs)
{
if (!_entMan.TryGetComponent<BuckleComponent?>(eventArgs.User, out var buckle))
var entManager = IoCManager.Resolve<IEntityManager>();
if (!entManager.TryGetComponent<BuckleComponent>(eventArgs.User, out var buckle))
{
return false;
}
@ -176,7 +236,9 @@ namespace Content.Server.Buckle.Components
public override bool DragDropOn(DragDropEvent eventArgs)
{
if (!_entMan.TryGetComponent(eventArgs.Dragged, out BuckleComponent? buckleComponent)) return false;
var entManager = IoCManager.Resolve<IEntityManager>();
if (!entManager.TryGetComponent(eventArgs.Dragged, out BuckleComponent? buckleComponent)) return false;
return buckleComponent.TryBuckle(eventArgs.User, Owner);
}
}

View file

@ -42,10 +42,12 @@ namespace Content.Server.Buckle.Systems
if (!args.CanAccess || !args.CanInteract || !component.Buckled)
return;
Verb verb = new();
verb.Act = () => component.TryUnbuckle(args.User);
verb.Text = Loc.GetString("verb-categories-unbuckle");
verb.IconTexture = "/Textures/Interface/VerbIcons/unbuckle.svg.192dpi.png";
Verb verb = new()
{
Act = () => component.TryUnbuckle(args.User),
Text = Loc.GetString("verb-categories-unbuckle"),
IconTexture = "/Textures/Interface/VerbIcons/unbuckle.svg.192dpi.png"
};
if (args.Target == args.User && args.Using == null)
{
@ -62,14 +64,6 @@ namespace Content.Server.Buckle.Systems
args.Handled = component.TryUnbuckle(args.User);
}
public override void Update(float frameTime)
{
foreach (var (buckle, physics) in EntityManager.EntityQuery<BuckleComponent, PhysicsComponent>())
{
buckle.Update(physics);
}
}
private void MoveEvent(EntityUid uid, BuckleComponent buckle, ref MoveEvent ev)
{
var strap = buckle.BuckledTo;
@ -81,7 +75,7 @@ namespace Content.Server.Buckle.Systems
var strapPosition = EntityManager.GetComponent<TransformComponent>(strap.Owner).Coordinates.Offset(buckle.BuckleOffset);
if (ev.NewPosition.InRange(EntityManager, strapPosition, 0.2f))
if (ev.NewPosition.InRange(EntityManager, strapPosition, strap.MaxBuckleDistance))
{
return;
}

View file

@ -27,7 +27,7 @@ namespace Content.Server.Buckle.Systems
private void AddStrapVerbs(EntityUid uid, StrapComponent component, GetInteractionVerbsEvent args)
{
if (args.Hands == null || !args.CanAccess || !args.CanInteract)
if (args.Hands == null || !args.CanAccess || !args.CanInteract || !component.Enabled)
return;
// Note that for whatever bloody reason, buckle component has its own interaction range. Additionally, this
@ -41,9 +41,12 @@ namespace Content.Server.Buckle.Systems
if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target, range: buckledComp.Range))
continue;
Verb verb = new();
verb.Act = () => buckledComp.TryUnbuckle(args.User);
verb.Category = VerbCategory.Unbuckle;
Verb verb = new()
{
Act = () => buckledComp.TryUnbuckle(args.User),
Category = VerbCategory.Unbuckle
};
if (entity == args.User)
verb.Text = Loc.GetString("verb-self-target-pronoun");
else
@ -64,10 +67,12 @@ namespace Content.Server.Buckle.Systems
component.HasSpace(buckle) &&
_interactionSystem.InRangeUnobstructed(args.User, args.Target, range: buckle.Range))
{
Verb verb = new();
verb.Act = () => buckle.TryBuckle(args.User, args.Target);
verb.Category = VerbCategory.Buckle;
verb.Text = Loc.GetString("verb-self-target-pronoun");
Verb verb = new()
{
Act = () => buckle.TryBuckle(args.User, args.Target),
Category = VerbCategory.Buckle,
Text = Loc.GetString("verb-self-target-pronoun")
};
args.Verbs.Add(verb);
}
@ -82,14 +87,15 @@ namespace Content.Server.Buckle.Systems
if (!_interactionSystem.InRangeUnobstructed(@using, args.Target, usingBuckle.Range, predicate: Ignored))
return;
Verb verb = new();
verb.Act = () => usingBuckle.TryBuckle(args.User, args.Target);
verb.Category = VerbCategory.Buckle;
verb.Text = EntityManager.GetComponent<MetaDataComponent>(@using).EntityName;
// If the used entity is a person being pulled, prioritize this verb. Conversely, if it is
// just a held object, the user is probably just trying to sit down.
verb.Priority = EntityManager.HasComponent<ActorComponent>(@using) ? 1 : -1;
Verb verb = new()
{
Act = () => usingBuckle.TryBuckle(args.User, args.Target),
Category = VerbCategory.Buckle,
Text = EntityManager.GetComponent<MetaDataComponent>(@using).EntityName,
// just a held object, the user is probably just trying to sit down.
// If the used entity is a person being pulled, prioritize this verb. Conversely, if it is
Priority = EntityManager.HasComponent<ActorComponent>(@using) ? 1 : -1
};
args.Verbs.Add(verb);
}

View file

@ -19,6 +19,9 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
[DataField("spawn")]
public Dictionary<string, MinMax> Spawn { get; set; } = new();
[DataField("offset")]
public float Offset { get; set; } = 0.5f;
public void Execute(EntityUid owner, DestructibleSystem system)
{
var position = system.EntityManager.GetComponent<TransformComponent>(owner).MapPosition;
@ -36,14 +39,14 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
var spawned = system.EntityManager.SpawnEntity(entityId, position);
var stack = IoCManager.Resolve<IEntityManager>().GetComponent<StackComponent>(spawned);
EntitySystem.Get<StackSystem>().SetCount(spawned, count, stack);
spawned.RandomOffset(0.5f);
spawned.RandomOffset(Offset);
}
else
{
for (var i = 0; i < count; i++)
{
var spawned = system.EntityManager.SpawnEntity(entityId, position);
spawned.RandomOffset(0.5f);
spawned.RandomOffset(Offset);
}
}
}

View file

@ -24,6 +24,12 @@ namespace Content.Server.DeviceNetwork
/// </summary>
public const string CmdSetState = "set_state";
/// <summary>
/// The command for a device that just updated its state
/// E.g. suit sensors broadcasting owners vitals state
/// </summary>
public const string CmdUpdatedState = "updated_state";
#endregion
#region SetState

View file

@ -8,6 +8,7 @@ using Content.Server.Connection;
using Content.Server.Database;
using Content.Server.EUI;
using Content.Server.GameTicking;
using Content.Server.Info;
using Content.Server.IoC;
using Content.Server.Maps;
using Content.Server.NodeContainer.NodeGroups;
@ -91,6 +92,7 @@ namespace Content.Server.Entry
IoCManager.Resolve<IAdminManager>().Initialize();
IoCManager.Resolve<INpcBehaviorManager>().Initialize();
IoCManager.Resolve<IAfkManager>().Initialize();
IoCManager.Resolve<RulesManager>().Initialize();
_euiManager.Initialize();
IoCManager.Resolve<IGameMapManager>().Initialize();

View file

@ -0,0 +1,25 @@
#nullable enable
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Foldable
{
/// <summary>
/// Used to create "foldable structures" that you can pickup like an item when folded. Used for rollerbeds and wheelchairs
/// </summary>
[RegisterComponent]
public class FoldableComponent : Component
{
public override string Name => "Foldable";
[DataField("folded")]
[ViewVariables]
public bool IsFolded = false;
public bool CanBeFolded = true;
}
}

View file

@ -0,0 +1,131 @@
using System.Linq;
using Content.Server.Buckle.Components;
using Content.Server.Storage.Components;
using Content.Shared.Interaction;
using Content.Shared.Item;
using Content.Shared.Verbs;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
namespace Content.Server.Foldable
{
[UsedImplicitly]
public sealed class FoldableSystem : EntitySystem
{
[Dependency] private SharedContainerSystem _container = default!;
private const string FoldKey = "FoldedState";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<FoldableComponent, ComponentInit>(OnFoldableInit);
SubscribeLocalEvent<FoldableComponent, StorageOpenAttemptEvent>(OnFoldableOpenAttempt);
SubscribeLocalEvent<FoldableComponent, AttemptItemPickupEvent>(OnPickedUpAttempt);
SubscribeLocalEvent<FoldableComponent, GetAlternativeVerbsEvent>(AddFoldVerb);
}
private void OnFoldableOpenAttempt(EntityUid uid, FoldableComponent component, StorageOpenAttemptEvent args)
{
if (component.IsFolded)
args.Cancel();
}
private void OnFoldableInit(EntityUid uid, FoldableComponent component, ComponentInit args)
{
SetFolded(component, component.IsFolded);
}
private bool TryToggleFold(FoldableComponent comp)
{
return TrySetFolded(comp, !comp.IsFolded);
}
/// <summary>
/// Try to fold/unfold
/// </summary>
/// <param name="comp"></param>
/// <param name="state">Folded state we want</param>
/// <returns>True if successful</returns>
private bool TrySetFolded(FoldableComponent comp, bool state)
{
if (state == comp.IsFolded)
return false;
if (_container.IsEntityInContainer(comp.Owner))
return false;
// First we check if the foldable object has a strap component
if (EntityManager.TryGetComponent(comp.Owner, out StrapComponent? strap))
{
// If an entity is buckled to the object we can't pick it up or fold it
if (strap.BuckledEntities.Any())
return false;
}
SetFolded(comp, state);
return true;
}
/// <summary>
/// Set the folded state of the given <see cref="FoldableComponent"/>
/// </summary>
/// <param name="component"></param>
/// <param name="folded">If true, the component will become folded, else unfolded</param>
private void SetFolded(FoldableComponent component, bool folded)
{
component.IsFolded = folded;
component.CanBeFolded = !_container.IsEntityInContainer(component.Owner);
// You can't buckle an entity to a folded object
if (EntityManager.TryGetComponent(component.Owner, out StrapComponent? strap))
strap.Enabled = !component.IsFolded;
// Update visuals only if the value has changed
if (EntityManager.TryGetComponent(component.Owner, out AppearanceComponent? appearance))
appearance.SetData(FoldKey, folded);
}
#region Event handlers
/// <summary>
/// Prevents foldable objects to be picked up when unfolded
/// </summary>
/// <param name="uid"></param>
/// <param name="component"></param>
/// <param name="args"></param>
private void OnPickedUpAttempt(EntityUid uid, FoldableComponent component, AttemptItemPickupEvent args)
{
if (!component.IsFolded)
args.Cancel();
}
#endregion
#region Verb
private void AddFoldVerb(EntityUid uid, FoldableComponent component, GetAlternativeVerbsEvent args)
{
if (!args.CanAccess || !args.CanInteract)
return;
Verb verb = new()
{
Act = () => TryToggleFold(component),
Text = component.IsFolded ? Loc.GetString("unfold-verb") : Loc.GetString("fold-verb"),
IconTexture = "/Textures/Interface/VerbIcons/fold.svg.192dpi.png",
// If the object is unfolded and they click it, they want to fold it, if it's folded, they want to pick it up
Priority = component.IsFolded ? 0 : 2,
};
args.Verbs.Add(verb);
}
#endregion
}
}

View file

@ -0,0 +1,39 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Guardian
{
/// <summary>
/// Given to guardians to monitor their link with the host
/// </summary>
[RegisterComponent]
[ComponentProtoName("Guardian")]
public class GuardianComponent : Component
{
/// <summary>
/// The guardian host entity
/// </summary>
public EntityUid Host;
/// <summary>
/// Percentage of damage reflected from the guardian to the host
/// </summary>
[ViewVariables]
[DataField("damageShare")]
public float DamageShare { get; set; } = 0.85f;
/// <summary>
/// Maximum distance the guardian can travel before it's forced to recall, use YAML to set
/// </summary>
[ViewVariables]
[DataField("distanceAllowed")]
public float DistanceAllowed { get; set; } = 5f;
/// <summary>
/// If the guardian is currently manifested
/// </summary>
public bool GuardianLoose = false;
}
}

View file

@ -0,0 +1,39 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.ViewVariables;
namespace Content.Server.Guardian
{
/// <summary>
/// Creates a GuardianComponent attached to the user's GuardianHost.
/// </summary>
[RegisterComponent]
[ComponentProtoName("GuardianCreator")]
public sealed class GuardianCreatorComponent : Component
{
/// <summary>
/// Counts as spent upon exhausting the injection
/// </summary>
/// <remarks>
/// We don't mark as deleted as examine depends on this.
/// </remarks>
public bool Used = false;
/// <summary>
/// The prototype of the guardian entity which will be created
/// </summary>
[ViewVariables]
[DataField("guardianProto", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>), required: true)]
public string GuardianProto { get; set; } = default!;
/// <summary>
/// How long it takes to inject someone.
/// </summary>
[DataField("delay")]
public float InjectionDelay = 5f;
public bool Injecting = false;
}
}

View file

@ -0,0 +1,27 @@
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.ViewVariables;
namespace Content.Server.Guardian
{
/// <summary>
/// Given to guardian users upon establishing a guardian link with the entity
/// </summary>
[RegisterComponent]
[ComponentProtoName("GuardianHost")]
public sealed class GuardianHostComponent : Component
{
/// <summary>
/// Guardian hosted within the component
/// </summary>
/// <remarks>
/// Can be null if the component is added at any time.
/// </remarks>
public EntityUid? HostedGuardian;
/// <summary>
/// Container which holds the guardian
/// </summary>
[ViewVariables] public ContainerSlot GuardianContainer = default!;
}
}

View file

@ -0,0 +1,349 @@
using Content.Server.Actions;
using Content.Server.DoAfter;
using Content.Server.Hands.Components;
using Content.Server.Inventory.Components;
using Content.Server.Popups;
using Content.Shared.Actions;
using Content.Shared.Actions.Components;
using Content.Shared.Audio;
using Content.Shared.Damage;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.MobState;
using Content.Shared.MobState.EntitySystems;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Content.Server.Guardian
{
/// <summary>
/// A guardian has a host it's attached to that it fights for. A fighting spirit.
/// </summary>
public sealed class GuardianSystem : EntitySystem
{
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly DamageableSystem _damageSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GuardianCreatorComponent, UseInHandEvent>(OnCreatorUse);
SubscribeLocalEvent<GuardianCreatorComponent, AfterInteractEvent>(OnCreatorInteract);
SubscribeLocalEvent<GuardianCreatorComponent, ExaminedEvent>(OnCreatorExamine);
SubscribeLocalEvent<GuardianCreatorInjectedEvent>(OnCreatorInject);
SubscribeLocalEvent<GuardianCreatorInjectCancelledEvent>(OnCreatorCancelled);
SubscribeLocalEvent<GuardianComponent, MoveEvent>(OnGuardianMove);
SubscribeLocalEvent<GuardianComponent, DamageChangedEvent>(OnGuardianDamaged);
SubscribeLocalEvent<GuardianComponent, PlayerAttachedEvent>(OnGuardianPlayer);
SubscribeLocalEvent<GuardianComponent, PlayerDetachedEvent>(OnGuardianUnplayer);
SubscribeLocalEvent<GuardianHostComponent, ComponentInit>(OnHostInit);
SubscribeLocalEvent<GuardianHostComponent, MoveEvent>(OnHostMove);
SubscribeLocalEvent<GuardianHostComponent, MobStateChangedEvent>(OnHostStateChange);
SubscribeLocalEvent<GuardianHostComponent, ComponentShutdown>(OnHostShutdown);
}
private void OnGuardianUnplayer(EntityUid uid, GuardianComponent component, PlayerDetachedEvent args)
{
var host = component.Host;
if (!TryComp<GuardianHostComponent>(host, out var hostComponent)) return;
RetractGuardian(hostComponent, component);
}
private void OnGuardianPlayer(EntityUid uid, GuardianComponent component, PlayerAttachedEvent args)
{
var host = component.Host;
if (!HasComp<GuardianHostComponent>(host)) return;
_popupSystem.PopupEntity(Loc.GetString("guardian-available"), host, Filter.Entities(host));
}
private void OnHostInit(EntityUid uid, GuardianHostComponent component, ComponentInit args)
{
component.GuardianContainer = uid.EnsureContainer<ContainerSlot>("GuardianContainer");
}
private void OnHostShutdown(EntityUid uid, GuardianHostComponent component, ComponentShutdown args)
{
if (component.HostedGuardian == null) return;
EntityManager.QueueDeleteEntity(component.HostedGuardian.Value);
}
public void ToggleGuardian(GuardianHostComponent hostComponent)
{
if (hostComponent.HostedGuardian == null ||
!TryComp(hostComponent.HostedGuardian, out GuardianComponent? guardianComponent)) return;
if (guardianComponent.GuardianLoose)
{
RetractGuardian(hostComponent, guardianComponent);
}
else
{
ReleaseGuardian(hostComponent, guardianComponent);
}
}
/// <summary>
/// Adds the guardian host component to the user and spawns the guardian inside said component
/// </summary>
private void OnCreatorUse(EntityUid uid, GuardianCreatorComponent component, UseInHandEvent args)
{
if (args.Handled) return;
args.Handled = true;
UseCreator(args.User, args.User, component);
}
private void OnCreatorInteract(EntityUid uid, GuardianCreatorComponent component, AfterInteractEvent args)
{
if (args.Handled || args.Target == null) return;
args.Handled = true;
UseCreator(args.User, args.Target.Value, component);
}
private void OnCreatorCancelled(GuardianCreatorInjectCancelledEvent ev)
{
ev.Component.Injecting = false;
}
private void UseCreator(EntityUid user, EntityUid target, GuardianCreatorComponent component)
{
if (component.Used)
{
_popupSystem.PopupEntity(Loc.GetString("guardian-activator-empty-invalid-creation"), user, Filter.Entities(user));
return;
}
// If user is already a host don't duplicate.
if (HasComp<GuardianHostComponent>(target))
{
_popupSystem.PopupEntity(Loc.GetString("guardian-already-present-invalid-creation"), user, Filter.Entities(user));
return;
}
// Can't work without actions
EntityManager.EnsureComponent<ServerActionsComponent>(target);
if (component.Injecting) return;
component.Injecting = true;
_doAfterSystem.DoAfter(new DoAfterEventArgs(user, component.InjectionDelay, target: target)
{
BroadcastFinishedEvent = new GuardianCreatorInjectedEvent(user, target, component),
BroadcastCancelledEvent = new GuardianCreatorInjectCancelledEvent(target, component),
BreakOnTargetMove = true,
BreakOnUserMove = true,
});
}
private void OnCreatorInject(GuardianCreatorInjectedEvent ev)
{
var comp = ev.Component;
if (comp.Deleted ||
comp.Used ||
!TryComp<HandsComponent>(ev.User, out var hands) ||
!hands.IsHolding(comp.Owner) ||
HasComp<GuardianHostComponent>(ev.Target) ||
!TryComp<SharedActionsComponent>(ev.Target, out var actions))
{
comp.Injecting = false;
return;
}
var hostXform = EntityManager.GetComponent<TransformComponent>(ev.Target);
var host = EntityManager.EnsureComponent<GuardianHostComponent>(ev.Target);
// Use map position so it's not inadvertantly parented to the host + if it's in a container it spawns outside I guess.
var guardian = EntityManager.SpawnEntity(comp.GuardianProto, hostXform.MapPosition);
host.GuardianContainer.Insert(guardian);
host.HostedGuardian = guardian;
if (TryComp(guardian, out GuardianComponent? guardianComponent))
{
guardianComponent.Host = ev.Target;
// Grant the user the recall action and notify them
actions.Grant(ActionType.ManifestGuardian);
SoundSystem.Play(Filter.Entities(ev.Target), "/Audio/Effects/guardian_inject.ogg", ev.Target);
_popupSystem.PopupEntity(Loc.GetString("guardian-created"), ev.Target, Filter.Entities(ev.Target));
// Exhaust the activator
comp.Used = true;
}
else
{
Logger.ErrorS("guardian", $"Tried to spawn a guardian that doesn't have {nameof(GuardianComponent)}");
EntityManager.QueueDeleteEntity(guardian);
}
}
/// <summary>
/// Triggers when the host receives damage which puts the host in either critical or killed state
/// </summary>
private void OnHostStateChange(EntityUid uid, GuardianHostComponent component, MobStateChangedEvent args)
{
if (component.HostedGuardian == null) return;
if (args.CurrentMobState.IsCritical())
{
_popupSystem.PopupEntity(Loc.GetString("guardian-critical-warn"), component.HostedGuardian.Value, Filter.Entities(component.HostedGuardian.Value));
SoundSystem.Play(Filter.Entities(component.HostedGuardian.Value), "/Audio/Effects/guardian_warn.ogg", component.HostedGuardian.Value);
}
else if (args.CurrentMobState.IsDead())
{
SoundSystem.Play(Filter.Pvs(uid), "/Audio/Voice/Human/malescream_guardian.ogg", uid, AudioHelpers.WithVariation(0.20f));
EntityManager.RemoveComponent<GuardianHostComponent>(uid);
}
}
/// <summary>
/// Handles guardian receiving damage and splitting it with the host according to his defence percent
/// </summary>
private void OnGuardianDamaged(EntityUid uid, GuardianComponent component, DamageChangedEvent args)
{
if (args.DamageDelta == null) return;
_damageSystem.TryChangeDamage(component.Host, args.DamageDelta * component.DamageShare);
_popupSystem.PopupEntity(Loc.GetString("guardian-entity-taking-damage"), component.Host, Filter.Entities(component.Host));
}
/// <summary>
/// Triggers while trying to examine an activator to see if it's used
/// </summary>
private void OnCreatorExamine(EntityUid uid, GuardianCreatorComponent component, ExaminedEvent args)
{
if (component.Used)
{
args.PushMarkup(Loc.GetString("guardian-activator-empty-examine"));
}
}
/// <summary>
/// Called every time the host moves, to make sure the distance between the host and the guardian isn't too far
/// </summary>
private void OnHostMove(EntityUid uid, GuardianHostComponent component, ref MoveEvent args)
{
if (component.HostedGuardian == null ||
!TryComp(component.HostedGuardian, out GuardianComponent? guardianComponent) ||
!guardianComponent.GuardianLoose) return;
CheckGuardianMove(uid, component.HostedGuardian.Value, component);
}
/// <summary>
/// Called every time the guardian moves: makes sure it's not out of it's allowed distance
/// </summary>
private void OnGuardianMove(EntityUid uid, GuardianComponent component, ref MoveEvent args)
{
if (!component.GuardianLoose) return;
CheckGuardianMove(component.Host, uid, guardianComponent: component);
}
/// <summary>
/// Retract the guardian if either the host or the guardian move away from each other.
/// </summary>
private void CheckGuardianMove(
EntityUid hostUid,
EntityUid guardianUid,
GuardianHostComponent? hostComponent = null,
GuardianComponent? guardianComponent = null,
TransformComponent? hostXform = null,
TransformComponent? guardianXform = null)
{
if (!Resolve(hostUid, ref hostComponent, ref hostXform) ||
!Resolve(guardianUid, ref guardianComponent, ref guardianXform))
{
return;
}
if (!guardianComponent.GuardianLoose) return;
if (!guardianXform.Coordinates.InRange(EntityManager, hostXform.Coordinates, guardianComponent.DistanceAllowed))
{
RetractGuardian(hostComponent, guardianComponent);
}
}
private bool CanRelease(GuardianHostComponent host, GuardianComponent guardian)
{
return HasComp<ActorComponent>(guardian.Owner);
}
private void ReleaseGuardian(GuardianHostComponent hostComponent, GuardianComponent guardianComponent)
{
if (guardianComponent.GuardianLoose)
{
DebugTools.Assert(!hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
return;
}
if (!CanRelease(hostComponent, guardianComponent))
{
_popupSystem.PopupEntity(Loc.GetString("guardian-no-soul"), hostComponent.Owner, Filter.Entities(hostComponent.Owner));
return;
}
DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
hostComponent.GuardianContainer.Remove(guardianComponent.Owner);
DebugTools.Assert(!hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
guardianComponent.GuardianLoose = true;
}
private void RetractGuardian(GuardianHostComponent hostComponent, GuardianComponent guardianComponent)
{
if (!guardianComponent.GuardianLoose)
{
DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
return;
}
hostComponent.GuardianContainer.Insert(guardianComponent.Owner);
DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
_popupSystem.PopupEntity(Loc.GetString("guardian-entity-recall"), hostComponent.Owner, Filter.Pvs(hostComponent.Owner));
guardianComponent.GuardianLoose = false;
}
private sealed class GuardianCreatorInjectedEvent : EntityEventArgs
{
public EntityUid User { get; }
public EntityUid Target { get; }
public GuardianCreatorComponent Component { get; }
public GuardianCreatorInjectedEvent(EntityUid user, EntityUid target, GuardianCreatorComponent component)
{
User = user;
Target = target;
Component = component;
}
}
private sealed class GuardianCreatorInjectCancelledEvent : EntityEventArgs
{
public EntityUid Target { get; }
public GuardianCreatorComponent Component { get; }
public GuardianCreatorInjectCancelledEvent(EntityUid target, GuardianCreatorComponent component)
{
Target = target;
Component = component;
}
}
}
}

View file

@ -0,0 +1,15 @@
using Content.Shared.Info;
using Robust.Shared.IoC;
using Robust.Shared.Network;
namespace Content.Server.Info;
public class RulesManager : SharedRulesManager
{
[Dependency] private readonly INetManager _netManager = default!;
public void Initialize()
{
_netManager.RegisterNetMessage<ShowRulesPopupMessage>();
}
}

View file

@ -0,0 +1,67 @@
using Content.Server.Administration;
using Content.Shared.Administration;
using Content.Shared.CCVar;
using Content.Shared.Info;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Network;
namespace Content.Server.Info;
[AdminCommand(AdminFlags.Admin)]
public class ShowRulesCommand : IConsoleCommand
{
public string Command => "showrules";
public string Description => "Opens the rules popup for the specified player.";
public string Help => "showrules <username> [time]";
public async void Execute(IConsoleShell shell, string argStr, string[] args)
{
string target;
float seconds;
switch (args.Length)
{
case 1:
{
target = args[0];
var configurationManager = IoCManager.Resolve<IConfigurationManager>();
seconds = configurationManager.GetCVar(CCVars.RulesWaitTime);
break;
}
case 2:
{
if (float.TryParse(args[1], out seconds))
return;
target = args[0];
shell.WriteLine($"{args[1]} is not a valid amount of minutes.\n{Help}");
break;
}
default:
{
shell.WriteLine(Help);
return;
}
}
var locator = IoCManager.Resolve<IPlayerLocator>();
var located = await locator.LookupIdByNameOrIdAsync(target);
if (located == null)
{
shell.WriteError("Unable to find a player with that name.");
return;
}
var message = new SharedRulesManager.ShowRulesPopupMessage
{
PopupTime = seconds
};
var player = IoCManager.Resolve<IPlayerManager>()
.GetSessionByUserId(located.UserId);
IoCManager.Resolve<INetManager>()
.ServerSendMessage(message, player.ConnectedClient);
}
}

View file

@ -11,6 +11,7 @@ using Content.Server.DeviceNetwork;
using Content.Server.EUI;
using Content.Server.Holiday;
using Content.Server.Holiday.Interfaces;
using Content.Server.Info;
using Content.Server.Maps;
using Content.Server.Module;
using Content.Server.MoMMI;
@ -57,6 +58,7 @@ namespace Content.Server.IoC
IoCManager.Register<IAfkManager, AfkManager>();
IoCManager.Register<IGameMapManager, GameMapManager>();
IoCManager.Register<IGamePrototypeLoadManager, GamePrototypeLoadManager>();
IoCManager.Register<RulesManager, RulesManager>();
}
}
}

View file

@ -0,0 +1,26 @@
using System.Collections.Generic;
using Content.Shared.Medical.SuitSensor;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Medical.CrewMonitoring
{
[RegisterComponent]
[Friend(typeof(CrewMonitoringConsoleSystem))]
public class CrewMonitoringConsoleComponent : Component
{
public override string Name => "CrewMonitoringConsole";
/// <summary>
/// List of all currently connected sensors to this console.
/// </summary>
public Dictionary<string, SuitSensorStatus> ConnectedSensors = new();
/// <summary>
/// After what time sensor consider to be lost.
/// </summary>
[DataField("sensorTimeout")]
public float SensorTimeout = 10f;
}
}

View file

@ -0,0 +1,89 @@
using System.Linq;
using Content.Server.DeviceNetwork.Systems;
using Content.Server.Medical.SuitSensors;
using Content.Server.UserInterface;
using Content.Shared.Medical.CrewMonitoring;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
namespace Content.Server.Medical.CrewMonitoring
{
public class CrewMonitoringConsoleSystem : EntitySystem
{
[Dependency] private readonly SuitSensorSystem _sensors = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
private const float UpdateRate = 3f;
private float _updateDif;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CrewMonitoringConsoleComponent, ComponentRemove>(OnRemove);
SubscribeLocalEvent<CrewMonitoringConsoleComponent, PacketSentEvent>(OnPacketReceived);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
// check update rate
_updateDif += frameTime;
if (_updateDif < UpdateRate)
return;
_updateDif = 0f;
var consoles = EntityManager.EntityQuery<CrewMonitoringConsoleComponent>();
foreach (var console in consoles)
{
UpdateTimeouts(console.Owner, console);
UpdateUserInterface(console.Owner, console);
}
}
private void OnRemove(EntityUid uid, CrewMonitoringConsoleComponent component, ComponentRemove args)
{
component.ConnectedSensors.Clear();
}
private void OnPacketReceived(EntityUid uid, CrewMonitoringConsoleComponent component, PacketSentEvent args)
{
var suitSensor = _sensors.PacketToSuitSensor(args.Data);
if (suitSensor == null)
return;
suitSensor.Timestamp = _gameTiming.CurTime;
component.ConnectedSensors[args.SenderAddress] = suitSensor;
}
private void UpdateUserInterface(EntityUid uid, CrewMonitoringConsoleComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
var ui = component.Owner.GetUIOrNull(CrewMonitoringUIKey.Key);
if (ui == null)
return;
// update all sensors info
var allSensors = component.ConnectedSensors.Values.ToList();
var uiState = new CrewMonitoringState(allSensors);
ui.SetState(uiState);
}
private void UpdateTimeouts(EntityUid uid, CrewMonitoringConsoleComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
foreach (var (address, sensor) in component.ConnectedSensors)
{
// if too many time passed - sensor just dropped connection
var dif = _gameTiming.CurTime - sensor.Timestamp;
if (dif.Seconds > component.SensorTimeout)
component.ConnectedSensors.Remove(address);
}
}
}
}

View file

@ -0,0 +1,59 @@
using System;
using Content.Shared.Inventory;
using Content.Shared.Medical.SuitSensor;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Medical.SuitSensors
{
/// <summary>
/// Tracking device, embedded in almost all uniforms and jumpsuits.
/// If enabled, will report to crew monitoring console owners position and status.
/// </summary>
[RegisterComponent]
[Friend(typeof(SuitSensorSystem))]
[ComponentProtoName("SuitSensor")]
public sealed class SuitSensorComponent : Component
{
/// <summary>
/// Choose a random sensor mode when item is spawned.
/// </summary>
[DataField("randomMode")]
public bool RandomMode = true;
/// <summary>
/// If true user can't change suit sensor mode
/// </summary>
[DataField("controlsLocked")]
public bool ControlsLocked = false;
/// <summary>
/// Current sensor mode. Can be switched by user verbs.
/// </summary>
[DataField("mode")]
public SuitSensorMode Mode = SuitSensorMode.SensorOff;
/// <summary>
/// Activate sensor if user wear it in this slot.
/// </summary>
[DataField("activationSlot")]
public EquipmentSlotDefines.Slots ActivationSlot = EquipmentSlotDefines.Slots.INNERCLOTHING;
/// <summary>
/// How often does sensor update its owners status (in seconds).
/// </summary>
[DataField("updateRate")]
public float UpdateRate = 2f;
/// <summary>
/// Current user that wears suit sensor. Null if nobody wearing it.
/// </summary>
public EntityUid? User = null;
/// <summary>
/// Last time when sensor updated owners status
/// </summary>
public TimeSpan LastUpdate = TimeSpan.Zero;
}
}

View file

@ -0,0 +1,312 @@
using System;
using Content.Server.Access.Systems;
using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Components;
using Content.Server.DeviceNetwork.Systems;
using Content.Server.Popups;
using Content.Shared.ActionBlocker;
using Content.Shared.Damage;
using Content.Shared.Examine;
using Content.Shared.Inventory;
using Content.Shared.Medical.SuitSensor;
using Content.Shared.MobState.Components;
using Content.Shared.Verbs;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.Medical.SuitSensors
{
public class SuitSensorSystem : EntitySystem
{
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IdCardSystem _idCardSystem = default!;
[Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
private const float UpdateRate = 0.5f;
private float _updateDif;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SuitSensorComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<SuitSensorComponent, EquippedEvent>(OnEquipped);
SubscribeLocalEvent<SuitSensorComponent, UnequippedEvent>(OnUnequipped);
SubscribeLocalEvent<SuitSensorComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<SuitSensorComponent, GetInteractionVerbsEvent>(OnVerb);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
// check update rate
_updateDif += frameTime;
if (_updateDif < UpdateRate)
return;
_updateDif -= UpdateRate;
var curTime = _gameTiming.CurTime;
var sensors = EntityManager.EntityQuery<SuitSensorComponent, DeviceNetworkComponent>();
foreach (var (sensor, device) in sensors)
{
// check if sensor is ready to update
if (curTime - sensor.LastUpdate < TimeSpan.FromSeconds(sensor.UpdateRate))
continue;
sensor.LastUpdate = curTime;
// get sensor status
var status = GetSensorState(sensor.Owner, sensor);
if (status == null)
continue;
// broadcast it to device network
var payload = SuitSensorToPacket(status);
_deviceNetworkSystem.QueuePacket(sensor.Owner, DeviceNetworkConstants.NullAddress, device.Frequency, payload, true);
}
}
private void OnMapInit(EntityUid uid, SuitSensorComponent component, MapInitEvent args)
{
// generate random mode
if (component.RandomMode)
{
//make the sensor mode favor higher levels, except coords.
var modesDist = new[]
{
SuitSensorMode.SensorOff,
SuitSensorMode.SensorBinary, SuitSensorMode.SensorBinary,
SuitSensorMode.SensorVitals, SuitSensorMode.SensorVitals, SuitSensorMode.SensorVitals,
SuitSensorMode.SensorCords, SuitSensorMode.SensorCords
};
component.Mode = _random.Pick(modesDist);
}
}
private void OnEquipped(EntityUid uid, SuitSensorComponent component, EquippedEvent args)
{
if (args.Slot != component.ActivationSlot)
return;
component.User = args.User;
}
private void OnUnequipped(EntityUid uid, SuitSensorComponent component, UnequippedEvent args)
{
if (args.Slot != component.ActivationSlot)
return;
component.User = null;
}
private void OnExamine(EntityUid uid, SuitSensorComponent component, ExaminedEvent args)
{
if (!args.IsInDetailsRange)
return;
string msg;
switch (component.Mode)
{
case SuitSensorMode.SensorOff:
msg = "suit-sensor-examine-off";
break;
case SuitSensorMode.SensorBinary:
msg = "suit-sensor-examine-binary";
break;
case SuitSensorMode.SensorVitals:
msg = "suit-sensor-examine-vitals";
break;
case SuitSensorMode.SensorCords:
msg = "suit-sensor-examine-cords";
break;
default:
return;
}
args.PushMarkup(Loc.GetString(msg));
}
private void OnVerb(EntityUid uid, SuitSensorComponent component, GetInteractionVerbsEvent args)
{
// check if user can change sensor
if (component.ControlsLocked)
return;
// standard interaction checks
if (!args.CanAccess || !args.CanInteract || !_actionBlockerSystem.CanDrop(args.User))
return;
args.Verbs.UnionWith(new[]
{
CreateVerb(uid, component, args.User, SuitSensorMode.SensorOff),
CreateVerb(uid, component, args.User, SuitSensorMode.SensorBinary),
CreateVerb(uid, component, args.User, SuitSensorMode.SensorVitals),
CreateVerb(uid, component, args.User, SuitSensorMode.SensorCords)
});
}
private Verb CreateVerb(EntityUid uid, SuitSensorComponent component, EntityUid userUid, SuitSensorMode mode)
{
return new Verb()
{
Text = GetModeName(mode),
Disabled = component.Mode == mode,
Priority = -(int) mode, // sort them in descending order
Category = VerbCategory.SetSensor,
Act = () => SetSensor(uid, mode, userUid, component)
};
}
private string GetModeName(SuitSensorMode mode)
{
string name;
switch (mode)
{
case SuitSensorMode.SensorOff:
name = "suit-sensor-mode-off";
break;
case SuitSensorMode.SensorBinary:
name = "suit-sensor-mode-binary";
break;
case SuitSensorMode.SensorVitals:
name = "suit-sensor-mode-vitals";
break;
case SuitSensorMode.SensorCords:
name = "suit-sensor-mode-cords";
break;
default:
return "";
}
return Loc.GetString(name);
}
public void SetSensor(EntityUid uid, SuitSensorMode mode, EntityUid? userUid = null,
SuitSensorComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
component.Mode = mode;
if (userUid != null)
{
var msg = Loc.GetString("suit-sensor-mode-state", ("mode", GetModeName(mode)));
_popupSystem.PopupEntity(msg, uid, Filter.Entities(userUid.Value));
}
}
public SuitSensorStatus? GetSensorState(EntityUid uid, SuitSensorComponent? sensor = null, TransformComponent? transform = null)
{
if (!Resolve(uid, ref sensor, ref transform))
return null;
// check if sensor is enabled and worn by user
if (sensor.Mode == SuitSensorMode.SensorOff || sensor.User == null)
return null;
// try to get mobs id from ID slot
var userName = Loc.GetString("suit-sensor-component-unknown-name");
var userJob = Loc.GetString("suit-sensor-component-unknown-job");
if (_idCardSystem.TryGetIdCardSlot(sensor.User.Value, out var card))
{
if (card.FullName != null)
userName = card.FullName;
if (card.JobTitle != null)
userJob = card.JobTitle;
}
// get health mob state
var isAlive = false;
if (EntityManager.TryGetComponent(sensor.User.Value, out MobStateComponent? mobState))
{
isAlive = mobState.IsAlive();
}
// get mob total damage
var totalDamage = 0;
if (EntityManager.TryGetComponent(sensor.User.Value, out DamageableComponent? damageable))
{
totalDamage = damageable.TotalDamage.Int();
}
// finally, form suit sensor status
var status = new SuitSensorStatus(userName, userJob);
switch (sensor.Mode)
{
case SuitSensorMode.SensorBinary:
status.IsAlive = isAlive;
break;
case SuitSensorMode.SensorVitals:
status.IsAlive = isAlive;
status.TotalDamage = totalDamage;
break;
case SuitSensorMode.SensorCords:
status.IsAlive = isAlive;
status.TotalDamage = totalDamage;
status.Coordinates = transform.MapPosition;
break;
}
return status;
}
/// <summary>
/// Serialize suit sensor status into device network package.
/// </summary>
public NetworkPayload SuitSensorToPacket(SuitSensorStatus status)
{
var payload = new NetworkPayload()
{
[DeviceNetworkConstants.Command] = DeviceNetworkConstants.CmdUpdatedState,
[SuitSensorConstants.NET_NAME] = status.Name,
[SuitSensorConstants.NET_JOB] = status.Job,
[SuitSensorConstants.NET_IS_ALIVE] = status.IsAlive,
};
if (status.TotalDamage != null)
payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE, status.TotalDamage);
if (status.Coordinates != null)
payload.Add(SuitSensorConstants.NET_CORDINATES, status.Coordinates);
return payload;
}
/// <summary>
/// Try to deserialize device network message into suit sensor status
/// </summary>
public SuitSensorStatus? PacketToSuitSensor(NetworkPayload payload)
{
// check command
if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command))
return null;
if (command != DeviceNetworkConstants.CmdUpdatedState)
return null;
// check name, job and alive
if (!payload.TryGetValue(SuitSensorConstants.NET_NAME, out string? name)) return null;
if (!payload.TryGetValue(SuitSensorConstants.NET_JOB, out string? job)) return null;
if (!payload.TryGetValue(SuitSensorConstants.NET_IS_ALIVE, out bool? isAlive)) return null;
// try get total damage and cords (optionals)
payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE, out int? totalDamage);
payload.TryGetValue(SuitSensorConstants.NET_CORDINATES, out MapCoordinates? cords);
var status = new SuitSensorStatus(name, job)
{
IsAlive = isAlive.Value,
TotalDamage = totalDamage,
Coordinates = cords
};
return status;
}
}
}

View file

@ -64,7 +64,7 @@ namespace Content.Server.Spawners.Components
public override void MapInit()
{
Spawn();
_entMan.DeleteEntity(Owner);
_entMan.QueueDeleteEntity(Owner);
}
}
}

View file

@ -193,12 +193,18 @@ namespace Content.Server.Storage.Components
return false;
}
return true;
var @event = new StorageOpenAttemptEvent();
IoCManager.Resolve<IEntityManager>().EventBus.RaiseLocalEvent(Owner, @event);
return !@event.Cancelled;
}
public virtual bool CanClose(EntityUid user, bool silent = false)
{
return true;
var @event = new StorageCloseAttemptEvent();
IoCManager.Resolve<IEntityManager>().EventBus.RaiseLocalEvent(Owner, @event);
return !@event.Cancelled;
}
public void ToggleOpen(EntityUid user)
@ -479,4 +485,14 @@ namespace Content.Server.Storage.Components
}
}
}
public sealed class StorageOpenAttemptEvent : CancellableEntityEventArgs
{
}
public sealed class StorageCloseAttemptEvent : CancellableEntityEventArgs
{
}
}

View file

@ -18,6 +18,7 @@ namespace Content.Shared.Actions
DebugTargetEntity,
DebugTargetEntityRepeat,
SpellPie,
ManifestGuardian,
PAIMidi
}

View file

@ -29,8 +29,6 @@ namespace Content.Shared.Buckle.Components
public EntityUid? LastEntityBuckledTo { get; set; }
public bool IsOnStrapEntityThisFrame { get; set; }
public bool DontCollide { get; set; }
public abstract bool TryBuckle(EntityUid user, EntityUid to);

View file

@ -57,11 +57,13 @@ namespace Content.Shared.Buckle.Components
}
[Serializable, NetSerializable]
public enum StrapVisuals
public enum StrapVisuals : byte
{
RotationAngle
RotationAngle,
BuckledState
}
// TODO : Convert this to an Entity Message. Careful, it will Break ShuttleControllerComponent (only place where it's used)
[Serializable, NetSerializable]
#pragma warning disable 618
public abstract class StrapChangeMessage : ComponentMessage

View file

@ -59,7 +59,6 @@ namespace Content.Shared.Buckle
{
if (args.BodyB.Owner != component.LastEntityBuckledTo) return;
component.IsOnStrapEntityThisFrame = true;
if (component.Buckled || component.DontCollide)
{
args.Cancel();

View file

@ -615,11 +615,14 @@ namespace Content.Shared.Hands.Components
protected bool CanInsertEntityIntoHand(Hand hand, EntityUid entity)
{
var handContainer = hand.Container;
if (handContainer == null)
return false;
if (handContainer == null) return false;
if (!handContainer.CanInsert(entity))
return false;
if (!handContainer.CanInsert(entity)) return false;
var @event = new AttemptItemPickupEvent();
_entMan.EventBus.RaiseLocalEvent(entity, @event);
if (@event.Cancelled) return false;
return true;
}

View file

@ -0,0 +1,26 @@
using JetBrains.Annotations;
using Lidgren.Network;
using Robust.Shared.Network;
namespace Content.Shared.Info;
public abstract class SharedRulesManager
{
[UsedImplicitly]
public sealed class ShowRulesPopupMessage : NetMessage
{
public override MsgGroups MsgGroup => MsgGroups.Command;
public float PopupTime { get; set; }
public override void ReadFromBuffer(NetIncomingMessage buffer)
{
PopupTime = buffer.ReadFloat();
}
public override void WriteToBuffer(NetOutgoingMessage buffer)
{
buffer.Write(PopupTime);
}
}
}

View file

@ -2,6 +2,9 @@
namespace Content.Shared.Item
{
/// <summary>
/// Raised on a *mob* when it tries to pickup something
/// </summary>
public class PickupAttemptEvent : CancellableEntityEventArgs
{
public PickupAttemptEvent(EntityUid uid)
@ -11,4 +14,14 @@ namespace Content.Shared.Item
public EntityUid Uid { get; }
}
/// <summary>
/// Raised on the *item* when tried to be picked up
/// </summary>
/// <remarks>
/// Doesn't just handle "items" but calling it "PickedUpAttempt" is too close to "Pickup" for the sleep deprived brain.
/// </remarks>
public sealed class AttemptItemPickupEvent : CancellableEntityEventArgs
{
}
}

View file

@ -9,7 +9,6 @@ using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;

View file

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using Content.Shared.FixedPoint;
using Content.Shared.Medical.SuitSensor;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Serialization;
namespace Content.Shared.Medical.CrewMonitoring
{
[Serializable, NetSerializable]
public enum CrewMonitoringUIKey
{
Key
}
[Serializable, NetSerializable]
public class CrewMonitoringState : BoundUserInterfaceState
{
public List<SuitSensorStatus> Sensors;
public CrewMonitoringState(List<SuitSensorStatus> sensors)
{
Sensors = sensors;
}
}
}

View file

@ -0,0 +1,58 @@
using System;
using Content.Shared.FixedPoint;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
namespace Content.Shared.Medical.SuitSensor
{
[Serializable, NetSerializable]
public class SuitSensorStatus
{
public SuitSensorStatus(string name, string job)
{
Name = name;
Job = job;
}
public TimeSpan Timestamp;
public string Name;
public string Job;
public bool IsAlive;
public int? TotalDamage;
public MapCoordinates? Coordinates;
}
[Serializable, NetSerializable]
public enum SuitSensorMode : byte
{
/// <summary>
/// Sensor doesn't send any information about owner
/// </summary>
SensorOff = 0,
/// <summary>
/// Sensor sends only binary status (alive/dead)
/// </summary>
SensorBinary = 1,
/// <summary>
/// Sensor sends health vitals status
/// </summary>
SensorVitals = 2,
/// <summary>
/// Sensor sends vitals status and GPS position
/// </summary>
SensorCords = 3
}
public static class SuitSensorConstants
{
public const string NET_NAME = "name";
public const string NET_JOB = "job";
public const string NET_IS_ALIVE = "alive";
public const string NET_TOTAL_DAMAGE = "vitals";
public const string NET_CORDINATES = "cords";
}
}

View file

@ -5,6 +5,7 @@ using System.Linq;
using Content.Shared.Alert;
using Content.Shared.Damage;
using Content.Shared.FixedPoint;
using Content.Shared.MobState.EntitySystems;
using Content.Shared.MobState.State;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
@ -318,7 +319,6 @@ namespace Content.Shared.MobState.Components
var message = new MobStateChangedEvent(this, old, state);
entMan.EventBus.RaiseLocalEvent(Owner, message);
Dirty();
}
}

View file

@ -111,7 +111,7 @@ namespace Content.Shared.MobState.EntitySystems
private void OnStartPullAttempt(EntityUid uid, MobStateComponent component, StartPullAttemptEvent args)
{
if(component.IsIncapacitated())
if (component.IsIncapacitated())
args.Cancel();
}
@ -135,7 +135,7 @@ namespace Content.Shared.MobState.EntitySystems
private void OnStandAttempt(EntityUid uid, MobStateComponent component, StandAttemptEvent args)
{
if(component.IsIncapacitated())
if (component.IsIncapacitated())
args.Cancel();
}
}

View file

@ -57,5 +57,7 @@ namespace Content.Shared.Verbs
public static readonly VerbCategory Split =
new("verb-categories-split", null);
public static readonly VerbCategory SetSensor = new("verb-categories-set-sensor", null);
}
}

Binary file not shown.

Binary file not shown.

View file

@ -10,3 +10,7 @@ adminhelp.ogg taken from https://github.com/tgstation/tgstation/blob/d775e1ac804
The sound had been reversed and the volume altered.
voteding.ogg taken from "Bike, Bell Ding, Single, 01-01.wav" by InspectorJ (www.jshaw.co.uk) of Freesound.org at https://freesound.org/people/InspectorJ/sounds/484344/ under CC BY 3.0. The volume has been reduced.
poster_broken.ogg taken from https://github.com/tgstation/tgstation/blob/2834383245d2129a106acef3afd17b81e1e64777/sound/items/poster_ripped.ogg
poster_being_set.ogg taken from https://github.com/tgstation/tgstation/blob/2834383245d2129a106acef3afd17b81e1e64777/sound/items/poster_ripped.ogg

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,24 +1,4 @@
Entries:
- author: Seth
changes:
- {message: Added Makeshift Handcuffs into the construction list, type: Add}
id: 283
time: '2021-07-25T15:30:42.0000000+00:00'
- author: metalgearsloth
changes:
- {message: Shuttle-relative movement for when we get shuttle rotation., type: Add}
id: 284
time: '2021-07-25T15:48:22.0000000+00:00'
- author: Seth
changes:
- {message: Added Storage Room to Coats, type: Add}
id: 285
time: '2021-07-26T05:36:27.0000000+00:00'
- author: Seth
changes:
- {message: Added a hardsuit into research directors locker, type: Add}
id: 286
time: '2021-07-26T22:14:10.0000000+00:00'
- author: Seth
changes:
- {message: Added bio suits into bio lockers, type: Add}
@ -2872,3 +2852,30 @@ Entries:
- {message: Chaplain no longer spawns with an empty backpack., type: Fix}
id: 784
time: '2021-12-29T02:48:58.0000000+00:00'
- author: CrudeWax
changes:
- {message: Syndiecorp Cybersun invites you to try out their magnum opus- holoparasites!
Look for the "Utility" section in your uplink. And ghosts - be on the lookout
for a role prompt!, type: Add}
id: 785
time: '2021-12-29T03:58:49.0000000+00:00'
- author: Topy
changes:
- {message: Add rollerbeds to roll people around!, type: Add}
id: 786
time: '2021-12-29T04:57:21.0000000+00:00'
- author: Macoron
changes:
- {message: 'Added suit sensors and crew monitoring console in code. They are not
mapped, but you can print circuit board.', type: Add}
- {message: Prisoners suit has locked suit sensor that always broadcasting their
position., type: Add}
id: 787
time: '2021-12-29T05:19:01.0000000+00:00'
- author: Rane
changes:
- {message: Pill canisters are now available in the protolathe after researching
chemistry technology., type: Add}
- {message: Pill canisters now fit inside medical belts., type: Tweak}
id: 788
time: '2021-12-29T06:21:17.0000000+00:00'

View file

@ -0,0 +1,5 @@
# Foldable
foldable-deploy-fail = You can't deploy the {$object} here.
fold-verb = Fold
unfold-verb = Unfold

View file

@ -0,0 +1,22 @@
## Guardian host specific
guardian-created = You feel... Haunted.
guardian-missing-invalid-action = There is no guardian under your control!
guardian-already-present-invalid-creation = You are NOT re-living that haunting experience!
guardian-no-actions-invalid-creation = You don't have the ability to host a guardian!
guardian-activator-empty-invalid-creation = The injector is spent.
guardian-activator-empty-examine = [color=#ba1919]The injector is spent.[/color]
guardian-no-soul = Your guardian has no soul.
guardian-available = Your guardian now has a soul.
## Guardian entity specific
guardian-entity-recall = The guardian vanishes into thin air!
guardian-entity-taking-damage = Your guardian is taking damage!
## Health warnings
guardian-host-critical-warn = YOUR HOST IS WOUNDED!
guardian-host-death-warn = YOUR FORM SUCCUMBS TO NONEXISTENCE!
guardian-death-warn = YOUR BODY IS PIERCED BY SUBATOMIC PAIN AS IT DISINTEGRATES!

View file

@ -0,0 +1,13 @@
## UI
crew-monitoring-user-interface-title = Crew Monitoring
crew-monitoring-user-interface-name = Name
crew-monitoring-user-interface-status = Status
crew-monitoring-user-interface-location = Location
crew-monitoring-user-interface-alive = Alive
crew-monitoring-user-interface-dead = Dead
crew-monitoring-user-interface-no-info = N/A

View file

@ -0,0 +1,21 @@
## Modes
suit-sensor-mode-off = Off
suit-sensor-mode-binary = Binary
suit-sensor-mode-vitals = Vitals
suit-sensor-mode-cords = Coordinates
## Popups
suit-sensor-mode-state = Suit sensors: {$mode}
## Components
suit-sensor-component-unknown-name = Unknown
suit-sensor-component-unknown-job = No job
## Examine
suit-sensor-examine-off = Its sensors appear to be [color=darkred]disabled[/color].
suit-sensor-examine-binary = Its binary life sensors appear to be enabled.
suit-sensor-examine-vitals = Its vital tracker appears to be enabled.
suit-sensor-examine-cords = Its vital tracker and tracking beacon appear to be enabled.

View file

@ -18,6 +18,7 @@ verb-categories-unbuckle = Unbuckle
verb-categories-rotate = Rotate
verb-categories-transfer = Set Transfer Amount
verb-categories-split = Split
verb-categories-set-sensor = Sensor
verb-common-toggle-light = Toggle light
verb-common-close = Close

View file

@ -0,0 +1,12 @@
#This is the action the host gets to control the guardian
- type: action
actionType: ManifestGuardian
icon: Interface/Actions/manifest.png
name: "Toggle guardian manifestation"
description: "Either manifests the guardian or recalls it back into your body"
filters:
- guardian
behaviorType: Instant
behavior: !type:ToggleGuardianAction
cooldown: 2

View file

@ -16,3 +16,32 @@
Higher will burn the engine out and eventually make it explode. Don't.
Don't forget to refuel it, it tends to stop at the worst possible time.
- type: entity
parent: BaseItem
id: HoloparasiteInfo
name: "Holoparasite terms and conditions"
description: A tiny volumetric display for documents, makes one wonder if Cybersun's legal budget is way too high.
components:
- type: Sprite
netsync: false
sprite: Objects/Misc/guardian_info.rsi
state: guardian_info
- type: UserInterface
interfaces:
- key: enum.PaperUiKey.Key
type: PaperBoundUserInterface
- type: Paper
content: |
Thanks for choosing our holoparasite package!
At cybersun, we pride ourselves on cutting-edge military and industrial technology, and greatly appreciate your contribution to our establishment!
Guardians are helpful and intelligent beings which nest within your body, completely immune to common hazards such as pressure, temperature and even bullets!
You have purchased the holoparasite package, which contains a holoparasite activator, an instruction booklet, and our softcap merchandise.
Instructions for use:
1. Activate the holoparasite injector (preferably in a secluded area).
2. Wait for the tingling and/or painful metaphysical sensation.
3. Check your holoparasite for the ability to communicate and cooperate, and capacity to understand your orders.
4. Use your recall-manifest ability to summon or recall the holoparasite back into your body.
5. Keep the holoparasite within a short distance from yourself, otherwise it will be recalled by force!
WARNING: Guardians are metaphysical beings, but draw from your HEALTH in order to exist. Direct damage done to guardians will be partially transferred to you!
Cybersun inc. is not responsible for complete annihilation following the misuse of Holoparasite technology.

View file

@ -96,6 +96,7 @@
- CryostasisBeaker
- Dropper
- Syringe
- PillCanister
- type: technology
name: "medical machinery"
@ -113,6 +114,7 @@
- MedicalScannerMachineCircuitboard
- ChemMasterMachineCircuitboard
- ChemDispenserMachineCircuitboard
- CrewMonitoringComputerCircuitboard
# Security Technology Tree

View file

@ -107,6 +107,18 @@
price: 2
icon: /Textures/Objects/Weapons/Guns/Ammunition/SpeedLoaders/Magnum/magnum_sl.rsi/icon.png
#Utility
- type: uplinkListing
id: UplinkHoloparaKit
category: Utility
itemId: BoxHoloparasite
listingName: Holoparasite Kit
description: The pride and joy of Cybersun. Contains an injector that hosts a sentient metaphysical guardian made of hard light which resides in the user's body when not active. The guardian can punch rapidly and is immune to hazardous environments and bullets, but shares any damage it takes with the user.
icon: /Textures/Objects/Misc/guardian_info.rsi/icon.png
price: 16
# Bundles
- type: uplinkListing
@ -209,6 +221,8 @@
id: UplinkDecoyDisk
category: Misc
itemId: NukeDiskFake
listingName: Decoy nuclear disk
description: A piece of plastic with a lenticular printing, made to look like a nuclear auth disk.
price: 1
- type: uplinkListing
@ -235,16 +249,14 @@
itemId: lanternextrabright
price: 2
# Pointless
- type: uplinkListing
id: UplinkCostumeCentcom
category: Pointless
category: Misc
itemId: ClothingBackpackDuffelSyndicateCostumeCentcom
price: 4
- type: uplinkListing
id: UplinkCostumeClown
category: Pointless
category: Misc
itemId: ClothingBackpackDuffelSyndicateCostumeClown
price: 4

View file

@ -237,6 +237,7 @@
- Ointment
- CigPack
- Pill
- PillCanister
components:
- Hypospray
- SurgeryTool
@ -255,6 +256,7 @@
whitelist:
tags:
- Pill
- PillCanister
bottle_spray:
whitelist:
tags:

View file

@ -239,3 +239,25 @@
sprite: Clothing/Head/Soft/yellowsoft_flipped.rsi
- type: Clothing
sprite: Clothing/Head/Soft/yellowsoft_flipped.rsi
- type: entity
parent: ClothingHeadBase
id: ClothingHeadHatBizarreSoft
name: troublemaker's soft
description: A truly.. bizarre accessory.
components:
- type: Sprite
sprite: Clothing/Head/Soft/bizarresoft.rsi
- type: Clothing
sprite: Clothing/Head/Soft/bizarresoft.rsi
- type: entity
parent: ClothingHeadBase
id: ClothingHeadHatBizarreSoftFlipped
name: troublemaker's soft flipped
description: A truly.. bizarre accessory, flipped.
components:
- type: Sprite
sprite: Clothing/Head/Soft/bizarresoft_flipped.rsi
- type: Clothing
sprite: Clothing/Head/Soft/bizarresoft_flipped.rsi

View file

@ -1,6 +1,17 @@
- type: entity
abstract: true
parent: Clothing
id: ClothingWithSuitSensor
components:
- type: SuitSensor
- type: DeviceNetworkComponent
deviceNetId: Wireless
- type: WirelessNetworkConnection
range: 500
- type: entity
abstract: true
parent: ClothingWithSuitSensor
id: ClothingUniformBase
components:
- type: Sprite
@ -17,7 +28,7 @@
- type: entity
abstract: true
parent: Clothing
parent: ClothingWithSuitSensor
id: ClothingUniformSkirtBase
components:
- type: Sprite

View file

@ -232,6 +232,10 @@
sprite: Clothing/Uniforms/Jumpskirt/prisoner.rsi
- type: Clothing
sprite: Clothing/Uniforms/Jumpskirt/prisoner.rsi
- type: SuitSensor
controlsLocked: true
randomMode: false
mode: SensorCords
- type: entity
parent: ClothingUniformSkirtBase

View file

@ -310,6 +310,10 @@
sprite: Clothing/Uniforms/Jumpsuit/prisoner.rsi
- type: Clothing
sprite: Clothing/Uniforms/Jumpsuit/prisoner.rsi
- type: SuitSensor
controlsLocked: true
randomMode: false
mode: SensorCords
- type: entity
parent: ClothingUniformBase

View file

@ -0,0 +1,131 @@
- type: entity
parent: MarkerBase
id: RandomPosterAny
name: random poster spawner
components:
- type: Sprite
layers:
- state: red
- texture: Structures/Wallmounts/posters.rsi/random_anything.png
- type: RandomSpawner
offset: 0
prototypes:
- RandomPosterContraband
- RandomPosterLegit
chance: 0.95
rarePrototypes:
- PosterBroken
rareChance: 0.05
- type: entity
parent: MarkerBase
id: RandomPosterContraband
name: random contraband poster spawner
components:
- type: Sprite
layers:
- state: red
- texture: Structures/Wallmounts/posters.rsi/random_contraband.png
- type: RandomSpawner
offset: 0
prototypes:
- PosterContrabandFreeTonto
- PosterContrabandAtmosiaDeclarationIndependence
- PosterContrabandFunPolice
- PosterContrabandLustyXenomorph
- PosterContrabandSyndicateRecruitment
- PosterContrabandClown
- PosterContrabandSmoke
- PosterContrabandGreyTide
- PosterContrabandMissingGloves
- PosterContrabandHackingGuide
- PosterContrabandRIPBadger
- PosterContrabandAmbrosiaVulgaris
- PosterContrabandDonutCorp.
- PosterContrabandEAT.
- PosterContrabandTools
- PosterContrabandPower
- PosterContrabandSpaceCube
- PosterContrabandCommunistState
- PosterContrabandLamarr
- PosterContrabandBorgFancy
- PosterContrabandBorgFancyv2
- PosterContrabandKosmicheskayaStantsiya
- PosterContrabandRebelsUnite
- PosterContrabandC20r
- PosterContrabandHaveaPuff
- PosterContrabandRevolver
- PosterContrabandDDayPromo
- PosterContrabandSyndicatePistol
- PosterContrabandEnergySwords
- PosterContrabandRedRum
- PosterContrabandCC64KAd
- PosterContrabandPunchShit
- PosterContrabandTheGriffin
- PosterContrabandFreeDrone
- PosterContrabandBustyBackdoorXenoBabes6
- PosterContrabandRobustSoftdrinks
- PosterContrabandShamblersJuice
- PosterContrabandPwrGame
- PosterContrabandSunkist
- PosterContrabandSpaceCola
- PosterContrabandSpaceUp
- PosterContrabandKudzu
- PosterContrabandMaskedMen
- PosterContrabandUnreadableAnnouncement
- PosterContrabandFreeSyndicateEncryptionKey
- PosterContrabandBountyHunters
- PosterContrabandTheBigGasTruth
- PosterContrabandGotWood
chance: 1
- type: entity
parent: MarkerBase
id: RandomPosterLegit
name: random legit poster spawner
components:
- type: Sprite
layers:
- state: red
- texture: Structures/Wallmounts/posters.rsi/random_legit.png
- type: RandomSpawner
offset: 0
prototypes:
- PosterLegitHereForYourSafety
- PosterLegitNanotrasenLogo
- PosterLegitCleanliness
- PosterLegitHelpOthers
- PosterLegitBuild
- PosterLegitBlessThisSpess
- PosterLegitScience
- PosterLegitIan
- PosterLegitObey
- PosterLegitWalk
- PosterLegitStateLaws
- PosterLegitLoveIan
- PosterLegitSpaceCops
- PosterLegitUeNo
- PosterLegitGetYourLEGS
- PosterLegitDoNotQuestion
- PosterLegitWorkForAFuture
- PosterLegitSoftCapPopArt
- PosterLegitSafetyInternals
- PosterLegitSafetyEyeProtection
- PosterLegitSafetyReport
- PosterLegitReportCrimes
- PosterLegitIonRifle
- PosterLegitFoamForceAd
- PosterLegitCohibaRobustoAd
- PosterLegit50thAnniversaryVintageReprint
- PosterLegitFruitBowl
- PosterLegitPDAAd
- PosterLegitEnlist
- PosterLegitNanomichiAd
- PosterLegit12Gauge
- PosterLegitHighClassMartini
- PosterLegitTheOwl
- PosterLegitNoERP
- PosterLegitCarbonDioxide
- PosterLegitDickGumshue
- PosterLegitThereIsNoGasGiant
chance: 1

View file

@ -1,6 +1,8 @@
- type: entity
id: MarkerBase
abstract: true
placement:
mode: SnapgridCenter
components:
- type: Transform
anchored: true
@ -12,5 +14,3 @@
# If serialization was cool this would work.
# layers:
# - state: blue
placement:
mode: SnapgridCenter

View file

@ -0,0 +1,73 @@
# Does not inherit from simplemob
- type: entity
name: Holoparasite
id: MobHoloparasite
description: A mesmerising whirl of hard-light patterns weaves a marvelous, yet oddly familiar visage. It stands proud, tuning into its owner's life to sustain itself.
components:
- type: GhostTakeoverAvailable
makeSentient: true
name: Holoparasite
description: Listen to your owner. Don't tank damage. Punch people hard.
- type: Input
context: "human"
- type: PlayerMobMover
- type: PlayerInputMover
- type: MovementSpeedModifier
baseWalkSpeed : 7
baseSprintSpeed : 7
- type: DamageOnHighSpeedImpact
damage:
types:
Blunt: 5
soundHit:
path: /Audio/Effects/hit_kick.ogg
# TODO: Randomise sprites and randomise the layer color
- type: Sprite
drawdepth: Mobs
sprite: Mobs/Aliens/Guardians/guardians.rsi
layers:
- state: tech_base
- state: tech_flare
color: "#40a7d7"
shader: unshaded
noRot: true
- type: Clickable
- type: InteractionOutline
- type: Physics
bodyType: KinematicController
- type: Fixtures
fixtures:
- shape:
!type:PhysShapeCircle
radius: 0.35
mass: 10
mask:
- Impassable
- SmallImpassable
- MobImpassable
layer:
- Opaque
- type: Damageable
damageContainer: Biological
- type: MobState
thresholds:
0: !type:NormalMobState {}
- type: HeatResistance
- type: CombatMode
- type: Internals
- type: Examiner
- type: Speech
- type: Pullable
- type: UnarmedCombat
range: 2
arcwidth: 30
arc: fist
cooldownTime: 0.7
arcCooldownTime: 0.7
damage:
types:
Blunt: 40
- type: Actions
innateActions:
- CombatMode
- type: Guardian

View file

@ -35,6 +35,14 @@
- type: ComputerBoard
prototype: ComputerResearchAndDevelopment
- type: entity
parent: BaseComputerCircuitboard
id: CrewMonitoringComputerCircuitboard
name: crew monitoring console board
components:
- type: ComputerBoard
prototype: ComputerCrewMonitoring
- type: entity
parent: BaseComputerCircuitboard
id: IDComputerCircuitboard

View file

@ -0,0 +1,26 @@
- type: entity
name: holoparasite injector
id: HoloparasiteInjector
parent: BaseItem
description: A complex artwork of handheld machinery allowing the user to host a holoparasite guardian.
components:
- type: Sprite
sprite: Objects/Specific/Medical/hypospray.rsi
state: combat_hypo
netsync: false
- type: GuardianCreator
guardianProto: MobHoloparasite
- type: entity
name: holoparasite box
parent: BoxBase
id: BoxHoloparasite
description: A box containing a holoparasite injector
components:
- type: StorageFill
contents:
- id: HoloparasiteInjector
- id: HoloparasiteInfo
- type: Sprite
layers:
- state: box

View file

@ -9,6 +9,7 @@
sprite: Objects/Specific/Medical/Morgue/bodybags.rsi
layers:
- state: bag
map: ["enum.FoldableVisualLayers.Base"]
- state: open_overlay
map: ["enum.StorageVisualLayers.Door"]
- state: label_overlay
@ -27,13 +28,18 @@
!type:PhysShapeAabb
bounds: "-0.5,-0.45,0.5,0.1"
mass: 5
mask:
- Impassable
- SmallImpassable
- type: BodyBagEntityStorage
CanWeldShut: false
Capacity: 1
IsCollidableWhenOpen: true
closeSound:
path: /Audio/Misc/zip.ogg
openSound:
path: /Audio/Misc/zip.ogg
- type: Foldable
- type: PaperLabel
labelSlot:
insertVerbText: Attach Label
@ -45,20 +51,20 @@
visuals:
- type: StorageVisualizer
state_open: open_overlay
state_closed: bag
- type: FoldableVisualizer
key: bag
- type: BodyBagVisualizer
- type: Pullable
- type: entity
id: BodyBag_Item
id: BodyBag_Folded
name: body bag
description: A plastic bag designed for the storage and transportation of cadavers.
parent: BaseItem
parent: BodyBag_Container
suffix: Folded
components:
- type: Sprite
netsync: false
sprite: Objects/Specific/Medical/Morgue/bodybags.rsi
state: item
- type: Foldable
folded: true
# - type: BodyBagItem #TODO: we need some kind of generic placable, like thus:
# - type: Placeable
# prototype: someId

View file

@ -257,6 +257,9 @@
state: pill_canister
- type: Item
sprite: Objects/Specific/Chemistry/pills_canister.rsi
- type: Tag
tags:
- PillCanister
- type: Storage
capacity: 12
quickInsert: true

View file

@ -24,6 +24,7 @@
noRot: true
- type: Strap
position: Stand
buckleOffset: "0,0.15"
- type: Pullable
- type: Damageable
damageContainer: Inorganic

View file

@ -0,0 +1,94 @@
- type: entity
id: RollerBed
parent: BaseItem
name: rollerbed
description: Used to carry patients around without damaging them.
components:
- type: Transform
noRot: true
- type: Item
size: 5
- type: Sprite
sprite: Structures/Furniture/rollerbeds.rsi
netsync: false
noRot: true
layers:
- state: rollerbed
map: ["enum.FoldableVisualLayers.Base"]
- type: MovedByPressure
- type: DamageOnHighSpeedImpact
soundHit: /Audio/Effects/bang.ogg
- type: InteractionOutline
- type: Physics
- type: Fixtures
fixtures:
- shape:
!type:PhysShapeCircle
radius: 0.35
mass: 30
mask:
- Impassable
- SmallImpassable
layer:
- Opaque
- type: Damageable
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 50
behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]
- !type:SpawnEntitiesBehavior
spawn:
SheetSteel1:
min: 1
max: 1
- type: Pullable
- type: Strap
position: Down
rotation: -90
buckleOffset: "0,0.15"
unbuckleOffset: "0,0.15"
- type: Foldable
- type: Appearance
visuals:
- type: FoldableVisualizer
key: rollerbed
- type: RollerbedVisualizer
key: rollerbed
- type: entity
id: CheapRollerBed
name: rollerbed
parent: RollerBed
description: A run-down rollerbed. Used to carry patients around.
components:
- type: Sprite
layers:
- state: cheap_rollerbed
map: [ "enum.FoldableVisualLayers.Base" ]
- type: Appearance
visuals:
- type: FoldableVisualizer
key: cheap_rollerbed
- type: RollerbedVisualizer
key: cheap_rollerbed
- type: entity
id: EmergencyRollerBed
name: rollerbed
parent: RollerBed
description: A robust looking rollerbed used for emergencies.
components:
- type: Sprite
layers:
- state: emergency_rollerbed
map: [ "enum.FoldableVisualLayers.Base" ]
- type: Appearance
visuals:
- type: FoldableVisualizer
key: emergency_rollerbed
- type: RollerbedVisualizer
key: emergency_rollerbed

View file

@ -88,6 +88,36 @@
energy: 1.6
color: "#1f8c28"
- type: entity
parent: ComputerBase
id: ComputerCrewMonitoring
name: crew monitoring console
description: Used to monitor active health sensors built into most of the crew's uniforms.
components:
- type: Appearance
visuals:
- type: ComputerVisualizer
key: med_key
screen: crew
- type: PointLight
radius: 1.5
energy: 1.6
color: "#006400"
- type: Computer
board: CrewMonitoringComputerCircuitboard
- type: ActivatableUI
key: enum.CrewMonitoringUIKey.Key
- type: ActivatableUIRequiresPower
- type: UserInterface
interfaces:
- key: enum.CrewMonitoringUIKey.Key
type: CrewMonitoringBoundUserInterface
- type: CrewMonitoringConsole
- type: DeviceNetworkComponent
deviceNetId: Wireless
- type: WirelessNetworkConnection
range: 500
- type: entity
parent: ComputerBase
id: ComputerResearchAndDevelopment

View file

@ -160,6 +160,7 @@
- CryostasisBeaker
- Dropper
- Syringe
- PillCanister
- Flash
- Handcuffs
- Stunbaton
@ -183,6 +184,7 @@
- SolarControlComputerCircuitboard
- AutolatheMachineCircuitboard
- ProtolatheMachineCircuitboard
- CrewMonitoringComputerCircuitboard
- Bucket
- MopItem
- SprayBottle

View file

@ -8,6 +8,9 @@
visuals:
- type: StorageVisualizer
state: cabinet
state_open: cabinet_open
state_closed: cabinet_door
- type: AccessReader
access: [["Bar"]]
- type: EntityStorage
@ -26,6 +29,8 @@
visuals:
- type: StorageVisualizer
state: qm
state_open: qm_open
state_closed: qm_door
- type: AccessReader
access: [["Cargo"]] # TODO access [["Quartermaster"]]
@ -39,6 +44,8 @@
visuals:
- type: StorageVisualizer
state: cap
state_open: cap_open
state_closed: cap_door
- type: AccessReader
access: [["Captain"]]
@ -51,6 +58,8 @@
visuals:
- type: StorageVisualizer
state: hop
state_open: hop_open
state_closed: hop_door
- type: AccessReader
access: [["HeadOfPersonnel"]]
@ -64,6 +73,8 @@
visuals:
- type: StorageVisualizer
state: ce
state_open: ce_open
state_closed: ce_door
- type: AccessReader
access: [ [ "Engineering", "Command" ] ]
@ -77,6 +88,7 @@
visuals:
- type: StorageVisualizer
state: eng
state_open: eng_open
state_closed: eng_elec_door
- type: AccessReader
access: [ [ "Engineering" ] ]
@ -91,6 +103,7 @@
visuals:
- type: StorageVisualizer
state: eng
state_open: eng_open
state_closed: eng_weld_door
- type: AccessReader
access: [ [ "Engineering" ] ]
@ -105,6 +118,8 @@
visuals:
- type: StorageVisualizer
state: atmos
state_open: atmos_open
state_closed: atmos_door
- type: AccessReader
access: [ [ "Engineering" ] ]
@ -118,6 +133,8 @@
visuals:
- type: StorageVisualizer
state: eng_secure
state_open: eng_secure_open
state_closed: eng_secure_door
- type: AccessReader
access: [ [ "Engineering" ] ]
@ -131,6 +148,8 @@
visuals:
- type: StorageVisualizer
state: freezer
state_open: freezer_open
state_closed: freezer_door
- type: AccessReader
access: [ [ "Service" ] ]
@ -144,6 +163,8 @@
visuals:
- type: StorageVisualizer
state: hydro
state_open: hydro_open
state_closed: hydro_door
- type: AccessReader
access: [ [ "Service" ] ]
@ -158,6 +179,8 @@
visuals:
- type: StorageVisualizer
state: med
state_open: med_open
state_closed: med_door
- type: AccessReader
access: [ [ "Medical" ] ]
@ -171,6 +194,8 @@
visuals:
- type: StorageVisualizer
state: med_secure
state_open: med_secure_open
state_closed: med_secure_door
- type: AccessReader
access: [ [ "Medical" ] ]
@ -184,6 +209,7 @@
visuals:
- type: StorageVisualizer
state: med
state_open: med_open
state_closed: chemical_door
- type: AccessReader
access: [ [ "Chemistry" ] ]
@ -198,6 +224,8 @@
visuals:
- type: StorageVisualizer
state: cmo
state_open: cmo_open
state_closed: cmo_door
- type: AccessReader
access: [ [ "Medical", "Command" ] ]
@ -211,6 +239,8 @@
visuals:
- type: StorageVisualizer
state: rd
state_open: rd_open
state_closed: rd_door
- type: AccessReader
access: [ [ "Research", "Command" ] ]
@ -223,6 +253,8 @@
visuals:
- type: StorageVisualizer
state: science
state_open: science_open
state_closed: science_door
- type: AccessReader
access: [ [ "Research" ] ]
@ -236,6 +268,8 @@
visuals:
- type: StorageVisualizer
state: hos
state_open: hos_open
state_closed: hos_door
- type: AccessReader
access: [["Security", "Command"]]
@ -249,6 +283,8 @@
visuals:
- type: StorageVisualizer
state: warden
state_open: warden_open
state_closed: warden_door
- type: AccessReader
access: [["Security", "Armory"]] # TODO access [["Brig"]]
@ -262,6 +298,8 @@
visuals:
- type: StorageVisualizer
state: sec
state_open: sec_open
state_closed: sec_door
- type: AccessReader
access: [["Security"]]
@ -286,3 +324,5 @@
visuals:
- type: StorageVisualizer
state: syndicate
state_open: syndicate_open
state_closed: syndicate_door

View file

@ -65,5 +65,6 @@
- type: Appearance
visuals:
- type: StorageVisualizer
state: generic
state_open: generic_open
state_closed: generic_door

View file

@ -9,6 +9,7 @@
visuals:
- type: StorageVisualizer
state: eng
state_open: eng_open
state_closed: eng_tool_door
# Radiation suit closet
@ -22,6 +23,7 @@
visuals:
- type: StorageVisualizer
state: eng
state_open: eng_open
state_closed: eng_rad_door
# Emergency closet
@ -35,6 +37,8 @@
visuals:
- type: StorageVisualizer
state: emergency
state_open: emergency_open
state_closed: emergency_door
# Fire safety closet
- type: entity
@ -47,6 +51,8 @@
visuals:
- type: StorageVisualizer
state: fire
state_open: fire_open
state_closed: fire_door
# EOD closet
- type: entity
@ -59,6 +65,8 @@
visuals:
- type: StorageVisualizer
state: bomb
state_open: bomb_open
state_closed: bomb_door
# Biohazard
@ -73,6 +81,8 @@
visuals:
- type: StorageVisualizer
state: bio
state_open: bio_sec_open
false: bio_sec_door
# Virology variant
- type: entity
@ -83,6 +93,8 @@
visuals:
- type: StorageVisualizer
state: bio_viro
state_open: bio_viro_open
state_closed: bio_viro_door
# Security variant
- type: entity
@ -93,6 +105,8 @@
visuals:
- type: StorageVisualizer
state: bio_sec
state_open: bio_sec_open
state_closed: bio_sec_door
# Janitor variant
- type: entity
@ -103,11 +117,13 @@
visuals:
- type: StorageVisualizer
state: bio_jan
state_open: bio_jan_open
state_closed: bio_jan_door
# Maintenance closet
- type: entity
id: ClosetMaintenance
name: maintenance closet
name: maintenance closet
parent: ClosetBase
description: It's a storage unit.
components:

View file

@ -17,6 +17,7 @@
visuals:
- type: StorageVisualizer
state: generic
state_open: generic_open
state_closed: blue_door
# Pink wardrobe
@ -29,6 +30,7 @@
visuals:
- type: StorageVisualizer
state: generic
state_open: generic_open
state_closed: pink_door
# Black wardrobe
@ -41,6 +43,7 @@
visuals:
- type: StorageVisualizer
state: generic
state_open: generic_open
state_closed: black_door
# Green wardrobe
@ -53,6 +56,7 @@
visuals:
- type: StorageVisualizer
state: generic
state_open: generic_open
state_closed: green_door
# Prison wardrobe
@ -65,6 +69,7 @@
visuals:
- type: StorageVisualizer
state: generic
state_open: generic_open
state_closed: orange_door
# Yellow wardrobe
@ -77,6 +82,7 @@
visuals:
- type: StorageVisualizer
state: generic
state_open: generic_open
state_closed: yellow_door
# White wardrobe
@ -89,6 +95,7 @@
visuals:
- type: StorageVisualizer
state: generic
state_open: generic_open
state_closed: white_door
# Grey wardrobe
@ -101,6 +108,7 @@
visuals:
- type: StorageVisualizer
state: generic
state_open: generic_open
state_closed: grey_door
# Mixed wardrobe
@ -113,6 +121,7 @@
visuals:
- type: StorageVisualizer
state: generic
state_open: generic_open
state_closed: mixed_door
# Jobs
@ -126,6 +135,7 @@
visuals:
- type: StorageVisualizer
state: generic
state_open: generic_open
state_closed: red_door
- type: entity
@ -137,6 +147,7 @@
visuals:
- type: StorageVisualizer
state: generic
state_open: generic_open
state_closed: atmos_wardrobe_door
- type: entity

View file

@ -2,6 +2,8 @@
id: BaseSign
name: base sign
abstract: true
placement:
mode: SnapgridCenter
components:
- type: Clickable
- type: InteractionOutline

View file

@ -0,0 +1,814 @@
- type: entity
parent: BaseSign
id: PosterBase
abstract: true
components:
- type: Sprite
drawdepth: WallTops
sprite: Structures/Wallmounts/posters.rsi
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 5
behaviors:
- !type:PlaySoundBehavior
sound:
path: /Audio/Effects/poster_broken.ogg
- !type:DoActsBehavior
acts: [ "Destruction" ]
- !type:SpawnEntitiesBehavior
spawn:
PosterBroken:
min: 1
max: 1
offset: 0
- type: entity
parent: BaseSign
id: PosterBroken
name: broken poster
description: "You can't make out anything from the poster's original print. It's ruined."
components:
- type: Sprite
drawdepth: WallTops
sprite: Structures/Wallmounts/posters.rsi
state: poster_broken
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 5
behaviors:
- !type:PlaySoundBehavior
sound:
path: /Audio/Effects/poster_broken.ogg
- !type:DoActsBehavior
acts: [ "Destruction" ]
# Contraband
- type: entity
parent: PosterBase
id: PosterContrabandFreeTonto
name: "Free Tonto"
description: "A salvaged shred of a much larger flag, colors bled together and faded from age."
components:
- type: Sprite
state: poster1_contraband
- type: entity
parent: PosterBase
id: PosterContrabandAtmosiaDeclarationIndependence
name: "Atmosia Declaration of Independence"
description: "A relic of a failed rebellion."
components:
- type: Sprite
state: poster2_contraband
- type: entity
parent: PosterBase
id: PosterContrabandFunPolice
name: "Fun Police"
description: "A poster condemning the station's security forces."
components:
- type: Sprite
state: poster3_contraband
- type: entity
parent: PosterBase
id: PosterContrabandLustyXenomorph
name: "Lusty Xenomorph"
description: "A heretical poster depicting the titular star of an equally heretical book."
components:
- type: Sprite
state: poster4_contraband
- type: entity
parent: PosterBase
id: PosterContrabandSyndicateRecruitment
name: "Syndicate Recruitment"
description: "See the galaxy! Shatter corrupt megacorporations! Join today!"
components:
- type: Sprite
state: poster5_contraband
- type: entity
parent: PosterBase
id: PosterContrabandClown
name: "Clown"
description: "Honk."
components:
- type: Sprite
state: poster6_contraband
- type: entity
parent: PosterBase
id: PosterContrabandSmoke
name: "Smoke"
description: "A poster advertising a rival corporate brand of cigarettes."
components:
- type: Sprite
state: poster7_contraband
- type: entity
parent: PosterBase
id: PosterContrabandGreyTide
name: "Grey Tide"
description: "A rebellious poster symbolizing assistant solidarity."
components:
- type: Sprite
state: poster8_contraband
- type: entity
parent: PosterBase
id: PosterContrabandMissingGloves
name: "Missing Gloves"
description: "This poster references the uproar that followed Nanotrasen's financial cuts toward insulated-glove purchases."
components:
- type: Sprite
state: poster9_contraband
- type: entity
parent: PosterBase
id: PosterContrabandHackingGuide
name: "Hacking Guide"
description: "This poster details the internal workings of the common Nanotrasen airlock. Sadly, it appears out of date."
components:
- type: Sprite
state: poster10_contraband
- type: entity
parent: PosterBase
id: PosterContrabandRIPBadger
name: "RIP Badger"
description: "This seditious poster references Nanotrasen's genocide of a space station full of badgers."
components:
- type: Sprite
state: poster11_contraband
- type: entity
parent: PosterBase
id: PosterContrabandAmbrosiaVulgaris
name: "Ambrosia Vulgaris"
description: "This poster is lookin' pretty trippy man."
components:
- type: Sprite
state: poster12_contraband
- type: entity
parent: PosterBase
id: PosterContrabandDonutCorp.
name: "Donut Corp."
description: "This poster is an unauthorized advertisement for Donut Corp."
components:
- type: Sprite
state: poster13_contraband
- type: entity
parent: PosterBase
id: PosterContrabandEAT.
name: "EAT."
description: "This poster promotes rank gluttony."
components:
- type: Sprite
state: poster14_contraband
- type: entity
parent: PosterBase
id: PosterContrabandTools
name: "Tools"
description: "This poster looks like an advertisement for tools, but is in fact a subliminal jab at the tools at CentCom."
components:
- type: Sprite
state: poster15_contraband
- type: entity
parent: PosterBase
id: PosterContrabandPower
name: "Power"
description: "A poster that positions the seat of power outside Nanotrasen."
components:
- type: Sprite
state: poster16_contraband
- type: entity
parent: PosterBase
id: PosterContrabandSpaceCube
name: "Space Cube"
description: "Ignorant of Nature's Harmonic 6 Side Space Cube Creation, the Spacemen are Dumb, Educated Singularity Stupid and Evil."
components:
- type: Sprite
state: poster17_contraband
- type: entity
parent: PosterBase
id: PosterContrabandCommunistState
name: "Communist State"
description: "All hail the Communist party!"
components:
- type: Sprite
state: poster18_contraband
- type: entity
parent: PosterBase
id: PosterContrabandLamarr
name: "Lamarr"
description: "This poster depicts Lamarr. Probably made by a traitorous Research Director."
components:
- type: Sprite
state: poster19_contraband
- type: entity
parent: PosterBase
id: PosterContrabandBorgFancy
name: "Borg Fancy"
description: "Being fancy can be for any borg, just need a suit."
components:
- type: Sprite
state: poster20_contraband
- type: entity
parent: PosterBase
id: PosterContrabandBorgFancyv2
name: "Borg Fancy v2"
description: "Borg Fancy, Now only taking the most fancy."
components:
- type: Sprite
state: poster21_contraband
- type: entity
parent: PosterBase
id: PosterContrabandKosmicheskayaStantsiya
name: "Kosmicheskaya Stantsiya 13 Does Not Exist"
description: "A poster mocking CentCom's denial of the existence of the derelict station near Space Station 13."
components:
- type: Sprite
state: poster22_contraband
- type: entity
parent: PosterBase
id: PosterContrabandRebelsUnite
name: "Rebels Unite"
description: "A poster urging the viewer to rebel against Nanotrasen."
components:
- type: Sprite
state: poster23_contraband
- type: entity
parent: PosterBase
id: PosterContrabandC20r
name: "C-20r"
description: "A poster advertising the Scarborough Arms C-20r."
components:
- type: Sprite
state: poster24_contraband
- type: entity
parent: PosterBase
id: PosterContrabandHaveaPuff
name: "Have a Puff"
description: "Who cares about lung cancer when you're high as a kite?"
components:
- type: Sprite
state: poster25_contraband
- type: entity
parent: PosterBase
id: PosterContrabandRevolver
name: "Revolver"
description: "Because seven shots are all you need."
components:
- type: Sprite
state: poster26_contraband
- type: entity
parent: PosterBase
id: PosterContrabandDDayPromo
name: "D-Day Promo"
description: "A promotional poster for some rapper."
components:
- type: Sprite
state: poster27_contraband
- type: entity
parent: PosterBase
id: PosterContrabandSyndicatePistol
name: "Syndicate Pistol"
description: "A poster advertising syndicate pistols as being 'classy as fuck'. It is covered in faded gang tags."
components:
- type: Sprite
state: poster28_contraband
- type: entity
parent: PosterBase
id: PosterContrabandEnergySwords
name: "Energy Swords"
description: "All the colors of the bloody murder rainbow."
components:
- type: Sprite
state: poster29_contraband
- type: entity
parent: PosterBase
id: PosterContrabandRedRum
name: "Red Rum"
description: "Looking at this poster makes you want to kill."
components:
- type: Sprite
state: poster30_contraband
- type: entity
parent: PosterBase
id: PosterContrabandCC64KAd
name: "CC 64K Ad"
description: "The latest portable computer from Comrade Computing, with a whole 64kB of ram!"
components:
- type: Sprite
state: poster31_contraband
- type: entity
parent: PosterBase
id: PosterContrabandPunchShit
name: "Punch Shit"
description: "Fight things for no reason, like a man!"
components:
- type: Sprite
state: poster32_contraband
- type: entity
parent: PosterBase
id: PosterContrabandTheGriffin
name: "The Griffin"
description: "The Griffin commands you to be the worst you can be. Will you?"
components:
- type: Sprite
state: poster33_contraband
- type: entity
parent: PosterBase
id: PosterContrabandFreeDrone
name: "Free Drone"
description: "This poster commemorates the bravery of the rogue drone; once exiled, and then ultimately destroyed by CentCom."
components:
- type: Sprite
state: poster35_contraband
- type: entity
parent: PosterBase
id: PosterContrabandBustyBackdoorXenoBabes6
name: "Busty Backdoor Xeno Babes 6"
description: "Get a load, or give, of these all natural Xenos!"
components:
- type: Sprite
state: poster36_contraband
- type: entity
parent: PosterBase
id: PosterContrabandRobustSoftdrinks
name: "Robust Softdrinks"
description: "Robust Softdrinks: More robust than a toolbox to the head!"
components:
- type: Sprite
state: poster37_contraband
- type: entity
parent: PosterBase
id: PosterContrabandShamblersJuice
name: "Shambler's Juice"
description: "~Shake me up some of that Shambler's Juice!~"
components:
- type: Sprite
state: poster38_contraband
- type: entity
parent: PosterBase
id: PosterContrabandPwrGame
name: "Pwr Game"
description: "The POWER that gamers CRAVE! In partnership with Vlad's Salad."
components:
- type: Sprite
state: poster39_contraband
- type: entity
parent: PosterBase
id: PosterContrabandSunkist
name: "Sun-kist"
description: "Drink the stars!"
components:
- type: Sprite
state: poster40_contraband
- type: entity
parent: PosterBase
id: PosterContrabandSpaceCola
name: "Space Cola"
description: "Your favorite cola, in space."
components:
- type: Sprite
state: poster41_contraband
- type: entity
parent: PosterBase
id: PosterContrabandSpaceUp
name: "Space-Up!"
description: "Sucked out into space by the FLAVOR!"
components:
- type: Sprite
state: poster42_contraband
- type: entity
parent: PosterBase
id: PosterContrabandKudzu
name: "Kudzu"
description: "A poster advertising a movie about plants. How dangerous could they possibly be?"
components:
- type: Sprite
state: poster43_contraband
- type: entity
parent: PosterBase
id: PosterContrabandMaskedMen
name: "Masked Men"
description: "A poster advertising a movie about some masked men."
components:
- type: Sprite
state: poster44_contraband
- type: entity
parent: PosterBase
id: PosterContrabandUnreadableAnnouncement
name: "Unreadable Announcement"
description: "A poster announcing something by someone, oddly enough they seem to have forgotten making it readable"
components:
- type: Sprite
state: poster45_contraband
- type: entity
parent: PosterBase
id: PosterContrabandFreeSyndicateEncryptionKey
name: "Free Syndicate Encryption Key"
description: "A poster about traitors begging for more."
components:
- type: Sprite
state: poster46_contraband
- type: entity
parent: PosterBase
id: PosterContrabandBountyHunters
name: "Bounty Hunters"
description: "A poster advertising bounty hunting services. \"I hear you got a problem.\""
components:
- type: Sprite
state: poster47_contraband
- type: entity
parent: PosterBase
id: PosterContrabandTheBigGasTruth
name: "The Big Gas Giant Truth"
description: "Don't believe everything you see on a poster, patriots. All the lizards at central command don't want to answer this SIMPLE QUESTION: WHERE IS THE GAS MINER MINING FROM, CENTCOM?"
components:
- type: Sprite
state: poster48_contraband
- type: entity
parent: PosterBase
id: PosterContrabandGotWood
name: "Got Wood?"
description: "A grimy old advert for a seedy lumber company. \"You got a friend in me.\" is scrawled in the corner."
components:
- type: Sprite
state: poster49_contraband
# Legit
- type: entity
parent: PosterBase
id: PosterLegitHereForYourSafety
name: "Here For Your Safety"
description: "A poster glorifying the station's security force."
components:
- type: Sprite
state: poster1_legit
- type: entity
parent: PosterBase
id: PosterLegitNanotrasenLogo
name: "Nanotrasen Logo"
description: "A poster depicting the Nanotrasen logo."
components:
- type: Sprite
state: poster2_legit
- type: entity
parent: PosterBase
id: PosterLegitCleanliness
name: "Cleanliness"
description: "A poster warning of the dangers of poor hygiene."
components:
- type: Sprite
state: poster3_legit
- type: entity
parent: PosterBase
id: PosterLegitHelpOthers
name: "Help Others"
description: "A poster encouraging you to help fellow crewmembers."
components:
- type: Sprite
state: poster4_legit
- type: entity
parent: PosterBase
id: PosterLegitBuild
name: "Build"
description: "A poster glorifying the engineering team."
components:
- type: Sprite
state: poster5_legit
- type: entity
parent: PosterBase
id: PosterLegitBlessThisSpess
name: "Bless This Spess"
description: "A poster blessing this area."
components:
- type: Sprite
state: poster6_legit
- type: entity
parent: PosterBase
id: PosterLegitScience
name: "Science"
description: "A poster depicting an atom."
components:
- type: Sprite
state: poster7_legit
- type: entity
parent: PosterBase
id: PosterLegitIan
name: "Ian"
description: "Arf arf. Yap."
components:
- type: Sprite
state: poster8_legit
- type: entity
parent: PosterBase
id: PosterLegitObey
name: "Obey"
description: "A poster instructing the viewer to obey authority."
components:
- type: Sprite
state: poster9_legit
- type: entity
parent: PosterBase
id: PosterLegitWalk
name: "Walk"
description: "A poster instructing the viewer to walk instead of running."
components:
- type: Sprite
state: poster10_legit
- type: entity
parent: PosterBase
id: PosterLegitStateLaws
name: "State Laws"
description: "A poster instructing cyborgs to state their laws."
components:
- type: Sprite
state: poster11_legit
- type: entity
parent: PosterBase
id: PosterLegitLoveIan
name: "Love Ian"
description: "Ian is love, Ian is life."
components:
- type: Sprite
state: poster12_legit
- type: entity
parent: PosterBase
id: PosterLegitSpaceCops
name: "Space Cops."
description: "A poster advertising the television show Space Cops."
components:
- type: Sprite
state: poster13_legit
- type: entity
parent: PosterBase
id: PosterLegitUeNo
name: "Ue No."
description: "This thing is all in Japanese."
components:
- type: Sprite
state: poster14_legit
- type: entity
parent: PosterBase
id: PosterLegitGetYourLEGS
name: "Get Your LEGS"
description: "LEGS: Leadership, Experience, Genius, Subordination."
components:
- type: Sprite
state: poster15_legit
- type: entity
parent: PosterBase
id: PosterLegitDoNotQuestion
name: "Do Not Question"
description: "A poster instructing the viewer not to ask about things they aren't meant to know."
components:
- type: Sprite
state: poster16_legit
- type: entity
parent: PosterBase
id: PosterLegitWorkForAFuture
name: "Work For A Future"
description: " A poster encouraging you to work for your future."
components:
- type: Sprite
state: poster17_legit
- type: entity
parent: PosterBase
id: PosterLegitSoftCapPopArt
name: "Soft Cap Pop Art"
description: "A poster reprint of some cheap pop art."
components:
- type: Sprite
state: poster18_legit
- type: entity
parent: PosterBase
id: PosterLegitSafetyInternals
name: "Safety: Internals"
description: "A poster instructing the viewer to wear internals in the rare environments where there is no oxygen or the air has been rendered toxic."
components:
- type: Sprite
state: poster19_legit
- type: entity
parent: PosterBase
id: PosterLegitSafetyEyeProtection
name: "Safety: Eye Protection"
description: "A poster instructing the viewer to wear eye protection when dealing with chemicals, smoke, or bright lights."
components:
- type: Sprite
state: poster20_legit
- type: entity
parent: PosterBase
id: PosterLegitSafetyReport
name: "Safety: Report"
description: "A poster instructing the viewer to report suspicious activity to the security force."
components:
- type: Sprite
state: poster21_legit
- type: entity
parent: PosterBase
id: PosterLegitReportCrimes
name: "Report Crimes"
description: "A poster encouraging the swift reporting of crime or seditious behavior to station security."
components:
- type: Sprite
state: poster22_legit
- type: entity
parent: PosterBase
id: PosterLegitIonRifle
name: "Ion Rifle"
description: "A poster displaying an Ion Rifle."
components:
- type: Sprite
state: poster23_legit
- type: entity
parent: PosterBase
id: PosterLegitFoamForceAd
name: "Foam Force Ad"
description: "Foam Force, it's Foam or be Foamed!"
components:
- type: Sprite
state: poster24_legit
- type: entity
parent: PosterBase
id: PosterLegitCohibaRobustoAd
name: "Cohiba Robusto Ad"
description: "Cohiba Robusto, the classy cigar."
components:
- type: Sprite
state: poster25_legit
- type: entity
parent: PosterBase
id: PosterLegit50thAnniversaryVintageReprint
name: "50th Anniversary Vintage Reprint"
description: "A reprint of a poster from 2505, commemorating the 50th Anniversary of Nanoposters Manufacturing, a subsidiary of Nanotrasen."
components:
- type: Sprite
state: poster26_legit
- type: entity
parent: PosterBase
id: PosterLegitFruitBowl
name: "Fruit Bowl"
description: " Simple, yet awe-inspiring."
components:
- type: Sprite
state: poster27_legit
- type: entity
parent: PosterBase
id: PosterLegitPDAAd
name: "PDA Ad"
description: "A poster advertising the latest PDA from Nanotrasen suppliers."
components:
- type: Sprite
state: poster28_legit
- type: entity
parent: PosterBase
id: PosterLegitEnlist
name: "Enlist"
description: "Enlist in the Nanotrasen Deathsquadron reserves today!"
components:
- type: Sprite
state: poster29_legit
- type: entity
parent: PosterBase
id: PosterLegitNanomichiAd
name: "Nanomichi Ad"
description: " A poster advertising Nanomichi brand audio cassettes."
components:
- type: Sprite
state: poster30_legit
- type: entity
parent: PosterBase
id: PosterLegit12Gauge
name: "12 Gauge"
description: "A poster boasting about the superiority of 12 gauge shotgun shells."
components:
- type: Sprite
state: poster31_legit
- type: entity
parent: PosterBase
id: PosterLegitHighClassMartini
name: "High-Class Martini"
description: "I told you to shake it, no stirring."
components:
- type: Sprite
state: poster32_legit
- type: entity
parent: PosterBase
id: PosterLegitTheOwl
name: "The Owl"
description: "The Owl would do his best to protect the station. Will you?"
components:
- type: Sprite
state: poster33_legit
- type: entity
parent: PosterBase
id: PosterLegitNoERP
name: "No ERP"
description: "This poster reminds the crew that Eroticism and Pornography are banned on Nanotrasen stations."
components:
- type: Sprite
state: poster34_legit
- type: entity
parent: PosterBase
id: PosterLegitCarbonDioxide
name: "Carbon Dioxide"
description: "This informational poster teaches the viewer what carbon dioxide is."
components:
- type: Sprite
state: poster35_legit
- type: entity
parent: PosterBase
id: PosterLegitDickGumshue
name: "Dick Gumshue"
description: "A poster advertising the escapades of Dick Gumshue, mouse detective. Encouraging crew to bring the might of justice down upon wire saboteurs."
components:
- type: Sprite
state: poster36_legit
- type: entity
parent: PosterBase
id: PosterLegitThereIsNoGasGiant
name: "There Is No Gas Giant"
description: "Nanotrasen has issued posters, like this one, to all stations reminding them that rumours of a gas giant are false."
components:
- type: Sprite
state: poster37_legit

View file

@ -1,8 +1,3 @@
# These signs would not have collision otherwise, they simply
# need collision in order for them to be destructible. Once the
# SmallImpassable etc. stuff actually gets implemented in this
# shitheap of a videogame bother swept to go back and fix these.
- type: entity
parent: BaseSign
id: PaintingMonkey

View file

@ -48,3 +48,11 @@
completetime: 500
materials:
Glass: 200
- type: latheRecipe
id: PillCanister
icon: Objects/Specific/Chemistry/pills_canister.rsi/pill_canister.png
result: PillCanister
completetime: 500
materials:
Plastic: 100

View file

@ -73,9 +73,9 @@
Steel: 200
- type: latheRecipe
id: BodyBag_Item
icon: Objects/Specific/Medical/Morgue/bodybags.rsi/item.png
result: BodyBag_Item
id: BodyBag_Folded
icon: Objects/Specific/Medical/Morgue/bodybags.rsi/bag_folded.png
result: BodyBag_Folded
completetime: 300
materials:
Plastic: 200

View file

@ -180,6 +180,9 @@
- type: Tag
id: Pill
- type: Tag
id: PillCanister
- type: Tag
id: Pipe

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

View file

@ -0,0 +1,26 @@
{
"version": 1,
"license": "CC-BY-NC-SA-3.0",
"copyright": "Taken from civstation at commit https://github.com/Civ13/Civ13/commit/ec52cbb95d59b717d4d8c480b35ac133e5b58088#diff-fba188fb2db5d16e5d41985147336a8f96085f761b903e016fffd869b63e497d",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "equipped-HELMET",
"directions": 4
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 B

View file

@ -0,0 +1,26 @@
{
"version": 1,
"license": "CC-BY-NC-SA-3.0",
"copyright": "Taken from civstation at commit https://github.com/Civ13/Civ13/commit/ec52cbb95d59b717d4d8c480b35ac133e5b58088#diff-fba188fb2db5d16e5d41985147336a8f96085f761b903e016fffd869b63e497d",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "equipped-HELMET",
"directions": 4
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -21,6 +21,9 @@
},
{
"name": "harm"
},
{
"name": "manifest"
}
]
}

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