Exosuit: Ripley (#12668)
* mechs * interaction relay * atmos handling * fuck around with interaction events SPAGHETTI CODE OH MY GOD * more sprites and whatever the hell * more mech shit * more shit for equipment * starting equipment (for nukie mechs and such) * equipment cycling * starting with some of the ui * a fat chunk of ui prototyping * done tinkering with ui * a bunch of ui stuff and what have yous * cleaning up grabber and state handling * make the ui actually functional + watch me port a million icons I swear i'll prune the sprites later blease * start on construction * construction yo mamma * remove some unused files * fix a silly * make the graph sane * make it actually constructible. * print the boards as well, bozo * rebalance part prices * eject action also i appease the russians by remembering to localize * Punch Shit * make mech integrity and repairs work * Make the UI more based STOMP STOMP STOMP STOMP * make equipment even more based * batteries and other such delights * make the ui look pimpin af * make the construction mega based * UI but so epic * equipment * some sweat tweaks * damage rebalancing * restructure tech * fix some shit * mechs inherit access * make icons actually use sprite specifiers * TRAILING COMMAA!!!!! * fix a mild indentation sin * undo this change because it isn't needed * actually fix this * secret webeditting shhhh * place this tech here * comments * foo
|
|
@ -131,7 +131,7 @@ public abstract class CartridgeLoaderBoundUserInterface : BoundUserInterface
|
|||
private UIFragment? RetrieveCartridgeUI(EntityUid? cartridgeUid)
|
||||
{
|
||||
var component = _entityManager?.GetComponentOrNull<UIFragmentComponent>(cartridgeUid);
|
||||
component?.Ui?.Setup(this);
|
||||
component?.Ui?.Setup(this, cartridgeUid);
|
||||
return component?.Ui;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public sealed class NetProbeUi : UIFragment
|
|||
return _fragment!;
|
||||
}
|
||||
|
||||
public override void Setup(BoundUserInterface userInterface)
|
||||
public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner)
|
||||
{
|
||||
_fragment = new NetProbeUiFragment();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ public sealed class NotekeeperUi : UIFragment
|
|||
return _fragment!;
|
||||
}
|
||||
|
||||
public override void Setup(BoundUserInterface userInterface)
|
||||
public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner)
|
||||
{
|
||||
_fragment = new NotekeeperUiFragment();
|
||||
_fragment.OnNoteRemoved += note => SendNotekeeperMessage(NotekeeperUiAction.Remove, note, userInterface);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
using Content.Shared.Mech;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Mech;
|
||||
|
||||
/// <summary>
|
||||
/// Handles the sprite state changes while
|
||||
/// constructing mech assemblies.
|
||||
/// </summary>
|
||||
public sealed class MechAssemblyVisualizerSystem : VisualizerSystem<MechAssemblyVisualsComponent>
|
||||
{
|
||||
protected override void OnAppearanceChange(EntityUid uid, MechAssemblyVisualsComponent component,
|
||||
ref AppearanceChangeEvent args)
|
||||
{
|
||||
base.OnAppearanceChange(uid, component, ref args);
|
||||
|
||||
if (!args.Component.TryGetData(MechAssemblyVisuals.State, out int stage))
|
||||
return;
|
||||
|
||||
var state = component.StatePrefix + stage;
|
||||
|
||||
args.Sprite?.LayerSetState(0, state);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
namespace Content.Client.Mech;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for visualizing mech constructions
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class MechAssemblyVisualsComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The prefix that is followed by the number which
|
||||
/// denotes the current state to use.
|
||||
/// </summary>
|
||||
[DataField("statePrefix", required: true)]
|
||||
public string StatePrefix = string.Empty;
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
using Content.Shared.Mech.Components;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Client.Mech;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[ComponentReference(typeof(SharedMechComponent))]
|
||||
public sealed class MechComponent : SharedMechComponent
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
using Content.Shared.Mech;
|
||||
using Content.Shared.Mech.EntitySystems;
|
||||
using Robust.Client.GameObjects;
|
||||
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
|
||||
|
||||
namespace Content.Client.Mech;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class MechSystem : SharedMechSystem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<MechComponent, AppearanceChangeEvent>(OnAppearanceChanged);
|
||||
}
|
||||
|
||||
private void OnAppearanceChanged(EntityUid uid, MechComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
if (!args.Sprite.TryGetLayer((int) MechVisualLayers.Base, out var layer))
|
||||
return;
|
||||
|
||||
var state = component.BaseState;
|
||||
var drawDepth = DrawDepth.Mobs;
|
||||
if (component.BrokenState != null && args.Component.TryGetData(MechVisuals.Broken, out bool broken) && broken)
|
||||
{
|
||||
state = component.BrokenState;
|
||||
drawDepth = DrawDepth.SmallMobs;
|
||||
}
|
||||
else if (component.OpenState != null && args.Component.TryGetData(MechVisuals.Open, out bool open) && open)
|
||||
{
|
||||
state = component.OpenState;
|
||||
drawDepth = DrawDepth.SmallMobs;
|
||||
}
|
||||
|
||||
layer.SetState(state);
|
||||
args.Sprite.DrawDepth = (int) drawDepth;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using Content.Client.UserInterface.Fragments;
|
||||
using Content.Shared.Mech;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Mech.Ui.Equipment;
|
||||
|
||||
public sealed class MechGrabberUi : UIFragment
|
||||
{
|
||||
private MechGrabberUiFragment? _fragment;
|
||||
|
||||
public override Control GetUIFragmentRoot()
|
||||
{
|
||||
return _fragment!;
|
||||
}
|
||||
|
||||
public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner)
|
||||
{
|
||||
if (fragmentOwner == null)
|
||||
return;
|
||||
|
||||
_fragment = new MechGrabberUiFragment();
|
||||
_fragment.OnEjectAction += e =>
|
||||
{
|
||||
userInterface.SendMessage(new MechGrabberEjectMessage(fragmentOwner.Value, e));
|
||||
};
|
||||
}
|
||||
|
||||
public override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
if (state is not MechGrabberUiState grabberState)
|
||||
return;
|
||||
|
||||
_fragment?.UpdateContents(grabberState);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<equipment:MechGrabberUiFragment
|
||||
xmlns:equipment="clr-namespace:Content.Client.Mech.Ui.Equipment"
|
||||
xmlns="https://spacestation14.io" Margin="1 0 2 0" HorizontalExpand="True" VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<ItemList Name="ItemList"
|
||||
VerticalExpand="True"
|
||||
MinHeight="120"
|
||||
HorizontalExpand="True"
|
||||
SelectMode="Button">
|
||||
</ItemList>
|
||||
<Label Name="SpaceLabel" HorizontalAlignment="Right" StyleClasses="LabelSubText"></Label>
|
||||
</BoxContainer>
|
||||
</equipment:MechGrabberUiFragment>
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
using Content.Shared.Mech;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Mech.Ui.Equipment;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MechGrabberUiFragment : BoxContainer
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entity = default!;
|
||||
|
||||
public event Action<EntityUid>? OnEjectAction;
|
||||
|
||||
public MechGrabberUiFragment()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
public void UpdateContents(MechGrabberUiState state)
|
||||
{
|
||||
SpaceLabel.Text = $"{state.Contents.Count}/{state.MaxContents}";
|
||||
for (var i = 0; i < state.Contents.Count; i++)
|
||||
{
|
||||
var ent = state.Contents[i];
|
||||
|
||||
if (!_entity.TryGetComponent<MetaDataComponent>(ent, out var meta))
|
||||
continue;
|
||||
|
||||
ItemList.AddItem(meta.EntityName);
|
||||
ItemList[i].OnSelected += _ => OnEjectAction?.Invoke(ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
using Content.Client.UserInterface.Fragments;
|
||||
using Content.Shared.Mech;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Mech.Ui;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class MechBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _ent = default!;
|
||||
|
||||
private readonly EntityUid _mech;
|
||||
|
||||
private MechMenu? _menu;
|
||||
|
||||
public MechBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_mech = owner.Owner;
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = new(_mech);
|
||||
|
||||
_menu.OnClose += Close;
|
||||
_menu.OpenCenteredLeft();
|
||||
|
||||
_menu.OnRemoveButtonPressed += uid =>
|
||||
{
|
||||
SendMessage(new MechEquipmentRemoveMessage(uid));
|
||||
};
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (state is not MechBoundUiState msg)
|
||||
return;
|
||||
UpdateEquipmentControls(msg);
|
||||
_menu?.UpdateMechStats();
|
||||
_menu?.UpdateEquipmentView();
|
||||
}
|
||||
|
||||
public void UpdateEquipmentControls(MechBoundUiState state)
|
||||
{
|
||||
if (!_ent.TryGetComponent<MechComponent>(_mech, out var mechComp))
|
||||
return;
|
||||
|
||||
foreach (var ent in mechComp.EquipmentContainer.ContainedEntities)
|
||||
{
|
||||
var ui = GetEquipmentUi(ent);
|
||||
if (ui == null)
|
||||
continue;
|
||||
foreach (var (attached, estate) in state.EquipmentStates)
|
||||
{
|
||||
if (ent == attached)
|
||||
ui.UpdateState(estate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
_menu?.Close();
|
||||
}
|
||||
|
||||
public UIFragment? GetEquipmentUi(EntityUid? uid)
|
||||
{
|
||||
var component = _ent.GetComponentOrNull<UIFragmentComponent>(uid);
|
||||
component?.Ui?.Setup(this, uid);
|
||||
return component?.Ui;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls">
|
||||
<Button Disabled="True"
|
||||
Margin="0 0 0 0"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
StyleClasses="ButtonSquare">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" VerticalAlignment="Top">
|
||||
<BoxContainer HorizontalExpand="True" VerticalExpand="True" Orientation="Horizontal">
|
||||
<SpriteView
|
||||
Name="EquipmentView"
|
||||
OverrideDirection="South"
|
||||
MinSize="32 32"
|
||||
SetSize="32 32"
|
||||
Scale="1 1"
|
||||
RectClipContent="True"/>
|
||||
<RichTextLabel Name="EquipmentName" VerticalAlignment="Center"/>
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||
<TextureButton Name="RemoveButton" Scale="0.5 0.5"></TextureButton>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<customControls:HSeparator Name="Separator" StyleClasses="LowDivider" Visible="False"/>
|
||||
<BoxContainer Name="CustomControlContainer" Margin="0 10 0 0" HorizontalExpand="True" VerticalExpand="True"></BoxContainer>
|
||||
</BoxContainer>
|
||||
</Button>
|
||||
</Control>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Mech.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MechEquipmentControl : Control
|
||||
{
|
||||
public event Action? OnRemoveButtonPressed;
|
||||
|
||||
public MechEquipmentControl(string itemName, SpriteComponent? sprite, Control? fragment)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
EquipmentName.SetMessage(itemName);
|
||||
EquipmentView.Sprite = sprite;
|
||||
RemoveButton.TexturePath = "/Textures/Interface/Nano/cross.svg.png";
|
||||
|
||||
if (fragment != null)
|
||||
{
|
||||
Separator.Visible = true;
|
||||
CustomControlContainer.AddChild(fragment);
|
||||
}
|
||||
|
||||
RemoveButton.OnPressed += _ => OnRemoveButtonPressed?.Invoke();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'mech-menu-title'}"
|
||||
MinSize="350 440"
|
||||
SetSize="350 440">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
|
||||
<BoxContainer Margin="10 10 10 10" Orientation="Horizontal" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<ProgressBar Name="IntegrityDisplayBar"
|
||||
HorizontalExpand="True"
|
||||
SetHeight="25"
|
||||
MaxValue="1"
|
||||
Value="0">
|
||||
<Label Name="IntegrityDisplay"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="5 0 0 0"
|
||||
VerticalAlignment="Center" />
|
||||
</ProgressBar>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 5 0 0">
|
||||
<ProgressBar Name="EnergyDisplayBar"
|
||||
HorizontalExpand="True"
|
||||
SetHeight="25"
|
||||
MaxValue="1"
|
||||
Value="0">
|
||||
<Label Name="EnergyDisplay"
|
||||
Margin="5 0 0 0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"></Label>
|
||||
</ProgressBar>
|
||||
</BoxContainer>
|
||||
<Label Name="SlotDisplay"
|
||||
HorizontalAlignment="Left"
|
||||
Access="Public"
|
||||
HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<SpriteView Name="MechView"
|
||||
Margin="10 0 0 0"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
OverrideDirection="South"
|
||||
SetSize="64 64"
|
||||
MaxSize="64 64"
|
||||
Scale="2 2">
|
||||
</SpriteView>
|
||||
</BoxContainer>
|
||||
<BoxContainer VerticalExpand="True" Margin="10 0 10 10" Orientation="Vertical">
|
||||
<PanelContainer VerticalExpand="True" MinSize="0 200">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<ScrollContainer
|
||||
HScrollEnabled="False"
|
||||
HorizontalExpand="True"
|
||||
MinSize="100 256"
|
||||
SizeFlagsStretchRatio="2"
|
||||
VerticalExpand="True">
|
||||
<BoxContainer
|
||||
Name="EquipmentControlContainer"
|
||||
MinSize="100 256"
|
||||
Orientation="Vertical"
|
||||
SizeFlagsStretchRatio="2"
|
||||
VerticalExpand="True">
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.UserInterface.Fragments;
|
||||
using Content.Shared.Mech.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Mech.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MechMenu : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _ent = default!;
|
||||
|
||||
private readonly EntityUid _mech;
|
||||
|
||||
public event Action<EntityUid>? OnRemoveButtonPressed;
|
||||
|
||||
public MechMenu(EntityUid mech)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_mech = mech;
|
||||
|
||||
if (!_ent.TryGetComponent<SpriteComponent>(mech, out var sprite))
|
||||
return;
|
||||
|
||||
MechView.Sprite = sprite;
|
||||
}
|
||||
|
||||
public void UpdateMechStats()
|
||||
{
|
||||
if (!_ent.TryGetComponent<SharedMechComponent>(_mech, out var mechComp))
|
||||
return;
|
||||
|
||||
var integrityPercent = mechComp.Integrity / mechComp.MaxIntegrity;
|
||||
IntegrityDisplayBar.Value = integrityPercent.Float();
|
||||
IntegrityDisplay.Text = Loc.GetString("mech-integrity-display", ("amount", (integrityPercent*100).Int()));
|
||||
|
||||
var energyPercent = mechComp.Energy / mechComp.MaxEnergy;
|
||||
EnergyDisplayBar.Value = energyPercent.Float();
|
||||
EnergyDisplay.Text = Loc.GetString("mech-energy-display", ("amount", (energyPercent*100).Int()));
|
||||
|
||||
SlotDisplay.Text = Loc.GetString("mech-slot-display",
|
||||
("amount", mechComp.MaxEquipmentAmount - mechComp.EquipmentContainer.ContainedEntities.Count));
|
||||
}
|
||||
|
||||
public void UpdateEquipmentView()
|
||||
{
|
||||
if (!_ent.TryGetComponent<MechComponent>(_mech, out var mechComp))
|
||||
return;
|
||||
|
||||
EquipmentControlContainer.Children.Clear();
|
||||
foreach (var ent in mechComp.EquipmentContainer.ContainedEntities)
|
||||
{
|
||||
if (!_ent.TryGetComponent<SpriteComponent>(ent, out var sprite) ||
|
||||
!_ent.TryGetComponent<MetaDataComponent>(ent, out var metaData))
|
||||
continue;
|
||||
|
||||
var uicomp = _ent.GetComponentOrNull<UIFragmentComponent>(ent);
|
||||
var ui = uicomp?.Ui?.GetUIFragmentRoot();
|
||||
|
||||
var control = new MechEquipmentControl(metaData.EntityName, sprite, ui);
|
||||
|
||||
control.OnRemoveButtonPressed += () => OnRemoveButtonPressed?.Invoke(ent);
|
||||
|
||||
EquipmentControlContainer.AddChild(control);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ public abstract class UIFragment
|
|||
{
|
||||
public abstract Control GetUIFragmentRoot();
|
||||
|
||||
public abstract void Setup(BoundUserInterface userInterface);
|
||||
public abstract void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner);
|
||||
|
||||
public abstract void UpdateState(BoundUserInterfaceState state);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
using Content.Server.Mech.Components;
|
||||
using Content.Server.Mech.Systems;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Construction;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Construction.Completions;
|
||||
|
||||
/// <summary>
|
||||
/// Creates the mech entity while transferring all relevant parts inside of it,
|
||||
/// for right now, the cell that was used in construction.
|
||||
/// </summary>
|
||||
[UsedImplicitly, DataDefinition]
|
||||
public sealed class BuildMech : IGraphAction
|
||||
{
|
||||
[DataField("mechPrototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string MechPrototype = string.Empty;
|
||||
|
||||
[DataField("container")]
|
||||
public string Container = "battery-container";
|
||||
|
||||
public void PerformAction(EntityUid uid, EntityUid? userUid, IEntityManager entityManager)
|
||||
{
|
||||
if (!entityManager.TryGetComponent(uid, out ContainerManagerComponent? containerManager))
|
||||
{
|
||||
Logger.Warning($"Mech construct entity {uid} did not have a container manager! Aborting build mech action.");
|
||||
return;
|
||||
}
|
||||
|
||||
var containerSystem = entityManager.EntitySysManager.GetEntitySystem<ContainerSystem>();
|
||||
var mechSys = entityManager.System<MechSystem>();
|
||||
|
||||
if (!containerSystem.TryGetContainer(uid, Container, out var container, containerManager))
|
||||
{
|
||||
Logger.Warning($"Mech construct entity {uid} did not have the specified '{Container}' container! Aborting build mech action.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (container.ContainedEntities.Count != 1)
|
||||
{
|
||||
Logger.Warning($"Mech construct entity {uid} did not have exactly one item in the specified '{Container}' container! Aborting build mech action.");
|
||||
}
|
||||
|
||||
var cell = container.ContainedEntities[0];
|
||||
|
||||
if (!entityManager.TryGetComponent<BatteryComponent>(cell, out var batteryComponent))
|
||||
{
|
||||
Logger.Warning($"Mech construct entity {uid} had an invalid entity in container \"{Container}\"! Aborting build mech action.");
|
||||
return;
|
||||
}
|
||||
|
||||
container.Remove(cell);
|
||||
|
||||
var transform = entityManager.GetComponent<TransformComponent>(uid);
|
||||
var mech = entityManager.SpawnEntity(MechPrototype, transform.Coordinates);
|
||||
|
||||
if (entityManager.TryGetComponent<MechComponent>(mech, out var mechComp) && mechComp.BatterySlot.ContainedEntity == null)
|
||||
{
|
||||
mechSys.InsertBattery(mech, cell, mechComp, batteryComponent);
|
||||
mechComp.BatterySlot.Insert(cell);
|
||||
}
|
||||
|
||||
// Delete the original entity.
|
||||
entityManager.DeleteEntity(uid);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
using Content.Shared.Storage.Components;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Tools;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
|
||||
namespace Content.Server.Mech.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A component used to create a mech chassis
|
||||
/// after the correct parts have been placed inside
|
||||
/// of it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The actual visualization of the parts being inserted is
|
||||
/// done via <see cref="ItemMapperComponent"/>
|
||||
/// </remarks>
|
||||
[RegisterComponent]
|
||||
public sealed class MechAssemblyComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The parts needed to be placed within the assembly,
|
||||
/// stored as a tag and a bool tracking whether or not
|
||||
/// they're present.
|
||||
/// </summary>
|
||||
[DataField("requiredParts", required: true, customTypeSerializer: typeof(PrototypeIdDictionarySerializer<bool, TagPrototype>))]
|
||||
public Dictionary<string, bool> RequiredParts = new();
|
||||
|
||||
/// <summary>
|
||||
/// The prototype spawned when the assembly is finished
|
||||
/// </summary>
|
||||
[DataField("finishedPrototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string FinishedPrototype = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The container that stores all of the parts when
|
||||
/// they're being assembled.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Container PartsContainer = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The quality of tool needed to remove all the parts
|
||||
/// from the parts container.
|
||||
/// </summary>
|
||||
[DataField("qualityNeeded", customTypeSerializer: typeof(PrototypeIdSerializer<ToolQualityPrototype>))]
|
||||
public string QualityNeeded = "Prying";
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
using System.Threading;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Shared.Mech.Components;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Server.Mech.Components;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[ComponentReference(typeof(SharedMechComponent))]
|
||||
public sealed class MechComponent : SharedMechComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// How long it takes to enter the mech.
|
||||
/// </summary>
|
||||
[DataField("entryDelay")]
|
||||
public float EntryDelay = 3;
|
||||
|
||||
/// <summary>
|
||||
/// How long it takes to pull *another person*
|
||||
/// outside of the mech. You can exit instantly yourself.
|
||||
/// </summary>
|
||||
[DataField("exitDelay")]
|
||||
public float ExitDelay = 3;
|
||||
|
||||
/// <summary>
|
||||
/// How long it takes to pull out the battery.
|
||||
/// </summary>
|
||||
[DataField("batteryRemovalDelay")]
|
||||
public float BatteryRemovalDelay = 2;
|
||||
|
||||
public CancellationTokenSource? EntryTokenSource;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the mech is airtight.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This needs to be redone
|
||||
/// when mech internals are added
|
||||
/// </remarks>
|
||||
[DataField("airtight"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Airtight;
|
||||
|
||||
/// <summary>
|
||||
/// The equipment that the mech initially has when it spawns.
|
||||
/// Good for things like nukie mechs that start with guns.
|
||||
/// </summary>
|
||||
[DataField("startingEquipment", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||
public List<string> StartingEquipment = new();
|
||||
|
||||
/// <summary>
|
||||
/// The battery the mech initially has when it spawns
|
||||
/// Good for admemes and nukie mechs.
|
||||
/// </summary>
|
||||
[DataField("startingBattery", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? StartingBattery;
|
||||
|
||||
//TODO: this doesn't support a tank implant for mechs or anything like that
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public GasMixture Air = new (GasMixVolume);
|
||||
public const float GasMixVolume = 70f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a person successfully enters a mech
|
||||
/// </summary>
|
||||
public sealed class MechEntryFinishedEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid User;
|
||||
|
||||
public MechEntryFinishedEvent(EntityUid user)
|
||||
{
|
||||
User = user;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a person fails to enter a mech
|
||||
/// </summary>
|
||||
public sealed class MechEntryCanclledEvent : EntityEventArgs
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a person successfully removes someone from a mech
|
||||
/// </summary>
|
||||
public sealed class MechExitFinishedEvent : EntityEventArgs
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a person fails to remove someone from a mech
|
||||
/// </summary>
|
||||
public sealed class MechExitCanclledEvent : EntityEventArgs
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the battery is successfully removed from the mech
|
||||
/// </summary>
|
||||
public sealed class MechRemoveBatteryFinishedEvent : EntityEventArgs
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the battery fails to be removed from the mech
|
||||
/// </summary>
|
||||
public sealed class MechRemoveBatteryCancelledEvent : EntityEventArgs
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
using System.Threading;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Mech.Equipment.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A piece of mech equipment that grabs entities and stores them
|
||||
/// inside of a container so large objects can be moved.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class MechGrabberComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The change in energy after each grab.
|
||||
/// </summary>
|
||||
[DataField("grabEnergyDelta")]
|
||||
public float GrabEnergyDelta = -30;
|
||||
|
||||
/// <summary>
|
||||
/// How long does it take to grab something?
|
||||
/// </summary>
|
||||
[DataField("grabDelay")]
|
||||
public float GrabDelay = 2.5f;
|
||||
|
||||
/// <summary>
|
||||
/// The offset from the mech when an item is dropped.
|
||||
/// This is here for things like lockers and vendors
|
||||
/// </summary>
|
||||
[DataField("depositOffset")]
|
||||
public Vector2 DepositOffset = new(0, -1);
|
||||
|
||||
/// <summary>
|
||||
/// The maximum amount of items that can be fit in this grabber
|
||||
/// </summary>
|
||||
[DataField("maxContents")]
|
||||
public int MaxContents = 10;
|
||||
|
||||
/// <summary>
|
||||
/// The sound played when a mech is grabbing something
|
||||
/// </summary>
|
||||
[DataField("grabSound")]
|
||||
public SoundSpecifier GrabSound = new SoundPathSpecifier("/Audio/Mecha/sound_mecha_hydraulic.ogg");
|
||||
|
||||
public IPlayingAudioStream? AudioStream;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Container ItemContainer = default!;
|
||||
public CancellationTokenSource? Token;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised on the user when the grab is complete.
|
||||
/// </summary>
|
||||
public sealed class MechGrabberGrabFinishedEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The thing that was grabbed.
|
||||
/// </summary>
|
||||
public EntityUid Grabbed;
|
||||
|
||||
public MechGrabberGrabFinishedEvent(EntityUid grabbed)
|
||||
{
|
||||
Grabbed = grabbed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised on the user when the grab fails
|
||||
/// </summary>
|
||||
public sealed class MechGrabberGrabCancelledEvent : EntityEventArgs
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
using System.Linq;
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Mech.Components;
|
||||
using Content.Server.Mech.Equipment.Components;
|
||||
using Content.Server.Mech.Systems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Mech;
|
||||
using Content.Shared.Mech.Equipment.Components;
|
||||
using Content.Shared.Wall;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Mech.Equipment.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// Handles <see cref="MechGrabberComponent"/> and all related UI logic
|
||||
/// </summary>
|
||||
public sealed class MechGrabberSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly MechSystem _mech = default!;
|
||||
[Dependency] private readonly DoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly InteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<MechGrabberComponent, MechEquipmentUiMessageRelayEvent>(OnGrabberMessage);
|
||||
SubscribeLocalEvent<MechGrabberComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<MechGrabberComponent, MechEquipmentUiStateReadyEvent>(OnUiStateReady);
|
||||
SubscribeLocalEvent<MechGrabberComponent, MechEquipmentRemovedEvent>(OnEquipmentRemoved);
|
||||
SubscribeLocalEvent<MechGrabberComponent, AttemptRemoveMechEquipmentEvent>(OnAttemptRemove);
|
||||
|
||||
SubscribeLocalEvent<MechGrabberComponent, InteractNoHandEvent>(OnInteract);
|
||||
SubscribeLocalEvent<MechGrabberComponent, MechGrabberGrabFinishedEvent>(OnGrabFinished);
|
||||
SubscribeLocalEvent<MechGrabberComponent, MechGrabberGrabCancelledEvent>(OnGrabCancelled);
|
||||
}
|
||||
|
||||
private void OnGrabberMessage(EntityUid uid, MechGrabberComponent component, MechEquipmentUiMessageRelayEvent args)
|
||||
{
|
||||
if (args.Message is not MechGrabberEjectMessage msg)
|
||||
return;
|
||||
|
||||
if (!TryComp<MechEquipmentComponent>(uid, out var equipmentComponent) ||
|
||||
equipmentComponent.EquipmentOwner == null)
|
||||
return;
|
||||
var mech = equipmentComponent.EquipmentOwner.Value;
|
||||
|
||||
var targetCoords = new EntityCoordinates(mech, component.DepositOffset);
|
||||
if (!_interaction.InRangeUnobstructed(mech, targetCoords))
|
||||
return;
|
||||
|
||||
if (!component.ItemContainer.Contains(msg.Item))
|
||||
return;
|
||||
|
||||
RemoveItem(uid, mech, msg.Item, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an item from the grabber's container
|
||||
/// </summary>
|
||||
/// <param name="uid">The mech grabber</param>
|
||||
/// <param name="mech">The mech it belongs to</param>
|
||||
/// <param name="toRemove">The item being removed</param>
|
||||
/// <param name="component"></param>
|
||||
public void RemoveItem(EntityUid uid, EntityUid mech, EntityUid toRemove, MechGrabberComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
component.ItemContainer.Remove(toRemove);
|
||||
var mechxform = Transform(mech);
|
||||
var xform = Transform(toRemove);
|
||||
xform.AttachToGridOrMap();
|
||||
xform.WorldPosition = mechxform.WorldPosition + mechxform.WorldRotation.RotateVec(component.DepositOffset);
|
||||
xform.WorldRotation = Angle.Zero;
|
||||
_mech.UpdateUserInterface(mech);
|
||||
}
|
||||
|
||||
private void OnEquipmentRemoved(EntityUid uid, MechGrabberComponent component, ref MechEquipmentRemovedEvent args)
|
||||
{
|
||||
if (!TryComp<MechEquipmentComponent>(uid, out var equipmentComponent) ||
|
||||
equipmentComponent.EquipmentOwner == null)
|
||||
return;
|
||||
var mech = equipmentComponent.EquipmentOwner.Value;
|
||||
|
||||
var allItems = new List<EntityUid>(component.ItemContainer.ContainedEntities);
|
||||
foreach (var item in allItems)
|
||||
{
|
||||
RemoveItem(uid, mech, item, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAttemptRemove(EntityUid uid, MechGrabberComponent component, ref AttemptRemoveMechEquipmentEvent args)
|
||||
{
|
||||
args.Cancelled = component.ItemContainer.ContainedEntities.Any();
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, MechGrabberComponent component, ComponentStartup args)
|
||||
{
|
||||
component.ItemContainer = _container.EnsureContainer<Container>(uid, "item-container");
|
||||
}
|
||||
|
||||
private void OnUiStateReady(EntityUid uid, MechGrabberComponent component, MechEquipmentUiStateReadyEvent args)
|
||||
{
|
||||
var state = new MechGrabberUiState
|
||||
{
|
||||
Contents = component.ItemContainer.ContainedEntities.ToList(),
|
||||
MaxContents = component.MaxContents
|
||||
};
|
||||
args.States.Add(uid, state);
|
||||
}
|
||||
|
||||
private void OnInteract(EntityUid uid, MechGrabberComponent component, InteractNoHandEvent args)
|
||||
{
|
||||
if (args.Handled || args.Target == null)
|
||||
return;
|
||||
|
||||
var xform = Transform(args.Target.Value);
|
||||
if (xform.Anchored || HasComp<WallMountComponent>(args.Target.Value))
|
||||
return;
|
||||
|
||||
if (component.ItemContainer.ContainedEntities.Count >= component.MaxContents)
|
||||
return;
|
||||
|
||||
if (!TryComp<MechComponent>(args.User, out var mech))
|
||||
return;
|
||||
|
||||
if (mech.Energy + component.GrabEnergyDelta < 0)
|
||||
return;
|
||||
|
||||
if (component.Token != null)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
component.Token = new();
|
||||
component.AudioStream = _audio.PlayPvs(component.GrabSound, uid);
|
||||
_doAfter.DoAfter(new DoAfterEventArgs(args.User, component.GrabDelay, component.Token.Token, args.Target, uid)
|
||||
{
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnUserMove = true,
|
||||
UsedFinishedEvent = new MechGrabberGrabFinishedEvent(args.Target.Value),
|
||||
UserCancelledEvent = new MechGrabberGrabCancelledEvent()
|
||||
});
|
||||
}
|
||||
|
||||
private void OnGrabFinished(EntityUid uid, MechGrabberComponent component, MechGrabberGrabFinishedEvent args)
|
||||
{
|
||||
component.Token = null;
|
||||
|
||||
if (!TryComp<MechEquipmentComponent>(uid, out var equipmentComponent) || equipmentComponent.EquipmentOwner == null)
|
||||
return;
|
||||
if (!_mech.TryChangeEnergy(equipmentComponent.EquipmentOwner.Value, component.GrabEnergyDelta))
|
||||
return;
|
||||
|
||||
component.ItemContainer.Insert(args.Grabbed);
|
||||
_mech.UpdateUserInterface(equipmentComponent.EquipmentOwner.Value);
|
||||
}
|
||||
|
||||
private void OnGrabCancelled(EntityUid uid, MechGrabberComponent component, MechGrabberGrabCancelledEvent args)
|
||||
{
|
||||
component.AudioStream?.Stop();
|
||||
component.Token = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
using Content.Server.Mech.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Tools.Components;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Mech.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Handles <see cref="MechAssemblyComponent"/> and the insertion
|
||||
/// and removal of parts from the assembly.
|
||||
/// </summary>
|
||||
public sealed class MechAssemblySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<MechAssemblyComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<MechAssemblyComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, MechAssemblyComponent component, ComponentInit args)
|
||||
{
|
||||
component.PartsContainer = _container.EnsureContainer<Container>(uid, "mech-assembly-container");
|
||||
}
|
||||
|
||||
private void OnInteractUsing(EntityUid uid, MechAssemblyComponent component, InteractUsingEvent args)
|
||||
{
|
||||
if (TryComp<ToolComponent>(args.Used, out var toolComp) && toolComp.Qualities.Contains(component.QualityNeeded))
|
||||
{
|
||||
foreach (var tag in component.RequiredParts.Keys)
|
||||
{
|
||||
component.RequiredParts[tag] = false;
|
||||
}
|
||||
_container.EmptyContainer(component.PartsContainer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp<TagComponent>(args.Used, out var tagComp))
|
||||
return;
|
||||
|
||||
foreach (var (tag, val) in component.RequiredParts)
|
||||
{
|
||||
if (!val && tagComp.Tags.Contains(tag))
|
||||
{
|
||||
component.RequiredParts[tag] = true;
|
||||
component.PartsContainer.Insert(args.Used);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//check to see if we have all the parts
|
||||
foreach (var val in component.RequiredParts.Values)
|
||||
{
|
||||
if (!val)
|
||||
return;
|
||||
}
|
||||
Spawn(component.FinishedPrototype, Transform(uid).Coordinates);
|
||||
EntityManager.DeleteEntity(uid);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
using Content.Server.DoAfter;
|
||||
using Content.Server.Mech.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Mech.Equipment.Components;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Mech.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Handles the insertion of mech equipment into mechs.
|
||||
/// </summary>
|
||||
public sealed class MechEquipmentSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly MechSystem _mech = default!;
|
||||
[Dependency] private readonly DoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<MechEquipmentComponent, AfterInteractEvent>(OnUsed);
|
||||
SubscribeLocalEvent<MechEquipmentComponent, MechEquipmentInstallFinished>(OnFinished);
|
||||
SubscribeLocalEvent<MechEquipmentComponent, MechEquipmentInstallCancelled>(OnCancelled);
|
||||
}
|
||||
|
||||
private void OnUsed(EntityUid uid, MechEquipmentComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (component.TokenSource != null)
|
||||
return;
|
||||
|
||||
if (args.Handled || !args.CanReach || args.Target == null)
|
||||
return;
|
||||
|
||||
var mech = args.Target.Value;
|
||||
if (!TryComp<MechComponent>(mech, out var mechComp))
|
||||
return;
|
||||
|
||||
if (args.User == mechComp.PilotSlot.ContainedEntity)
|
||||
return;
|
||||
|
||||
if (mechComp.EquipmentContainer.ContainedEntities.Count >= mechComp.MaxEquipmentAmount)
|
||||
return;
|
||||
|
||||
if (mechComp.EquipmentWhitelist != null && !mechComp.EquipmentWhitelist.IsValid(uid))
|
||||
return;
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("mech-equipment-begin-install", ("item", uid)), mech, Filter.Pvs(mech));
|
||||
|
||||
component.TokenSource = new();
|
||||
_doAfter.DoAfter(new DoAfterEventArgs(args.User, component.InstallDuration, component.TokenSource.Token, mech, uid)
|
||||
{
|
||||
BreakOnStun = true,
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnUserMove = true,
|
||||
UsedFinishedEvent = new MechEquipmentInstallFinished(mech),
|
||||
UsedCancelledEvent = new MechEquipmentInstallCancelled()
|
||||
});
|
||||
}
|
||||
|
||||
private void OnFinished(EntityUid uid, MechEquipmentComponent component, MechEquipmentInstallFinished args)
|
||||
{
|
||||
component.TokenSource = null;
|
||||
_popup.PopupEntity(Loc.GetString("mech-equipment-finish-install", ("item", uid)), args.Mech, Filter.Pvs(args.Mech));
|
||||
|
||||
_mech.InsertEquipment(args.Mech, uid);
|
||||
}
|
||||
|
||||
private void OnCancelled(EntityUid uid, MechEquipmentComponent component, MechEquipmentInstallCancelled args)
|
||||
{
|
||||
component.TokenSource = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,428 @@
|
|||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Server.Mech.Components;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Wires;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Mech;
|
||||
using Content.Shared.Mech.Components;
|
||||
using Content.Shared.Mech.EntitySystems;
|
||||
using Content.Shared.Tools.Components;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Mech.Systems;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class MechSystem : SharedMechSystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly DoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_sawmill = Logger.GetSawmill("mech");
|
||||
|
||||
SubscribeLocalEvent<MechComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
SubscribeLocalEvent<MechComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<MechComponent, GetVerbsEvent<AlternativeVerb>>(OnAlternativeVerb);
|
||||
SubscribeLocalEvent<MechComponent, MechOpenUiEvent>(OnOpenUi);
|
||||
SubscribeLocalEvent<MechComponent, MechEntryFinishedEvent>(OnEntryFinished);
|
||||
SubscribeLocalEvent<MechComponent, MechEntryCanclledEvent>(OnEntryExitCancelled);
|
||||
SubscribeLocalEvent<MechComponent, MechExitFinishedEvent>(OnExitFinished);
|
||||
SubscribeLocalEvent<MechComponent, MechExitCanclledEvent>(OnEntryExitCancelled);
|
||||
SubscribeLocalEvent<MechComponent, MechRemoveBatteryFinishedEvent>(OnRemoveBatteryFinished);
|
||||
SubscribeLocalEvent<MechComponent, MechRemoveBatteryCancelledEvent>(OnRemoveBatteryCancelled);
|
||||
|
||||
SubscribeLocalEvent<MechComponent, DamageChangedEvent>(OnDamageChanged);
|
||||
SubscribeLocalEvent<MechComponent, MechEquipmentRemoveMessage>(OnRemoveEquipmentMessage);
|
||||
|
||||
SubscribeLocalEvent<MechPilotComponent, InhaleLocationEvent>(OnInhale);
|
||||
SubscribeLocalEvent<MechPilotComponent, ExhaleLocationEvent>(OnExhale);
|
||||
SubscribeLocalEvent<MechPilotComponent, AtmosExposedGetAirEvent>(OnExpose);
|
||||
|
||||
#region Equipment UI message relays
|
||||
SubscribeLocalEvent<MechComponent, MechGrabberEjectMessage>(RecieveEquipmentUiMesssages);
|
||||
#endregion
|
||||
}
|
||||
|
||||
private void OnInteractUsing(EntityUid uid, MechComponent component, InteractUsingEvent args)
|
||||
{
|
||||
if (TryComp<WiresComponent>(uid, out var wires) && !wires.IsPanelOpen)
|
||||
return;
|
||||
|
||||
if (component.BatterySlot.ContainedEntity == null && TryComp<BatteryComponent>(args.Used, out var battery))
|
||||
{
|
||||
InsertBattery(uid, args.Used, component, battery);
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.EntryTokenSource == null &&
|
||||
TryComp<ToolComponent>(args.Used, out var tool) &&
|
||||
tool.Qualities.Contains("Prying") &&
|
||||
component.BatterySlot.ContainedEntity != null)
|
||||
{
|
||||
component.EntryTokenSource = new();
|
||||
_doAfter.DoAfter(new DoAfterEventArgs(args.User, component.BatteryRemovalDelay, component.EntryTokenSource.Token, uid, args.Target)
|
||||
{
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnUserMove = true,
|
||||
TargetFinishedEvent = new MechRemoveBatteryFinishedEvent(),
|
||||
TargetCancelledEvent = new MechRemoveBatteryCancelledEvent()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRemoveBatteryFinished(EntityUid uid, MechComponent component, MechRemoveBatteryFinishedEvent args)
|
||||
{
|
||||
component.EntryTokenSource = null;
|
||||
|
||||
RemoveBattery(uid, component);
|
||||
}
|
||||
|
||||
private void OnRemoveBatteryCancelled(EntityUid uid, MechComponent component, MechRemoveBatteryCancelledEvent args)
|
||||
{
|
||||
component.EntryTokenSource = null;
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, MechComponent component, MapInitEvent args)
|
||||
{
|
||||
var xform = Transform(uid);
|
||||
foreach (var ent in component.StartingEquipment.Select(equipment => Spawn(equipment, xform.Coordinates)))
|
||||
{
|
||||
InsertEquipment(uid, ent, component);
|
||||
}
|
||||
|
||||
component.Integrity = component.MaxIntegrity;
|
||||
component.Energy = component.MaxEnergy;
|
||||
|
||||
if (component.StartingBattery != null)
|
||||
{
|
||||
var battery = Spawn(component.StartingBattery, Transform(uid).Coordinates);
|
||||
InsertBattery(uid, battery, component);
|
||||
}
|
||||
|
||||
Dirty(component);
|
||||
}
|
||||
|
||||
private void OnRemoveEquipmentMessage(EntityUid uid, SharedMechComponent component, MechEquipmentRemoveMessage args)
|
||||
{
|
||||
if (!Exists(args.Equipment) || Deleted(args.Equipment))
|
||||
return;
|
||||
|
||||
if (!component.EquipmentContainer.ContainedEntities.Contains(args.Equipment))
|
||||
return;
|
||||
|
||||
RemoveEquipment(uid, args.Equipment, component);
|
||||
}
|
||||
|
||||
private void OnOpenUi(EntityUid uid, MechComponent component, MechOpenUiEvent args)
|
||||
{
|
||||
args.Handled = true;
|
||||
ToggleMechUi(uid, component);
|
||||
}
|
||||
|
||||
private void OnAlternativeVerb(EntityUid uid, MechComponent component, GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract || component.Broken)
|
||||
return;
|
||||
|
||||
if (CanInsert(uid, args.User, component))
|
||||
{
|
||||
var enterVerb = new AlternativeVerb
|
||||
{
|
||||
Text = Loc.GetString("mech-verb-enter"),
|
||||
Act = () =>
|
||||
{
|
||||
if (component.EntryTokenSource != null)
|
||||
return;
|
||||
component.EntryTokenSource = new CancellationTokenSource();
|
||||
_doAfter.DoAfter(new DoAfterEventArgs(args.User, component.EntryDelay, component.EntryTokenSource.Token, uid)
|
||||
{
|
||||
BreakOnUserMove = true,
|
||||
BreakOnStun = true,
|
||||
TargetFinishedEvent = new MechEntryFinishedEvent(args.User),
|
||||
TargetCancelledEvent = new MechEntryCanclledEvent()
|
||||
});
|
||||
}
|
||||
};
|
||||
var openUiVerb = new AlternativeVerb //can't hijack someone else's mech
|
||||
{
|
||||
Act = () => ToggleMechUi(uid, component, args.User),
|
||||
Text = Loc.GetString("mech-ui-open-verb")
|
||||
};
|
||||
args.Verbs.Add(enterVerb);
|
||||
args.Verbs.Add(openUiVerb);
|
||||
}
|
||||
else if (!IsEmpty(component))
|
||||
{
|
||||
var ejectVerb = new AlternativeVerb
|
||||
{
|
||||
Text = Loc.GetString("mech-verb-exit"),
|
||||
Priority = 1, // Promote to top to make ejecting the ALT-click action
|
||||
Act = () =>
|
||||
{
|
||||
if (component.EntryTokenSource != null)
|
||||
return;
|
||||
if (args.User == component.PilotSlot.ContainedEntity)
|
||||
{
|
||||
TryEject(uid, component);
|
||||
return;
|
||||
}
|
||||
|
||||
component.EntryTokenSource = new CancellationTokenSource();
|
||||
_doAfter.DoAfter(new DoAfterEventArgs(args.User, component.ExitDelay, component.EntryTokenSource.Token, uid)
|
||||
{
|
||||
BreakOnUserMove = true,
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnStun = true,
|
||||
TargetFinishedEvent = new MechExitFinishedEvent(),
|
||||
TargetCancelledEvent = new MechExitCanclledEvent()
|
||||
});
|
||||
}
|
||||
};
|
||||
args.Verbs.Add(ejectVerb);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEntryFinished(EntityUid uid, MechComponent component, MechEntryFinishedEvent args)
|
||||
{
|
||||
component.EntryTokenSource = null;
|
||||
TryInsert(uid, args.User, component);
|
||||
}
|
||||
|
||||
private void OnExitFinished(EntityUid uid, MechComponent component, MechExitFinishedEvent args)
|
||||
{
|
||||
component.EntryTokenSource = null;
|
||||
TryEject(uid, component);
|
||||
}
|
||||
|
||||
private void OnEntryExitCancelled(EntityUid uid, MechComponent component, EntityEventArgs args)
|
||||
{
|
||||
component.EntryTokenSource = null;
|
||||
}
|
||||
|
||||
private void OnDamageChanged(EntityUid uid, SharedMechComponent component, DamageChangedEvent args)
|
||||
{
|
||||
var integrity = component.MaxIntegrity - args.Damageable.TotalDamage;
|
||||
SetIntegrity(uid, integrity, component);
|
||||
|
||||
if (args.DamageIncreased &&
|
||||
args.DamageDelta != null &&
|
||||
component.PilotSlot.ContainedEntity != null)
|
||||
{
|
||||
var damage = args.DamageDelta * component.MechToPilotDamageMultiplier;
|
||||
_damageable.TryChangeDamage(component.PilotSlot.ContainedEntity, damage);
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleMechUi(EntityUid uid, MechComponent? component = null, EntityUid? user = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
user ??= component.PilotSlot.ContainedEntity;
|
||||
if (user == null)
|
||||
return;
|
||||
|
||||
if (!TryComp<ActorComponent>(user, out var actor))
|
||||
return;
|
||||
|
||||
_ui.TryToggleUi(uid, MechUiKey.Key, actor.PlayerSession);
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
private void RecieveEquipmentUiMesssages<T>(EntityUid uid, MechComponent component, T args) where T : MechEquipmentUiMessage
|
||||
{
|
||||
var ev = new MechEquipmentUiMessageRelayEvent(args);
|
||||
var allEquipment = new List<EntityUid>(component.EquipmentContainer.ContainedEntities);
|
||||
foreach (var equipment in allEquipment)
|
||||
{
|
||||
if (args.Equipment == equipment)
|
||||
RaiseLocalEvent(equipment, ev);
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateUserInterface(EntityUid uid, SharedMechComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
base.UpdateUserInterface(uid, component);
|
||||
|
||||
var ev = new MechEquipmentUiStateReadyEvent();
|
||||
foreach (var ent in component.EquipmentContainer.ContainedEntities)
|
||||
{
|
||||
RaiseLocalEvent(ent, ev);
|
||||
}
|
||||
|
||||
var state = new MechBoundUiState
|
||||
{
|
||||
EquipmentStates = ev.States
|
||||
};
|
||||
var ui = _ui.GetUi(uid, MechUiKey.Key);
|
||||
_ui.SetUiState(ui, state);
|
||||
}
|
||||
|
||||
public override bool TryInsert(EntityUid uid, EntityUid? toInsert, SharedMechComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return false;
|
||||
|
||||
if (!base.TryInsert(uid, toInsert, component))
|
||||
return false;
|
||||
|
||||
var mech = (MechComponent) component;
|
||||
|
||||
if (mech.Airtight)
|
||||
{
|
||||
var coordinates = Transform(uid).MapPosition;
|
||||
if (_map.TryFindGridAt(coordinates, out var grid))
|
||||
{
|
||||
var tile = grid.GetTileRef(coordinates);
|
||||
|
||||
if (_atmosphere.GetTileMixture(tile.GridUid, null, tile.GridIndices, true) is {} environment)
|
||||
{
|
||||
_atmosphere.Merge(mech.Air, environment.RemoveVolume(MechComponent.GasMixVolume));
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool TryEject(EntityUid uid, SharedMechComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return false;
|
||||
|
||||
if (!base.TryEject(uid, component))
|
||||
return false;
|
||||
|
||||
var mech = (MechComponent) component;
|
||||
|
||||
if (mech.Airtight)
|
||||
{
|
||||
var coordinates = Transform(uid).MapPosition;
|
||||
if (_map.TryFindGridAt(coordinates, out var grid))
|
||||
{
|
||||
var tile = grid.GetTileRef(coordinates);
|
||||
|
||||
if (_atmosphere.GetTileMixture(tile.GridUid, null, tile.GridIndices, true) is {} environment)
|
||||
{
|
||||
_atmosphere.Merge(environment, mech.Air);
|
||||
mech.Air.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void BreakMech(EntityUid uid, SharedMechComponent? component = null)
|
||||
{
|
||||
base.BreakMech(uid, component);
|
||||
|
||||
_ui.TryCloseAll(uid, MechUiKey.Key);
|
||||
}
|
||||
|
||||
public override bool TryChangeEnergy(EntityUid uid, FixedPoint2 delta, SharedMechComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return false;
|
||||
|
||||
if (!base.TryChangeEnergy(uid, delta, component))
|
||||
return false;
|
||||
|
||||
var battery = component.BatterySlot.ContainedEntity;
|
||||
if (battery == null)
|
||||
return false;
|
||||
|
||||
if (!TryComp<BatteryComponent>(battery, out var batteryComp))
|
||||
return false;
|
||||
|
||||
batteryComp.CurrentCharge = batteryComp.CurrentCharge + delta.Float();
|
||||
if (batteryComp.CurrentCharge != component.Energy) //if there's a discrepency, we have to resync them
|
||||
{
|
||||
_sawmill.Debug($"Battery charge was not equal to mech charge. Battery {batteryComp.CurrentCharge}. Mech {component.Energy}");
|
||||
component.Energy = batteryComp.CurrentCharge;
|
||||
Dirty(component);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void InsertBattery(EntityUid uid, EntityUid toInsert, MechComponent? component = null, BatteryComponent? battery = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false))
|
||||
return;
|
||||
|
||||
if (!Resolve(toInsert, ref battery, false))
|
||||
return;
|
||||
|
||||
component.BatterySlot.Insert(toInsert);
|
||||
component.Energy = battery.CurrentCharge;
|
||||
component.MaxEnergy = battery.MaxCharge;
|
||||
|
||||
Dirty(component);
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
public void RemoveBattery(EntityUid uid, MechComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
_container.EmptyContainer(component.BatterySlot);
|
||||
component.Energy = 0;
|
||||
component.MaxEnergy = 0;
|
||||
|
||||
Dirty(component);
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
#region Atmos Handling
|
||||
private void OnInhale(EntityUid uid, MechPilotComponent component, InhaleLocationEvent args)
|
||||
{
|
||||
if (!TryComp<MechComponent>(component.Mech, out var mech))
|
||||
return;
|
||||
|
||||
if (mech.Airtight)
|
||||
args.Gas = mech.Air;
|
||||
}
|
||||
|
||||
private void OnExhale(EntityUid uid, MechPilotComponent component, ExhaleLocationEvent args)
|
||||
{
|
||||
if (!TryComp<MechComponent>(component.Mech, out var mech))
|
||||
return;
|
||||
|
||||
if (mech.Airtight)
|
||||
args.Gas = mech.Air;
|
||||
}
|
||||
|
||||
private void OnExpose(EntityUid uid, MechPilotComponent component, ref AtmosExposedGetAirEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (!TryComp<MechComponent>(component.Mech, out var mech))
|
||||
return;
|
||||
|
||||
args.Gas = mech.Airtight ? mech.Air : _atmosphere.GetContainingMixture(component.Mech);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -59,9 +59,9 @@ public sealed partial class RevenantSystem
|
|||
|
||||
private void OnInteract(EntityUid uid, RevenantComponent component, InteractNoHandEvent args)
|
||||
{
|
||||
var target = args.Target;
|
||||
if (target == args.User)
|
||||
if (args.Target == args.User || args.Target == null)
|
||||
return;
|
||||
var target = args.Target.Value;
|
||||
|
||||
if (HasComp<PoweredLightComponent>(target))
|
||||
{
|
||||
|
|
@ -72,9 +72,6 @@ public sealed partial class RevenantSystem
|
|||
if (!HasComp<MobStateComponent>(target) || !HasComp<HumanoidComponent>(target) || HasComp<RevenantComponent>(target))
|
||||
return;
|
||||
|
||||
if (!_interact.InRangeUnobstructed(uid, target))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
if (!TryComp<EssenceComponent>(target, out var essence) || !essence.SearchComplete)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ using Content.Shared.Emoting;
|
|||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Movement;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Events;
|
||||
using Content.Shared.Speech;
|
||||
|
|
@ -148,7 +147,11 @@ namespace Content.Shared.ActionBlocker
|
|||
public bool CanAttack(EntityUid uid, EntityUid? target = null)
|
||||
{
|
||||
if (_container.IsEntityInContainer(uid))
|
||||
return false;
|
||||
{
|
||||
var containerEv = new CanAttackFromContainerEvent(uid, target);
|
||||
RaiseLocalEvent(uid, containerEv);
|
||||
return containerEv.CanAttack;
|
||||
}
|
||||
|
||||
var ev = new AttackAttemptEvent(uid, target);
|
||||
RaiseLocalEvent(uid, ev);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Interaction.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Relays an entities interactions to another entity.
|
||||
/// This doesn't raise the same events, but just relays
|
||||
/// the clicks of the mouse.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(SharedInteractionSystem))]
|
||||
public sealed class InteractionRelayComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity the interactions are being relayed to.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? RelayEntity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains network state for <see cref="InteractionRelayComponent"/>
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class InteractionRelayComponentState : ComponentState
|
||||
{
|
||||
public EntityUid? RelayEntity;
|
||||
|
||||
public InteractionRelayComponentState(EntityUid? relayEntity)
|
||||
{
|
||||
RelayEntity = relayEntity;
|
||||
}
|
||||
}
|
||||
|
|
@ -18,4 +18,20 @@ namespace Content.Shared.Interaction.Events
|
|||
Target = target;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised directed at an entity to check if they can attack while inside of a container.
|
||||
/// </summary>
|
||||
public sealed class CanAttackFromContainerEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid Uid;
|
||||
public EntityUid? Target;
|
||||
public bool CanAttack = false;
|
||||
|
||||
public CanAttackFromContainerEvent(EntityUid uid, EntityUid? target = null)
|
||||
{
|
||||
Uid = uid;
|
||||
Target = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Shared.Interaction
|
||||
{
|
||||
|
|
@ -37,44 +38,57 @@ namespace Content.Shared.Interaction
|
|||
}
|
||||
}
|
||||
|
||||
public sealed class InteractNoHandEvent : HandledEntityEventArgs, ITargetedInteractEventArgs
|
||||
/// <summary>
|
||||
/// Low-level interaction event used for entities without hands.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// SHIT IS CURSED.
|
||||
/// </remarks>
|
||||
//TODO: KILLLLLLL
|
||||
public sealed class InteractNoHandEvent : HandledEntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity that triggered the interaction.
|
||||
/// </summary>
|
||||
public EntityUid User { get; }
|
||||
public EntityUid User;
|
||||
|
||||
/// <summary>
|
||||
/// Entity that was interacted on.
|
||||
/// </summary>
|
||||
public EntityUid Target { get; }
|
||||
public EntityUid? Target;
|
||||
|
||||
public InteractNoHandEvent(EntityUid user, EntityUid target)
|
||||
public EntityCoordinates ClickLocation;
|
||||
|
||||
public InteractNoHandEvent(EntityUid user, EntityUid? target, EntityCoordinates clickLocation)
|
||||
{
|
||||
User = user;
|
||||
Target = target;
|
||||
ClickLocation = clickLocation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverse of the InteractNoHandEvent - raised on what was interacted on, rather than the other way around.
|
||||
/// </summary>
|
||||
public sealed class InteractedNoHandEvent : HandledEntityEventArgs, ITargetedInteractEventArgs
|
||||
public sealed class InteractedNoHandEvent : HandledEntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity that was interacted on
|
||||
/// </summary>
|
||||
public EntityUid Target { get; }
|
||||
public EntityUid Target;
|
||||
|
||||
/// <summary>
|
||||
/// Entity that triggered this interaction
|
||||
/// </summary>
|
||||
public EntityUid User { get; }
|
||||
public EntityUid User;
|
||||
|
||||
public InteractedNoHandEvent(EntityUid target, EntityUid user)
|
||||
public EntityCoordinates ClickLocation;
|
||||
|
||||
public InteractedNoHandEvent(EntityUid target, EntityUid user, EntityCoordinates clickLocation)
|
||||
{
|
||||
Target = target;
|
||||
User = user;
|
||||
ClickLocation = clickLocation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
using Content.Shared.Interaction.Components;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Interaction;
|
||||
|
||||
public abstract partial class SharedInteractionSystem
|
||||
{
|
||||
public void InitializeRelay()
|
||||
{
|
||||
SubscribeLocalEvent<InteractionRelayComponent, ComponentGetState>(OnGetState);
|
||||
SubscribeLocalEvent<InteractionRelayComponent, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
|
||||
private void OnGetState(EntityUid uid, InteractionRelayComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new InteractionRelayComponentState(component.RelayEntity);
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, InteractionRelayComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not InteractionRelayComponentState state)
|
||||
return;
|
||||
|
||||
component.RelayEntity = state.RelayEntity;
|
||||
}
|
||||
|
||||
public void SetRelay(EntityUid uid, EntityUid? relayEntity, InteractionRelayComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
component.RelayEntity = relayEntity;
|
||||
Dirty(component);
|
||||
}
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ namespace Content.Shared.Interaction
|
|||
/// Governs interactions during clicking on entities
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public abstract class SharedInteractionSystem : EntitySystem
|
||||
public abstract partial class SharedInteractionSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
|
@ -81,6 +81,8 @@ namespace Content.Shared.Interaction
|
|||
.Bind(ContentKeyFunctions.TryPullObject,
|
||||
new PointerInputCmdHandler(HandleTryPullObject))
|
||||
.Register<SharedInteractionSystem>();
|
||||
|
||||
InitializeRelay();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
|
|
@ -224,6 +226,11 @@ namespace Content.Shared.Interaction
|
|||
bool checkAccess = true,
|
||||
bool checkCanUse = true)
|
||||
{
|
||||
if (TryComp<InteractionRelayComponent>(user, out var relay) && relay.RelayEntity is not null)
|
||||
{
|
||||
UserInteraction(relay.RelayEntity.Value, coordinates, target, altInteract, checkCanInteract, checkAccess, checkCanUse);
|
||||
}
|
||||
|
||||
if (target != null && Deleted(target.Value))
|
||||
return;
|
||||
|
||||
|
|
@ -255,25 +262,25 @@ namespace Content.Shared.Interaction
|
|||
&& !CanAccessViaStorage(user, target.Value))
|
||||
return;
|
||||
|
||||
var inRangeUnobstructed = target == null
|
||||
? !checkAccess || InRangeUnobstructed(user, coordinates)
|
||||
: !checkAccess || InRangeUnobstructed(user, target.Value); // permits interactions with wall mounted entities
|
||||
|
||||
// Does the user have hands?
|
||||
if (!TryComp(user, out SharedHandsComponent? hands) || hands.ActiveHand == null)
|
||||
{
|
||||
var ev = new InteractNoHandEvent(user, target, coordinates);
|
||||
RaiseLocalEvent(user, ev);
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
var ev = new InteractNoHandEvent(user, target.Value);
|
||||
RaiseLocalEvent(user, ev);
|
||||
|
||||
var interactedEv = new InteractedNoHandEvent(target.Value, user);
|
||||
var interactedEv = new InteractedNoHandEvent(target.Value, user, coordinates);
|
||||
RaiseLocalEvent(target.Value, interactedEv);
|
||||
DoContactInteraction(user, target.Value, ev);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var inRangeUnobstructed = target == null
|
||||
? !checkAccess || InRangeUnobstructed(user, coordinates)
|
||||
: !checkAccess || InRangeUnobstructed(user, target.Value); // permits interactions with wall mounted entities
|
||||
|
||||
// empty-hand interactions
|
||||
if (hands.ActiveHandEntity is not { } held)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Mech.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Attached to entities piloting a <see cref="SharedMechComponent"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Get in the robot, Shinji
|
||||
/// </remarks>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed class MechPilotComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The mech being piloted
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntityUid Mech;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class MechPilotComponentState : ComponentState
|
||||
{
|
||||
public EntityUid Mech;
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Mech.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A large, pilotable machine that has equipment that is
|
||||
/// powered via an internal battery.
|
||||
/// </summary>
|
||||
public abstract class SharedMechComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How much "health" the mech has left.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public FixedPoint2 Integrity;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum amount of damage the mech can take.
|
||||
/// </summary>
|
||||
[DataField("maxIntegrity")]
|
||||
public FixedPoint2 MaxIntegrity = 250;
|
||||
|
||||
/// <summary>
|
||||
/// How much energy the mech has.
|
||||
/// Derived from the currently inserted battery.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public FixedPoint2 Energy = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum amount of energy the mech can have.
|
||||
/// Derived from the currently inserted battery.
|
||||
/// </summary>
|
||||
[DataField("maxEnergy")]
|
||||
public FixedPoint2 MaxEnergy = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The slot the battery is stored in.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public ContainerSlot BatterySlot = default!;
|
||||
|
||||
[ViewVariables]
|
||||
public readonly string BatterySlotId = "mech-battery-slot";
|
||||
|
||||
/// <summary>
|
||||
/// A multiplier used to calculate how much of the damage done to a mech
|
||||
/// is transfered to the pilot
|
||||
/// </summary>
|
||||
[DataField("mechToPilotDamageMultiplier")]
|
||||
public float MechToPilotDamageMultiplier;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the mech has been destroyed and is no longer pilotable.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Broken = false;
|
||||
|
||||
/// <summary>
|
||||
/// The slot the pilot is stored in.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ContainerSlot PilotSlot = default!;
|
||||
|
||||
[ViewVariables]
|
||||
public readonly string PilotSlotId = "mech-pilot-slot";
|
||||
|
||||
/// <summary>
|
||||
/// The current selected equipment of the mech.
|
||||
/// If null, the mech is using just its fists.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? CurrentSelectedEquipment;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum amount of equipment items that can be installed in the mech
|
||||
/// </summary>
|
||||
[DataField("maxEquipmentAmount")]
|
||||
public int MaxEquipmentAmount = 3;
|
||||
|
||||
/// <summary>
|
||||
/// A whitelist for inserting equipment items.
|
||||
/// </summary>
|
||||
[DataField("equipmentWhitelist")]
|
||||
public EntityWhitelist? EquipmentWhitelist;
|
||||
|
||||
/// <summary>
|
||||
/// A container for storing the equipment entities.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Container EquipmentContainer = default!;
|
||||
|
||||
[ViewVariables]
|
||||
public readonly string EquipmentContainerId = "mech-equipment-container";
|
||||
|
||||
#region Action Prototypes
|
||||
[DataField("mechCycleAction", customTypeSerializer: typeof(PrototypeIdSerializer<InstantActionPrototype>))]
|
||||
public string MechCycleAction = "MechCycleEquipment";
|
||||
[DataField("mechUiAction", customTypeSerializer: typeof(PrototypeIdSerializer<InstantActionPrototype>))]
|
||||
public string MechUiAction = "MechOpenUI";
|
||||
[DataField("mechEjectAction", customTypeSerializer: typeof(PrototypeIdSerializer<InstantActionPrototype>))]
|
||||
public string MechEjectAction = "MechEject";
|
||||
#endregion
|
||||
|
||||
#region Visualizer States
|
||||
[DataField("baseState")]
|
||||
public string? BaseState;
|
||||
[DataField("openState")]
|
||||
public string? OpenState;
|
||||
[DataField("brokenState")]
|
||||
public string? BrokenState;
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains network state for <see cref="SharedMechComponent"/>.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class MechComponentState : ComponentState
|
||||
{
|
||||
public FixedPoint2 Integrity;
|
||||
public FixedPoint2 MaxIntegrity;
|
||||
public FixedPoint2 Energy;
|
||||
public FixedPoint2 MaxEnergy;
|
||||
public EntityUid? CurrentSelectedEquipment;
|
||||
public bool Broken;
|
||||
}
|
||||
|
|
@ -0,0 +1,463 @@
|
|||
using System.Linq;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Mech.Components;
|
||||
using Content.Shared.Mech.Equipment.Components;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Weapons.Melee;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Mech.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// Handles all of the interactions, UI handling, and items shennanigans for <see cref="SharedMechComponent"/>
|
||||
/// </summary>
|
||||
public abstract class SharedMechSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||
[Dependency] private readonly AccessReaderSystem _access = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly SharedMoverController _mover = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<SharedMechComponent, ComponentGetState>(OnGetState);
|
||||
SubscribeLocalEvent<SharedMechComponent, ComponentHandleState>(OnHandleState);
|
||||
SubscribeLocalEvent<MechPilotComponent, ComponentGetState>(OnPilotGetState);
|
||||
SubscribeLocalEvent<MechPilotComponent, ComponentHandleState>(OnPilotHandleState);
|
||||
|
||||
SubscribeLocalEvent<SharedMechComponent, MechToggleEquipmentEvent>(OnToggleEquipmentAction);
|
||||
SubscribeLocalEvent<SharedMechComponent, MechEjectPilotEvent>(OnEjectPilotEvent);
|
||||
SubscribeLocalEvent<SharedMechComponent, InteractNoHandEvent>(RelayInteractionEvent);
|
||||
SubscribeLocalEvent<SharedMechComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<SharedMechComponent, DestructionEventArgs>(OnDestruction);
|
||||
SubscribeLocalEvent<SharedMechComponent, GetAdditionalAccessEvent>(OnGetAdditionalAccess);
|
||||
|
||||
SubscribeLocalEvent<MechPilotComponent, GetMeleeWeaponEvent>(OnGetMeleeWeapon);
|
||||
SubscribeLocalEvent<MechPilotComponent, CanAttackFromContainerEvent>(OnCanAttackFromContainer);
|
||||
}
|
||||
|
||||
#region State Handling
|
||||
private void OnGetState(EntityUid uid, SharedMechComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new MechComponentState
|
||||
{
|
||||
Integrity = component.Integrity,
|
||||
MaxIntegrity = component.MaxIntegrity,
|
||||
Energy = component.Energy,
|
||||
MaxEnergy = component.MaxEnergy,
|
||||
CurrentSelectedEquipment = component.CurrentSelectedEquipment,
|
||||
Broken = component.Broken
|
||||
};
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, SharedMechComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not MechComponentState state)
|
||||
return;
|
||||
|
||||
component.Integrity = state.Integrity;
|
||||
component.MaxIntegrity = state.MaxIntegrity;
|
||||
component.Energy = state.Energy;
|
||||
component.MaxEnergy = state.MaxEnergy;
|
||||
component.CurrentSelectedEquipment = state.CurrentSelectedEquipment;
|
||||
component.Broken = state.Broken;
|
||||
}
|
||||
|
||||
private void OnPilotGetState(EntityUid uid, MechPilotComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new MechPilotComponentState
|
||||
{
|
||||
Mech = component.Mech
|
||||
};
|
||||
}
|
||||
|
||||
private void OnPilotHandleState(EntityUid uid, MechPilotComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not MechPilotComponentState state)
|
||||
return;
|
||||
|
||||
component.Mech = state.Mech;
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void OnToggleEquipmentAction(EntityUid uid, SharedMechComponent component, MechToggleEquipmentEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
args.Handled = true;
|
||||
CycleEquipment(uid);
|
||||
}
|
||||
|
||||
private void OnEjectPilotEvent(EntityUid uid, SharedMechComponent component, MechEjectPilotEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
args.Handled = true;
|
||||
TryEject(uid, component);
|
||||
}
|
||||
|
||||
private void RelayInteractionEvent(EntityUid uid, SharedMechComponent component, InteractNoHandEvent args)
|
||||
{
|
||||
var pilot = component.PilotSlot.ContainedEntity;
|
||||
if (pilot == null)
|
||||
return;
|
||||
|
||||
if (!_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
if (component.CurrentSelectedEquipment != null)
|
||||
{
|
||||
RaiseLocalEvent(component.CurrentSelectedEquipment.Value, args);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, SharedMechComponent component, ComponentStartup args)
|
||||
{
|
||||
component.PilotSlot = _container.EnsureContainer<ContainerSlot>(uid, component.PilotSlotId);
|
||||
component.EquipmentContainer = _container.EnsureContainer<Container>(uid, component.EquipmentContainerId);
|
||||
component.BatterySlot = _container.EnsureContainer<ContainerSlot>(uid, component.BatterySlotId);
|
||||
UpdateAppearance(uid, component);
|
||||
}
|
||||
|
||||
private void OnDestruction(EntityUid uid, SharedMechComponent component, DestructionEventArgs args)
|
||||
{
|
||||
BreakMech(uid, component);
|
||||
}
|
||||
|
||||
private void OnGetAdditionalAccess(EntityUid uid, SharedMechComponent component, ref GetAdditionalAccessEvent args)
|
||||
{
|
||||
var pilot = component.PilotSlot.ContainedEntity;
|
||||
if (pilot == null)
|
||||
return;
|
||||
|
||||
args.Entities.Add(pilot.Value);
|
||||
_access.FindAccessItemsInventory(pilot.Value, out var items);
|
||||
args.Entities = args.Entities.Union(items).ToHashSet();
|
||||
}
|
||||
|
||||
private void SetupUser(EntityUid mech, EntityUid pilot, SharedMechComponent? component = null)
|
||||
{
|
||||
if (!Resolve(mech, ref component))
|
||||
return;
|
||||
|
||||
var rider = EnsureComp<MechPilotComponent>(pilot);
|
||||
var relay = EnsureComp<RelayInputMoverComponent>(pilot);
|
||||
var irelay = EnsureComp<InteractionRelayComponent>(pilot);
|
||||
|
||||
_mover.SetRelay(pilot, mech, relay);
|
||||
_interaction.SetRelay(pilot, mech, irelay);
|
||||
rider.Mech = mech;
|
||||
Dirty(rider);
|
||||
|
||||
_actions.AddAction(pilot, new InstantAction(_prototype.Index<InstantActionPrototype>(component.MechCycleAction)), mech);
|
||||
_actions.AddAction(pilot, new InstantAction(_prototype.Index<InstantActionPrototype>(component.MechUiAction)), mech);
|
||||
_actions.AddAction(pilot, new InstantAction(_prototype.Index<InstantActionPrototype>(component.MechEjectAction)), mech);
|
||||
}
|
||||
|
||||
private void RemoveUser(EntityUid mech, EntityUid pilot)
|
||||
{
|
||||
if (!RemComp<MechPilotComponent>(pilot))
|
||||
return;
|
||||
RemComp<RelayInputMoverComponent>(pilot);
|
||||
RemComp<InteractionRelayComponent>(pilot);
|
||||
|
||||
_actions.RemoveProvidedActions(pilot, mech);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the mech, removing the user and ejecting all installed equipment.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
public virtual void BreakMech(EntityUid uid, SharedMechComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
TryEject(uid, component);
|
||||
var equipment = new List<EntityUid>(component.EquipmentContainer.ContainedEntities);
|
||||
foreach (var ent in equipment)
|
||||
{
|
||||
RemoveEquipment(uid, ent, component, forced: true);
|
||||
}
|
||||
|
||||
component.Broken = true;
|
||||
UpdateAppearance(uid, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cycles through the currently selected equipment.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
public void CycleEquipment(EntityUid uid, SharedMechComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
var allEquipment = component.EquipmentContainer.ContainedEntities.ToList();
|
||||
|
||||
var equipmentIndex = -1;
|
||||
if (component.CurrentSelectedEquipment != null)
|
||||
{
|
||||
bool StartIndex(EntityUid u) => u == component.CurrentSelectedEquipment;
|
||||
equipmentIndex = allEquipment.FindIndex(StartIndex);
|
||||
}
|
||||
|
||||
equipmentIndex++;
|
||||
component.CurrentSelectedEquipment = equipmentIndex >= allEquipment.Count
|
||||
? null
|
||||
: allEquipment[equipmentIndex];
|
||||
|
||||
var popupString = component.CurrentSelectedEquipment != null
|
||||
? Loc.GetString("mech-equipment-select-popup", ("item", component.CurrentSelectedEquipment))
|
||||
: Loc.GetString("mech-equipment-select-none-popup");
|
||||
|
||||
if (_timing.IsFirstTimePredicted)
|
||||
_popup.PopupEntity(popupString, uid, Filter.Pvs(uid));
|
||||
Dirty(component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts an equipment item into the mech.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="toInsert"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="equipmentComponent"></param>
|
||||
public void InsertEquipment(EntityUid uid, EntityUid toInsert, SharedMechComponent? component = null, MechEquipmentComponent? equipmentComponent = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (!Resolve(toInsert, ref equipmentComponent))
|
||||
return;
|
||||
|
||||
if (component.EquipmentContainer.ContainedEntities.Count >= component.MaxEquipmentAmount)
|
||||
return;
|
||||
|
||||
if (component.EquipmentWhitelist != null && !component.EquipmentWhitelist.IsValid(uid))
|
||||
return;
|
||||
|
||||
equipmentComponent.EquipmentOwner = uid;
|
||||
component.EquipmentContainer.Insert(toInsert, EntityManager);
|
||||
var ev = new MechEquipmentInsertedEvent(uid);
|
||||
RaiseLocalEvent(toInsert, ref ev);
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an equipment item from a mech.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="toRemove"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="equipmentComponent"></param>
|
||||
/// <param name="forced">Whether or not the removal can be cancelled</param>
|
||||
public void RemoveEquipment(EntityUid uid, EntityUid toRemove, SharedMechComponent? component = null, MechEquipmentComponent? equipmentComponent = null, bool forced = false)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (!Resolve(toRemove, ref equipmentComponent))
|
||||
return;
|
||||
|
||||
if (!forced)
|
||||
{
|
||||
var attemptev = new AttemptRemoveMechEquipmentEvent();
|
||||
RaiseLocalEvent(toRemove, ref attemptev);
|
||||
if (attemptev.Cancelled)
|
||||
return;
|
||||
}
|
||||
|
||||
equipmentComponent.EquipmentOwner = null;
|
||||
component.EquipmentContainer.Remove(toRemove, EntityManager);
|
||||
var ev = new MechEquipmentRemovedEvent(uid);
|
||||
RaiseLocalEvent(toRemove, ref ev);
|
||||
|
||||
if (component.CurrentSelectedEquipment == toRemove)
|
||||
CycleEquipment(uid, component);
|
||||
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to change the amount of energy in the mech.
|
||||
/// </summary>
|
||||
/// <param name="uid">The mech itself</param>
|
||||
/// <param name="delta">The change in energy</param>
|
||||
/// <param name="component"></param>
|
||||
/// <returns>If the energy was successfully changed.</returns>
|
||||
public virtual bool TryChangeEnergy(EntityUid uid, FixedPoint2 delta, SharedMechComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return false;
|
||||
|
||||
if (component.Energy + delta < 0)
|
||||
return false;
|
||||
|
||||
component.Energy = FixedPoint2.Clamp(component.Energy + delta, 0, component.MaxEnergy);
|
||||
Dirty(component);
|
||||
UpdateUserInterface(uid, component);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the integrity of the mech.
|
||||
/// </summary>
|
||||
/// <param name="uid">The mech itself</param>
|
||||
/// <param name="value">The value the integrity will be set at</param>
|
||||
/// <param name="component"></param>
|
||||
public void SetIntegrity(EntityUid uid, FixedPoint2 value, SharedMechComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
component.Integrity = FixedPoint2.Clamp(value, 0, component.MaxIntegrity);
|
||||
|
||||
if (component.Integrity <= 0)
|
||||
{
|
||||
BreakMech(uid, component);
|
||||
}
|
||||
else if (component.Broken)
|
||||
{
|
||||
component.Broken = false;
|
||||
UpdateAppearance(uid, component);
|
||||
}
|
||||
|
||||
Dirty(component);
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the pilot is present
|
||||
/// </summary>
|
||||
/// <param name="component"></param>
|
||||
/// <returns>Whether or not the pilot is present</returns>
|
||||
public bool IsEmpty(SharedMechComponent component)
|
||||
{
|
||||
return component.PilotSlot.ContainedEntity == null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an entity can be inserted into the mech.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="toInsert"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <returns></returns>
|
||||
public bool CanInsert(EntityUid uid, EntityUid toInsert, SharedMechComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return false;
|
||||
|
||||
return IsEmpty(component) && _actionBlocker.CanMove(toInsert) && HasComp<BodyComponent>(toInsert);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the user interface
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is defined here so that UI updates can be accessed from shared.
|
||||
/// </remarks>
|
||||
public virtual void UpdateUserInterface(EntityUid uid, SharedMechComponent? component = null)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to insert a pilot into the mech.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="toInsert"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <returns>Whether or not the entity was inserted</returns>
|
||||
public virtual bool TryInsert(EntityUid uid, EntityUid? toInsert, SharedMechComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return false;
|
||||
|
||||
if (toInsert == null || component.PilotSlot.ContainedEntity == toInsert)
|
||||
return false;
|
||||
|
||||
if (!CanInsert(uid, toInsert.Value, component))
|
||||
return false;
|
||||
|
||||
SetupUser(uid, toInsert.Value);
|
||||
component.PilotSlot.Insert(toInsert.Value, EntityManager);
|
||||
UpdateAppearance(uid, component);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to eject the current pilot from the mech
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <returns>Whether or not the pilot was ejected.</returns>
|
||||
public virtual bool TryEject(EntityUid uid, SharedMechComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return false;
|
||||
|
||||
if (component.PilotSlot.ContainedEntity == null)
|
||||
return false;
|
||||
|
||||
var pilot = component.PilotSlot.ContainedEntity.Value;
|
||||
|
||||
RemoveUser(uid, pilot);
|
||||
_container.RemoveEntity(uid, pilot);
|
||||
UpdateAppearance(uid, component);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnGetMeleeWeapon(EntityUid uid, MechPilotComponent component, GetMeleeWeaponEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (!TryComp<SharedMechComponent>(component.Mech, out var mech))
|
||||
return;
|
||||
|
||||
var weapon = mech.CurrentSelectedEquipment ?? component.Mech;
|
||||
args.Weapon = weapon;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnCanAttackFromContainer(EntityUid uid, MechPilotComponent component, CanAttackFromContainerEvent args)
|
||||
{
|
||||
args.CanAttack = true;
|
||||
}
|
||||
|
||||
private void UpdateAppearance(EntityUid uid, SharedMechComponent ? component = null, AppearanceComponent? appearance = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, ref appearance, false))
|
||||
return;
|
||||
|
||||
_appearance.SetData(uid, MechVisuals.Open, IsEmpty(component), appearance);
|
||||
_appearance.SetData(uid, MechVisuals.Broken, component.Broken, appearance);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
using System.Threading;
|
||||
using Content.Shared.Mech.Components;
|
||||
|
||||
namespace Content.Shared.Mech.Equipment.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A piece of equipment that can be installed into <see cref="SharedMechComponent"/>
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class MechEquipmentComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How long does it take to install this piece of equipment
|
||||
/// </summary>
|
||||
[DataField("installDuration")]
|
||||
public float InstallDuration = 5;
|
||||
|
||||
/// <summary>
|
||||
/// The mech that the equipment is inside of.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? EquipmentOwner;
|
||||
|
||||
public CancellationTokenSource? TokenSource = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on the equipment when the installation is finished successfully
|
||||
/// </summary>
|
||||
public sealed class MechEquipmentInstallFinished : EntityEventArgs
|
||||
{
|
||||
public EntityUid Mech;
|
||||
|
||||
public MechEquipmentInstallFinished(EntityUid mech)
|
||||
{
|
||||
Mech = mech;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on the equipment when the installation fails.
|
||||
/// </summary>
|
||||
public sealed class MechEquipmentInstallCancelled : EntityEventArgs
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Mech;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum MechUiKey : byte
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised to collect BUI states for each of the mech's equipment items
|
||||
/// </summary>
|
||||
public sealed class MechEquipmentUiStateReadyEvent : EntityEventArgs
|
||||
{
|
||||
public Dictionary<EntityUid, BoundUserInterfaceState> States = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised to relay an equipment ui message
|
||||
/// </summary>
|
||||
public sealed class MechEquipmentUiMessageRelayEvent : EntityEventArgs
|
||||
{
|
||||
public MechEquipmentUiMessage Message;
|
||||
|
||||
public MechEquipmentUiMessageRelayEvent(MechEquipmentUiMessage message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI event raised to remove a piece of equipment from a mech
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class MechEquipmentRemoveMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public EntityUid Equipment;
|
||||
|
||||
public MechEquipmentRemoveMessage(EntityUid equipment)
|
||||
{
|
||||
Equipment = equipment;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// base for all mech ui messages
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public abstract class MechEquipmentUiMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public EntityUid Equipment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// event raised for the grabber equipment to eject an item from it's storage
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class MechGrabberEjectMessage : MechEquipmentUiMessage
|
||||
{
|
||||
public EntityUid Item;
|
||||
|
||||
public MechGrabberEjectMessage(EntityUid equipment, EntityUid uid)
|
||||
{
|
||||
Equipment = equipment;
|
||||
Item = uid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// BUI state for mechs that also contains all equipment ui states.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⢐⠤⢃⢰⠐⡄⣀⠀⠀
|
||||
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⣨⠀⢁⠁⠐⡐⠠⠜⠐⠀
|
||||
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠔⠐⢀⡁⣀⠔⡌⠡⢀⢐⠁⠀
|
||||
/// ⠀⠀⠀⠀⢀⠔⠀⡂⡄⠠⢀⡀⠀⣄⡀⠠⠤⠴⡋⠑⡠⠀⠔⠐⢂⠕⢀⡂⠀⠀
|
||||
/// ⠀⠀⠀⡔⠁⠠⡐⠁⠀⠀⠀⢘⠀⠀⠀⠀⠠⠀⠈⠪⠀⠑⠡⣃⠈⠤⡈⠀⠀⠀
|
||||
/// ⠀⠀⠨⠀⠄⡒⠀⡂⢈⠀⣀⢌⠀⠀⠁⡈⠀⢆⢀⠀⡀⠉⠒⢆⠑⠀⠀⠀⠀⠀
|
||||
/// ⠀⠀⠀⡁⠐⠠⠐⡀⠀⢀⣀⠣⡀⠢⡀⠀⢀⡃⠰⠀⠈⠠⢁⠎⠀⠀⠀⠀⠀⠀
|
||||
/// ⠀⠀⠀⠅⠒⣈⢣⠠⠈⠕⠁⠱⠄⢤⠈⠪⠡⠎⢘⠈⡁⢙⠈⠀⠀⠀⠀⠀⠀⠀
|
||||
/// ⠀⠀⠀⠃⠀⢡⠀⠧⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⢕⡈⠌⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
/// ⠀⠀⠀⠀⠀⠀⠈⡀⡀⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⡰⠀⡐⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
/// ⠀⠀⠀⠀⠀⠀⠀⢈⢂⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠀⡃⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
/// ⠀⠀⠀⠀⠀⠀⠀⠎⠐⢅⠀⠀⠀⠀⠀⠀⠀⠀⠀⢐⠅⠚⠄⠀⠀⠀⠀⠀⠀⠀
|
||||
/// ⠀⠀⢈⠩⠈⠀⠐⠁⠀⢀⠀⠄⡂⠒⠐⠀⠆⠁⠰⠠⠀⢅⠈⠐⠄⢁⢡⠀⠀⠀
|
||||
/// ⠀⠀⢈⡀⠰⡁⠀⠁⠴⠁⠔⠀⠀⠄⠄⡁⠀⠂⠀⠢⠠⠁⠀⠠⠈⠂⠬⠀⠀⠀
|
||||
/// ⠀⠀⠠⡂⢄⠤⠒⣁⠐⢕⢀⡈⡐⡠⠄⢐⠀⠈⠠⠈⡀⠂⢀⣀⠰⠁⠠⠀⠀
|
||||
/// trojan horse bui state⠀
|
||||
/// </remarks>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class MechBoundUiState : BoundUserInterfaceState
|
||||
{
|
||||
public Dictionary<EntityUid, BoundUserInterfaceState> EquipmentStates = new();
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class MechGrabberUiState : BoundUserInterfaceState
|
||||
{
|
||||
public List<EntityUid> Contents = new();
|
||||
public int MaxContents;
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
using Content.Shared.Actions;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Mech;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum MechVisuals : byte
|
||||
{
|
||||
Open, //whether or not it's open and has a rider
|
||||
Broken //if it broke and no longer works.
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum MechAssemblyVisuals : byte
|
||||
{
|
||||
State
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum MechVisualLayers : byte
|
||||
{
|
||||
Base
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised on equipment when it is inserted into a mech
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct MechEquipmentInsertedEvent(EntityUid Mech)
|
||||
{
|
||||
public readonly EntityUid Mech = Mech;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised on equipment when it is removed from a mech
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct MechEquipmentRemovedEvent(EntityUid Mech)
|
||||
{
|
||||
public readonly EntityUid Mech = Mech;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on the mech equipment before it is going to be removed.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct AttemptRemoveMechEquipmentEvent()
|
||||
{
|
||||
public bool Cancelled = false;
|
||||
}
|
||||
|
||||
public sealed class MechToggleEquipmentEvent : InstantActionEvent
|
||||
{
|
||||
}
|
||||
|
||||
public sealed class MechOpenUiEvent : InstantActionEvent
|
||||
{
|
||||
}
|
||||
|
||||
public sealed class MechEjectPilotEvent : InstantActionEvent
|
||||
{
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ using Robust.Shared.Physics.Controllers;
|
|||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Mech.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
|
||||
|
|
@ -414,6 +415,12 @@ namespace Content.Shared.Movement.Systems
|
|||
|
||||
mobMover.StepSoundDistance -= distanceNeeded;
|
||||
|
||||
if (TryComp<FootstepModifierComponent>(mover.Owner, out var moverModifier))
|
||||
{
|
||||
sound = moverModifier.Sound;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_inventory.TryGetSlotEntity(mover.Owner, "shoes", out var shoes) &&
|
||||
EntityManager.TryGetComponent<FootstepModifierComponent>(shoes, out var modifier))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -130,6 +130,15 @@ public sealed class MeleeWeaponComponent : Component
|
|||
public SoundSpecifier NoDamageSound { get; set; } = new SoundPathSpecifier("/Audio/Weapons/tap.ogg");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised on entity in GetWeapon function to allow systems to manually
|
||||
/// specify what the weapon should be.
|
||||
/// </summary>
|
||||
public sealed class GetMeleeWeaponEvent : HandledEntityEventArgs
|
||||
{
|
||||
public EntityUid? Weapon;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class MeleeWeaponComponentState : ComponentState
|
||||
{
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ using Robust.Shared.GameStates;
|
|||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
|
@ -227,6 +226,13 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
|||
{
|
||||
MeleeWeaponComponent? melee;
|
||||
|
||||
var ev = new GetMeleeWeaponEvent();
|
||||
RaiseLocalEvent(entity, ev);
|
||||
if (ev.Handled)
|
||||
{
|
||||
return EntityManager.GetComponentOrNull<MeleeWeaponComponent>(ev.Weapon);
|
||||
}
|
||||
|
||||
// Use inhands entity if we got one.
|
||||
if (EntityManager.TryGetComponent(entity, out SharedHandsComponent? hands) &&
|
||||
hands.ActiveHandEntity is { } held)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
- files: ["mechmove03.ogg"]
|
||||
license: "CC-BY-NC-SA-3.0"
|
||||
copyright: "Taken from TG station."
|
||||
source: "https://github.com/tgstation/tgstation/commit/d4f678a1772007ff8d7eddd21cf7218c8e07bfc0"
|
||||
- files: ["sound_mecha_hydraulic.ogg"]
|
||||
license: "CC-BY-NC-SA-3.0"
|
||||
copyright: "Taken from TG station."
|
||||
source: "https://github.com/tgstation/tgstation/commit/45123dd06cb6dc7c56e8004c528230682ea559b2"
|
||||
- files: ["sound_mecha_powerloader_step.ogg"]
|
||||
license: "CC-BY-NC-SA-3.0"
|
||||
copyright: "Taken from TG station."
|
||||
source: "https://github.com/tgstation/tgstation/commit/45123dd06cb6dc7c56e8004c528230682ea559b2"
|
||||
|
|
@ -1 +0,0 @@
|
|||
mechmove03.ogg taken from TG station at commit https://github.com/tgstation/tgstation/commit/d4f678a1772007ff8d7eddd21cf7218c8e07bfc0
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
action-name-mech-cycle = Cycle
|
||||
action-description-mech-cycle = Cycles currently selected equipment
|
||||
|
||||
action-name-mech-control-panel = Control Panel
|
||||
action-description-mech-control-panel = Opens the control panel for the mech
|
||||
|
||||
action-name-mech-eject = Eject
|
||||
action-description-mech-eject = Ejects the pilot from the mech
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
mech-verb-enter = Enter
|
||||
mech-verb-exit = Exit
|
||||
|
||||
mech-equipment-begin-install = Installing the {THE($item)}...
|
||||
mech-equipment-finish-install = Finished installing the {THE($item)}
|
||||
|
||||
mech-equipment-select-popup = {$item} selected
|
||||
mech-equipment-select-none-popup = Nothing selected
|
||||
|
||||
mech-ui-open-verb = Open control panel
|
||||
|
||||
mech-menu-title = mech control panel
|
||||
|
||||
mech-integrity-display = Integrity: {$amount}%
|
||||
mech-energy-display = Energy: {$amount}%
|
||||
mech-slot-display = Open Slots: {$amount}
|
||||
|
|
@ -76,5 +76,8 @@ technologies-robotics-technology-description = Parts needed for constructing mec
|
|||
technologies-archaeology = Archeological equipment
|
||||
technologies-archaeology-description = Advanced equipment for uncovering the secrets of artifacts.
|
||||
|
||||
technologies-adv-parts-technology = Advanced parts technology
|
||||
technologies-adv-parts-technology-description = Like the previous ones, but better!
|
||||
technologies-ripley-technology = Exosuit: Ripley
|
||||
technologies-ripley-technology-description = The latest and greatest in mechanized cargo construction.
|
||||
|
||||
technologies-adv-parts-technology-description = Like the previous ones, but better!
|
||||
technologies-adv-parts-technology = Advanced parts technology
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
- type: instantAction
|
||||
id: MechCycleEquipment
|
||||
name: action-name-mech-cycle
|
||||
description: action-description-mech-cycle
|
||||
itemIconStyle: NoItem
|
||||
icon:
|
||||
sprite: Interface/Actions/actions_mecha.rsi
|
||||
state: mech_cycle_equip_on
|
||||
event: !type:MechToggleEquipmentEvent
|
||||
useDelay: 0.5
|
||||
|
||||
- type: instantAction
|
||||
id: MechOpenUI
|
||||
name: action-name-mech-control-panel
|
||||
description: action-description-mech-control-panel
|
||||
itemIconStyle: NoItem
|
||||
icon:
|
||||
sprite: Interface/Actions/actions_mecha.rsi
|
||||
state: mech_view_stats
|
||||
event: !type:MechOpenUiEvent
|
||||
useDelay: 1
|
||||
|
||||
- type: instantAction
|
||||
id: MechEject
|
||||
name: action-name-mech-eject
|
||||
description: action-description-mech-eject
|
||||
itemIconStyle: NoItem
|
||||
icon:
|
||||
sprite: Interface/Actions/actions_mecha.rsi
|
||||
state: mech_eject
|
||||
event: !type:MechEjectPilotEvent
|
||||
|
|
@ -125,4 +125,4 @@
|
|||
description: action-desc-wake
|
||||
icon: { sprite: Objects/Consumable/Food/egg.rsi, state: icon }
|
||||
checkCanInteract: false
|
||||
event: !type:WakeActionEvent
|
||||
event: !type:WakeActionEvent
|
||||
|
|
@ -259,6 +259,26 @@
|
|||
#TODO- Gyroscope
|
||||
#TODO- Thruster
|
||||
|
||||
- type: technology
|
||||
name: technologies-ripley-technology
|
||||
id: RipleyTechnology
|
||||
description: technologies-ripley-technology-description
|
||||
icon:
|
||||
sprite: Objects/Specific/Mech/mecha.rsi
|
||||
state: ripley
|
||||
requiredPoints: 30000
|
||||
requiredTechnologies:
|
||||
- SalvageEquipment
|
||||
unlockedRecipes:
|
||||
- RipleyCentralElectronics
|
||||
- RipleyPeripheralsElectronics
|
||||
- MechEquipmentGrabber
|
||||
- RipleyHarness
|
||||
- RipleyLArm
|
||||
- RipleyRArm
|
||||
- RipleyLLeg
|
||||
- RipleyRLeg
|
||||
|
||||
# Industrial Engineering Technology Tree
|
||||
|
||||
- type: technology
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
- type: entity
|
||||
id: RipleyCentralElectronics
|
||||
parent: BaseElectronics
|
||||
name: ripley central control module
|
||||
description: The electrical control center for the ripley mech.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Misc/module.rsi
|
||||
state: mainboard
|
||||
- type: Tag
|
||||
tags:
|
||||
- RipleyCentralControlModule
|
||||
|
||||
- type: entity
|
||||
id: RipleyPeripheralsElectronics
|
||||
parent: BaseElectronics
|
||||
name: ripley peripherals control module
|
||||
description: The electrical peripherals control for the ripley mech.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Misc/module.rsi
|
||||
state: id_mod
|
||||
- type: Tag
|
||||
tags:
|
||||
- RipleyPeripheralsControlModule
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
- type: entity
|
||||
id: BaseMechPart
|
||||
abstract: true
|
||||
components:
|
||||
- type: Clickable
|
||||
- type: InteractionOutline
|
||||
- type: CollisionWake
|
||||
- type: TileFrictionModifier
|
||||
modifier: 0.5
|
||||
- type: Physics
|
||||
bodyType: Dynamic
|
||||
fixedRotation: false
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
- shape:
|
||||
!type:PhysShapeAabb
|
||||
bounds: "-0.25,-0.25,0.25,0.25"
|
||||
density: 100
|
||||
mask:
|
||||
- ItemMask
|
||||
restitution: 0.3 # fite me
|
||||
friction: 0.2
|
||||
- type: Pullable
|
||||
- type: Sprite
|
||||
drawdepth: Items
|
||||
noRot: false
|
||||
netsync: false
|
||||
sprite: Objects/Specific/Mech/ripley_construction.rsi
|
||||
|
||||
- type: entity
|
||||
id: BaseMechPartItem
|
||||
parent: BaseMechPart
|
||||
abstract: true
|
||||
components:
|
||||
- type: Item
|
||||
size: 50
|
||||
|
||||
- type: entity
|
||||
id: BaseMechConstruct
|
||||
parent: BaseMechPart
|
||||
abstract: true
|
||||
components:
|
||||
- type: Appearance
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
battery-container: !type:Container
|
||||
- type: MechAssemblyVisuals
|
||||
statePrefix: ripley
|
||||
|
||||
- type: entity
|
||||
parent: BaseMechPart
|
||||
id: RipleyHarness
|
||||
name: ripley harness
|
||||
description: The core of the ripley mech
|
||||
components:
|
||||
- type: Appearance
|
||||
- type: ItemMapper
|
||||
mapLayers:
|
||||
ripley_l_arm+o:
|
||||
whitelist:
|
||||
tags:
|
||||
- RipleyLArm
|
||||
ripley_r_arm+o:
|
||||
whitelist:
|
||||
tags:
|
||||
- RipleyRArm
|
||||
ripley_l_leg+o:
|
||||
whitelist:
|
||||
tags:
|
||||
- RipleyLLeg
|
||||
ripley_r_leg+o:
|
||||
whitelist:
|
||||
tags:
|
||||
- RipleyRLeg
|
||||
sprite: Objects/Specific/Mech/ripley_construction.rsi
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
mech-assembly-container: !type:Container
|
||||
- type: MechAssembly
|
||||
finishedPrototype: RipleyChassis
|
||||
requiredParts:
|
||||
RipleyLArm: false
|
||||
RipleyRArm: false
|
||||
RipleyLLeg: false
|
||||
RipleyRLeg: false
|
||||
- type: Sprite
|
||||
state: ripley_harness+o
|
||||
noRot: true
|
||||
|
||||
- type: entity
|
||||
parent: BaseMechPartItem
|
||||
id: RipleyLArm
|
||||
name: ripley left arm
|
||||
description: Ripley mech left arm
|
||||
components:
|
||||
- type: Sprite
|
||||
state: ripley_l_arm
|
||||
- type: Tag
|
||||
tags:
|
||||
- RipleyLArm
|
||||
|
||||
- type: entity
|
||||
parent: BaseMechPartItem
|
||||
id: RipleyLLeg
|
||||
name: ripley left leg
|
||||
description: Ripley mech left leg
|
||||
components:
|
||||
- type: Sprite
|
||||
state: ripley_l_leg
|
||||
- type: Tag
|
||||
tags:
|
||||
- RipleyLLeg
|
||||
|
||||
- type: entity
|
||||
parent: BaseMechPartItem
|
||||
id: RipleyRLeg
|
||||
name: ripley right leg
|
||||
description: Ripley mech right leg
|
||||
components:
|
||||
- type: Sprite
|
||||
state: ripley_r_leg
|
||||
- type: Tag
|
||||
tags:
|
||||
- RipleyRLeg
|
||||
|
||||
- type: entity
|
||||
parent: BaseMechPartItem
|
||||
id: RipleyRArm
|
||||
name: ripley right arm
|
||||
description: Ripley mech right arm
|
||||
components:
|
||||
- type: Sprite
|
||||
state: ripley_r_arm
|
||||
- type: Tag
|
||||
tags:
|
||||
- RipleyRArm
|
||||
|
||||
- type: entity
|
||||
id: RipleyChassis
|
||||
parent: BaseMechConstruct
|
||||
name: ripley chassis
|
||||
description: An in-progress construction of a ripley mech.
|
||||
components:
|
||||
- type: Sprite
|
||||
noRot: true
|
||||
state: ripley0
|
||||
- type: Construction
|
||||
graph: Ripley
|
||||
node: start
|
||||
defaultTarget: ripley
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
- type: entity
|
||||
parent: BaseItem
|
||||
id: BaseMechEquipment
|
||||
abstract: true
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Specific/Mech/mecha_equipment.rsi
|
||||
- type: Item
|
||||
sprite: Objects/Specific/Mech/mecha_equipment.rsi
|
||||
size: 50
|
||||
- type: MechEquipment
|
||||
|
||||
- type: entity
|
||||
id: MechEquipmentGrabber
|
||||
parent: BaseMechEquipment
|
||||
name: hydraulic clamp
|
||||
description: Gives the mech the ability to grab things and drag them around.
|
||||
components:
|
||||
- type: Sprite
|
||||
state: mecha_clamp
|
||||
- type: MechGrabber
|
||||
- type: UIFragment
|
||||
ui: !type:MechGrabberUi
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
item-container: !type:Container
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
- type: entity
|
||||
id: BaseMech
|
||||
save: false
|
||||
abstract: true
|
||||
components:
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
drawdepth: Mobs
|
||||
noRot: true
|
||||
sprite: Objects/Specific/Mech/mecha.rsi
|
||||
layers:
|
||||
- map: [ "enum.MechVisualLayers.Base" ]
|
||||
state: ripley
|
||||
- type: MobMover
|
||||
- type: Mech
|
||||
baseState: ripley
|
||||
openState: ripley-open
|
||||
brokenState: ripley-broken
|
||||
- type: DoAfter
|
||||
- type: Repairable
|
||||
fuelCost: 25
|
||||
doAfterDelay: 10
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.MechUiKey.Key
|
||||
type: MechBoundUserInterface
|
||||
- type: MeleeWeapon
|
||||
hidden: true
|
||||
attackRate: 0.75
|
||||
damage:
|
||||
types:
|
||||
Blunt: 25 #thwack
|
||||
Structural: 20
|
||||
- type: Puller
|
||||
needsHands: false
|
||||
- type: InputMover
|
||||
- type: InteractionOutline
|
||||
- type: MovementSpeedModifier
|
||||
baseWalkSpeed: 1
|
||||
baseSprintSpeed: 2
|
||||
- type: Tag
|
||||
tags:
|
||||
- DoorBumpOpener
|
||||
- FootstepSound
|
||||
- type: Pullable
|
||||
- type: Physics
|
||||
bodyType: KinematicController
|
||||
- type: Clickable
|
||||
- type: Wires #we just want the panel
|
||||
BoardName: Mech
|
||||
LayoutId: Mech
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
- shape:
|
||||
!type:PhysShapeCircle
|
||||
radius: 0.45
|
||||
density: 1000
|
||||
mask:
|
||||
- MobMask
|
||||
layer:
|
||||
- MobLayer
|
||||
- type: Appearance
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
mech-pilot-slot: !type:ContainerSlot
|
||||
mech-equipment-container: !type:Container
|
||||
mech-battery-slot: !type:ContainerSlot
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
damageModifierSet: Metallic
|
||||
- type: FootstepModifier
|
||||
footstepSoundCollection:
|
||||
path: /Audio/Mecha/mechmove03.ogg
|
||||
|
||||
- type: entity
|
||||
id: MechRipley
|
||||
parent: BaseMech
|
||||
name: ripley
|
||||
description: Cargo's favorite robotic box hauling friend.
|
||||
components:
|
||||
- type: FootstepModifier
|
||||
footstepSoundCollection:
|
||||
path: /Audio/Mecha/sound_mecha_powerloader_step.ogg
|
||||
- type: Mech
|
||||
baseState: ripley
|
||||
openState: ripley-open
|
||||
brokenState: ripley-broken
|
||||
mechToPilotDamageMultiplier: 0.5
|
||||
|
||||
- type: entity
|
||||
id: MechRipleyBattery
|
||||
parent: MechRipley
|
||||
suffix: Battery
|
||||
components:
|
||||
- type: Mech
|
||||
baseState: ripley
|
||||
openState: ripley-open
|
||||
brokenState: ripley-broken
|
||||
startingBattery: PowerCellHigh
|
||||
mechToPilotDamageMultiplier: 0.5
|
||||
|
|
@ -290,6 +290,8 @@
|
|||
- CloningConsoleComputerCircuitboard
|
||||
- StasisBedMachineCircuitboard
|
||||
- OreProcessorMachineCircuitboard
|
||||
- RipleyCentralElectronics
|
||||
- RipleyPeripheralsElectronics
|
||||
- GeneratorPlasmaMachineCircuitboard
|
||||
- GeneratorUraniumMachineCircuitboard
|
||||
- WallmountGeneratorElectronics
|
||||
|
|
@ -334,6 +336,12 @@
|
|||
- ProximitySensor
|
||||
- LeftArmBorg
|
||||
- RightArmBorg
|
||||
- RipleyHarness
|
||||
- RipleyLArm
|
||||
- RipleyRArm
|
||||
- RipleyLLeg
|
||||
- RipleyRLeg
|
||||
- MechEquipmentGrabber
|
||||
- type: MaterialStorage
|
||||
whitelist:
|
||||
tags:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,125 @@
|
|||
- type: constructionGraph
|
||||
id: Ripley
|
||||
start: start
|
||||
graph:
|
||||
- node: start
|
||||
edges:
|
||||
- to: ripley
|
||||
steps:
|
||||
- tool: Anchoring
|
||||
doAfter: 1
|
||||
completed:
|
||||
- !type:VisualizerDataInt
|
||||
key: "enum.MechAssemblyVisuals.State"
|
||||
data: 1
|
||||
|
||||
- tool: Screwing
|
||||
doAfter: 1
|
||||
completed:
|
||||
- !type:VisualizerDataInt
|
||||
key: "enum.MechAssemblyVisuals.State"
|
||||
data: 2
|
||||
|
||||
- material: Cable
|
||||
completed:
|
||||
- !type:VisualizerDataInt
|
||||
key: "enum.MechAssemblyVisuals.State"
|
||||
data: 3
|
||||
|
||||
- tool: Cutting
|
||||
doAfter: 1
|
||||
completed:
|
||||
- !type:VisualizerDataInt
|
||||
key: "enum.MechAssemblyVisuals.State"
|
||||
data: 4
|
||||
|
||||
- tag: RipleyCentralControlModule
|
||||
name: ripley central control module
|
||||
icon:
|
||||
sprite: "Objects/Misc/module.rsi"
|
||||
state: "mainboard"
|
||||
completed:
|
||||
- !type:VisualizerDataInt
|
||||
key: "enum.MechAssemblyVisuals.State"
|
||||
data: 5
|
||||
|
||||
- tool: Screwing
|
||||
doAfter: 1
|
||||
completed:
|
||||
- !type:VisualizerDataInt
|
||||
key: "enum.MechAssemblyVisuals.State"
|
||||
data: 6
|
||||
|
||||
- tag: RipleyPeripheralsControlModule
|
||||
name: ripley peripherals control module
|
||||
icon:
|
||||
sprite: "Objects/Misc/module.rsi"
|
||||
state: id_mod
|
||||
completed:
|
||||
- !type:VisualizerDataInt
|
||||
key: "enum.MechAssemblyVisuals.State"
|
||||
data: 7
|
||||
|
||||
- tool: Screwing
|
||||
doAfter: 1
|
||||
completed:
|
||||
- !type:VisualizerDataInt
|
||||
key: "enum.MechAssemblyVisuals.State"
|
||||
data: 12
|
||||
|
||||
#i omitted the steps involving inserting machine parts because
|
||||
#currently mechs don't support upgrading. add them back in once that's squared away.
|
||||
|
||||
- component: PowerCell
|
||||
name: power cell
|
||||
store: battery-container
|
||||
icon:
|
||||
sprite: Objects/Power/power_cells.rsi
|
||||
state: small
|
||||
completed:
|
||||
- !type:VisualizerDataInt
|
||||
key: "enum.MechAssemblyVisuals.State"
|
||||
data: 13
|
||||
|
||||
- tool: Screwing
|
||||
doAfter: 1
|
||||
completed:
|
||||
- !type:VisualizerDataInt
|
||||
key: "enum.MechAssemblyVisuals.State"
|
||||
data: 14
|
||||
|
||||
- material: Steel
|
||||
amount: 5
|
||||
completed:
|
||||
- !type:VisualizerDataInt
|
||||
key: "enum.MechAssemblyVisuals.State"
|
||||
data: 15
|
||||
|
||||
- tool: Anchoring
|
||||
doAfter: 1
|
||||
completed:
|
||||
- !type:VisualizerDataInt
|
||||
key: "enum.MechAssemblyVisuals.State"
|
||||
data: 16
|
||||
|
||||
- tool: Welding
|
||||
doAfter: 1
|
||||
completed:
|
||||
- !type:VisualizerDataInt
|
||||
key: "enum.MechAssemblyVisuals.State"
|
||||
data: 17
|
||||
|
||||
- material: MetalRod
|
||||
amount: 10
|
||||
completed:
|
||||
- !type:VisualizerDataInt
|
||||
key: "enum.MechAssemblyVisuals.State"
|
||||
data: 18
|
||||
|
||||
- tool: Welding
|
||||
doAfter: 1
|
||||
|
||||
- node: ripley
|
||||
actions:
|
||||
- !type:BuildMech
|
||||
mechPrototype: MechRipley
|
||||
|
|
@ -280,6 +280,27 @@
|
|||
Steel: 100
|
||||
Glass: 900
|
||||
Gold: 100
|
||||
|
||||
- type: latheRecipe
|
||||
id: RipleyCentralElectronics
|
||||
icon: Objects/Misc/module.rsi/mainboard.png
|
||||
result: RipleyCentralElectronics
|
||||
completetime: 4
|
||||
materials:
|
||||
Steel: 100
|
||||
Glass: 900
|
||||
Gold: 100
|
||||
|
||||
- type: latheRecipe
|
||||
id: RipleyPeripheralsElectronics
|
||||
icon: Objects/Misc/module.rsi/id_mod.png
|
||||
result: RipleyPeripheralsElectronics
|
||||
completetime: 4
|
||||
materials:
|
||||
Steel: 100
|
||||
Glass: 900
|
||||
Gold: 100
|
||||
|
||||
# Power
|
||||
- type: latheRecipe
|
||||
id: APCElectronics
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
- type: latheRecipe
|
||||
id: RipleyHarness
|
||||
icon:
|
||||
sprite: Objects/Specific/Mech/ripley_construction.rsi
|
||||
state: ripley_harness
|
||||
result: RipleyHarness
|
||||
completetime: 10
|
||||
materials:
|
||||
Steel: 3000
|
||||
Glass: 1200
|
||||
|
||||
- type: latheRecipe
|
||||
id: RipleyLArm
|
||||
icon:
|
||||
sprite: Objects/Specific/Mech/ripley_construction.rsi
|
||||
state: ripley_l_arm
|
||||
result: RipleyLArm
|
||||
completetime: 10
|
||||
materials:
|
||||
Steel: 3000
|
||||
Glass: 1200
|
||||
|
||||
- type: latheRecipe
|
||||
id: RipleyLLeg
|
||||
icon:
|
||||
sprite: Objects/Specific/Mech/ripley_construction.rsi
|
||||
state: ripley_l_leg
|
||||
result: RipleyLLeg
|
||||
completetime: 10
|
||||
materials:
|
||||
Steel: 3000
|
||||
Glass: 1200
|
||||
|
||||
- type: latheRecipe
|
||||
id: RipleyRLeg
|
||||
icon:
|
||||
sprite: Objects/Specific/Mech/ripley_construction.rsi
|
||||
state: ripley_r_leg
|
||||
result: RipleyRLeg
|
||||
completetime: 10
|
||||
materials:
|
||||
Steel: 3000
|
||||
Glass: 1200
|
||||
|
||||
- type: latheRecipe
|
||||
id: RipleyRArm
|
||||
icon:
|
||||
sprite: Objects/Specific/Mech/ripley_construction.rsi
|
||||
state: ripley_r_arm
|
||||
result: RipleyRArm
|
||||
completetime: 10
|
||||
materials:
|
||||
Steel: 3000
|
||||
Glass: 1200
|
||||
|
||||
- type: latheRecipe
|
||||
id: MechEquipmentGrabber
|
||||
icon:
|
||||
sprite: Objects/Specific/Mech/mecha_equipment.rsi
|
||||
state: mecha_clamp
|
||||
result: MechEquipmentGrabber
|
||||
completetime: 10
|
||||
materials:
|
||||
Steel: 500
|
||||
Plastic: 200
|
||||
|
|
@ -458,12 +458,30 @@
|
|||
- type: Tag
|
||||
id: RawMaterial
|
||||
|
||||
- type: Tag
|
||||
id: RCDDeconstructWhitelist
|
||||
|
||||
# Give this to something that doesn't need any special recycler behavior and just needs deleting.
|
||||
- type: Tag
|
||||
id: Recyclable
|
||||
|
||||
- type: Tag
|
||||
id: RCDDeconstructWhitelist
|
||||
id: RipleyCentralControlModule
|
||||
|
||||
- type: Tag
|
||||
id: RipleyPeripheralsControlModule
|
||||
|
||||
- type: Tag
|
||||
id: RipleyLArm
|
||||
|
||||
- type: Tag
|
||||
id: RipleyLLeg
|
||||
|
||||
- type: Tag
|
||||
id: RipleyRLeg
|
||||
|
||||
- type: Tag
|
||||
id: RipleyRArm
|
||||
|
||||
- type: Tag
|
||||
id: RodMetal1
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 474 B |
|
After Width: | Height: | Size: 572 B |
|
After Width: | Height: | Size: 546 B |
|
After Width: | Height: | Size: 591 B |
|
After Width: | Height: | Size: 543 B |
|
After Width: | Height: | Size: 549 B |
|
After Width: | Height: | Size: 547 B |
|
After Width: | Height: | Size: 477 B |
|
After Width: | Height: | Size: 635 B |
|
After Width: | Height: | Size: 642 B |
|
After Width: | Height: | Size: 676 B |
|
After Width: | Height: | Size: 964 B |
|
After Width: | Height: | Size: 553 B |
|
After Width: | Height: | Size: 558 B |
|
After Width: | Height: | Size: 503 B |
|
After Width: | Height: | Size: 513 B |
|
After Width: | Height: | Size: 541 B |
|
After Width: | Height: | Size: 550 B |
|
After Width: | Height: | Size: 640 B |
|
After Width: | Height: | Size: 926 B |
|
After Width: | Height: | Size: 636 B |
|
After Width: | Height: | Size: 591 B |
|
After Width: | Height: | Size: 605 B |
|
After Width: | Height: | Size: 636 B |
|
After Width: | Height: | Size: 509 B |
|
After Width: | Height: | Size: 611 B |
|
After Width: | Height: | Size: 617 B |
|
After Width: | Height: | Size: 650 B |
|
|
@ -0,0 +1,112 @@
|
|||
{
|
||||
"copyright" : "Taken from https://github.com/tgstation/tgstation at at https://github.com/tgstation/tgstation/commit/40d89d11ea4a5cb81d61dc1018b46f4e7d32c62a",
|
||||
"license" : "CC-BY-SA-3.0",
|
||||
"version": 1,
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "mech_lights_off"
|
||||
},
|
||||
{
|
||||
"name": "mech_lights_on"
|
||||
},
|
||||
{
|
||||
"name": "mech_view_stats"
|
||||
},
|
||||
{
|
||||
"name": "mech_eject"
|
||||
},
|
||||
{
|
||||
"name": "mech_seat_swap"
|
||||
},
|
||||
{
|
||||
"name": "mech_internals_off"
|
||||
},
|
||||
{
|
||||
"name": "mech_internals_on"
|
||||
},
|
||||
{
|
||||
"name": "mech_cycle_equip_off"
|
||||
},
|
||||
{
|
||||
"name": "mech_cycle_equip_on"
|
||||
},
|
||||
{
|
||||
"name": "mech_defense_mode_off"
|
||||
},
|
||||
{
|
||||
"name": "mech_defense_mode_on"
|
||||
},
|
||||
{
|
||||
"name": "mech_thrusters_off"
|
||||
},
|
||||
{
|
||||
"name": "mech_thrusters_on"
|
||||
},
|
||||
{
|
||||
"name": "mech_smoke"
|
||||
},
|
||||
{
|
||||
"name": "mech_zoom_off"
|
||||
},
|
||||
{
|
||||
"name": "mech_zoom_on"
|
||||
},
|
||||
{
|
||||
"name": "mech_phasing_off"
|
||||
},
|
||||
{
|
||||
"name": "mech_phasing_on"
|
||||
},
|
||||
{
|
||||
"name": "mech_damtype_brute"
|
||||
},
|
||||
{
|
||||
"name": "mech_damtype_burn"
|
||||
},
|
||||
{
|
||||
"name": "mech_damtype_toxin"
|
||||
},
|
||||
{
|
||||
"name": "mech_overload_off"
|
||||
},
|
||||
{
|
||||
"name": "mech_overload_on"
|
||||
},
|
||||
{
|
||||
"name": "strafe"
|
||||
},
|
||||
{
|
||||
"name": "meson"
|
||||
},
|
||||
{
|
||||
"name": "mech_ivanov"
|
||||
},
|
||||
{
|
||||
"name": "mech_ivanov_cooldown",
|
||||
"delays": [
|
||||
[
|
||||
0.2,
|
||||
0.2,
|
||||
0.2
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mech_savannah"
|
||||
},
|
||||
{
|
||||
"name": "mech_savannah_cooldown",
|
||||
"delays": [
|
||||
[
|
||||
0.2,
|
||||
0.2,
|
||||
0.2
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 488 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.1 KiB |