Adds the antimatter engine (#1905)

* adds antimatter engine

* fixes some nullables

* fixes ALL OF THE NULLABLES

* adds explosions

* adds fancy lighting

* requested changes + license info

Co-authored-by: ancientpower <ancientpowerer@gmail.com>
This commit is contained in:
ancientpower
2020-08-29 06:05:44 -05:00
committed by GitHub
parent 6156ce3534
commit 566ed6b770
49 changed files with 1642 additions and 0 deletions

View File

@@ -18,6 +18,7 @@ using Content.Shared.GameObjects.Components.Chemistry.ChemMaster;
using Content.Shared.GameObjects.Components.Chemistry.ReagentDispenser;
using Content.Shared.GameObjects.Components.Gravity;
using Content.Shared.GameObjects.Components.Markers;
using Content.Shared.GameObjects.Components.Power.AME;
using Content.Shared.GameObjects.Components.Research;
using Content.Shared.GameObjects.Components.VendingMachines;
using Content.Shared.Kitchen;
@@ -72,6 +73,7 @@ namespace Content.Client
factory.Register<SharedChemMasterComponent>();
factory.Register<SharedMicrowaveComponent>();
factory.Register<SharedGravityGeneratorComponent>();
factory.Register<SharedAMEControllerComponent>();
prototypes.RegisterIgnore("material");
prototypes.RegisterIgnore("reaction"); //Chemical reactions only needed by server. Reactions checks are server-side.

View File

@@ -0,0 +1,62 @@
using Robust.Client.GameObjects.Components.UserInterface;
using Robust.Shared.GameObjects.Components.UserInterface;
using static Content.Shared.GameObjects.Components.Power.AME.SharedAMEControllerComponent;
namespace Content.Client.GameObjects.Components.Power.AME
{
public class AMEControllerBoundUserInterface : BoundUserInterface
{
private AMEWindow _window;
public AMEControllerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_window = new AMEWindow();
_window.OnClose += Close;
_window.OpenCentered();
_window.EjectButton.OnPressed += _ => ButtonPressed(UiButton.Eject);
_window.ToggleInjection.OnPressed += _ => ButtonPressed(UiButton.ToggleInjection);
_window.IncreaseFuelButton.OnPressed += _ => ButtonPressed(UiButton.IncreaseFuel);
_window.DecreaseFuelButton.OnPressed += _ => ButtonPressed(UiButton.DecreaseFuel);
_window.RefreshPartsButton.OnPressed += _ => ButtonPressed(UiButton.RefreshParts);
}
/// <summary>
/// Update the ui each time new state data is sent from the server.
/// </summary>
/// <param name="state">
/// Data of the <see cref="SharedReagentDispenserComponent"/> that this ui represents.
/// Sent from the server.
/// </param>
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
var castState = (AMEControllerBoundUserInterfaceState) state;
_window?.UpdateState(castState); //Update window state
}
private void ButtonPressed(UiButton button, int dispenseIndex = -1)
{
SendMessage(new UiButtonPressedMessage(button));
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_window.Dispose();
}
}
}
}

View File

@@ -0,0 +1,57 @@
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
using System;
using System.Collections.Generic;
using System.Text;
using static Content.Shared.GameObjects.Components.Power.AME.SharedAMEControllerComponent;
namespace Content.Client.GameObjects.Components.Power.AME
{
public class AMEControllerVisualizer : AppearanceVisualizer
{
public override void InitializeEntity(IEntity entity)
{
base.InitializeEntity(entity);
var sprite = entity.GetComponent<ISpriteComponent>();
sprite.LayerMapSet(Layers.Display, sprite.AddLayerState("control_on"));
sprite.LayerSetVisible(Layers.Display, false);
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
var sprite = component.Owner.GetComponent<ISpriteComponent>();
if (component.TryGetData<string>(AMEControllerVisuals.DisplayState, out var state))
{
switch (state)
{
case "on":
sprite.LayerSetState(Layers.Display, "control_on");
sprite.LayerSetVisible(Layers.Display, true);
break;
case "critical":
sprite.LayerSetState(Layers.Display, "control_critical");
sprite.LayerSetVisible(Layers.Display, true);
break;
case "fuck":
sprite.LayerSetState(Layers.Display, "control_fuck");
sprite.LayerSetVisible(Layers.Display, true);
break;
case "off":
sprite.LayerSetVisible(Layers.Display, false);
break;
default:
sprite.LayerSetVisible(Layers.Display, false);
break;
}
}
}
enum Layers
{
Display,
}
}
}

View File

@@ -0,0 +1,63 @@
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
using System;
using System.Collections.Generic;
using System.Text;
using static Content.Shared.GameObjects.Components.Power.AME.SharedAMEShieldComponent;
namespace Content.Client.GameObjects.Components.Power.AME
{
public class AMEVisualizer : AppearanceVisualizer
{
public override void InitializeEntity(IEntity entity)
{
base.InitializeEntity(entity);
var sprite = entity.GetComponent<ISpriteComponent>();
sprite.LayerMapSet(Layers.Core, sprite.AddLayerState("core"));
sprite.LayerSetVisible(Layers.Core, false);
sprite.LayerMapSet(Layers.CoreState, sprite.AddLayerState("core_weak"));
sprite.LayerSetVisible(Layers.CoreState, false);
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
var sprite = component.Owner.GetComponent<ISpriteComponent>();
if (component.TryGetData<string>(AMEShieldVisuals.Core, out var core))
{
if (core == "isCore")
{
sprite.LayerSetState(Layers.Core, "core");
sprite.LayerSetVisible(Layers.Core, true);
}
else
{
sprite.LayerSetVisible(Layers.Core, false);
}
}
if (component.TryGetData<string>(AMEShieldVisuals.CoreState, out var coreState))
switch (coreState)
{
case "weak":
sprite.LayerSetState(Layers.CoreState, "core_weak");
sprite.LayerSetVisible(Layers.CoreState, true);
break;
case "strong":
sprite.LayerSetState(Layers.CoreState, "core_strong");
sprite.LayerSetVisible(Layers.CoreState, true);
break;
case "off":
sprite.LayerSetVisible(Layers.CoreState, false);
break;
}
}
}
enum Layers
{
Core,
CoreState,
}
}

View File

@@ -0,0 +1,162 @@
using Content.Client.UserInterface.Stylesheets;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using System;
using System.Collections.Generic;
using System.Text;
using static Content.Shared.GameObjects.Components.Power.AME.SharedAMEControllerComponent;
namespace Content.Client.GameObjects.Components.Power.AME
{
public class AMEWindow : SS14Window
{
public Label InjectionStatus { get; set; }
public Button EjectButton { get; set; }
public Button ToggleInjection { get; set; }
public Button IncreaseFuelButton { get; set; }
public Button DecreaseFuelButton { get; set; }
public Button RefreshPartsButton { get; set; }
public ProgressBar FuelMeter { get; set; }
public Label FuelAmount { get; set; }
public Label InjectionAmount { get; set; }
public Label CoreCount { get; set; }
public AMEWindow()
{
IoCManager.InjectDependencies(this);
Title = "Antimatter Control Unit";
Contents.AddChild(new VBoxContainer
{
Children =
{
new HBoxContainer
{
Children =
{
new Label {Text = Loc.GetString("Engine Status") + ": "},
(InjectionStatus = new Label {Text = "Not Injecting"})
}
},
new HBoxContainer
{
Children =
{
(ToggleInjection = new Button {Text = "Toggle Injection", StyleClasses = {StyleBase.ButtonOpenBoth}, Disabled = true}),
}
},
new HBoxContainer
{
Children =
{
new Label {Text = Loc.GetString("Fuel Status") + ": "},
(FuelAmount = new Label {Text = "No fuel inserted"})
}
},
new HBoxContainer
{
Children =
{
(EjectButton = new Button {Text = "Eject", StyleClasses = {StyleBase.ButtonOpenBoth}, Disabled = true}),
}
},
new HBoxContainer
{
Children =
{
new Label {Text = Loc.GetString("Injection amount") + ": "},
(InjectionAmount = new Label {Text = "0"})
}
},
new HBoxContainer
{
Children =
{
(IncreaseFuelButton = new Button {Text = "Increase", StyleClasses = {StyleBase.ButtonOpenRight}}),
(DecreaseFuelButton = new Button {Text = "Decrease", StyleClasses = {StyleBase.ButtonOpenLeft}}),
}
},
new HBoxContainer
{
Children =
{
(RefreshPartsButton = new Button {Text = "Refresh Parts", StyleClasses = {StyleBase.ButtonOpenBoth }, Disabled = true }),
new Label { Text = Loc.GetString("Core count") + ": "},
(CoreCount = new Label { Text = "0"}),
}
}
}
});
}
/// <summary>
/// This searches recursively through all the children of "parent"
/// and sets the Disabled value of any buttons found to "val"
/// </summary>
/// <param name="parent">The control which childrens get searched</param>
/// <param name="val">The value to which disabled gets set</param>
private void SetButtonDisabledRecursive(Control parent, bool val)
{
foreach (var child in parent.Children)
{
if (child is Button but)
{
but.Disabled = val;
continue;
}
if (child.Children != null)
{
SetButtonDisabledRecursive(child, val);
}
}
}
/// <summary>
/// Update the UI state when new state data is received from the server.
/// </summary>
/// <param name="state">State data sent by the server.</param>
public void UpdateState(BoundUserInterfaceState state)
{
var castState = (AMEControllerBoundUserInterfaceState) state;
// Disable all buttons if not powered
if (Contents.Children != null)
{
SetButtonDisabledRecursive(Contents, !castState.HasPower);
EjectButton.Disabled = false;
}
if(!castState.HasFuelJar)
{
EjectButton.Disabled = true;
ToggleInjection.Disabled = true;
FuelAmount.Text = Loc.GetString("No fuel inserted");
}
else
{
EjectButton.Disabled = false;
ToggleInjection.Disabled = false;
FuelAmount.Text = $"{castState.FuelAmount}";
}
if(!castState.IsMaster)
{
ToggleInjection.Disabled = true;
}
RefreshPartsButton.Disabled = castState.Injecting;
CoreCount.Text = $"{castState.CoreCount}";
InjectionAmount.Text = $"{castState.InjectionAmount}";
}
}
}

View File

@@ -160,6 +160,9 @@
"Metabolism",
"AiFactionTag",
"PressureProtection",
"AMEPart",
"AMEFuelContainer",
"AMEShield",
"DebugPump",
"DebugVent",
"DebugSiphon",

View File

@@ -0,0 +1,152 @@
using Content.Server.Explosions;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.GameObjects.Components.Power.AME;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.ViewVariables;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups
{
/// <summary>
/// Node group class for handling the Antimatter Engine's console and parts.
/// </summary>
[NodeGroup(NodeGroupID.AMEngine)]
public class AMENodeGroup : BaseNodeGroup
{
/// <summary>
/// The AME controller which is currently in control of this node group.
/// This could be tracked a few different ways, but this is most convenient,
/// since any part connected to the node group can easily find the master.
/// </summary>
[ViewVariables]
private AMEControllerComponent _masterController;
public AMEControllerComponent MasterController => _masterController;
private List<AMEShieldComponent> _cores = new List<AMEShieldComponent>();
public int CoreCount => _cores.Count;
protected override void OnAddNode(Node node)
{
base.OnAddNode(node);
if (_masterController == null)
{
node.Owner.TryGetComponent<AMEControllerComponent>(out var controller);
_masterController = controller;
}
}
protected override void OnRemoveNode(Node node)
{
base.OnRemoveNode(node);
RefreshAMENodes(_masterController);
if (_masterController != null && _masterController?.Owner == node.Owner) { _masterController = null; }
}
public void RefreshAMENodes(AMEControllerComponent controller)
{
if(_masterController == null && controller != null)
{
_masterController = controller;
}
if (_cores != null) {
foreach (AMEShieldComponent core in _cores)
{
core.UnsetCore();
}
_cores.Clear();
}
//Check each shield node to see if it meets core criteria
foreach (Node node in Nodes)
{
if (!node.Owner.TryGetComponent<AMEShieldComponent>(out var shield)) { continue; }
var nodeNeighbors = node.Owner
.GetComponent<SnapGridComponent>()
.GetCellsInSquareArea()
.Select(sgc => sgc.Owner)
.Where(entity => entity != node.Owner)
.Select(entity => entity.TryGetComponent<AMEShieldComponent>(out var adjshield) ? adjshield : null)
.Where(adjshield => adjshield != null);
if (nodeNeighbors.Count() >= 8) { _cores.Add(shield); }
}
if (_cores == null) { return; }
foreach (AMEShieldComponent core in _cores)
{
core.SetCore();
}
}
public void UpdateCoreVisuals(int injectionAmount, bool injecting)
{
var injectionStrength = CoreCount > 0 ? injectionAmount / CoreCount : 0;
foreach (AMEShieldComponent core in _cores)
{
core.UpdateCoreVisuals(injectionStrength, injecting);
}
}
public int InjectFuel(int injectionAmount)
{
if(injectionAmount > 0 && CoreCount > 0)
{
var instability = 2 * (injectionAmount / CoreCount);
foreach(AMEShieldComponent core in _cores)
{
core.CoreIntegrity -= instability;
}
return CoreCount * injectionAmount * 15000; //2 core engine injecting 2 fuel per core = 60kW(?)
}
return 0;
}
public int GetTotalStability()
{
if(CoreCount < 1) { return 100; }
var stability = 0;
foreach(AMEShieldComponent core in _cores)
{
stability += core.CoreIntegrity;
}
stability = stability / CoreCount;
return stability;
}
public void ExplodeCores()
{
if(_cores.Count < 1 || MasterController == null) { return; }
var intensity = 0;
/*
* todo: add an exact to the shielding and make this find the core closest to the controller
* so they chain explode, after helpers have been added to make it not cancer
*/
var epicenter = _cores.First();
foreach (AMEShieldComponent core in _cores)
{
intensity += MasterController.InjectionAmount;
}
intensity = Math.Min(intensity, 8);
ExplosionHelper.SpawnExplosion(epicenter.Owner.Transform.GridPosition, intensity / 2, intensity, intensity * 2, intensity * 3);
}
}
}

View File

@@ -62,6 +62,7 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups
HVPower,
MVPower,
Apc,
AMEngine,
Pipe,
}
}

View File

@@ -0,0 +1,369 @@
#nullable enable
using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.Components.NodeContainer;
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.Components.Power.PowerNetComponents;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Utility;
using Content.Shared.GameObjects.Components.Power.AME;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
using Microsoft.EntityFrameworkCore.Internal;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.Container;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.ViewVariables;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Content.Server.GameObjects.Components.Power.AME
{
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(IInteractUsing))]
public class AMEControllerComponent : SharedAMEControllerComponent, IActivate, IInteractUsing
{
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(AMEControllerUiKey.Key);
[ViewVariables] private bool _injecting;
[ViewVariables] public int InjectionAmount;
private AppearanceComponent? _appearance;
private PowerSupplierComponent? _powerSupplier;
private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
[ViewVariables]
private int _stability = 100;
private ContainerSlot _jarSlot = default!;
[ViewVariables] private bool HasJar => _jarSlot.ContainedEntity != null;
public override void Initialize()
{
base.Initialize();
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += OnUiReceiveMessage;
}
Owner.TryGetComponent(out _appearance);
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
receiver.OnPowerStateChanged += OnPowerChanged;
}
Owner.TryGetComponent(out _powerSupplier);
_injecting = false;
InjectionAmount = 2;
_jarSlot = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-fuelJarContainer", Owner);
}
internal void OnUpdate(float frameTime)
{
if(!_injecting)
{
return;
}
_jarSlot.ContainedEntity.TryGetComponent<AMEFuelContainerComponent>(out var fuelJar);
if(fuelJar != null && _powerSupplier != null && fuelJar.FuelAmount > InjectionAmount)
{
_powerSupplier.SupplyRate = GetAMENodeGroup().InjectFuel(InjectionAmount);
fuelJar.FuelAmount -= InjectionAmount;
InjectSound();
UpdateUserInterface();
}
_stability = GetAMENodeGroup().GetTotalStability();
UpdateDisplay(_stability);
if(_stability <= 0) { GetAMENodeGroup().ExplodeCores(); }
}
/// <summary>
/// Called when you click the owner entity with an empty hand. Opens the UI client-side if possible.
/// </summary>
/// <param name="args">Data relevant to the event such as the actor which triggered it.</param>
void IActivate.Activate(ActivateEventArgs args)
{
if (!args.User.TryGetComponent(out IActorComponent? actor))
{
return;
}
if (!args.User.TryGetComponent(out IHandsComponent? hands))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
Loc.GetString("You have no hands."));
return;
}
var activeHandEntity = hands.GetActiveHand?.Owner;
if (activeHandEntity == null)
{
UserInterface?.Open(actor.playerSession);
}
}
private void OnPowerChanged(object? sender, PowerStateEventArgs e)
{
UpdateUserInterface();
}
private AMEControllerBoundUserInterfaceState GetUserInterfaceState()
{
var jar = _jarSlot.ContainedEntity;
if (jar == null)
{
return new AMEControllerBoundUserInterfaceState(Powered, IsMasterController(), false, HasJar, 0, InjectionAmount, GetCoreCount());
}
var jarcomponent = jar.GetComponent<AMEFuelContainerComponent>();
return new AMEControllerBoundUserInterfaceState(Powered, IsMasterController(), _injecting, HasJar, jarcomponent.FuelAmount, InjectionAmount, GetCoreCount());
}
/// <summary>
/// Checks whether the player entity is able to use the controller.
/// </summary>
/// <param name="playerEntity">The player entity.</param>
/// <returns>Returns true if the entity can use the controller, and false if it cannot.</returns>
private bool PlayerCanUseController(IEntity playerEntity, bool needsPower = true)
{
//Need player entity to check if they are still able to use the dispenser
if (playerEntity == null)
return false;
//Check if player can interact in their current state
if (!ActionBlockerSystem.CanInteract(playerEntity) || !ActionBlockerSystem.CanUse(playerEntity))
return false;
//Check if device is powered
if (needsPower && !Powered)
return false;
return true;
}
private void UpdateUserInterface()
{
var state = GetUserInterfaceState();
UserInterface?.SetState(state);
}
/// <summary>
/// Handles ui messages from the client. For things such as button presses
/// which interact with the world and require server action.
/// </summary>
/// <param name="obj">A user interface message from the client.</param>
private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
{
if (obj.Session.AttachedEntity == null)
{
return;
}
var msg = (UiButtonPressedMessage) obj.Message;
var needsPower = msg.Button switch
{
UiButton.Eject => false,
_ => true,
};
if (!PlayerCanUseController(obj.Session.AttachedEntity, needsPower))
return;
switch (msg.Button)
{
case UiButton.Eject:
TryEject(obj.Session.AttachedEntity);
break;
case UiButton.ToggleInjection:
ToggleInjection();
break;
case UiButton.IncreaseFuel:
InjectionAmount += 2;
break;
case UiButton.DecreaseFuel:
InjectionAmount = InjectionAmount > 0 ? InjectionAmount -= 2 : 0;
break;
case UiButton.RefreshParts:
RefreshParts();
break;
}
GetAMENodeGroup().UpdateCoreVisuals(InjectionAmount, _injecting);
UpdateUserInterface();
ClickSound();
}
private void TryEject(IEntity user)
{
if (!HasJar || _injecting)
return;
var jar = _jarSlot.ContainedEntity;
_jarSlot.Remove(_jarSlot.ContainedEntity);
UpdateUserInterface();
if (!user.TryGetComponent<HandsComponent>(out var hands) || !jar.TryGetComponent<ItemComponent>(out var item))
return;
if (hands.CanPutInHand(item))
hands.PutInHand(item);
}
private void ToggleInjection()
{
if (!_injecting)
{
_appearance?.SetData(AMEControllerVisuals.DisplayState, "on");
}
else
{
_appearance?.SetData(AMEControllerVisuals.DisplayState, "off");
if (_powerSupplier != null)
{
_powerSupplier.SupplyRate = 0;
}
}
_injecting = !_injecting;
UpdateUserInterface();
}
private void UpdateDisplay(int stability)
{
if(_appearance == null) { return; }
_appearance.TryGetData<string>(AMEControllerVisuals.DisplayState, out var state);
var newState = "on";
if (stability < 50) { newState = "critical"; }
if (stability < 10) { newState = "fuck"; }
if (state != newState)
{
_appearance?.SetData(AMEControllerVisuals.DisplayState, newState);
}
}
private void RefreshParts()
{
GetAMENodeGroup().RefreshAMENodes(this);
UpdateUserInterface();
}
private AMENodeGroup GetAMENodeGroup()
{
Owner.TryGetComponent(out NodeContainerComponent? nodeContainer);
var engineNodeGroup = nodeContainer?.Nodes
.Select(node => node.NodeGroup)
.OfType<AMENodeGroup>()
.First();
return engineNodeGroup ?? default!;
}
private bool IsMasterController()
{
if(GetAMENodeGroup().MasterController == this)
{
return true;
}
return false;
}
private int GetCoreCount()
{
var coreCount = 0;
if(GetAMENodeGroup() != null)
{
coreCount = GetAMENodeGroup().CoreCount;
}
return coreCount;
}
private void ClickSound()
{
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f));
}
private void InjectSound()
{
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Effects/bang.ogg", Owner, AudioParams.Default.WithVolume(0f));
}
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs args)
{
if (!args.User.TryGetComponent(out IHandsComponent? hands))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
Loc.GetString("You have no hands."));
return true;
}
if (hands.GetActiveHand == null)
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
Loc.GetString("You have nothing on your hand."));
return false;
}
var activeHandEntity = hands.GetActiveHand.Owner;
if (activeHandEntity.TryGetComponent<AMEFuelContainerComponent>(out var fuelContainer))
{
if (HasJar)
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
Loc.GetString("The controller already has a jar loaded."));
}
else
{
_jarSlot.Insert(activeHandEntity);
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
Loc.GetString("You insert the jar into the fuel slot."));
UpdateUserInterface();
}
}
else
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
Loc.GetString("You can't put that in the controller..."));
}
return true;
}
}
}

View File

@@ -0,0 +1,45 @@
using Robust.Shared.GameObjects;
using Robust.Shared.ViewVariables;
using System;
using System.Collections.Generic;
using System.Text;
namespace Content.Server.GameObjects.Components.Power.AME
{
[RegisterComponent]
public class AMEFuelContainerComponent : Component
{
public override string Name => "AMEFuelContainer";
private int _fuelAmount;
private int _maxFuelAmount;
/// <summary>
/// The amount of fuel in the jar.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public int FuelAmount
{
get => _fuelAmount;
set => _fuelAmount = value;
}
/// <summary>
/// The maximum fuel capacity of the jar.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public int MaxFuelAmount
{
get => _maxFuelAmount;
set => _maxFuelAmount = value;
}
public override void Initialize()
{
base.Initialize();
_maxFuelAmount = 1000;
_fuelAmount = 1000;
}
}
}

View File

@@ -0,0 +1,51 @@
using Content.Server.GameObjects.Components.Interactable;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Shared.GameObjects.Components.Interactable;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using System.Threading.Tasks;
namespace Content.Server.GameObjects.Components.Power.AME
{
[RegisterComponent]
[ComponentReference(typeof(IInteractUsing))]
public class AMEPartComponent : Component, IInteractUsing
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IServerEntityManager _serverEntityManager = default!;
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
public override string Name => "AMEPart";
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs args)
{
if (!args.User.TryGetComponent(out IHandsComponent hands))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
Loc.GetString("You have no hands."));
return true;
}
var activeHandEntity = hands.GetActiveHand.Owner;
if (activeHandEntity.TryGetComponent<ToolComponent>(out var multitool) && multitool.Qualities == ToolQuality.Multitool)
{
var mapGrid = _mapManager.GetGrid(args.ClickLocation.GridID);
var tile = mapGrid.GetTileRef(args.ClickLocation);
var snapPos = mapGrid.SnapGridCellFor(args.ClickLocation, SnapGridOffset.Center);
var ent = _serverEntityManager.SpawnEntity("AMEShielding", mapGrid.GridTileToLocal(snapPos));
ent.Transform.LocalRotation = Owner.Transform.LocalRotation;
Owner.Delete();
}
return true;
}
}
}

View File

@@ -0,0 +1,73 @@
#nullable enable
using Content.Shared.GameObjects.Components.Power.AME;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.ViewVariables;
using System;
using System.Collections.Generic;
using System.Text;
namespace Content.Server.GameObjects.Components.Power.AME
{
[RegisterComponent]
public class AMEShieldComponent : SharedAMEShieldComponent
{
private bool _isCore = false;
[ViewVariables]
public int CoreIntegrity = 100;
private AppearanceComponent? _appearance;
private PointLightComponent? _pointLight;
public override void Initialize()
{
base.Initialize();
Owner.TryGetComponent(out _appearance);
Owner.TryGetComponent(out _pointLight);
}
internal void OnUpdate(float frameTime)
{
throw new NotImplementedException();
}
public void SetCore()
{
if(_isCore) { return; }
_isCore = true;
_appearance?.SetData(AMEShieldVisuals.Core, "isCore");
}
public void UnsetCore()
{
_isCore = false;
_appearance?.SetData(AMEShieldVisuals.Core, "isNotCore");
}
public void UpdateCoreVisuals(int injectionStrength, bool injecting)
{
if (!injecting)
{
_appearance?.SetData(AMEShieldVisuals.CoreState, "off");
if (_pointLight != null) { _pointLight.Enabled = false; }
return;
}
if (_pointLight != null)
{
_pointLight.Radius = Math.Clamp(injectionStrength, 1, 12);
_pointLight.Enabled = true;
}
if (injectionStrength > 2)
{
_appearance?.SetData(AMEShieldVisuals.CoreState, "strong");
return;
}
_appearance?.SetData(AMEShieldVisuals.CoreState, "weak");
}
}
}

View File

@@ -0,0 +1,32 @@
using Content.Server.GameObjects.Components.Power.AME;
using JetBrains.Annotations;
using Robust.Shared.GameObjects.Systems;
namespace Content.Server.GameObjects.EntitySystems
{
[UsedImplicitly]
public class AntimatterEngineSystem : EntitySystem
{
private float _accumulatedFrameTime;
public override void Initialize()
{
base.Initialize();
}
public override void Update(float frameTime)
{
base.Update(frameTime);
_accumulatedFrameTime += frameTime;
if (_accumulatedFrameTime >= 10)
{
foreach (var comp in ComponentManager.EntityQuery<AMEControllerComponent>())
{
comp.OnUpdate(frameTime);
}
_accumulatedFrameTime -= 10;
}
}
}
}

View File

@@ -0,0 +1,69 @@
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.Serialization;
using System;
using System.Collections.Generic;
using System.Text;
namespace Content.Shared.GameObjects.Components.Power.AME
{
public class SharedAMEControllerComponent : Component
{
public override string Name => "AMEController";
[Serializable, NetSerializable]
public class AMEControllerBoundUserInterfaceState : BoundUserInterfaceState
{
public readonly bool HasPower;
public readonly bool IsMaster;
public readonly bool Injecting;
public readonly bool HasFuelJar;
public readonly int FuelAmount;
public readonly int InjectionAmount;
public readonly int CoreCount;
public AMEControllerBoundUserInterfaceState(bool hasPower, bool isMaster, bool injecting, bool hasFuelJar, int fuelAmount, int injectionAmount, int coreCount)
{
HasPower = hasPower;
IsMaster = isMaster;
Injecting = injecting;
HasFuelJar = hasFuelJar;
FuelAmount = fuelAmount;
InjectionAmount = injectionAmount;
CoreCount = coreCount;
}
}
[Serializable, NetSerializable]
public class UiButtonPressedMessage : BoundUserInterfaceMessage
{
public readonly UiButton Button;
public UiButtonPressedMessage(UiButton button)
{
Button = button;
}
}
[Serializable, NetSerializable]
public enum AMEControllerUiKey
{
Key
}
public enum UiButton
{
Eject,
ToggleInjection,
IncreaseFuel,
DecreaseFuel,
RefreshParts
}
[Serializable, NetSerializable]
public enum AMEControllerVisuals
{
DisplayState,
}
}
}

View File

@@ -0,0 +1,27 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using System;
using System.Collections.Generic;
using System.Text;
namespace Content.Shared.GameObjects.Components.Power.AME
{
public class SharedAMEShieldComponent : Component
{
public override string Name => "AMEShield";
[Serializable, NetSerializable]
public enum AMEShieldVisuals
{
Core,
CoreState
}
public enum AMECoreState
{
Off,
Weak,
Strong
}
}
}

View File

@@ -0,0 +1,44 @@
- type: entity
id: AMEController
name: AME Controller
placement:
mode: SnapgridCenter
components:
- type: Clickable
- type: Icon
sprite: Constructible/Power/ame_controller.rsi
state: control
- type: Sprite
sprite: Constructible/Power/ame_controller.rsi
state: control
- type: Collidable
shapes:
- !type:PhysShapeAabb
layer:
- Opaque
- Impassable
- MobImpassable
- VaultImpassable
- SmallImpassable
- type: Destructible
maxHP: 500
- type: SnapGrid
offset: Center
- type: Anchorable
- type: AMEController
- type: UserInterface
interfaces:
- key: enum.AMEControllerUiKey.Key
type: AMEControllerBoundUserInterface
- type: Appearance
visuals:
- type: AMEControllerVisualizer
- type: NodeContainer
nodes:
- !type:AdjacentNode
nodeGroupID: AMEngine
- !type:AdjacentNode
nodeGroupID: HVPower
- type: PowerReceiver
- type: PowerSupplier
supplyRate: 0

View File

@@ -0,0 +1,46 @@
- type: entity
id: AMEShielding
name: AME shielding
description: Keeps the antimatter in and the matter out.
placement:
mode: SnapgridCenter
components:
- type: Clickable
- type: Sprite
drawdepth: Walls
sprite: Constructible/Power/ame_shielding.rsi
state: shield_0
- type: Icon
texture: Constructible/Power/ame_shielding_base.png
- type: Collidable
shapes:
- !type:PhysShapeAabb
layer:
- Opaque
- Impassable
- MobImpassable
- VaultImpassable
- SmallImpassable
- type: Destructible
maxHP: 500
spawnondestroy: AMEPart
- type: SnapGrid
offset: Center
- type: Airtight
- type: IconSmooth
mode: CardinalFlags
base: shield_
key: ame_shield
- type: AMEShield
- type: NodeContainer
nodes:
- !type:AdjacentNode
nodeGroupID: AMEngine
- type: PointLight
enabled: false
radius: 5
energy: 0.5
color: "#00AAFF"
- type: Appearance
visuals:
- type: AMEVisualizer

View File

@@ -0,0 +1,16 @@
- type: entity
id: AMEJar
name: Antimatter Fuel Jar
parent: BaseItem
description: A hermetically sealed jar containing antimatter for use in an antimatter reactor.
components:
- type: Item
size: 5
sprite: Objects/Power/AME/ame_jar.rsi
- type: Sprite
sprite: Objects/Power/AME/ame_jar.rsi
state: jar
- type: Icon
sprite: Objects/Power/AME/ame_jar.rsi
state: jar
- type: AMEFuelContainer

View File

@@ -0,0 +1,16 @@
- type: entity
id: AMEPart
name: Antimatter Engine Part
parent: BaseItem
description: "A flatpack used for constructing an antimatter engine reactor.\nUse a multitool to unpack it."
components:
- type: Item
size: 5
sprite: Objects/Power/AME/ame_part.rsi
- type: Sprite
sprite: Objects/Power/AME/ame_part.rsi
state: box
- type: Icon
sprite: Objects/Power/AME/ame_part.rsi
state: box
- type: AMEPart

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 959 B

View File

@@ -0,0 +1,120 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"license": "CC-BY-SA-3.0",
"copyright": "Taken from https://github.com/vgstation-coders/vgstation13/blob/Bleeding-Edge/icons/obj/machines/new_ame.dmi at 1b7952787c06c21ef1623e494dcfe7cb1f46e041",
"states": [
{
"name": "control",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "control_critical",
"directions": 1,
"delays": [
[
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1
]
]
},
{
"name": "control_fuck",
"directions": 1,
"delays": [
[
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1
]
]
},
{
"name": "control_on",
"directions": 1,
"delays": [
[
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,192 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"license": "CC-BY-SA-3.0",
"copyright": "Taken from https://github.com/vgstation-coders/vgstation13/blob/Bleeding-Edge/icons/obj/machines/new_ame.dmi at 1b7952787c06c21ef1623e494dcfe7cb1f46e041",
"states": [
{
"name": "shield_0",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "shield_1",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "shield_10",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "shield_11",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "shield_12",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "shield_13",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "shield_14",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "shield_15",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "shield_2",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "shield_3",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "shield_4",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "shield_5",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "shield_6",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "shield_7",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "shield_8",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "core",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "shield_9",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "core_weak",
"directions": 1,
"delays": [
[
0.1,
0.1,
0.1,
0.1,
0.1,
0.1
]
]
},
{
"name": "core_strong",
"directions": 1,
"delays": [
[
0.1,
0.1,
0.1,
0.1,
0.1,
0.1
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 831 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

View File

@@ -0,0 +1,20 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"license": "CC-BY-SA-3.0",
"copyright": "Taken from https://github.com/vgstation-coders/vgstation13/blob/Bleeding-Edge/icons/obj/machines/new_ame.dmi at 1b7952787c06c21ef1623e494dcfe7cb1f46e041",
"states": [
{
"name": "jar",
"directions": 1,
"delays": [
[
1.0
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

View File

@@ -0,0 +1,20 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"license": "CC-BY-SA-3.0",
"copyright": "Taken from https://github.com/vgstation-coders/vgstation13/blob/Bleeding-Edge/icons/obj/machines/new_ame.dmi at 1b7952787c06c21ef1623e494dcfe7cb1f46e041",
"states": [
{
"name": "box",
"directions": 1,
"delays": [
[
1.0
]
]
}
]
}