Add the trash man (#1367)

* Add disposal.rsi

* Rename disposal resource to disposal.rsi and create basic components

* Add disposal nets

* Add pushing entities along the disposal network

* Add disposal unit

* Unregister disposable component

* Add flush and selfinsert verbs to disposal unit

* Add gradual disposals movement

* Fix being able to walk through space for a while after exiting disposals

* Multiply disposals speed by 10

And fix early returns when moving an entity

* Rename Disposable component to InDisposals

* Remove DisposalNet and add on anchor events

* Remove anchored events, moved to interfaces

* Code cleanup

* Fix adjacent tubes' connections when a tube connects

* Fix jittery movement in disposals

* Remove Logger.Debug call

* Move disposals updates to InDisposalsComponent

* Fix adjacent connection valid directions check

* Disposal tubes now throw you out where they are facing

* Add disposal unit exit cooldown

* Set different disposal pipe sprite state depending on anchored value

* Add recycler

* Add recycler animation

* Add bloody texture to the recycler when grinding a living being

* Add PowerDevice component to the disposal unit

* Made the Recycler center on the grid

* Add disposal junction

* Add picking a random direction if junction is entered from the output side

* Add disposal flush and clang sounds

Taken from VGStation

* Move disposal flush and clang sound file names to exposedata

* Add disposalsmap.yml to test with

* Add summaries to DisposalUnit fields

* Add sideDegrees yaml property to disposal junctions

* Fix outdated usings

* Add conveyor resources

* Update RobustToolbox

* More merge fixes

Add conveyor collision masks

* Add ConveyorComponent

* Fix crash when reentering a body

* Merge branch 'master' into disposals-1147

* Reduce recycler bounds, set hard to false, add summary and expose "safe" to yaml

* Move IAnchored and IUnAnchored to AnchorableComponent

* Update power components and remove old disposals map

* Remove redundant sprite layers

* Add tile pry command

* Fix tilepry command

* Fix DisposalJunctionComponent missing a component reference

* Add anchor by radius command

* Add Y-Junctions

* Add disposal bend

* Add unanchor command

* Change DisposalJunction prototypes to specify their angles

* Fix disposal units being hidden below the floor

* Removed IAnhored and IUnAnchored interfaces

* Replace CanBeNull annotation with nullable reference types

* Update showwires command

* Add recycler recycling items

* Added angle and speed properties to ConveyorComponent

* Fix conveyort textures

* Add animation to the disposal unit

* Fix anchor and unanchor commands sometimes not finding any entities

* Fix not reading flush_time from disposal unit prototype

* Fix merge conflict wrong using

* Fix disposal, recycling and conveyor texture paths

Delete diverters

* Update visualizer names

* Add DisposableComponent, change drag and drop to work with multiple components

Ignoreinsideblocker client side for drag and drops, like on the server
Add more comments

* Add conveyor belts properly moving entities on top

* Anchorr wires

* Change conveyor bounds to 0.49

* Anchor catwalks, airlocks, gravity generators, low walls, wires and windows

* Add starting/stopping conveyors

* Add reversed conveyors

* Add conveyor switches

* Move InDisposalsComponent code to DisposableComponent

* Add ExitVector method to tubes

* Fix not updating tube references when disconnecting one

* Replace IoCManager call with dependency

* Add tubes disconnecting if they move too far apart from one another

* Move disposals action blocking to shared

* Add rotating and flipping pipes

* Make conveyor intersection calculations approximate

* Fix 1% chance of the server crashing when initializing the map

Happens when emergency lockers remove themselves

* Add disposal unit interface

* Make disposal units refuse items if not powered

* Make disposal tubes hide only when anchored

* Make disposal junction arrows visible to mere mortals

* Add disposal tubes breaking

* Add tubeconnections command

* Add missing verb attribute

* Add flipped disposal junction

* Add ids and linking to conveyors and switches

* Add conveyor switch prying and placing

* Add anchoring conveyor switches and refactor placing them

* Add missing serializable attributes from DisposableComponentState

* Make conveyor speed VV ReadWrite

* Change drawdepth of conveyors to FloorObjects

* Make conveyor anchored check consistent

* Remove anchoring interaction from switches

* Add conveyor switch id syncing and move switches slightly when pried

* Make entities in containers not able to be moved by conveyors

* Add conveyor and switches loose textures

* Merge conflict fixes

* Add disposal unit test

* Add flushing test to disposal unit test

* Add disposal unit flush fail test

* Add disposals to the saltern map

* Fix saltern disposal junctions

* Add power checks to the recycler

* Fix disposal unit placement in maintenance closet

* Remove disposal junctions from saltern

* Readd junctions to saltern

* Add the chemmaster to saltern at the request of Ike

* Move the chemistry disposal unit

* Fix casing of disposal flush sound

* More merge conflict fixes

* Fix a compiler warning.

* Remove popup invocation from buckle

* Remove showPopup parameter from InteractionChecks

* Remove unnecessary physics components

Fixes the physics system dying

* Replace PhysicsComponent usages with CollidableComponent

* Update existing code for the new controller system

* Change conveyors to use a VirtualController instead of teleporting the entity

* Remove visualizer 2d suffix and update physics code

* Transition code to new controller system

* Fix shuttles not moving

* Fix throwing

* Fix guns

* Change hands to use physics.Stop() and remove item fumble method

* Add syncing conveyor switches states

* Fix the recycler wanting to be a conveyor too hard

* Fix showwires > showsubfloor rename in mapping command

* Fix wifi air conveyors

* Fix test error

* Add showsubfloorforever command

Changes drawdepth of the relevant entities

* Disable opening the disposal unit interface while inside

* Add closing the disposal unit interface when getting inside

* Add closing the interface when the disposal unit component is removed

* Add removing entities on disposal unit component removal

* Delay disposal unit flush and fix serialization

* Implement pressure in disposal units

* Fix chain engaging a disposal unit

* Implement states to the disposal unit

* Fix missing imports from merge conflict

* Update Content.Server/GameObjects/Components/Conveyor/ConveyorComponent.cs

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>

* Address some reviews

* Fix za buildo

* Use container helper to detach disposables

* Make conveyors use the construction system

* Make conveyor groups and syncing sane

* Make flip flip

brave

* Add activate interface to conveyor switches

* Fix not removing the switch from its group when it's deleted

* Fix not registering conveyors and switches on initialize

* Stop using 0 as null

* Disconnect conveyors and switches when disposing of a group

* Make disposal units not able to be exited when flushing

* Make disposal units flush after a configurable 30 seconds

* Add handle and light layers to the disposal unit

* Merge engaging and flushing

* Update saltern.yml

* I love using 0 as null

* Make disposal unit visual layers make sense

* Remove duplicate remove method in disposal units and update light

* Replace DisposableComponent with disposal holders

* Fix disposal holders deleting their contents on deletion

* Account for disposal unit pressure in tests and make a failed flush autoengage

* Rename disposable to holder

* Fix junction connections

* Disable self insert and flush verbs when inside a disposal unit

* Fix spamming the engage button making the animation reset

* Make the recycler take materials into account properly

Fix cablestack1 not existing

* Merge conflict fixes

* Fix pipes not being saved anchored

* Change conveyors and groups to not use an id

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
This commit is contained in:
DrSmugleaf
2020-07-30 23:45:28 +02:00
committed by GitHub
parent 2ba86c6476
commit bda5ce655f
140 changed files with 19810 additions and 12029 deletions

View File

@@ -1,3 +1,4 @@
using Content.Client.GameObjects.Components;
using Content.Client.GameObjects.EntitySystems;
using Content.Client.Interfaces;
using Content.Shared.GameObjects.Components.Markers;
@@ -8,6 +9,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using DrawDepth = Content.Shared.GameObjects.DrawDepth;
namespace Content.Client.Commands
{
@@ -27,12 +29,12 @@ namespace Content.Client.Commands
}
}
internal sealed class ShowWiresCommand : IConsoleCommand
internal sealed class ShowSubFloor : IConsoleCommand
{
// ReSharper disable once StringLiteralTypo
public string Command => "showwires";
public string Description => "Makes wires always visible.";
public string Help => "";
public string Command => "showsubfloor";
public string Description => "Makes entities below the floor always visible.";
public string Help => $"Usage: {Command}";
public bool Execute(IDebugConsole console, params string[] args)
{
@@ -43,6 +45,32 @@ namespace Content.Client.Commands
}
}
internal sealed class ShowSubFloorForever : IConsoleCommand
{
// ReSharper disable once StringLiteralTypo
public string Command => "showsubfloorforever";
public string Description => "Makes entities below the floor always visible until the client is restarted.";
public string Help => $"Usage: {Command}";
public bool Execute(IDebugConsole console, params string[] args)
{
EntitySystem.Get<SubFloorHideSystem>()
.EnableAll = true;
var components = IoCManager.Resolve<IEntityManager>().ComponentManager
.EntityQuery<SubFloorHideComponent>();
foreach (var component in components)
{
if (component.Owner.TryGetComponent(out ISpriteComponent sprite))
{
sprite.DrawDepth = (int) DrawDepth.Overlays;
}
}
return false;
}
}
internal sealed class NotifyCommand : IConsoleCommand
{
@@ -76,7 +104,7 @@ namespace Content.Client.Commands
}
console.Commands["togglelight"].Execute(console);
console.Commands["showwires"].Execute(console);
console.Commands["showsubfloorforever"].Execute(console);
return true;
}

View File

@@ -0,0 +1,72 @@
using System;
using Content.Shared.GameObjects.Components.Conveyor;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components.Conveyor
{
[UsedImplicitly]
public class ConveyorSwitchVisualizer : AppearanceVisualizer
{
private string _stateForward;
private string _stateOff;
private string _stateReversed;
private string _stateLoose;
private void ChangeState(AppearanceComponent appearance)
{
if (!appearance.Owner.TryGetComponent(out ISpriteComponent sprite))
{
return;
}
appearance.TryGetData(ConveyorVisuals.State, out ConveyorState state);
var texture = state switch
{
ConveyorState.Off => _stateOff,
ConveyorState.Forward => _stateForward,
ConveyorState.Reversed => _stateReversed,
ConveyorState.Loose => _stateLoose,
_ => throw new ArgumentOutOfRangeException()
};
sprite.LayerSetState(0, texture);
}
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
_stateForward = node.GetNode("state_forward").AsString();
_stateOff = node.GetNode("state_off").AsString();
_stateReversed = node.GetNode("state_reversed").AsString();
_stateLoose = node.GetNode("state_loose").AsString();
}
public override void InitializeEntity(IEntity entity)
{
base.InitializeEntity(entity);
var appearance = entity.EnsureComponent<AppearanceComponent>();
ChangeState(appearance);
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (component.Owner.Deleted)
{
return;
}
ChangeState(component);
}
}
}

View File

@@ -0,0 +1,72 @@
using System;
using Content.Shared.GameObjects.Components.Conveyor;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components.Conveyor
{
[UsedImplicitly]
public class ConveyorVisualizer : AppearanceVisualizer
{
private string _stateRunning;
private string _stateStopped;
private string _stateReversed;
private string _stateLoose;
private void ChangeState(AppearanceComponent appearance)
{
if (!appearance.Owner.TryGetComponent(out ISpriteComponent sprite))
{
return;
}
appearance.TryGetData(ConveyorVisuals.State, out ConveyorState state);
var texture = state switch
{
ConveyorState.Off => _stateStopped,
ConveyorState.Forward => _stateRunning,
ConveyorState.Reversed => _stateReversed,
ConveyorState.Loose => _stateLoose,
_ => throw new ArgumentOutOfRangeException()
};
sprite.LayerSetState(0, texture);
}
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
_stateRunning = node.GetNode("state_running").AsString();
_stateStopped = node.GetNode("state_stopped").AsString();
_stateReversed = node.GetNode("state_reversed").AsString();
_stateLoose = node.GetNode("state_loose").AsString();
}
public override void InitializeEntity(IEntity entity)
{
base.InitializeEntity(entity);
var appearance = entity.EnsureComponent<AppearanceComponent>();
ChangeState(appearance);
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (component.Owner.Deleted)
{
return;
}
ChangeState(component);
}
}
}

View File

@@ -0,0 +1,63 @@
#nullable enable
using JetBrains.Annotations;
using Robust.Client.GameObjects.Components.UserInterface;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.Localization;
using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalUnitComponent;
namespace Content.Client.GameObjects.Components.Disposal
{
/// <summary>
/// Initializes a <see cref="DisposalUnitWindow"/> and updates it when new server messages are received.
/// </summary>
[UsedImplicitly]
public class DisposalUnitBoundUserInterface : BoundUserInterface
{
private DisposalUnitWindow? _window;
public DisposalUnitBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
{
}
private void ButtonPressed(UiButton button)
{
SendMessage(new UiButtonPressedMessage(button));
}
protected override void Open()
{
base.Open();
_window = new DisposalUnitWindow();
_window.OpenCenteredMinSize();
_window.OnClose += Close;
_window.Eject.OnPressed += _ => ButtonPressed(UiButton.Eject);
_window.Engage.OnPressed += _ => ButtonPressed(UiButton.Engage);
_window.Power.OnPressed += _ => ButtonPressed(UiButton.Power);
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (!(state is DisposalUnitBoundUserInterfaceState cast))
{
return;
}
_window?.UpdateState(cast);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_window?.Dispose();
}
}
}
}

View File

@@ -0,0 +1,10 @@
using Content.Shared.GameObjects.Components.Disposal;
using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components.Disposal
{
[RegisterComponent]
public class DisposalUnitComponent : SharedDisposalUnitComponent
{
}
}

View File

@@ -0,0 +1,164 @@
using System;
using JetBrains.Annotations;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Client.GameObjects.Components.Animations;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalUnitComponent;
namespace Content.Client.GameObjects.Components.Disposal
{
[UsedImplicitly]
public class DisposalUnitVisualizer : AppearanceVisualizer
{
private const string AnimationKey = "disposal_unit_animation";
private string _stateAnchored;
private string _stateUnAnchored;
private string _overlayCharging;
private string _overlayReady;
private string _overlayFull;
private string _overlayEngaged;
private string _stateFlush;
private Animation _flushAnimation;
private void ChangeState(AppearanceComponent appearance)
{
if (!appearance.TryGetData(Visuals.VisualState, out VisualState state))
{
return;
}
if (!appearance.Owner.TryGetComponent(out ISpriteComponent sprite))
{
return;
}
switch (state)
{
case VisualState.UnAnchored:
sprite.LayerSetState(DisposalUnitVisualLayers.Base, _stateUnAnchored);
break;
case VisualState.Anchored:
sprite.LayerSetState(DisposalUnitVisualLayers.Base, _stateAnchored);
break;
case VisualState.Flushing:
sprite.LayerSetState(DisposalUnitVisualLayers.Base, _stateAnchored);
var animPlayer = appearance.Owner.GetComponent<AnimationPlayerComponent>();
if (!animPlayer.HasRunningAnimation(AnimationKey))
{
animPlayer.Play(_flushAnimation, AnimationKey);
}
break;
default:
throw new ArgumentOutOfRangeException();
}
if (!appearance.TryGetData(Visuals.Handle, out HandleState handleState))
{
handleState = HandleState.Normal;
}
sprite.LayerSetVisible(DisposalUnitVisualLayers.Handle, handleState != HandleState.Normal);
switch (handleState)
{
case HandleState.Normal:
break;
case HandleState.Engaged:
sprite.LayerSetState(DisposalUnitVisualLayers.Handle, _overlayEngaged);
break;
default:
throw new ArgumentOutOfRangeException();
}
if (!appearance.TryGetData(Visuals.Light, out LightState lightState))
{
lightState = LightState.Off;
}
sprite.LayerSetVisible(DisposalUnitVisualLayers.Light, lightState != LightState.Off);
switch (lightState)
{
case LightState.Off:
break;
case LightState.Charging:
sprite.LayerSetState(DisposalUnitVisualLayers.Light, _overlayCharging);
break;
case LightState.Full:
sprite.LayerSetState(DisposalUnitVisualLayers.Light, _overlayFull);
break;
case LightState.Ready:
sprite.LayerSetState(DisposalUnitVisualLayers.Light, _overlayReady);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
_stateAnchored = node.GetNode("state_anchored").AsString();
_stateUnAnchored = node.GetNode("state_unanchored").AsString();
_overlayCharging = node.GetNode("overlay_charging").AsString();
_overlayReady = node.GetNode("overlay_ready").AsString();
_overlayFull = node.GetNode("overlay_full").AsString();
_overlayEngaged = node.GetNode("overlay_engaged").AsString();
_stateFlush = node.GetNode("state_flush").AsString();
var flushSound = node.GetNode("flush_sound").AsString();
var flushTime = node.GetNode("flush_time").AsFloat();
_flushAnimation = new Animation {Length = TimeSpan.FromSeconds(flushTime)};
var flick = new AnimationTrackSpriteFlick();
_flushAnimation.AnimationTracks.Add(flick);
flick.LayerKey = DisposalUnitVisualLayers.Base;
flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame(_stateFlush, 0));
var sound = new AnimationTrackPlaySound();
_flushAnimation.AnimationTracks.Add(sound);
sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(flushSound, 0));
}
public override void InitializeEntity(IEntity entity)
{
base.InitializeEntity(entity);
entity.EnsureComponent<AnimationPlayerComponent>();
var appearance = entity.EnsureComponent<AppearanceComponent>();
ChangeState(appearance);
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (component.Owner.Deleted)
{
return;
}
ChangeState(component);
}
}
public enum DisposalUnitVisualLayers
{
Base,
Handle,
Light
}
}

View File

@@ -0,0 +1,140 @@
using System.Runtime.CompilerServices;
using Content.Shared.GameObjects.Components.Disposal;
using Robust.Client.Graphics.Drawing;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalUnitComponent;
namespace Content.Client.GameObjects.Components.Disposal
{
/// <summary>
/// Client-side UI used to control a <see cref="SharedDisposalUnitComponent"/>
/// </summary>
public class DisposalUnitWindow : SS14Window
{
private readonly Label _unitState;
private readonly ProgressBar _pressureBar;
private readonly Label _pressurePercentage;
public readonly Button Engage;
public readonly Button Eject;
public readonly Button Power;
protected override Vector2? CustomSize => (300, 200);
public DisposalUnitWindow()
{
Contents.AddChild(new VBoxContainer
{
Children =
{
new HBoxContainer
{
Children =
{
new Label {Text = Loc.GetString("State: ")},
(_unitState = new Label {Text = Loc.GetString("Ready")})
}
},
new Control {CustomMinimumSize = (0, 10)},
new HBoxContainer
{
Children =
{
new Label {Text = Loc.GetString("Pressure:")},
(_pressureBar = new ProgressBar
{
CustomMinimumSize = (200, 20),
SizeFlagsHorizontal = SizeFlags.ShrinkEnd,
MinValue = 0,
MaxValue = 1,
Page = 0,
Value = 0.5f,
Children =
{
(_pressurePercentage = new Label())
}
})
}
},
new Control {CustomMinimumSize = (0, 10)},
new HBoxContainer
{
Children =
{
new Label {Text = Loc.GetString("Handle:")},
(Engage = new Button {Text = Loc.GetString("Engage")})
}
},
new Control {CustomMinimumSize = (0, 10)},
new HBoxContainer
{
Children =
{
new Label {Text = Loc.GetString("Eject:")},
(Eject = new Button {Text = Loc.GetString("Eject Contents")})
}
},
new Control {CustomMinimumSize = (0, 10)},
new HBoxContainer
{
Children =
{
(Power = new CheckButton {Text = Loc.GetString("Power")}),
}
}
}
});
}
private void UpdatePressureBar(float pressure)
{
_pressureBar.Value = pressure;
var normalized = pressure / _pressureBar.MaxValue;
const float leftHue = 0.0f; // Red
const float middleHue = 0.066f; // Orange
const float rightHue = 0.33f; // Green
const float saturation = 1.0f; // Uniform saturation
const float value = 0.8f; // Uniform value / brightness
const float alpha = 1.0f; // Uniform alpha
// These should add up to 1.0 or your transition won't be smooth
const float leftSideSize = 0.5f; // Fraction of _chargeBar lerped from leftHue to middleHue
const float rightSideSize = 0.5f; // Fraction of _chargeBar lerped from middleHue to rightHue
float finalHue;
if (normalized <= leftSideSize)
{
normalized /= leftSideSize; // Adjust range to 0.0 to 1.0
finalHue = FloatMath.Lerp(leftHue, middleHue, normalized);
}
else
{
normalized = (normalized - leftSideSize) / rightSideSize; // Adjust range to 0.0 to 1.0.
finalHue = FloatMath.Lerp(middleHue, rightHue, normalized);
}
// Check if null first to avoid repeatedly creating this.
_pressureBar.ForegroundStyleBoxOverride ??= new StyleBoxFlat();
var foregroundStyleBoxOverride = (StyleBoxFlat) _pressureBar.ForegroundStyleBoxOverride;
foregroundStyleBoxOverride.BackgroundColor =
Color.FromHsv(new Vector4(finalHue, saturation, value, alpha));
var percentage = pressure / _pressureBar.MaxValue * 100;
_pressurePercentage.Text = $" {percentage:0}%";
}
public void UpdateState(DisposalUnitBoundUserInterfaceState state)
{
Title = state.UnitName;
_unitState.Text = state.UnitState;
UpdatePressureBar(state.Pressure);
Power.Pressed = state.Powered;
}
}
}

View File

@@ -0,0 +1,81 @@
using System;
using Content.Shared.GameObjects.Components.Disposal;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components.Disposal
{
[UsedImplicitly]
public class DisposalVisualizer : AppearanceVisualizer
{
private string _stateFree;
private string _stateAnchored;
private string _stateBroken;
private void ChangeState(AppearanceComponent appearance)
{
if (!appearance.Owner.TryGetComponent(out ISpriteComponent sprite))
{
return;
}
if (!appearance.TryGetData(DisposalTubeVisuals.VisualState, out DisposalTubeVisualState state))
{
return;
}
var texture = state switch
{
DisposalTubeVisualState.Free => _stateFree,
DisposalTubeVisualState.Anchored => _stateAnchored,
DisposalTubeVisualState.Broken => _stateBroken,
_ => throw new ArgumentOutOfRangeException()
};
sprite.LayerSetState(0, texture);
if (state == DisposalTubeVisualState.Anchored)
{
appearance.Owner.EnsureComponent<SubFloorHideComponent>();
}
else if (appearance.Owner.HasComponent<SubFloorHideComponent>())
{
appearance.Owner.RemoveComponent<SubFloorHideComponent>();
}
}
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
_stateFree = node.GetNode("state_free").AsString();
_stateAnchored = node.GetNode("state_anchored").AsString();
_stateBroken = node.GetNode("state_broken").AsString();
}
public override void InitializeEntity(IEntity entity)
{
base.InitializeEntity(entity);
var appearance = entity.EnsureComponent<AppearanceComponent>();
ChangeState(appearance);
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (component.Owner.Deleted)
{
return;
}
ChangeState(component);
}
}
}

View File

@@ -1,4 +1,4 @@
using Content.Client.GameObjects.Components.Storage;
using Content.Client.GameObjects.Components.Disposal;
using Content.Client.Interfaces.GameObjects.Components.Interaction;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Items;
@@ -18,7 +18,7 @@ namespace Content.Client.GameObjects.Components.Items
{
[RegisterComponent]
[ComponentReference(typeof(IItemComponent))]
public class ItemComponent : Component, IItemComponent
public class ItemComponent : Component, IItemComponent, IClientDraggable
{
public override string Name => "Item";
public override uint? NetID => ContentNetIDs.ITEM;
@@ -80,5 +80,15 @@ namespace Content.Client.GameObjects.Components.Items
var itemComponentState = (ItemComponentState)curState;
EquippedPrefix = itemComponentState.EquippedPrefix;
}
bool IClientDraggable.ClientCanDropOn(CanDropEventArgs eventArgs)
{
return eventArgs.Target.HasComponent<DisposalUnitComponent>();
}
bool IClientDraggable.ClientCanDrag(CanDragEventArgs eventArgs)
{
return true;
}
}
}

View File

@@ -1,3 +1,5 @@
using Content.Client.GameObjects.Components.Disposal;
using Content.Client.Interfaces.GameObjects.Components.Interaction;
using Content.Shared.GameObjects.Components.Mobs;
using Robust.Shared.GameObjects;
@@ -5,8 +7,16 @@ namespace Content.Client.GameObjects.Components.Mobs
{
[RegisterComponent]
[ComponentReference(typeof(SharedSpeciesComponent))]
public class SpeciesComponent : SharedSpeciesComponent
public class SpeciesComponent : SharedSpeciesComponent, IClientDraggable
{
bool IClientDraggable.ClientCanDropOn(CanDropEventArgs eventArgs)
{
return eventArgs.Target.HasComponent<DisposalUnitComponent>();
}
bool IClientDraggable.ClientCanDrag(CanDragEventArgs eventArgs)
{
return true;
}
}
}

View File

@@ -0,0 +1,68 @@
using Content.Shared.GameObjects.Components.Recycling;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components.Recycling
{
[UsedImplicitly]
public class RecyclerVisualizer : AppearanceVisualizer
{
private string _stateClean;
private string _stateBloody;
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
if (node.TryGetNode("state_clean", out var child))
{
_stateClean = child.AsString();
}
if (node.TryGetNode("state_bloody", out child))
{
_stateBloody = child.AsString();
}
}
public override void InitializeEntity(IEntity entity)
{
base.InitializeEntity(entity);
if (!entity.TryGetComponent(out ISpriteComponent sprite) ||
!entity.TryGetComponent(out AppearanceComponent appearance))
{
return;
}
appearance.TryGetData(RecyclerVisuals.Bloody, out bool bloody);
sprite.LayerSetState(RecyclerVisualLayers.Bloody, bloody
? _stateBloody
: _stateClean);
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (!component.Owner.TryGetComponent(out ISpriteComponent sprite))
{
return;
}
component.TryGetData(RecyclerVisuals.Bloody, out bool bloody);
sprite.LayerSetState(RecyclerVisualLayers.Bloody, bloody
? _stateBloody
: _stateClean);
}
}
public enum RecyclerVisualLayers
{
Bloody
}
}

View File

@@ -17,8 +17,8 @@ namespace Content.Client.GameObjects.EntitySystems.AI
{
#pragma warning disable 649
[Dependency] private IEyeManager _eyeManager;
#pragma restore disable 649
#pragma warning restore 649
private AiDebugMode _tooltips = AiDebugMode.None;
private readonly Dictionary<IEntity, PanelContainer> _aiBoxes = new Dictionary<IEntity,PanelContainer>();
@@ -33,7 +33,7 @@ namespace Content.Client.GameObjects.EntitySystems.AI
{
panel.Dispose();
}
_aiBoxes.Clear();
}
return;

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using Content.Client.Interfaces.GameObjects.Components.Interaction;
using Content.Client.State;
using Content.Shared.GameObjects;
@@ -55,7 +56,7 @@ namespace Content.Client.GameObjects.EntitySystems
// entity performing the drag action
private IEntity _dragger;
private IEntity _draggedEntity;
private IClientDraggable _draggable;
private readonly List<IClientDraggable> _draggables = new List<IClientDraggable>();
private IEntity _dragShadow;
private DragState _state;
// time since mouse down over the dragged entity
@@ -146,10 +147,10 @@ namespace Content.Client.GameObjects.EntitySystems
if (_interactionSystem.InRangeUnobstructed(dragger.Transform.MapPosition,
entity.Transform.MapPosition, ignoredEnt: dragger) == false)
{
{
return false;
}
return false;
}
var canDrag = false;
foreach (var draggable in entity.GetAllComponents<IClientDraggable>())
{
var dragEventArgs = new CanDragEventArgs(args.Session.AttachedEntity, entity);
@@ -158,7 +159,7 @@ namespace Content.Client.GameObjects.EntitySystems
// wait to initiate a drag
_dragger = dragger;
_draggedEntity = entity;
_draggable = draggable;
_draggables.Add(draggable);
_mouseDownTime = 0;
_state = DragState.MouseDown;
_mouseDownScreenPos = _inputManager.MouseScreenPosition;
@@ -166,9 +167,11 @@ namespace Content.Client.GameObjects.EntitySystems
// but we will save the event so we can "re-play" it if this drag does
// not turn into an actual drag so the click can be handled normally
_savedMouseDown = args;
return true;
canDrag = true;
}
}
return canDrag;
}
return false;
@@ -193,19 +196,21 @@ namespace Content.Client.GameObjects.EntitySystems
// We don't use args.EntityUid here because drag interactions generally should
// work even if there's something "on top" of the drop target
if (_interactionSystem.InRangeUnobstructed(_dragger.Transform.MapPosition,
args.Coordinates.ToMap(_mapManager), ignoredEnt: _dragger) == false)
args.Coordinates.ToMap(_mapManager), ignoredEnt: _dragger, ignoreInsideBlocker: true) == false)
{
{
CancelDrag(false, null);
return false;
}
CancelDrag(false, null);
return false;
}
var entities = GameScreenBase.GetEntitiesUnderPosition(_stateManager, args.Coordinates);
foreach (var entity in entities)
{
// check if it's able to be dropped on by current dragged entity
if (_draggable.ClientCanDropOn(new CanDropEventArgs(_dragger, _draggedEntity, entity)))
var canDropArgs = new CanDropEventArgs(_dragger, _draggedEntity, entity);
var anyValidDraggable = _draggables.Any(draggable => draggable.ClientCanDropOn(canDropArgs));
if (anyValidDraggable)
{
// tell the server about the drop attempt
RaiseNetworkEvent(new DragDropMessage(args.Coordinates, _draggedEntity.Uid,
@@ -279,7 +284,10 @@ namespace Content.Client.GameObjects.EntitySystems
if (inRangeSprite.Visible == false) continue;
// check if it's able to be dropped on by current dragged entity
if (_draggable.ClientCanDropOn(new CanDropEventArgs(_dragger, _draggedEntity, pvsEntity)))
var canDropArgs = new CanDropEventArgs(_dragger, _draggedEntity, pvsEntity);
var anyValidDraggable = _draggables.Any(draggable => draggable.ClientCanDropOn(canDropArgs));
if (anyValidDraggable)
{
// highlight depending on whether its in or out of range
var inRange = _interactionSystem.InRangeUnobstructed(_dragger.Transform.MapPosition,
@@ -320,7 +328,7 @@ namespace Content.Client.GameObjects.EntitySystems
_dragShadow = null;
_draggedEntity = null;
_draggable = null;
_draggables.Clear();
_dragger = null;
_state = DragState.NotDragging;

View File

@@ -139,7 +139,16 @@
"Pullable",
"CursedEntityStorage",
"Listening",
"Radio"
"Radio",
"DisposalHolder",
"DisposalTransit",
"DisposalEntry",
"DisposalJunction",
"DisposalBend",
"Recycler",
"Conveyor",
"ConveyorSwitch",
"Flippable",
};
}
}

View File

@@ -33,6 +33,12 @@ namespace Content.Client.Interfaces.GameObjects.Components.Interaction
public class CanDropEventArgs : EventArgs
{
/// <summary>
/// Creates a new instance of <see cref="CanDropEventArgs"/>.
/// </summary>
/// <param name="user">The entity doing the drag and drop.</param>
/// <param name="dragged">The entity that is being dragged and dropped.</param>
/// <param name="target">The entity that <see cref="dragged"/> is being dropped onto.</param>
public CanDropEventArgs(IEntity user, IEntity dragged, IEntity target)
{
User = user;
@@ -40,20 +46,43 @@ namespace Content.Client.Interfaces.GameObjects.Components.Interaction
Target = target;
}
/// <summary>
/// The entity doing the drag and drop.
/// </summary>
public IEntity User { get; }
/// <summary>
/// The entity that is being dragged and dropped.
/// </summary>
public IEntity Dragged { get; }
/// <summary>
/// The entity that <see cref="Dragged"/> is being dropped onto.
/// </summary>
public IEntity Target { get; }
}
public class CanDragEventArgs : EventArgs
{
/// <summary>
/// Creates a new instance of <see cref="CanDragEventArgs"/>.
/// </summary>
/// <param name="user">The entity doing the drag and drop.</param>
/// <param name="dragged">The entity that is being dragged and dropped.</param>
public CanDragEventArgs(IEntity user, IEntity dragged)
{
User = user;
Dragged = dragged;
}
/// <summary>
/// The entity doing the drag and drop.
/// </summary>
public IEntity User { get; }
/// <summary>
/// The entity that is being dragged and dropped.
/// </summary>
public IEntity Dragged { get; }
}
}

View File

@@ -0,0 +1,130 @@
#nullable enable
using System.Linq;
using System.Threading.Tasks;
using Content.Server.GameObjects.Components;
using Content.Server.GameObjects.Components.Disposal;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using NUnit.Framework;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Map;
namespace Content.IntegrationTests.Tests.Disposal
{
[TestFixture]
[TestOf(typeof(DisposalHolderComponent))]
[TestOf(typeof(DisposalEntryComponent))]
[TestOf(typeof(DisposalUnitComponent))]
public class DisposalUnitTest : ContentIntegrationTest
{
private void UnitInsert(DisposalUnitComponent unit, bool result, params IEntity[] entities)
{
foreach (var entity in entities)
{
Assert.That(unit.CanInsert(entity), Is.EqualTo(result));
Assert.That(unit.TryInsert(entity), Is.EqualTo(result));
if (result)
{
// Not in a tube yet
Assert.That(entity.Transform.Parent == unit.Owner.Transform);
}
}
}
private void UnitContains(DisposalUnitComponent unit, bool result, params IEntity[] entities)
{
foreach (var entity in entities)
{
Assert.That(unit.ContainedEntities.Contains(entity), Is.EqualTo(result));
}
}
private void UnitInsertContains(DisposalUnitComponent unit, bool result, params IEntity[] entities)
{
UnitInsert(unit, result, entities);
UnitContains(unit, result, entities);
}
private void Flush(DisposalUnitComponent unit, bool result, DisposalEntryComponent? entry = null, IDisposalTubeComponent? next = null, params IEntity[] entities)
{
Assert.That(unit.ContainedEntities, Is.SupersetOf(entities));
Assert.AreEqual(unit.ContainedEntities.Count, entities.Length);
Assert.AreEqual(unit.TryFlush(), result);
Assert.AreEqual(unit.ContainedEntities.Count == 0, entry != null || entities.Length == 0);
}
[Test]
public async Task Test()
{
var server = StartServerDummyTicker();
IEntity human = null!;
IEntity wrench = null!;
DisposalUnitComponent unit = null!;
DisposalEntryComponent entry = null!;
server.Assert(() =>
{
var mapManager = IoCManager.Resolve<IMapManager>();
mapManager.CreateNewMapEntity(MapId.Nullspace);
var entityManager = IoCManager.Resolve<IEntityManager>();
// Spawn the entities
human = entityManager.SpawnEntity("HumanMob_Content", MapCoordinates.Nullspace);
wrench = entityManager.SpawnEntity("Wrench", MapCoordinates.Nullspace);
var disposalUnit = entityManager.SpawnEntity("DisposalUnit", MapCoordinates.Nullspace);
var disposalTrunk = entityManager.SpawnEntity("DisposalTrunk", disposalUnit.Transform.MapPosition);
// Test for components existing
Assert.True(disposalUnit.TryGetComponent(out unit));
Assert.True(disposalTrunk.TryGetComponent(out entry));
// Can't insert, unanchored and unpowered
UnitInsertContains(unit, false, human, wrench, disposalUnit, disposalTrunk);
Assert.False(unit.Anchored);
// Anchor the disposal unit
Assert.True(disposalUnit.TryGetComponent(out AnchorableComponent anchorableUnit));
Assert.True(anchorableUnit.TryAnchor(human, wrench));
Assert.True(unit.Anchored);
// Can't insert, unpowered
UnitInsertContains(unit, false, human, wrench, disposalUnit, disposalTrunk);
Assert.False(unit.Powered);
// Remove power need
Assert.True(disposalUnit.TryGetComponent(out PowerReceiverComponent power));
power.NeedsPower = false;
Assert.True(unit.Powered);
// Can't insert the trunk or the unit into itself
UnitInsertContains(unit, false, disposalUnit, disposalTrunk);
// Can insert mobs and items
UnitInsertContains(unit, true, human, wrench);
// Move the disposal trunk away
disposalTrunk.Transform.WorldPosition += (1, 0);
// Fail to flush with a mob and an item
Flush(unit, false, null, null, human, wrench);
// Move the disposal trunk back
disposalTrunk.Transform.WorldPosition -= (1, 0);
// Flush with a mob and an item
Flush(unit, true, entry, null, human, wrench);
// Re-pressurizing
Flush(unit, false, entry);
});
await server.WaitIdleAsync();
}
}
}

View File

@@ -1,8 +1,11 @@
using Content.Server.GameObjects.Components.Interactable;
#nullable enable
using System.Diagnostics.CodeAnalysis;
using Content.Server.GameObjects.Components.Interactable;
using Content.Shared.GameObjects.Components.Interactable;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.GameObjects.Components
{
@@ -11,24 +14,100 @@ namespace Content.Server.GameObjects.Components
{
public override string Name => "Anchorable";
/// <summary>
/// Checks if a tool can change the anchored status.
/// </summary>
/// <param name="user">The user doing the action</param>
/// <param name="utilizing">The tool being used, can be null if forcing it</param>
/// <param name="collidable">The physics component of the owning entity</param>
/// <param name="force">Whether or not to check if the tool is valid</param>
/// <returns>true if it is valid, false otherwise</returns>
private bool Valid(IEntity user, IEntity? utilizing, [MaybeNullWhen(false)] out ICollidableComponent collidable, bool force = false)
{
if (!Owner.TryGetComponent(out collidable))
{
return false;
}
if (!force)
{
if (utilizing == null ||
!utilizing.TryGetComponent(out ToolComponent tool) ||
!tool.UseTool(user, Owner, ToolQuality.Anchoring))
{
return false;
}
}
return true;
}
/// <summary>
/// Tries to anchor the owner of this component.
/// </summary>
/// <param name="user">The entity doing the anchoring</param>
/// <param name="utilizing">The tool being used, if any</param>
/// <param name="force">Whether or not to ignore valid tool checks</param>
/// <returns>true if anchored, false otherwise</returns>
public bool TryAnchor(IEntity user, IEntity? utilizing = null, bool force = false)
{
if (!Valid(user, utilizing, out var physics, force))
{
return false;
}
physics.Anchored = true;
return true;
}
/// <summary>
/// Tries to unanchor the owner of this component.
/// </summary>
/// <param name="user">The entity doing the unanchoring</param>
/// <param name="utilizing">The tool being used, if any</param>
/// <param name="force">Whether or not to ignore valid tool checks</param>
/// <returns>true if unanchored, false otherwise</returns>
public bool TryUnAnchor(IEntity user, IEntity? utilizing = null, bool force = false)
{
if (!Valid(user, utilizing, out var physics, force))
{
return false;
}
physics.Anchored = false;
return true;
}
/// <summary>
/// Tries to toggle the anchored status of this component's owner.
/// </summary>
/// <param name="user">The entity doing the unanchoring</param>
/// <param name="utilizing">The tool being used, if any</param>
/// <param name="force">Whether or not to ignore valid tool checks</param>
/// <returns>true if toggled, false otherwise</returns>
private bool TryToggleAnchor(IEntity user, IEntity? utilizing = null, bool force = false)
{
if (!Owner.TryGetComponent(out ICollidableComponent collidable))
{
return false;
}
return collidable.Anchored ?
TryUnAnchor(user, utilizing, force) :
TryAnchor(user, utilizing, force);
}
public override void Initialize()
{
base.Initialize();
Owner.EnsureComponent<PhysicsComponent>();
}
public bool InteractUsing(InteractUsingEventArgs eventArgs)
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
if (!Owner.TryGetComponent(out IPhysicsComponent physics)
|| !eventArgs.Using.TryGetComponent(out ToolComponent tool))
return false;
if (!tool.UseTool(eventArgs.User, Owner, ToolQuality.Anchoring))
return false;
physics.Anchored = !physics.Anchored;
return true;
return TryToggleAnchor(eventArgs.User, eventArgs.Using);
}
}
}

View File

@@ -1,5 +1,6 @@
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.Components.Strap;
@@ -159,19 +160,10 @@ namespace Content.Server.GameObjects.Components.Buckle
}
}
/// <summary>
/// Tries to make an entity buckle the owner of this component to another.
/// </summary>
/// <param name="user">
/// The entity buckling the owner of this component, can be the owner itself.
/// </param>
/// <param name="to">The entity to buckle the owner of this component to.</param>
/// <returns>
/// true if the owner was buckled, otherwise false even if the owner was
/// previously already buckled.
/// </returns>
public bool TryBuckle(IEntity user, IEntity to)
private bool CanBuckle(IEntity user, IEntity to, [MaybeNullWhen(false)] out StrapComponent strap)
{
strap = null;
if (user == null || user == to)
{
return false;
@@ -181,22 +173,25 @@ namespace Content.Server.GameObjects.Components.Buckle
{
_notifyManager.PopupMessage(user, user,
Loc.GetString("You can't do that!"));
return false;
}
if (!to.TryGetComponent(out StrapComponent strap))
if (!to.TryGetComponent(out strap))
{
_notifyManager.PopupMessage(Owner, user,
Loc.GetString(Owner == user
? "You can't buckle yourself there!"
: "You can't buckle {0:them} there!", Owner));
return false;
}
var ownerPosition = Owner.Transform.MapPosition;
var strapPosition = strap.Owner.Transform.MapPosition;
var interaction = EntitySystem.Get<SharedInteractionSystem>();
bool Ignored(IEntity entity) => entity == Owner || entity == user || entity == strap.Owner;
var component = strap;
bool Ignored(IEntity entity) => entity == Owner || entity == user || entity == component.Owner;
if (!interaction.InRangeUnobstructed(ownerPosition, strapPosition, _range, predicate: Ignored))
{
@@ -213,8 +208,8 @@ namespace Content.Server.GameObjects.Components.Buckle
if (!ContainerHelpers.TryGetContainer(strap.Owner, out var strapContainer) ||
ownerContainer != strapContainer)
{
_notifyManager.PopupMessage(strap.Owner, user,
Loc.GetString("You can't reach there!"));
_notifyManager.PopupMessage(strap.Owner, user, Loc.GetString("You can't reach there!"));
return false;
}
}
@@ -223,6 +218,7 @@ namespace Content.Server.GameObjects.Components.Buckle
{
_notifyManager.PopupMessage(user, user,
Loc.GetString("You don't have hands!"));
return false;
}
@@ -232,6 +228,7 @@ namespace Content.Server.GameObjects.Components.Buckle
Loc.GetString(Owner == user
? "You are already buckled in!"
: "{0:They} are already buckled in!", Owner));
return false;
}
@@ -244,6 +241,7 @@ namespace Content.Server.GameObjects.Components.Buckle
Loc.GetString(Owner == user
? "You can't buckle yourself there!"
: "You can't buckle {0:them} there!", Owner));
return false;
}
@@ -256,6 +254,28 @@ namespace Content.Server.GameObjects.Components.Buckle
Loc.GetString(Owner == user
? "You can't fit there!"
: "{0:They} can't fit there!", Owner));
return false;
}
return true;
}
/// <summary>
/// Tries to make an entity buckle the owner of this component to another.
/// </summary>
/// <param name="user">
/// The entity buckling the owner of this component, can be the owner itself.
/// </param>
/// <param name="to">The entity to buckle the owner of this component to.</param>
/// <returns>
/// true if the owner was buckled, otherwise false even if the owner was
/// previously already buckled.
/// </returns>
public bool TryBuckle(IEntity user, IEntity to)
{
if (!CanBuckle(user, to, out var strap))
{
return false;
}
@@ -545,6 +565,11 @@ namespace Content.Server.GameObjects.Components.Buckle
return TryUnbuckle(eventArgs.User);
}
bool IDragDrop.CanDragDrop(DragDropEventArgs eventArgs)
{
return eventArgs.Target.HasComponent<StrapComponent>();
}
bool IDragDrop.DragDrop(DragDropEventArgs eventArgs)
{
return TryBuckle(eventArgs.User, eventArgs.Target);

View File

@@ -0,0 +1,257 @@
#nullable enable
using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects.Components.Interactable;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.Conveyor;
using Content.Shared.GameObjects.Components.Interactable;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Physics;
using Robust.Server.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.GameObjects.Components.Map;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Conveyor
{
[RegisterComponent]
public class ConveyorComponent : Component, IInteractUsing
{
#pragma warning disable 649
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
#pragma warning restore 649
public override string Name => "Conveyor";
/// <summary>
/// The angle to move entities by in relation to the owner's rotation.
/// </summary>
[ViewVariables]
private Angle _angle;
/// <summary>
/// The amount of units to move the entity by per second.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
private float _speed;
private ConveyorState _state;
/// <summary>
/// The current state of this conveyor
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
private ConveyorState State
{
get => _state;
set
{
_state = value;
if (!Owner.TryGetComponent(out AppearanceComponent appearance))
{
return;
}
appearance.SetData(ConveyorVisuals.State, value);
}
}
private ConveyorGroup? _group = new ConveyorGroup();
/// <summary>
/// Calculates the angle in which entities on top of this conveyor
/// belt are pushed in
/// </summary>
/// <returns>
/// The angle when taking into account if the conveyor is reversed
/// </returns>
private Angle GetAngle()
{
var adjustment = _state == ConveyorState.Reversed ? MathHelper.Pi : 0;
var radians = MathHelper.DegreesToRadians(_angle);
return new Angle(Owner.Transform.LocalRotation.Theta + radians + adjustment);
}
private bool CanRun()
{
if (State == ConveyorState.Off)
{
return false;
}
if (Owner.TryGetComponent(out PowerReceiverComponent receiver) &&
!receiver.Powered)
{
return false;
}
if (Owner.HasComponent<ItemComponent>())
{
return false;
}
return true;
}
private bool CanMove(IEntity entity)
{
if (entity == Owner)
{
return false;
}
if (!entity.TryGetComponent(out ICollidableComponent collidable) ||
collidable.Anchored)
{
return false;
}
if (entity.HasComponent<ConveyorComponent>())
{
return false;
}
if (entity.HasComponent<IMapGridComponent>())
{
return false;
}
if (ContainerHelpers.IsInContainer(entity))
{
return false;
}
return true;
}
public void Update()
{
if (!CanRun())
{
return;
}
var intersecting = _entityManager.GetEntitiesIntersecting(Owner, true);
var direction = GetAngle().ToVec();
foreach (var entity in intersecting)
{
if (!CanMove(entity))
{
continue;
}
if (entity.TryGetComponent(out ICollidableComponent collidable))
{
var controller = collidable.EnsureController<ConveyedController>();
controller.Move(direction, _speed);
}
}
}
private bool ToolUsed(IEntity user, ToolComponent tool)
{
if (!Owner.HasComponent<ItemComponent>() &&
tool.UseTool(user, Owner, ToolQuality.Prying))
{
State = ConveyorState.Loose;
Owner.AddComponent<ItemComponent>();
_group?.RemoveConveyor(this);
Owner.Transform.WorldPosition += (_random.NextFloat() * 0.4f - 0.2f, _random.NextFloat() * 0.4f - 0.2f);
return true;
}
return false;
}
public void Sync(ConveyorGroup group)
{
_group = group;
if (State == ConveyorState.Loose)
{
return;
}
State = group.State == ConveyorState.Loose
? ConveyorState.Off
: group.State;
}
/// <summary>
/// Disconnects this conveyor from any switch.
/// </summary>
private void Disconnect()
{
_group?.RemoveConveyor(this);
_group = null;
State = ConveyorState.Off;
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataReadWriteFunction(
"switches",
new List<IEntity>(),
switches =>
{
if (switches == null)
{
return;
}
foreach (var @switch in switches)
{
if (!@switch.TryGetComponent(out ConveyorSwitchComponent component))
{
continue;
}
component.Connect(this);
}
},
() => _group?.Switches.Select(@switch => @switch.Owner));
serializer.DataField(ref _angle, "angle", 0);
serializer.DataField(ref _speed, "speed", 2);
}
public override void OnRemove()
{
base.OnRemove();
Disconnect();
}
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
if (eventArgs.Using.TryGetComponent(out ConveyorSwitchComponent conveyorSwitch))
{
conveyorSwitch.Connect(this, eventArgs.User);
return true;
}
if (eventArgs.Using.TryGetComponent(out ToolComponent tool))
{
return ToolUsed(eventArgs.User, tool);
}
return false;
}
}
}

View File

@@ -0,0 +1,208 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.Conveyor;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Conveyor
{
[RegisterComponent]
public class ConveyorSwitchComponent : Component, IInteractHand, IInteractUsing, IActivate
{
public override string Name => "ConveyorSwitch";
private ConveyorState _state;
/// <summary>
/// The current state of this switch
/// </summary>
[ViewVariables]
public ConveyorState State
{
get => _state;
private set
{
_state = value;
if (Owner.TryGetComponent(out AppearanceComponent appearance))
{
appearance.SetData(ConveyorVisuals.State, value);
}
}
}
private ConveyorGroup? _group;
public void Sync(ConveyorGroup group)
{
_group = group;
if (State == ConveyorState.Loose)
{
return;
}
State = group.State == ConveyorState.Loose
? ConveyorState.Off
: group.State;
}
/// <summary>
/// Disconnects this switch from any conveyors and other switches.
/// </summary>
private void Disconnect()
{
_group?.RemoveSwitch(this);
_group = null;
State = ConveyorState.Off;
}
/// <summary>
/// Connects a conveyor to this switch.
/// </summary>
/// <param name="conveyor">The conveyor to be connected.</param>
/// <param name="user">The user doing the connecting, if any.</param>
public void Connect(ConveyorComponent conveyor, IEntity? user = null)
{
if (_group == null)
{
_group = new ConveyorGroup();
_group.AddSwitch(this);
}
_group.AddConveyor(conveyor);
user?.PopupMessage(user, Loc.GetString("Conveyor linked."));
}
/// <summary>
/// Cycles this conveyor switch to its next valid state
/// </summary>
/// <returns>
/// true if the switch can be operated and the state could be cycled,
/// false otherwise
/// </returns>
private bool NextState()
{
State = State switch
{
ConveyorState.Off => ConveyorState.Forward,
ConveyorState.Forward => ConveyorState.Reversed,
ConveyorState.Reversed => ConveyorState.Off,
ConveyorState.Loose => ConveyorState.Off,
_ => throw new ArgumentOutOfRangeException()
};
_group?.SetState(this);
return true;
}
/// <summary>
/// Moves this switch to the group of another.
/// </summary>
/// <param name="other">The conveyor switch to synchronize with.</param>
/// <param name="user">The user doing the syncing, if any.</param>
private void SyncWith(ConveyorSwitchComponent other, IEntity? user = null)
{
other._group?.AddSwitch(this);
if (user == null)
{
return;
}
Owner.PopupMessage(user, Loc.GetString("Switches synchronized."));
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataReadWriteFunction(
"conveyors",
new List<IEntity>(),
conveyors =>
{
if (conveyors == null)
{
return;
}
foreach (var conveyor in conveyors)
{
if (!conveyor.TryGetComponent(out ConveyorComponent component))
{
continue;
}
Connect(component);
}
},
() => _group?.Conveyors.Select(conveyor => conveyor.Owner));
serializer.DataReadWriteFunction(
"switches",
new List<IEntity>(),
switches =>
{
if (switches == null)
{
return;
}
foreach (var @switch in switches)
{
if (!@switch.TryGetComponent(out ConveyorSwitchComponent component))
{
continue;
}
component.SyncWith(this);
}
},
() => _group?.Switches.Select(@switch => @switch.Owner));
}
public override void OnRemove()
{
base.OnRemove();
Disconnect();
}
bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs)
{
return NextState();
}
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
if (eventArgs.Using.TryGetComponent(out ConveyorComponent conveyor))
{
Connect(conveyor, eventArgs.User);
return true;
}
if (eventArgs.Using.TryGetComponent(out ConveyorSwitchComponent otherSwitch))
{
SyncWith(otherSwitch, eventArgs.User);
return true;
}
return true;
}
void IActivate.Activate(ActivateEventArgs eventArgs)
{
NextState();
}
}
}

View File

@@ -0,0 +1,44 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Disposal
{
[RegisterComponent]
[ComponentReference(typeof(IDisposalTubeComponent))]
public class DisposalBendComponent : DisposalTubeComponent
{
private int _sideDegrees;
public override string Name => "DisposalBend";
protected override Direction[] ConnectableDirections()
{
var direction = Owner.Transform.LocalRotation;
var side = new Angle(MathHelper.DegreesToRadians(direction.Degrees + _sideDegrees));
return new[] {direction.GetDir(), side.GetDir()};
}
public override Direction NextDirection(DisposalHolderComponent holder)
{
var directions = ConnectableDirections();
var previousTube = holder.PreviousTube;
if (previousTube == null || !Connected.ContainsValue(previousTube))
{
return directions[0];
}
var first = Connected.GetValueOrDefault(directions[0]);
return previousTube == first ? directions[1] : directions[0];
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _sideDegrees, "sideDegrees", -90);
}
}
}

View File

@@ -0,0 +1,54 @@
#nullable enable
using Content.Server.Interfaces;
using Robust.Server.Interfaces.Console;
using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
namespace Content.Server.GameObjects.Components.Disposal
{
public class TubeConnectionsCommand : IClientCommand
{
public string Command => "tubeconnections";
public string Description => Loc.GetString("Shows all the directions that a tube can connect in.");
public string Help => $"Usage: {Command} <entityUid>";
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
{
if (player?.AttachedEntity == null)
{
shell.SendText(player, Loc.GetString("Only players can use this command"));
return;
}
if (args.Length < 1)
{
shell.SendText(player, Help);
return;
}
if (!EntityUid.TryParse(args[0], out var id))
{
shell.SendText(player, Loc.GetString("{0} isn't a valid entity uid", args[0]));
return;
}
var entityManager = IoCManager.Resolve<IEntityManager>();
if (!entityManager.TryGetEntity(id, out var entity))
{
shell.SendText(player, Loc.GetString("No entity exists with uid {0}", id));
return;
}
if (!entity.TryGetComponent(out IDisposalTubeComponent tube))
{
shell.SendText(player, Loc.GetString("Entity with uid {0} doesn't have a {1} component", id, nameof(IDisposalTubeComponent)));
return;
}
tube.PopupDirections(player.AttachedEntity);
}
}
}

View File

@@ -0,0 +1,51 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Maths;
namespace Content.Server.GameObjects.Components.Disposal
{
[RegisterComponent]
[ComponentReference(typeof(IDisposalTubeComponent))]
public class DisposalEntryComponent : DisposalTubeComponent
{
private const string HolderPrototypeId = "DisposalHolder";
public override string Name => "DisposalEntry";
public bool TryInsert(IReadOnlyCollection<IEntity> entities)
{
var holder = Owner.EntityManager.SpawnEntity(HolderPrototypeId, Owner.Transform.MapPosition);
var holderComponent = holder.GetComponent<DisposalHolderComponent>();
foreach (var entity in entities)
{
holderComponent.TryInsert(entity);
}
return TryInsert(holderComponent);
}
public bool TryInsert(DisposalHolderComponent holder)
{
if (!Contents.Insert(holder.Owner))
{
return false;
}
holder.EnterTube(this);
return true;
}
protected override Direction[] ConnectableDirections()
{
return new[] {Owner.Transform.LocalRotation.GetDir()};
}
public override Direction NextDirection(DisposalHolderComponent holder)
{
return ConnectableDirections()[0];
}
}
}

View File

@@ -0,0 +1,150 @@
#nullable enable
using System.Linq;
using Robust.Server.GameObjects.Components.Container;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Disposal
{
// TODO: Add gas
[RegisterComponent]
public class DisposalHolderComponent : Component
{
public override string Name => "DisposalHolder";
private Container _contents = null!;
/// <summary>
/// The total amount of time that it will take for this entity to
/// be pushed to the next tube
/// </summary>
[ViewVariables]
private float StartingTime { get; set; }
/// <summary>
/// Time left until the entity is pushed to the next tube
/// </summary>
[ViewVariables]
private float TimeLeft { get; set; }
[ViewVariables]
public IDisposalTubeComponent? PreviousTube { get; set; }
[ViewVariables]
public IDisposalTubeComponent? CurrentTube { get; private set; }
[ViewVariables]
public IDisposalTubeComponent? NextTube { get; set; }
private bool CanInsert(IEntity entity)
{
if (!_contents.CanInsert(entity))
{
return false;
}
return entity.HasComponent<ItemComponent>() ||
entity.HasComponent<SpeciesComponent>();
}
public bool TryInsert(IEntity entity)
{
if (!CanInsert(entity) || !_contents.Insert(entity))
{
return false;
}
return true;
}
public void EnterTube(IDisposalTubeComponent tube)
{
if (CurrentTube != null)
{
PreviousTube = CurrentTube;
}
Owner.Transform.GridPosition = tube.Owner.Transform.GridPosition;
CurrentTube = tube;
NextTube = tube.NextTube(this);
StartingTime = 0.1f;
TimeLeft = 0.1f;
}
public void ExitDisposals()
{
PreviousTube = null;
CurrentTube = null;
NextTube = null;
StartingTime = 0;
TimeLeft = 0;
foreach (var entity in _contents.ContainedEntities.ToArray())
{
_contents.ForceRemove(entity);
if (entity.Transform.Parent == Owner.Transform)
{
ContainerHelpers.AttachParentToContainerOrGrid(entity.Transform);
}
}
Owner.Delete();
}
public void Update(float frameTime)
{
while (frameTime > 0)
{
var time = frameTime;
if (time > TimeLeft)
{
time = TimeLeft;
}
TimeLeft -= time;
frameTime -= time;
if (CurrentTube == null)
{
ExitDisposals();
break;
}
if (TimeLeft > 0)
{
var progress = 1 - TimeLeft / StartingTime;
var origin = CurrentTube.Owner.Transform.WorldPosition;
var destination = CurrentTube.NextDirection(this).ToVec();
var newPosition = destination * progress;
Owner.Transform.WorldPosition = origin + newPosition;
continue;
}
if (NextTube == null || !CurrentTube.TransferTo(this, NextTube))
{
CurrentTube.Remove(this);
break;
}
}
}
public override void OnRemove()
{
base.OnRemove();
ExitDisposals();
}
public override void Initialize()
{
base.Initialize();
_contents = ContainerManagerComponent.Ensure<Container>(nameof(DisposalHolderComponent), Owner);
}
}
}

View File

@@ -0,0 +1,57 @@
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Disposal
{
[RegisterComponent]
[ComponentReference(typeof(IDisposalTubeComponent))]
public class DisposalJunctionComponent : DisposalTubeComponent
{
#pragma warning disable 649
[Dependency] private readonly IRobustRandom _random;
#pragma warning restore 649
/// <summary>
/// The angles to connect to.
/// </summary>
[ViewVariables]
private List<Angle> _degrees;
public override string Name => "DisposalJunction";
protected override Direction[] ConnectableDirections()
{
var direction = Owner.Transform.LocalRotation;
return _degrees.Select(degree => new Angle(degree.Theta + direction.Theta).GetDir()).ToArray();
}
public override Direction NextDirection(DisposalHolderComponent holder)
{
var next = Owner.Transform.LocalRotation;
var directions = ConnectableDirections().Skip(1).ToArray();
if (Connected.TryGetValue(next.GetDir(), out var forwardTube) &&
holder.PreviousTube == forwardTube)
{
return _random.Pick(directions);
}
return next.GetDir();
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _degrees, "degrees", null);
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
namespace Content.Server.GameObjects.Components.Disposal
{
// TODO: Different types of tubes eject in random direction with no exit point
[RegisterComponent]
[ComponentReference(typeof(IDisposalTubeComponent))]
public class DisposalTransitComponent : DisposalTubeComponent
{
public override string Name => "DisposalTransit";
protected override Direction[] ConnectableDirections()
{
var rotation = Owner.Transform.LocalRotation;
var opposite = new Angle(rotation.Theta + Math.PI);
return new[] {rotation.GetDir(), opposite.GetDir()};
}
public override Direction NextDirection(DisposalHolderComponent holder)
{
var directions = ConnectableDirections();
var previousTube = holder.PreviousTube;
var forward = directions[0];
if (previousTube == null || !Connected.ContainsValue(previousTube))
{
return forward;
}
var forwardTube = Connected.GetValueOrDefault(forward);
var backward = directions[1];
return previousTube == forwardTube ? backward : forward;
}
}
}

View File

@@ -0,0 +1,380 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Server.Interfaces.GameObjects.Components.Interaction;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Disposal;
using Content.Shared.Interfaces;
using Robust.Server.Console;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.Container;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Disposal
{
// TODO: Make unanchored pipes pullable
public abstract class DisposalTubeComponent : Component, IDisposalTubeComponent, IBreakAct
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
private static readonly TimeSpan ClangDelay = TimeSpan.FromSeconds(0.5);
private TimeSpan _lastClang;
private bool _connected;
private bool _broken;
private string _clangSound;
/// <summary>
/// Container of entities that are currently inside this tube
/// </summary>
[ViewVariables]
public Container Contents { get; private set; }
/// <summary>
/// Dictionary of tubes connecting to this one mapped by their direction
/// </summary>
[ViewVariables]
protected Dictionary<Direction, IDisposalTubeComponent> Connected { get; } =
new Dictionary<Direction, IDisposalTubeComponent>();
[ViewVariables]
private bool Anchored =>
!Owner.TryGetComponent(out CollidableComponent collidable) ||
collidable.Anchored;
/// <summary>
/// The directions that this tube can connect to others from
/// </summary>
/// <returns>a new array of the directions</returns>
protected abstract Direction[] ConnectableDirections();
public abstract Direction NextDirection(DisposalHolderComponent holder);
public virtual Vector2 ExitVector(DisposalHolderComponent holder)
{
return NextDirection(holder).ToVec();
}
public IDisposalTubeComponent NextTube(DisposalHolderComponent holder)
{
var nextDirection = NextDirection(holder);
return Connected.GetValueOrDefault(nextDirection);
}
public bool Remove(DisposalHolderComponent holder)
{
var removed = Contents.Remove(holder.Owner);
holder.ExitDisposals();
return removed;
}
public bool TransferTo(DisposalHolderComponent holder, IDisposalTubeComponent to)
{
var position = holder.Owner.Transform.LocalPosition;
if (!to.Contents.Insert(holder.Owner))
{
return false;
}
holder.Owner.Transform.LocalPosition = position;
Contents.Remove(holder.Owner);
holder.EnterTube(to);
return true;
}
// TODO: Make disposal pipes extend the grid
private void Connect()
{
if (_connected || _broken)
{
return;
}
_connected = true;
var snapGrid = Owner.GetComponent<SnapGridComponent>();
foreach (var direction in ConnectableDirections())
{
var tube = snapGrid
.GetInDir(direction)
.Select(x => x.TryGetComponent(out IDisposalTubeComponent c) ? c : null)
.FirstOrDefault(x => x != null && x != this);
if (tube == null)
{
continue;
}
var oppositeDirection = new Angle(direction.ToAngle().Theta + Math.PI).GetDir();
if (!tube.AdjacentConnected(oppositeDirection, this))
{
continue;
}
Connected.Add(direction, tube);
}
}
public bool AdjacentConnected(Direction direction, IDisposalTubeComponent tube)
{
if (_broken)
{
return false;
}
if (Connected.ContainsKey(direction) ||
!ConnectableDirections().Contains(direction))
{
return false;
}
Connected.Add(direction, tube);
return true;
}
private void Disconnect()
{
if (!_connected)
{
return;
}
_connected = false;
foreach (var entity in Contents.ContainedEntities)
{
if (!entity.TryGetComponent(out DisposalHolderComponent holder))
{
continue;
}
holder.ExitDisposals();
}
foreach (var connected in Connected.Values)
{
connected.AdjacentDisconnected(this);
}
Connected.Clear();
}
public void AdjacentDisconnected(IDisposalTubeComponent adjacent)
{
foreach (var pair in Connected)
{
foreach (var entity in Contents.ContainedEntities)
{
if (!entity.TryGetComponent(out DisposalHolderComponent holder))
{
continue;
}
if (holder.PreviousTube == adjacent)
{
holder.PreviousTube = null;
}
if (holder.NextTube == adjacent)
{
holder.NextTube = null;
}
}
if (pair.Value == adjacent)
{
Connected.Remove(pair.Key);
}
}
}
public void MoveEvent(MoveEvent moveEvent)
{
if (!_connected)
{
return;
}
foreach (var tube in Connected.Values)
{
var distance = (tube.Owner.Transform.WorldPosition - Owner.Transform.WorldPosition).Length;
// Disconnect distance threshold
if (distance < 1.25)
{
continue;
}
AdjacentDisconnected(tube);
tube.AdjacentDisconnected(this);
}
}
public void PopupDirections(IEntity entity)
{
var directions = string.Join(", ", ConnectableDirections());
Owner.PopupMessage(entity, Loc.GetString("{0}", directions));
}
private void UpdateVisualState()
{
if (!Owner.TryGetComponent(out AppearanceComponent appearance))
{
return;
}
var state = _broken
? DisposalTubeVisualState.Broken
: Anchored
? DisposalTubeVisualState.Anchored
: DisposalTubeVisualState.Free;
appearance.SetData(DisposalTubeVisuals.VisualState, state);
}
private void AnchoredChanged()
{
if (!Owner.TryGetComponent(out CollidableComponent collidable))
{
return;
}
if (collidable.Anchored)
{
OnAnchor();
}
else
{
OnUnAnchor();
}
}
private void OnAnchor()
{
Connect();
UpdateVisualState();
}
private void OnUnAnchor()
{
Disconnect();
UpdateVisualState();
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _clangSound, "clangSound", "/Audio/effects/clang.ogg");
}
public override void Initialize()
{
base.Initialize();
Contents = ContainerManagerComponent.Ensure<Container>(Name, Owner);
Owner.EnsureComponent<AnchorableComponent>();
var collidable = Owner.EnsureComponent<CollidableComponent>();
collidable.AnchoredChanged += AnchoredChanged;
}
protected override void Startup()
{
base.Startup();
if (!Owner.GetComponent<CollidableComponent>().Anchored)
{
return;
}
Connect();
UpdateVisualState();
}
public override void OnRemove()
{
base.OnRemove();
var collidable = Owner.EnsureComponent<CollidableComponent>();
collidable.AnchoredChanged -= AnchoredChanged;
Disconnect();
}
public override void HandleMessage(ComponentMessage message, IComponent component)
{
base.HandleMessage(message, component);
switch (message)
{
case RelayMovementEntityMessage _:
if (_gameTiming.CurTime < _lastClang + ClangDelay)
{
break;
}
_lastClang = _gameTiming.CurTime;
EntitySystem.Get<AudioSystem>().PlayAtCoords(_clangSound, Owner.Transform.GridPosition);
break;
}
}
void IBreakAct.OnBreak(BreakageEventArgs eventArgs)
{
_broken = true; // TODO: Repair
Disconnect();
UpdateVisualState();
}
[Verb]
private sealed class TubeDirectionsVerb : Verb<IDisposalTubeComponent>
{
protected override void GetData(IEntity user, IDisposalTubeComponent component, VerbData data)
{
data.Text = "Tube Directions";
data.CategoryData = VerbCategories.Debug;
data.Visibility = VerbVisibility.Invisible;
var groupController = IoCManager.Resolve<IConGroupController>();
if (user.TryGetComponent<IActorComponent>(out var player))
{
if (groupController.CanCommand(player.playerSession, "tubeconnections"))
{
data.Visibility = VerbVisibility.Visible;
}
}
}
protected override void Activate(IEntity user, IDisposalTubeComponent component)
{
var groupController = IoCManager.Resolve<IConGroupController>();
if (user.TryGetComponent<IActorComponent>(out var player))
{
if (groupController.CanCommand(player.playerSession, "tubeconnections"))
{
component.PopupDirections(user);
}
}
}
}
}
}

View File

@@ -0,0 +1,563 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Disposal;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.Container;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using Timer = Robust.Shared.Timers.Timer;
namespace Content.Server.GameObjects.Components.Disposal
{
[RegisterComponent]
[ComponentReference(typeof(IInteractUsing))]
public class DisposalUnitComponent : SharedDisposalUnitComponent, IInteractHand, IInteractUsing, IDragDropOn
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
#pragma warning restore 649
public override string Name => "DisposalUnit";
/// <summary>
/// The delay for an entity trying to move out of this unit.
/// </summary>
private static readonly TimeSpan ExitAttemptDelay = TimeSpan.FromSeconds(0.5);
/// <summary>
/// Last time that an entity tried to exit this disposal unit.
/// </summary>
private TimeSpan _lastExitAttempt;
/// <summary>
/// The current pressure of this disposal unit.
/// Prevents it from flushing if it is not equal to or bigger than 1.
/// </summary>
private float _pressure;
private bool _engaged;
[ViewVariables]
private TimeSpan _engageTime;
[ViewVariables]
private TimeSpan _automaticEngageTime;
/// <summary>
/// Token used to cancel the automatic engage of a disposal unit
/// after an entity enters it.
/// </summary>
private CancellationTokenSource? _automaticEngageToken;
/// <summary>
/// Container of entities inside this disposal unit.
/// </summary>
[ViewVariables]
private Container _container = default!;
[ViewVariables] public IReadOnlyList<IEntity> ContainedEntities => _container.ContainedEntities;
[ViewVariables]
private BoundUserInterface _userInterface = default!;
[ViewVariables]
public bool Powered =>
!Owner.TryGetComponent(out PowerReceiverComponent receiver) ||
receiver.Powered;
[ViewVariables]
public bool Anchored =>
!Owner.TryGetComponent(out CollidableComponent collidable) ||
collidable.Anchored;
[ViewVariables]
private State State => _pressure >= 1 ? State.Ready : State.Pressurizing;
[ViewVariables]
private bool Engaged
{
get => _engaged;
set
{
var oldEngaged = _engaged;
_engaged = value;
if (oldEngaged == value)
{
return;
}
UpdateVisualState();
}
}
public bool CanInsert(IEntity entity)
{
if (!Powered || !Anchored)
{
return false;
}
if (!entity.HasComponent<ItemComponent>() &&
!entity.HasComponent<SpeciesComponent>())
{
return false;
}
return _container.CanInsert(entity);
}
private void AfterInsert(IEntity entity)
{
_automaticEngageToken = new CancellationTokenSource();
Timer.Spawn(_automaticEngageTime, () => TryFlush(), _automaticEngageToken.Token);
if (entity.TryGetComponent(out IActorComponent actor))
{
_userInterface.Close(actor.playerSession);
}
UpdateVisualState();
}
public bool TryInsert(IEntity entity)
{
if (!CanInsert(entity) || !_container.Insert(entity))
{
return false;
}
AfterInsert(entity);
return true;
}
private bool TryDrop(IEntity user, IEntity entity)
{
if (!user.TryGetComponent(out HandsComponent hands))
{
return false;
}
if (!CanInsert(entity) || !hands.Drop(entity, _container))
{
return false;
}
AfterInsert(entity);
return true;
}
private void Remove(IEntity entity)
{
_container.Remove(entity);
if (ContainedEntities.Count == 0)
{
_automaticEngageToken?.Cancel();
_automaticEngageToken = null;
}
UpdateVisualState();
}
private bool CanFlush()
{
return _pressure >= 1 && Powered && Anchored;
}
public bool TryFlush()
{
if (!CanFlush())
{
Engaged = true;
return false;
}
var snapGrid = Owner.GetComponent<SnapGridComponent>();
var entry = snapGrid
.GetLocal()
.FirstOrDefault(entity => entity.HasComponent<DisposalEntryComponent>());
if (entry == null)
{
return false;
}
var entryComponent = entry.GetComponent<DisposalEntryComponent>();
var entities = _container.ContainedEntities.ToList();
foreach (var entity in _container.ContainedEntities.ToList())
{
_container.Remove(entity);
}
entryComponent.TryInsert(entities);
_automaticEngageToken?.Cancel();
_automaticEngageToken = null;
_pressure = 0;
Engaged = false;
UpdateVisualState(true);
UpdateInterface();
return true;
}
private void TryEjectContents()
{
foreach (var entity in _container.ContainedEntities.ToArray())
{
Remove(entity);
}
}
private void TogglePower()
{
if (!Owner.TryGetComponent(out PowerReceiverComponent receiver))
{
return;
}
receiver.PowerDisabled = !receiver.PowerDisabled;
UpdateInterface();
}
private DisposalUnitBoundUserInterfaceState GetInterfaceState()
{
return new DisposalUnitBoundUserInterfaceState(Owner.Name, Loc.GetString($"{State}"), _pressure, Powered);
}
private void UpdateInterface()
{
var state = GetInterfaceState();
_userInterface.SetState(state);
}
private bool PlayerCanUse(IEntity player)
{
if (player == null)
{
return false;
}
if (!ActionBlockerSystem.CanInteract(player) ||
!ActionBlockerSystem.CanUse(player))
{
return false;
}
return true;
}
private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
{
if (obj.Session.AttachedEntity == null)
{
return;
}
if (!PlayerCanUse(obj.Session.AttachedEntity))
{
return;
}
if (!(obj.Message is UiButtonPressedMessage message))
{
return;
}
switch (message.Button)
{
case UiButton.Eject:
TryEjectContents();
break;
case UiButton.Engage:
TryFlush();
break;
case UiButton.Power:
TogglePower();
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f));
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private void UpdateVisualState()
{
UpdateVisualState(false);
}
private void UpdateVisualState(bool flush)
{
if (!Owner.TryGetComponent(out AppearanceComponent appearance))
{
return;
}
appearance.SetData(Visuals.Handle, Engaged
? HandleState.Engaged
: HandleState.Normal);
if (!Anchored)
{
appearance.SetData(Visuals.VisualState, VisualState.UnAnchored);
appearance.SetData(Visuals.Handle, HandleState.Normal);
appearance.SetData(Visuals.Light, LightState.Off);
return;
}
if (flush)
{
appearance.SetData(Visuals.VisualState, VisualState.Flushing);
appearance.SetData(Visuals.Light, LightState.Off);
}
else
{
appearance.SetData(Visuals.VisualState, VisualState.Anchored);
if (ContainedEntities.Count > 0)
{
appearance.SetData(Visuals.Light, LightState.Full);
return;
}
appearance.SetData(Visuals.Light, _pressure < 1
? LightState.Charging
: LightState.Ready);
}
}
public void Update(float frameTime)
{
if (!Powered)
{
return;
}
var oldPressure = _pressure;
_pressure = _pressure + frameTime > 1
? 1
: _pressure + 0.05f * frameTime;
if (oldPressure < 1 && _pressure >= 1)
{
UpdateVisualState();
if (Engaged)
{
TryFlush();
}
}
UpdateInterface();
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataReadWriteFunction(
"pressure",
1.0f,
pressure => _pressure = pressure,
() => _pressure);
serializer.DataReadWriteFunction(
"engageTime",
2,
seconds => _engageTime = TimeSpan.FromSeconds(seconds),
() => (int) _engageTime.TotalSeconds);
serializer.DataReadWriteFunction(
"automaticEngageTime",
30,
seconds => _automaticEngageTime = TimeSpan.FromSeconds(seconds),
() => (int) _automaticEngageTime.TotalSeconds);
}
public override void Initialize()
{
base.Initialize();
_container = ContainerManagerComponent.Ensure<Container>(Name, Owner);
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(DisposalUnitUiKey.Key);
_userInterface.OnReceiveMessage += OnUiReceiveMessage;
UpdateInterface();
}
protected override void Startup()
{
base.Startup();
Owner.EnsureComponent<AnchorableComponent>();
var collidable = Owner.EnsureComponent<CollidableComponent>();
collidable.AnchoredChanged += UpdateVisualState;
UpdateVisualState();
}
public override void OnRemove()
{
foreach (var entity in _container.ContainedEntities.ToArray())
{
_container.ForceRemove(entity);
}
_userInterface.CloseAll();
_automaticEngageToken?.Cancel();
_automaticEngageToken = null;
_container = null!;
base.OnRemove();
}
public override void HandleMessage(ComponentMessage message, IComponent? component)
{
base.HandleMessage(message, component);
switch (message)
{
case RelayMovementEntityMessage msg:
var timing = IoCManager.Resolve<IGameTiming>();
if (Engaged ||
!msg.Entity.HasComponent<HandsComponent>() ||
timing.CurTime < _lastExitAttempt + ExitAttemptDelay)
{
break;
}
_lastExitAttempt = timing.CurTime;
Remove(msg.Entity);
break;
}
}
bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs)
{
if (!ActionBlockerSystem.CanInteract(eventArgs.User))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, eventArgs.User,
Loc.GetString("You can't do that!"));
return false;
}
if (ContainerHelpers.IsInContainer(eventArgs.User))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, eventArgs.User,
Loc.GetString("You can't reach there!"));
return false;
}
if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
{
return false;
}
if (!eventArgs.User.HasComponent<IHandsComponent>())
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, eventArgs.User,
Loc.GetString("You have no hands!"));
return false;
}
_userInterface.Open(actor.playerSession);
return true;
}
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
return TryDrop(eventArgs.User, eventArgs.Using);
}
bool IDragDropOn.CanDragDropOn(DragDropEventArgs eventArgs)
{
return CanInsert(eventArgs.Dropped);
}
bool IDragDropOn.DragDropOn(DragDropEventArgs eventArgs)
{
return TryInsert(eventArgs.Dropped);
}
[Verb]
private sealed class SelfInsertVerb : Verb<DisposalUnitComponent>
{
protected override void GetData(IEntity user, DisposalUnitComponent component, VerbData data)
{
data.Visibility = VerbVisibility.Invisible;
if (!ActionBlockerSystem.CanInteract(user) ||
component.ContainedEntities.Contains(user))
{
return;
}
data.Visibility = VerbVisibility.Visible;
data.Text = Loc.GetString("Jump inside");
}
protected override void Activate(IEntity user, DisposalUnitComponent component)
{
component.TryInsert(user);
}
}
[Verb]
private sealed class FlushVerb : Verb<DisposalUnitComponent>
{
protected override void GetData(IEntity user, DisposalUnitComponent component, VerbData data)
{
data.Visibility = VerbVisibility.Invisible;
if (!ActionBlockerSystem.CanInteract(user) ||
component.ContainedEntities.Contains(user))
{
return;
}
data.Visibility = VerbVisibility.Visible;
data.Text = Loc.GetString("Flush");
}
protected override void Activate(IEntity user, DisposalUnitComponent component)
{
component.TryFlush();
}
}
}
}

View File

@@ -0,0 +1,23 @@
#nullable enable
using Robust.Server.GameObjects.Components.Container;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Maths;
namespace Content.Server.GameObjects.Components.Disposal
{
public interface IDisposalTubeComponent : IComponent
{
Container Contents { get; }
Direction NextDirection(DisposalHolderComponent holder);
Vector2 ExitVector(DisposalHolderComponent holder);
IDisposalTubeComponent? NextTube(DisposalHolderComponent holder);
bool Remove(DisposalHolderComponent holder);
bool TransferTo(DisposalHolderComponent holder, IDisposalTubeComponent to);
bool AdjacentConnected(Direction direction, IDisposalTubeComponent tube);
void AdjacentDisconnected(IDisposalTubeComponent adjacent);
void MoveEvent(MoveEvent moveEvent);
void PopupDirections(IEntity entity);
}
}

View File

@@ -0,0 +1,161 @@
#nullable enable
using System.Linq;
using Content.Shared.Maps;
using JetBrains.Annotations;
using Robust.Server.Interfaces.Console;
using Robust.Server.Interfaces.GameObjects;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Map;
namespace Content.Server.GameObjects.Components.Interactable
{
/// <summary>
/// <see cref="TilePryingComponent.TryPryTile"/>
/// </summary>
[UsedImplicitly]
class TilePryCommand : IClientCommand
{
public string Command => "tilepry";
public string Description => "Pries up all tiles in a radius around the user.";
public string Help => $"Usage: {Command} <radius>";
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
{
if (player?.AttachedEntity == null)
{
return;
}
if (args.Length != 1)
{
shell.SendText(player, Help);
return;
}
if (!int.TryParse(args[0], out var radius))
{
shell.SendText(player, $"{args[0]} isn't a valid integer.");
return;
}
if (radius < 0)
{
shell.SendText(player, "Radius must be positive.");
return;
}
var mapManager = IoCManager.Resolve<IMapManager>();
var playerGrid = player.AttachedEntity.Transform.GridID;
var mapGrid = mapManager.GetGrid(playerGrid);
var playerPosition = player.AttachedEntity.Transform.GridPosition;
var tileDefinitionManager = IoCManager.Resolve<ITileDefinitionManager>();
for (var i = -radius; i <= radius; i++)
{
for (var j = -radius; j <= radius; j++)
{
var tile = mapGrid.GetTileRef(playerPosition.Offset((i, j)));
var coordinates = mapGrid.GridTileToLocal(tile.GridIndices);
var tileDef = (ContentTileDefinition) tileDefinitionManager[tile.Tile.TypeId];
if (!tileDef.CanCrowbar) continue;
var underplating = tileDefinitionManager["underplating"];
mapGrid.SetTile(coordinates, new Tile(underplating.TileId));
}
}
}
}
[UsedImplicitly]
class AnchorCommand : IClientCommand
{
public string Command => "anchor";
public string Description => "Anchors all entities in a radius around the user";
public string Help => $"Usage: {Command} <radius>";
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
{
if (player?.AttachedEntity == null)
{
return;
}
if (args.Length != 1)
{
shell.SendText(player, Help);
return;
}
if (!int.TryParse(args[0], out var radius))
{
shell.SendText(player, $"{args[0]} isn't a valid integer.");
return;
}
if (radius < 0)
{
shell.SendText(player, "Radius must be positive.");
return;
}
var serverEntityManager = IoCManager.Resolve<IServerEntityManager>();
var entities = serverEntityManager.GetEntitiesInRange(player.AttachedEntity, radius).ToList();
foreach (var entity in entities)
{
if (entity.TryGetComponent(out AnchorableComponent anchorable))
{
anchorable.TryAnchor(player.AttachedEntity, force: true);
}
}
}
}
[UsedImplicitly]
class UnAnchorCommand : IClientCommand
{
public string Command => "unanchor";
public string Description => "Unanchors all anchorable entities in a radius around the user";
public string Help => $"Usage: {Command} <radius>";
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
{
if (player?.AttachedEntity == null)
{
return;
}
if (args.Length != 1)
{
shell.SendText(player, Help);
return;
}
if (!int.TryParse(args[0], out var radius))
{
shell.SendText(player, $"{args[0]} isn't a valid integer.");
return;
}
if (radius < 0)
{
shell.SendText(player, "Radius must be positive.");
return;
}
var serverEntityManager = IoCManager.Resolve<IServerEntityManager>();
var entities = serverEntityManager.GetEntitiesInRange(player.AttachedEntity, radius).ToList();
foreach (var entity in entities)
{
if (entity.TryGetComponent(out AnchorableComponent anchorable))
{
anchorable.TryUnAnchor(player.AttachedEntity, force: true);
}
}
}
}
}

View File

@@ -1,7 +1,6 @@
using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.Interfaces.GameObjects.Components.Interaction;
using Content.Server.Interfaces.GameObjects;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Throw;
using Content.Server.Utility;
@@ -17,8 +16,6 @@ using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components
@@ -86,12 +83,22 @@ namespace Content.Server.GameObjects.Components
public bool CanPickup(IEntity user)
{
if (!ActionBlockerSystem.CanPickup(user)) return false;
if (!ActionBlockerSystem.CanPickup(user))
{
return false;
}
if (user.Transform.MapID != Owner.Transform.MapID)
{
return false;
}
if (Owner.TryGetComponent(out PhysicsComponent physics) &&
physics.Anchored)
{
return false;
}
var userPos = user.Transform.MapPosition;
var itemPos = Owner.Transform.MapPosition;
return InteractionChecks.InRangeUnobstructed(user, itemPos, ignoredEnt: Owner, ignoreInsideBlocker:true);

View File

@@ -503,6 +503,12 @@ namespace Content.Server.GameObjects.Components.Items.Storage
}
}
bool IDragDrop.CanDragDrop(DragDropEventArgs eventArgs)
{
return eventArgs.Target.TryGetComponent(out PlaceableSurfaceComponent placeable) &&
placeable.IsPlaceable;
}
bool IDragDrop.DragDrop(DragDropEventArgs eventArgs)
{
if (!ActionBlockerSystem.CanInteract(eventArgs.User))
@@ -523,7 +529,8 @@ namespace Content.Server.GameObjects.Components.Items.Storage
return false;
}
foreach (var storedEntity in storedEntities)
// empty everything out
foreach (var storedEntity in StoredEntities.ToList())
{
if (Remove(storedEntity))
{

View File

@@ -0,0 +1,202 @@
using System.Diagnostics.CodeAnalysis;
using Content.Server.GameObjects.Components.Conveyor;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Construction;
using Content.Shared.GameObjects.Components.Recycling;
using Content.Shared.Physics;
using Robust.Server.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.GameObjects.Components.Map;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Recycling
{
// TODO: Add sound and safe beep
[RegisterComponent]
public class RecyclerComponent : Component, ICollideBehavior
{
#pragma warning disable 649
[Dependency] private readonly IEntityManager _entityManager = default!;
#pragma warning restore 649
public override string Name => "Recycler";
/// <summary>
/// Whether or not sentient beings will be recycled
/// </summary>
[ViewVariables]
private bool _safe;
/// <summary>
/// The percentage of material that will be recovered
/// </summary>
[ViewVariables]
private int _efficiency; // TODO
private bool Powered =>
!Owner.TryGetComponent(out PowerReceiverComponent receiver) ||
receiver.Powered;
private void Bloodstain()
{
if (Owner.TryGetComponent(out AppearanceComponent appearance))
{
appearance.SetData(RecyclerVisuals.Bloody, true);
}
}
private void Clean()
{
if (Owner.TryGetComponent(out AppearanceComponent appearance))
{
appearance.SetData(RecyclerVisuals.Bloody, false);
}
}
private bool CanGib(IEntity entity)
{
return entity.HasComponent<SpeciesComponent>() &&
!_safe &&
Powered;
}
private bool CanRecycle(IEntity entity, [MaybeNullWhen(false)] out ConstructionPrototype prototype)
{
prototype = null;
var constructionSystem = EntitySystem.Get<ConstructionSystem>();
var entityId = entity.MetaData.EntityPrototype?.ID;
if (entityId == null ||
!constructionSystem.CraftRecipes.TryGetValue(entityId, out prototype))
{
return false;
}
return Powered;
}
private void Recycle(IEntity entity)
{
// TODO: Prevent collision with recycled items
if (CanGib(entity))
{
entity.Delete(); // TODO: Gib
Bloodstain();
return;
}
if (!CanRecycle(entity, out var prototype))
{
return;
}
var constructionSystem = EntitySystem.Get<ConstructionSystem>();
var recyclerPosition = Owner.Transform.MapPosition;
foreach (var stage in prototype.Stages)
{
if (!(stage.Forward is ConstructionStepMaterial step))
{
continue;
}
constructionSystem.SpawnIngredient(recyclerPosition, step);
}
entity.Delete();
}
private bool CanRun()
{
if (Owner.TryGetComponent(out PowerReceiverComponent receiver) &&
!receiver.Powered)
{
return false;
}
if (Owner.HasComponent<ItemComponent>())
{
return false;
}
return true;
}
private bool CanMove(IEntity entity)
{
if (entity == Owner)
{
return false;
}
if (!entity.TryGetComponent(out ICollidableComponent collidable) ||
collidable.Anchored)
{
return false;
}
if (entity.HasComponent<ConveyorComponent>())
{
return false;
}
if (entity.HasComponent<IMapGridComponent>())
{
return false;
}
if (ContainerHelpers.IsInContainer(entity))
{
return false;
}
return true;
}
public void Update(float frameTime)
{
if (!CanRun())
{
return;
}
var intersecting = _entityManager.GetEntitiesIntersecting(Owner, true);
var direction = Vector2.UnitX;
foreach (var entity in intersecting)
{
if (!CanMove(entity))
{
continue;
}
if (entity.TryGetComponent(out ICollidableComponent collidable))
{
var controller = collidable.EnsureController<ConveyedController>();
controller.Move(direction, frameTime);
}
}
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _safe, "safe", true);
serializer.DataField(ref _efficiency, "efficiency", 25);
}
void ICollideBehavior.CollideWith(IEntity collidedWith)
{
Recycle(collidedWith);
}
}
}

View File

@@ -0,0 +1,70 @@
#nullable enable
using Content.Server.Interfaces;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Rotatable
{
[RegisterComponent]
public class FlippableComponent : Component
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
#pragma warning restore 649
public override string Name => "Flippable";
private string? _entity;
private void TryFlip(IEntity user)
{
if (Owner.TryGetComponent(out ICollidableComponent collidable) &&
collidable.Anchored)
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Loc.GetString("It's stuck."));
return;
}
if (_entity == null)
{
return;
}
Owner.EntityManager.SpawnEntity(_entity, Owner.Transform.GridPosition);
Owner.Delete();
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _entity, "entity", Owner.Prototype?.ID);
}
[Verb]
private sealed class FlippableVerb : Verb<FlippableComponent>
{
protected override void GetData(IEntity user, FlippableComponent component, VerbData data)
{
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
data.Text = Loc.GetString("Flip");
}
protected override void Activate(IEntity user, FlippableComponent component)
{
component.TryFlip(user);
}
}
}
}

View File

@@ -8,7 +8,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
namespace Content.Server.GameObjects.Components
namespace Content.Server.GameObjects.Components.Rotatable
{
[RegisterComponent]
public class RotatableComponent : Component

View File

@@ -76,13 +76,21 @@ namespace Content.Server.GameObjects.EntitySystems.Click
// trigger dragdrops on the dropped entity
foreach (var dragDrop in dropped.GetAllComponents<IDragDrop>())
{
if (dragDrop.DragDrop(interactionArgs)) return;
if (dragDrop.CanDragDrop(interactionArgs) &&
dragDrop.DragDrop(interactionArgs))
{
return;
}
}
// trigger dragdropons on the targeted entity
foreach (var dragDropOn in target.GetAllComponents<IDragDropOn>())
{
if (dragDropOn.DragDropOn(interactionArgs)) return;
if (dragDropOn.CanDragDropOn(interactionArgs) &&
dragDropOn.DragDropOn(interactionArgs))
{
return;
}
}
}

View File

@@ -41,6 +41,8 @@ namespace Content.Server.GameObjects.EntitySystems
private readonly Dictionary<string, ConstructionPrototype> _craftRecipes = new Dictionary<string, ConstructionPrototype>();
public IReadOnlyDictionary<string, ConstructionPrototype> CraftRecipes => _craftRecipes;
/// <inheritdoc />
public override void Initialize()
{
@@ -207,7 +209,7 @@ namespace Content.Server.GameObjects.EntitySystems
spriteComp.AddLayerWithSprite(prototype.Icon);
}
private void SpawnIngredient(MapCoordinates position, ConstructionStepMaterial lastStep)
public void SpawnIngredient(MapCoordinates position, ConstructionStepMaterial lastStep)
{
if(lastStep is null)
return;

View File

@@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Conveyor;
using Content.Shared.GameObjects.Components.Conveyor;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
namespace Content.Server.GameObjects.EntitySystems
{
[UsedImplicitly]
public class ConveyorSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
EntityQuery = new TypeEntityQuery(typeof(ConveyorComponent));
}
public override void Update(float frameTime)
{
foreach (var entity in RelevantEntities)
{
if (!entity.TryGetComponent(out ConveyorComponent conveyor))
{
continue;
}
conveyor.Update();
}
}
}
public class ConveyorGroup
{
private readonly HashSet<ConveyorComponent> _conveyors;
private readonly HashSet<ConveyorSwitchComponent> _switches;
public ConveyorGroup()
{
_conveyors = new HashSet<ConveyorComponent>(0);
_switches = new HashSet<ConveyorSwitchComponent>(0);
State = ConveyorState.Off;
}
public IReadOnlyCollection<ConveyorComponent> Conveyors => _conveyors;
public IReadOnlyCollection<ConveyorSwitchComponent> Switches => _switches;
public ConveyorState State { get; private set; }
public void AddConveyor(ConveyorComponent conveyor)
{
_conveyors.Add(conveyor);
conveyor.Sync(this);
}
public void RemoveConveyor(ConveyorComponent conveyor)
{
_conveyors.Remove(conveyor);
}
public void AddSwitch(ConveyorSwitchComponent conveyorSwitch)
{
_switches.Add(conveyorSwitch);
if (_switches.Count == 1)
{
SetState(conveyorSwitch);
}
conveyorSwitch.Sync(this);
}
public void RemoveSwitch(ConveyorSwitchComponent conveyorSwitch)
{
_switches.Remove(conveyorSwitch);
}
public void SetState(ConveyorSwitchComponent conveyorSwitch)
{
var state = conveyorSwitch.State;
if (state == ConveyorState.Loose)
{
if (_switches.Count > 0)
{
return;
}
state = ConveyorState.Off;
}
State = state;
foreach (var conveyor in Conveyors)
{
conveyor.Sync(this);
}
foreach (var connectedSwitch in _switches)
{
connectedSwitch.Sync(this);
}
}
}
}

View File

@@ -0,0 +1,26 @@
using Content.Server.GameObjects.Components.Disposal;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
namespace Content.Server.GameObjects.EntitySystems
{
[UsedImplicitly]
public class DisposableSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
EntityQuery = new TypeEntityQuery(typeof(DisposalHolderComponent));
}
public override void Update(float frameTime)
{
foreach (var disposable in RelevantEntities)
{
disposable.GetComponent<DisposalHolderComponent>().Update(frameTime);
}
}
}
}

View File

@@ -0,0 +1,33 @@
using Content.Server.GameObjects.Components.Disposal;
using JetBrains.Annotations;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.GameObjects.Systems;
namespace Content.Server.GameObjects.EntitySystems
{
[UsedImplicitly]
public class DisposalTubeSystem : EntitySystem
{
private void MoveEvent(MoveEvent moveEvent)
{
if (moveEvent.Sender.TryGetComponent(out IDisposalTubeComponent tube))
{
tube.MoveEvent(moveEvent);
}
}
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MoveEvent>(MoveEvent);
}
public override void Shutdown()
{
base.Shutdown();
UnsubscribeLocalEvent<MoveEvent>();
}
}
}

View File

@@ -0,0 +1,26 @@
using Content.Server.GameObjects.Components.Disposal;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
namespace Content.Server.GameObjects.EntitySystems
{
[UsedImplicitly]
public class DisposalUnitSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
EntityQuery = new TypeEntityQuery(typeof(DisposalUnitComponent));
}
public override void Update(float frameTime)
{
foreach (var entity in RelevantEntities)
{
entity.GetComponent<DisposalUnitComponent>().Update(frameTime);
}
}
}
}

View File

@@ -0,0 +1,26 @@
using Content.Server.GameObjects.Components.Recycling;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
namespace Content.Server.GameObjects.EntitySystems
{
[UsedImplicitly]
public class RecyclerSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
EntityQuery = new TypeEntityQuery(typeof(RecyclerComponent));
}
public override void Update(float frameTime)
{
foreach (var entity in RelevantEntities)
{
entity.GetComponent<RecyclerComponent>().Update(frameTime);
}
}
}
}

View File

@@ -105,8 +105,7 @@ namespace Content.Server.Utility
/// </summary>
public static bool InRangeUnobstructed(IEntity user, MapCoordinates otherCoords,
float range = SharedInteractionSystem.InteractionRange,
int collisionMask = (int) CollisionGroup.Impassable, IEntity ignoredEnt = null,
bool ignoreInsideBlocker = false)
int collisionMask = (int) CollisionGroup.Impassable, IEntity ignoredEnt = null, bool ignoreInsideBlocker = false)
{
var mapManager = IoCManager.Resolve<IMapManager>();
var interactionSystem = EntitySystem.Get<SharedInteractionSystem>();
@@ -115,13 +114,11 @@ namespace Content.Server.Utility
{
var localizationManager = IoCManager.Resolve<ILocalizationManager>();
user.PopupMessage(user, localizationManager.GetString("You can't reach there!"));
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Conveyor
{
[Serializable, NetSerializable]
public enum ConveyorVisuals
{
State
}
[Serializable, NetSerializable]
public enum ConveyorState
{
Off = 0,
Forward,
Reversed,
Loose
}
}

View File

@@ -0,0 +1,19 @@
using System;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Disposal
{
[Serializable, NetSerializable]
public enum DisposalTubeVisuals
{
VisualState
}
[Serializable, NetSerializable]
public enum DisposalTubeVisualState
{
Free = 0,
Anchored,
Broken,
}
}

View File

@@ -0,0 +1,96 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Disposal
{
public abstract class SharedDisposalUnitComponent : Component
{
public override string Name => "DisposalUnit";
[Serializable, NetSerializable]
public enum Visuals
{
VisualState,
Handle,
Light
}
[Serializable, NetSerializable]
public enum VisualState
{
UnAnchored,
Anchored,
Flushing
}
[Serializable, NetSerializable]
public enum HandleState
{
Normal,
Engaged
}
[Serializable, NetSerializable]
public enum LightState
{
Off,
Charging,
Full,
Ready
}
[Serializable, NetSerializable]
public enum UiButton
{
Eject,
Engage,
Power
}
[Serializable, NetSerializable]
public enum State
{
Ready,
Pressurizing
}
[Serializable, NetSerializable]
public class DisposalUnitBoundUserInterfaceState : BoundUserInterfaceState
{
public readonly string UnitName;
public readonly string UnitState;
public readonly float Pressure;
public readonly bool Powered;
public DisposalUnitBoundUserInterfaceState(string unitName, string unitState, float pressure, bool powered)
{
UnitName = unitName;
UnitState = unitState;
Pressure = pressure;
Powered = powered;
}
}
/// <summary>
/// Message data sent from client to server when a disposal unit ui button is pressed.
/// </summary>
[Serializable, NetSerializable]
public class UiButtonPressedMessage : BoundUserInterfaceMessage
{
public readonly UiButton Button;
public UiButtonPressedMessage(UiButton button)
{
Button = button;
}
}
[Serializable, NetSerializable]
public enum DisposalUnitUiKey
{
Key
}
}
}

View File

@@ -0,0 +1,11 @@
using System;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Recycling
{
[NetSerializable, Serializable]
public enum RecyclerVisuals
{
Bloody
}
}

View File

@@ -60,6 +60,7 @@
public const uint PROJECTILE = 1053;
public const uint THROWN_ITEM = 1054;
public const uint STRAP = 1055;
public const uint DISPOSABLE = 1056;
// Net IDs for integration tests.
public const uint PREDICTION_TEST = 10001;

View File

@@ -1,28 +1,47 @@
using System;
using System;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Map;
namespace Content.Shared.Interfaces.GameObjects.Components
{
/// <summary>
/// This interface allows the component's entity to be dragged and dropped by mouse onto another entity and gives it
/// behavior when that occurs.
/// This interface allows the component's entity to be dragged and dropped
/// by mouse onto another entity and gives it behavior when that occurs.
/// </summary>
public interface IDragDrop
{
/// <summary>
/// Invoked server-side when this component's entity is being dragged and dropped on another.
///
/// There is no other server-side drag and drop check other than a range check, so make sure to validate
/// if this object can be dropped on the target object!
/// Invoked server-side when this component's entity is being dragged
/// and dropped on another before invoking <see cref="DragDrop"/>.
/// Note that other drag and drop interactions may be attempted if
/// this one fails.
/// </summary>
/// <returns>true iff an interaction occurred and no further interaction should
/// be processed for this drop.</returns>
/// <param name="eventArgs"></param>
/// <returns>true if <see cref="eventArgs"/> is valid, false otherwise.</returns>
bool CanDragDrop(DragDropEventArgs eventArgs);
/// <summary>
/// Invoked server-side when this component's entity is being dragged
/// and dropped on another.
/// Note that other drag and drop interactions may be attempted if
/// this one fails.
/// </summary>
/// <returns>
/// true if an interaction occurred and no further interaction should
/// be processed for this drop.
/// </returns>
bool DragDrop(DragDropEventArgs eventArgs);
}
public class DragDropEventArgs : EventArgs
{
/// <summary>
/// Creates a new instance of <see cref="DragDropEventArgs"/>.
/// </summary>
/// <param name="user">The entity doing the drag and drop.</param>
/// <param name="dropLocation">The location where <see cref="dropped"/> is being dropped.</param>
/// <param name="dropped">The entity that is being dragged and dropped.</param>
/// <param name="target">The entity that <see cref="dropped"/> is being dropped onto.</param>
public DragDropEventArgs(IEntity user, GridCoordinates dropLocation, IEntity dropped, IEntity target)
{
User = user;
@@ -31,9 +50,24 @@ namespace Content.Shared.Interfaces.GameObjects.Components
Target = target;
}
/// <summary>
/// The entity doing the drag and drop.
/// </summary>
public IEntity User { get; }
/// <summary>
/// The location where <see cref="Dropped"/> is being dropped.
/// </summary>
public GridCoordinates DropLocation { get; }
/// <summary>
/// The entity that is being dragged and dropped.
/// </summary>
public IEntity Dropped { get; }
/// <summary>
/// The entity that <see cref="Dropped"/> is being dropped onto.
/// </summary>
public IEntity Target { get; }
}
}

View File

@@ -1,19 +1,31 @@
namespace Content.Shared.Interfaces.GameObjects.Components
namespace Content.Shared.Interfaces.GameObjects.Components
{
/// <summary>
/// This interface allows the component's entity to be dragged and dropped onto by another entity and gives it
/// behavior when that occurs.
/// This interface allows the component's entity to be dragged and dropped
/// onto by another entity and gives it behavior when that occurs.
/// </summary>
public interface IDragDropOn
{
/// <summary>
/// Invoked server-side when another entity is being dragged and dropped onto this one
///
/// There is no other server-side drag and drop check other than a range check, so make sure to validate
/// if this object can be dropped on the dropped object!
/// Invoked server-side when another entity is being dragged and dropped
/// onto this one before invoking <see cref="DragDropOn"/>.
/// Note that other drag and drop interactions may be attempted if
/// this one fails.
/// </summary>
/// <returns>true iff an interaction occurred and no further interaction should
/// be processed for this drop.</returns>
/// <param name="eventArgs"></param>
/// <returns>true if <see cref="eventArgs"/> is valid, false otherwise.</returns>
bool CanDragDropOn(DragDropEventArgs eventArgs);
/// <summary>
/// Invoked server-side when another entity is being dragged and dropped
/// onto this one before invoking <see cref="DragDropOn"/>
/// Note that other drag and drop interactions may be attempted if
/// this one fails.
/// </summary>
/// <returns>
/// true if an interaction occurred and no further interaction should
/// be processed for this drop.
/// </returns>
bool DragDropOn(DragDropEventArgs eventArgs);
}
}

View File

@@ -0,0 +1,38 @@
#nullable enable
using Content.Shared.GameObjects.Components.Movement;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.Interfaces.Physics;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
namespace Content.Shared.Physics
{
public class ConveyedController : VirtualController
{
public override ICollidableComponent? ControlledComponent { protected get; set; }
public void Move(Vector2 velocityDirection, float speed)
{
if (ControlledComponent?.Owner.HasComponent<MovementIgnoreGravityComponent>() == false &&
IoCManager.Resolve<IPhysicsManager>().IsWeightless(ControlledComponent.Owner.Transform.GridPosition))
{
return;
}
if (ControlledComponent?.Status == BodyStatus.InAir)
{
return;
}
LinearVelocity = velocityDirection * speed * 100;
}
public override void UpdateAfterProcessing()
{
base.UpdateAfterProcessing();
LinearVelocity = Vector2.Zero;
}
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -88,6 +88,10 @@
- mapping
- addhand
- removehand
- tilepry
- anchor
- unanchor
- tubeconnections
CanViewVar: true
CanAdminPlace: true
@@ -163,6 +167,10 @@
- mapping
- addhand
- removehand
- tilepry
- anchor
- unanchor
- tubeconnections
CanViewVar: true
CanAdminPlace: true
CanScript: true

File diff suppressed because it is too large Load Diff

View File

@@ -3465,14 +3465,14 @@ entities:
- parent: 384
type: Transform
- uid: 386
type: CableStack
type: CableStack1
components:
- parent: 0
pos: -15.5,-0.5
rot: -1.5707963267949 rad
type: Transform
- uid: 387
type: CableStack
type: CableStack1
components:
- parent: 0
pos: -15.5,-0.5
@@ -3756,14 +3756,14 @@ entities:
rot: -1.5707963267949 rad
type: Transform
- uid: 413
type: CableStack
type: CableStack1
components:
- parent: 0
pos: -15.5,0.5
rot: -1.5707963267949 rad
type: Transform
- uid: 414
type: CableStack
type: CableStack1
components:
- parent: 0
pos: -15.5,0.5
@@ -8567,13 +8567,13 @@ entities:
pos: 9.654155,21.628613
type: Transform
- uid: 947
type: CableStack
type: CableStack1
components:
- parent: 0
pos: 10.561831,21.767809
type: Transform
- uid: 948
type: CableStack
type: CableStack1
components:
- parent: 0
pos: 10.577456,21.424059

View File

@@ -3,38 +3,38 @@
name: gravity generator
description: It's what keeps you to the floor.
components:
- type: Sprite
sprite: Constructible/Power/gravity_generator.rsi
layers:
- state: on
- sprite: Constructible/Power/gravity_generator_core.rsi
state: activated
shader: unshaded
- type: Icon
sprite: Constructible/Power/gravity_generator.rsi
state: on
- type: SnapGrid
offset: Center
- type: PowerReceiver
powerLoad: 500
- type: Collidable
shapes:
- !type:PhysShapeAabb
bounds: "-1.5,-1.5,1.5,1.5"
layer:
- Opaque
- Impassable
- MobImpassable
- VaultImpassable
- type: Clickable
- type: InteractionOutline
- type: Damageable
- type: Breakable
threshold: 150
- type: GravityGenerator
- type: UserInterface
interfaces:
- key: enum.GravityGeneratorUiKey.Key
type: GravityGeneratorBoundUserInterface
- type: Sprite
sprite: Constructible/Power/gravity_generator.rsi
layers:
- state: on
- sprite: Constructible/Power/gravity_generator_core.rsi
state: activated
shader: unshaded
- type: Icon
sprite: Constructible/Power/gravity_generator.rsi
state: on
- type: SnapGrid
offset: Center
- type: PowerReceiver
powerLoad: 500
- type: Collidable
shapes:
- !type:PhysShapeAabb
bounds: "-1.5,-1.5,1.5,1.5"
layer:
- Opaque
- Impassable
- MobImpassable
- VaultImpassable
- type: Clickable
- type: InteractionOutline
- type: Damageable
- type: Breakable
threshold: 150
- type: GravityGenerator
- type: UserInterface
interfaces:
- key: enum.GravityGeneratorUiKey.Key
type: GravityGeneratorBoundUserInterface
placement:
mode: AlignTileAny

View File

@@ -14,11 +14,9 @@
color: "#889192"
drawdepth: Walls
sprite: Constructible/Structures/Walls/low_wall.rsi
- type: Icon
sprite: Constructible/Structures/Walls/low_wall.rsi
state: metal
- type: Collidable
shapes:
- !type:PhysShapeAabb
@@ -26,10 +24,8 @@
- type: Damageable
- type: Destructible
thresholdvalue: 100
- type: SnapGrid
offset: Center
- type: LowWall
key: walls
base: metal_

View File

@@ -13,10 +13,8 @@
- type: Sprite
netsync: false
drawdepth: Walls
- type: Icon
state: full
- type: Collidable
shapes:
- !type:PhysShapeAabb
@@ -33,10 +31,8 @@
- type: Occluder
sizeX: 32
sizeY: 32
- type: SnapGrid
offset: Center
- type: IconSmooth
key: walls
base: solid

View File

@@ -0,0 +1,61 @@
- type: entity
id: ConveyorBelt
name: conveyor belt
description: A conveyor belt, commonly used to transport large numbers of items elsewhere quite quickly.
placement:
mode: SnapgridCenter
components:
- type: Clickable
- type: InteractionOutline
- type: Collidable
hard: false
shapes:
- !type:PhysShapeAabb
bounds: "-0.49,-0.49,0.49,0.49"
mask:
- Impassable
- MobImpassable
- VaultImpassable
- SmallImpassable
- type: SnapGrid
offset: Center
- type: Sprite
netsync: false
sprite: Constructible/Power/conveyor.rsi
state: conveyor_started_cw
drawdepth: FloorObjects
- type: Icon
sprite: Constructible/Power/conveyor.rsi
state: conveyor_started_cw
- type: Conveyor
- type: Appearance
visuals:
- type: ConveyorVisualizer
state_running: conveyor_started_cw
state_stopped: conveyor_stopped_cw
state_reversed: conveyor_started_cw_r
state_loose: conveyor_loose
- type: PowerReceiver
- type: entity
id: ConveyorSwitch
name: conveyor switch
description: A conveyor control switch.
components:
- type: Clickable
- type: InteractionOutline
- type: Sprite
netsync: false
sprite: Constructible/Power/conveyor.rsi
state: switch-off
- type: Icon
sprite: Constructible/Power/conveyor.rsi
state: switch-off
- type: ConveyorSwitch
- type: Appearance
visuals:
- type: ConveyorSwitchVisualizer
state_forward: switch-fwd
state_off: switch-off
state_reversed: switch-rev
state_loose: switch

View File

@@ -0,0 +1,222 @@
- type: entity
id: DisposalPipeBase
abstract: true
placement:
mode: SnapgridCenter
snap:
- Disposal
components:
- type: Clickable
- type: InteractionOutline
- type: Physics
anchored: true
- type: SnapGrid
offset: Center
- type: Anchorable
- type: Damageable
- type: Breakable
- type: Rotatable
- type: entity
id: DisposalHolder
name: disposal holder
components:
- type: DisposalHolder
- type: entity
id: DisposalPipe
parent: DisposalPipeBase
name: disposal pipe segment
description: A huge pipe segment used for constructing disposal systems
components:
- type: Sprite
drawdepth: BelowFloor
sprite: Constructible/Power/disposal.rsi
state: conpipe-s
- type: Icon
sprite: Constructible/Power/disposal.rsi
state: conpipe-s
- type: DisposalTransit
- type: Appearance
visuals:
- type: DisposalVisualizer
state_free: conpipe-s
state_anchored: pipe-s
state_broken: pipe-b
- type: entity
id: DisposalTrunk
parent: DisposalPipeBase
name: disposal trunk
description: A pipe trunk used as an entry point for disposal systems
components:
- type: Sprite
drawdepth: BelowFloor
sprite: Constructible/Power/disposal.rsi
state: conpipe-t
- type: Icon
sprite: Constructible/Power/disposal.rsi
state: conpipe-t
- type: DisposalEntry
- type: Appearance
visuals:
- type: DisposalVisualizer
state_free: conpipe-t
state_anchored: pipe-t
state_broken: pipe-b
- type: entity
id: DisposalUnit
name: disposal unit
description: A pneumatic waste disposal unit
placement:
mode: SnapgridCenter
snap:
- Disposal
components:
- type: Sprite
netsync: false
sprite: Constructible/Power/disposal.rsi
layers:
- state: condisposal
map: ["enum.DisposalUnitVisualLayers.Base"]
- state: dispover-handle
map: ["enum.DisposalUnitVisualLayers.Handle"]
- state: dispover-ready
map: ["enum.DisposalUnitVisualLayers.Light"]
- type: Icon
sprite: Constructible/Power/disposal.rsi
state: disposal
- type: PowerReceiver
- type: DisposalUnit
flushTime: 2
- type: Clickable
- type: InteractionOutline
- type: Physics
anchored: true
shapes:
- !type:PhysShapeAabb
bounds: "-0.3,-0.3,0.3,0.3"
layer:
- Impassable
- MobImpassable
- type: SnapGrid
offset: Center
- type: Anchorable
- type: Destructible
thresholdvalue: 100
- type: Appearance
visuals:
- type: DisposalUnitVisualizer
state_unanchored: condisposal
state_anchored: disposal
overlay_charging: dispover-charge
overlay_ready: dispover-ready
overlay_full: dispover-full
overlay_engaged: dispover-handle
state_flush: disposal-flush
flush_sound: /Audio/Machines/disposalflush.ogg
flush_time: 2
- type: UserInterface
interfaces:
- key: enum.DisposalUnitUiKey.Key
type: DisposalUnitBoundUserInterface
- type: entity
id: DisposalJunction
parent: DisposalPipeBase
name: disposal junction
description: A three-way junction. The arrow indicates where items exit
components:
- type: Sprite
drawdepth: BelowFloor
sprite: Constructible/Power/disposal.rsi
state: conpipe-j1
- type: Icon
sprite: Constructible/Power/disposal.rsi
state: conpipe-j1
- type: DisposalJunction
degrees:
- 0
- -90
- 180
- type: Appearance
visuals:
- type: DisposalVisualizer
state_free: conpipe-j1
state_anchored: pipe-j1
state_broken: pipe-b
- type: Flippable
entity: DisposalJunctionFlipped
- type: entity
id: DisposalJunctionFlipped
parent: DisposalJunction
name: flipped disposal junction
components:
- type: Sprite
drawdepth: BelowFloor
sprite: Constructible/Power/disposal.rsi
state: conpipe-j2
- type: Icon
sprite: Constructible/Power/disposal.rsi
state: conpipe-j2
- type: DisposalJunction
degrees:
- 0
- 90
- 180
- type: Appearance
visuals:
- type: DisposalVisualizer
state_free: conpipe-j2
state_anchored: pipe-j2
state_broken: pipe-b
- type: Flippable
entity: DisposalJunction
- type: entity
id: DisposalYJunction
parent: DisposalPipeBase
name: disposal y-junction
description: A three-way junction with another exit point.
components:
- type: Sprite
drawdepth: BelowFloor
sprite: Constructible/Power/disposal.rsi
state: conpipe-y
- type: Icon
sprite: Constructible/Power/disposal.rsi
state: conpipe-y
- type: DisposalJunction
degrees:
- 0
- 90
- -90
- type: Appearance
visuals:
- type: DisposalVisualizer
state_free: conpipe-y
state_anchored: pipe-y
state_broken: pipe-b
- type: entity
id: DisposalBend
parent: DisposalPipeBase
name: disposal bend
description: A tube bent at a 90 degree angle.
components:
- type: Sprite
drawdepth: BelowFloor
sprite: Constructible/Power/disposal.rsi
state: conpipe-c
- type: Icon
sprite: Constructible/Power/disposal.rsi
state: conpipe-c
- type: DisposalBend
- type: Appearance
visuals:
- type: DisposalVisualizer
state_free: conpipe-c
state_anchored: pipe-c
state_broken: pipe-b

View File

@@ -0,0 +1,37 @@
- type: entity
id: Recycler
name: recycler
description: A large crushing machine used to recycle small items inefficiently. There are lights on the side.
placement:
mode: SnapgridCenter
components:
- type: Clickable
- type: InteractionOutline
- type: Collidable
hard: false
shapes:
- !type:PhysShapeAabb
bounds: "-0.49,-0.49,0.49,0.49"
layer:
- Opaque
- Impassable
- MobImpassable
- VaultImpassable
- type: SnapGrid
offset: Center
- type: Sprite
netsync: false
sprite: Constructible/Power/recycling.rsi
layers:
- state: grinder-o1
map: ["enum.RecyclerVisualLayers.Bloody"]
- type: Icon
sprite: Constructible/Power/recycling.rsi
state: grinder-o0
- type: Appearance
visuals:
- type: RecyclerVisualizer
state_clean: grinder-o1
state_bloody: grinder-o1bld
- type: Recycler
- type: PowerReceiver

View File

@@ -69,7 +69,8 @@
- type: entity
abstract: true
parent: BaseItem
id: CableStack
id: CableStack1
name: cable stack 1
suffix: Full
components:
- type: Stack
@@ -84,7 +85,7 @@
all: -0.15,-0.15,0.15,0.15
- type: entity
parent: CableStack
parent: CableStack1
id: HVWireStack
name: HV Wire Coil
components:
@@ -95,7 +96,7 @@
blockingWireType: HighVoltage
- type: entity
parent: CableStack
parent: CableStack1
id: MVWireStack
name: MV Wire Coil
components:
@@ -106,7 +107,7 @@
blockingWireType: MediumVoltage
- type: entity
parent: CableStack
parent: CableStack1
id: ApcExtensionCableStack
name: Apc Extension Cable Coil
components:
@@ -115,7 +116,7 @@
- type: WirePlacer
wirePrototypeID: ApcExtensionCable
blockingWireType: Apc
- type: entity
parent: HVWireStack
id: HVWireStack1
@@ -123,7 +124,7 @@
components:
- type: Stack
count: 1
- type: entity
parent: MVWireStack
id: MVWireStack1
@@ -131,7 +132,7 @@
components:
- type: Stack
count: 1
- type: entity
parent: ApcExtensionCableStack
id: ApcExtensionCableStack1
@@ -139,7 +140,7 @@
components:
- type: Stack
count: 1
- type: entity
name: gold bar
id: GoldStack

View File

@@ -13,7 +13,6 @@
bounds: "-0.25,-0.25,0.25,0.25"
layer:
- Clickable
IsScrapingFloor: true
- type: Physics
mass: 5

View File

@@ -0,0 +1,19 @@
- type: construction
name: conveyor belt
id: ConveyorBelt
category: Machines/Conveyor
keywords: [conveyor, belt]
placementMode: SnapgridCenter
description: A conveyor belt, commonly used to transport large numbers of items elsewhere quite quickly.
icon:
sprite: Constructible/Power/conveyor.rsi
state: conveyor_stopped_cw
result: ConveyorBelt
steps:
- material: Metal
amount: 1
icon:
sprite: Constructible/Power/conveyor.rsi
state: conveyor_stopped_cw
- material: Cable
amount: 1

View File

@@ -9,7 +9,6 @@
icon:
sprite: Constructible/Lighting/lighting.rsi
state: on
result: Poweredlight
steps:
- material: Metal
@@ -17,13 +16,10 @@
icon:
sprite: Constructible/Lighting/lighting.rsi
state: construct
- material: Cable
amount: 1
icon:
sprite: Constructible/Lighting/lighting.rsi
state: empty
- material: Glass
amount: 1

View File

@@ -15,7 +15,6 @@
icon: Constructible/Structures/Walls/wall_girder.png
reverse:
tool: Anchoring
- material: Metal
amount: 2
reverse:
@@ -90,7 +89,6 @@
amount: 2
reverse:
tool: Anchoring
# Should be replaced with Metal Rods when someone puts them in.
- material: Metal
amount: 2

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 B

View File

@@ -0,0 +1 @@
{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "https://github.com/discordia-space/CEV-Eris/blob/a846799c53f8ee9dcec4b075d7645f591f3ec19d/icons/obj/machines/conveyor.dmi", "states": [{"name": "conveyor_loose", "directions": 1, "delays": [[1.0]]}, {"name": "conveyor_started_ccw", "directions": 8, "delays": [[0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1]]}, {"name": "conveyor_started_ccw_r", "directions": 8, "delays": [[0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1]]}, {"name": "conveyor_started_cw", "directions": 8, "delays": [[0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1]]}, {"name": "conveyor_started_cw_r", "directions": 8, "delays": [[0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1]]}, {"name": "conveyor_stopped_ccw", "directions": 8, "delays": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]]}, {"name": "conveyor_stopped_cw", "directions": 8, "delays": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]]}, {"name": "greenlight", "directions": 1, "delays": [[1.0]]}, {"name": "redlight", "directions": 1, "delays": [[1.0]]}, {"name": "switch", "directions": 1, "delays": [[1.0]]}, {"name": "switch-fwd", "directions": 1, "delays": [[1.0]]}, {"name": "switch-off", "directions": 1, "delays": [[1.0]]}, {"name": "switch-rev", "directions": 1, "delays": [[1.0]]}]}

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1 @@
{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "https://github.com/discordia-space/CEV-Eris/blob/bbe32606902c90f5290b57d905a3f31b84dc6d7d/icons/obj/pipes/disposal.dmi and modified by DrSmugleaf", "states": [{"name": "condisposal", "directions": 1, "delays": [[1.0]]}, {"name": "conpipe-c", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "conpipe-j1", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "conpipe-j1s", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "conpipe-j2", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "conpipe-j2s", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "conpipe-s", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "conpipe-t", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "conpipe-y", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "disposal", "directions": 1, "delays": [[1.0]]}, {"name": "disposal-flush", "directions": 1, "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.5, 0.1, 0.1, 0.1]]}, {"name": "dispover-charge", "directions": 1, "delays": [[0.4, 0.4]]}, {"name": "dispover-full", "directions": 1, "delays": [[0.2, 0.2]]}, {"name": "dispover-handle", "directions": 1, "delays": [[1.0]]}, {"name": "dispover-ready", "directions": 1, "delays": [[1.0]]}, {"name": "intake", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "intake-closing", "directions": 4, "delays": [[0.5, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.5, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.5, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.5, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "outlet", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "outlet-open", "directions": 4, "delays": [[0.5, 0.5, 0.5, 0.5, 0.5, 0.1, 1.5, 0.1], [0.5, 0.5, 0.5, 0.5, 0.5, 0.1, 1.5, 0.1], [0.5, 0.5, 0.5, 0.5, 0.5, 0.1, 1.5, 0.1], [0.5, 0.5, 0.5, 0.5, 0.5, 0.1, 1.5, 0.1]]}, {"name": "pipe-b", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-bf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-c", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-cf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-d", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j1", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j1f", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j1s", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j1sf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j2", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j2f", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j2s", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j2sf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-s", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-sf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-t", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-tagger", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-tagger-partial", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-tf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-u", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-y", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-yf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}]}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

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