Merge branch 'master' into round_end_screen

This commit is contained in:
FL-OZ
2020-04-15 14:00:56 -05:00
committed by GitHub
176 changed files with 2541 additions and 1676 deletions

View File

@@ -288,7 +288,7 @@ namespace Content.Client.Chat
WriteChatMessage(storedMessage);
// Local messages that have an entity attached get a speech bubble.
if (msg.Channel == ChatChannel.Local && msg.SenderEntity != default)
if ((msg.Channel == ChatChannel.Local || msg.Channel == ChatChannel.Dead) && msg.SenderEntity != default)
{
AddSpeechBubble(msg);
}

View File

@@ -8,7 +8,9 @@ using Content.Client.Sandbox;
using Content.Client.UserInterface;
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
using Content.Shared.Chemistry;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.Chemistry;
using Robust.Shared.IoC;
namespace Content.Client

View File

@@ -0,0 +1,80 @@
using System.Threading;
using Content.Client.GameObjects.Components.Command;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Timer = Robust.Shared.Timers.Timer;
namespace Content.Client.Command
{
public class CommunicationsConsoleMenu : SS14Window
{
#pragma warning disable 649
[Dependency] private readonly ILocalizationManager _localizationManager;
#pragma warning restore 649
protected override Vector2? CustomSize => new Vector2(600, 400);
private CommunicationsConsoleBoundUserInterface Owner { get; set; }
private readonly CancellationTokenSource _timerCancelTokenSource = new CancellationTokenSource();
private readonly Button _emergencyShuttleButton;
private readonly RichTextLabel _countdownLabel;
public CommunicationsConsoleMenu(CommunicationsConsoleBoundUserInterface owner)
{
IoCManager.InjectDependencies(this);
Title = _localizationManager.GetString("Communications Console");
Owner = owner;
_countdownLabel = new RichTextLabel(){CustomMinimumSize = new Vector2(0, 200)};
_emergencyShuttleButton = new Button();
_emergencyShuttleButton.OnPressed += (e) => Owner.EmergencyShuttleButtonPressed();
var vbox = new VBoxContainer() {SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsVertical = SizeFlags.FillExpand};
vbox.AddChild(_countdownLabel);
vbox.AddChild(_emergencyShuttleButton);
var hbox = new HBoxContainer() {SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsVertical = SizeFlags.FillExpand};
hbox.AddChild(new Control(){CustomMinimumSize = new Vector2(100,0), SizeFlagsHorizontal = SizeFlags.FillExpand});
hbox.AddChild(vbox);
hbox.AddChild(new Control(){CustomMinimumSize = new Vector2(100,0), SizeFlagsHorizontal = SizeFlags.FillExpand});
Contents.AddChild(hbox);
UpdateCountdown();
Timer.SpawnRepeating(1000, UpdateCountdown, _timerCancelTokenSource.Token);
}
public void UpdateCountdown()
{
if (!Owner.CountdownStarted)
{
_countdownLabel.SetMessage("");
_emergencyShuttleButton.Text = _localizationManager.GetString("Call emergency shuttle");
return;
}
_emergencyShuttleButton.Text = _localizationManager.GetString("Recall emergency shuttle");
_countdownLabel.SetMessage($"Time remaining\n{Owner.Countdown.ToString()}s");
}
public override void Close()
{
base.Close();
_timerCancelTokenSource.Cancel();
}
protected override void Dispose(bool disposing)
{
if(disposing)
_timerCancelTokenSource.Cancel();
}
}
}

View File

@@ -15,6 +15,7 @@ using Content.Shared.GameObjects.Components.Chemistry;
using Content.Shared.GameObjects.Components.Markers;
using Content.Shared.GameObjects.Components.Research;
using Content.Shared.GameObjects.Components.VendingMachines;
using Robust.Client;
using Robust.Client.Interfaces;
using Robust.Client.Interfaces.Graphics.Overlays;
using Robust.Client.Interfaces.Input;
@@ -134,6 +135,7 @@ namespace Content.Client
"Paper",
"Write",
"Bloodstream",
"TransformableContainer",
"Mind",
"MovementSpeedModifier",
"StorageFill"
@@ -148,7 +150,7 @@ namespace Content.Client
factory.Register<SharedLatheComponent>();
factory.Register<SharedSpawnPointComponent>();
factory.Register<SolutionComponent>();
factory.Register<SharedSolutionComponent>();
factory.Register<SharedVendingMachineComponent>();
factory.Register<SharedWiresComponent>();
@@ -226,6 +228,14 @@ namespace Content.Client
IoCManager.Resolve<IClientPreferencesManager>().Initialize();
IoCManager.Resolve<IItemSlotManager>().Initialize();
_baseClient.RunLevelChanged += (sender, args) =>
{
if (args.NewLevel == ClientRunLevel.Initialize)
{
_stateManager.RequestStateChange<MainScreen>();
}
};
// Fire off into state dependent on launcher or not.
if (_gameController.LaunchState.FromLauncher)
{

View File

@@ -8,6 +8,7 @@ using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.ViewVariables;
using Content.Shared.Chemistry;
namespace Content.Client.GameObjects.Components.Chemistry
{
@@ -17,8 +18,8 @@ namespace Content.Client.GameObjects.Components.Chemistry
[RegisterComponent]
public class InjectorComponent : SharedInjectorComponent, IItemStatus
{
[ViewVariables] private int CurrentVolume { get; set; }
[ViewVariables] private int TotalVolume { get; set; }
[ViewVariables] private ReagentUnit CurrentVolume { get; set; }
[ViewVariables] private ReagentUnit TotalVolume { get; set; }
[ViewVariables] private InjectorToggleMode CurrentMode { get; set; }
[ViewVariables(VVAccess.ReadWrite)] private bool _uiUpdateNeeded;
@@ -29,7 +30,7 @@ namespace Content.Client.GameObjects.Components.Chemistry
//Handle net updates
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
var cast = (InjectorComponentState)curState;
var cast = (InjectorComponentState) curState;
if (cast != null)
{
CurrentVolume = cast.CurrentVolume;

View File

@@ -171,7 +171,7 @@ namespace Content.Client.GameObjects.Components.Chemistry
Title = castState.DispenserName;
UpdateContainerInfo(castState);
switch (castState.SelectedDispenseAmount)
switch (castState.SelectedDispenseAmount.Int())
{
case 1:
DispenseButton1.Pressed = true;

View File

@@ -0,0 +1,83 @@
using System;
using Content.Client.Command;
using Content.Shared.GameObjects.Components.Command;
using Robust.Client.GameObjects.Components.UserInterface;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.ViewVariables;
namespace Content.Client.GameObjects.Components.Command
{
public class CommunicationsConsoleBoundUserInterface : BoundUserInterface
{
[ViewVariables]
private CommunicationsConsoleMenu _menu;
[Dependency] private IGameTiming _gameTiming;
public bool CountdownStarted { get; private set; }
public int Countdown => _expectedCountdownTime == null
? 0 : Math.Max((int)_expectedCountdownTime.Value.Subtract(_gameTiming.CurTime).TotalSeconds, 0);
private TimeSpan? _expectedCountdownTime;
public CommunicationsConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_menu = new CommunicationsConsoleMenu(this);
_menu.OnClose += Close;
_menu.OpenCentered();
}
public void EmergencyShuttleButtonPressed()
{
if(CountdownStarted)
RecallShuttle();
else
CallShuttle();
}
public void CallShuttle()
{
SendMessage(new CommunicationsConsoleCallEmergencyShuttleMessage());
}
public void RecallShuttle()
{
SendMessage(new CommunicationsConsoleRecallEmergencyShuttleMessage());
}
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
{
switch (message)
{
}
}
protected override void UpdateState(BoundUserInterfaceState state)
{
if (!(state is CommunicationsConsoleInterfaceState commsState))
return;
_expectedCountdownTime = commsState.ExpectedCountdownEnd;
CountdownStarted = commsState.CountdownStarted;
_menu?.UpdateCountdown();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing) return;
_menu?.Dispose();
}
}
}

View File

@@ -76,10 +76,6 @@ namespace Content.Client.GameObjects.Components.Mobs
private void PlayerDetached()
{
if (!CurrentlyControlled)
{
return;
}
_ui?.Dispose();
_ui = null;
}

View File

@@ -0,0 +1,98 @@
using Content.Client.UserInterface;
using Content.Shared.GameObjects.Components.Observer;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.IoC;
using Robust.Shared.ViewVariables;
namespace Content.Client.GameObjects.Components.Observer
{
[RegisterComponent]
public class GhostComponent : SharedGhostComponent
{
private GhostGui _gui;
[ViewVariables(VVAccess.ReadOnly)]
public bool CanReturnToBody { get; private set; } = true;
#pragma warning disable 649
[Dependency] private readonly IGameHud _gameHud;
[Dependency] private readonly IPlayerManager _playerManager;
[Dependency] private IComponentManager _componentManager;
#pragma warning restore 649
public override void OnRemove()
{
base.OnRemove();
_gui?.Dispose();
}
private void SetGhostVisibility(bool visibility)
{
foreach (var ghost in _componentManager.GetAllComponents(typeof(GhostComponent)))
{
if (ghost.Owner.TryGetComponent(out SpriteComponent component))
component.Visible = visibility;
}
}
public override void Initialize()
{
base.Initialize();
if (Owner.TryGetComponent(out SpriteComponent component))
component.Visible = _playerManager.LocalPlayer.ControlledEntity?.HasComponent<GhostComponent>() ?? false;
}
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null,
IComponent component = null)
{
base.HandleMessage(message, netChannel, component);
switch (message)
{
case PlayerAttachedMsg _:
if (_gui == null)
{
_gui = new GhostGui(this);
}
else
{
_gui.Orphan();
}
_gameHud.HandsContainer.AddChild(_gui);
SetGhostVisibility(true);
break;
case PlayerDetachedMsg _:
_gui.Parent?.RemoveChild(_gui);
SetGhostVisibility(false);
break;
}
}
public void SendReturnToBodyMessage() => SendNetworkMessage(new ReturnToBodyComponentMessage());
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
base.HandleComponentState(curState, nextState);
if (!(curState is GhostComponentState state)) return;
CanReturnToBody = state.CanReturnToBody;
if (Owner == _playerManager.LocalPlayer.ControlledEntity)
{
_gui?.Update();
}
}
}
}

View File

@@ -17,9 +17,9 @@ namespace Content.Client.GameObjects.Components.Research
private IPrototypeManager _prototypeManager;
#pragma warning restore
[ViewVariables]
private LatheMenu menu;
private LatheMenu _menu;
[ViewVariables]
private LatheQueueMenu queueMenu;
private LatheQueueMenu _queueMenu;
public MaterialStorageComponent Storage { get; private set; }
public SharedLatheComponent Lathe { get; private set; }
@@ -48,30 +48,30 @@ namespace Content.Client.GameObjects.Components.Research
Lathe = lathe;
Database = database;
menu = new LatheMenu(this);
queueMenu = new LatheQueueMenu { Owner = this };
_menu = new LatheMenu(this);
_queueMenu = new LatheQueueMenu { Owner = this };
menu.OnClose += Close;
_menu.OnClose += Close;
menu.Populate();
menu.PopulateMaterials();
_menu.Populate();
_menu.PopulateMaterials();
menu.QueueButton.OnPressed += (args) => { queueMenu.OpenCentered(); };
_menu.QueueButton.OnPressed += (args) => { _queueMenu.OpenCentered(); };
menu.ServerConnectButton.OnPressed += (args) =>
_menu.ServerConnectButton.OnPressed += (args) =>
{
SendMessage(new SharedLatheComponent.LatheServerSelectionMessage());
};
menu.ServerSyncButton.OnPressed += (args) =>
_menu.ServerSyncButton.OnPressed += (args) =>
{
SendMessage(new SharedLatheComponent.LatheServerSyncMessage());
};
storage.OnMaterialStorageChanged += menu.PopulateDisabled;
storage.OnMaterialStorageChanged += menu.PopulateMaterials;
storage.OnMaterialStorageChanged += _menu.PopulateDisabled;
storage.OnMaterialStorageChanged += _menu.PopulateMaterials;
menu.OpenCentered();
_menu.OpenCentered();
}
public void Queue(LatheRecipePrototype recipe, int quantity = 1)
@@ -85,10 +85,10 @@ namespace Content.Client.GameObjects.Components.Research
{
case SharedLatheComponent.LatheProducingRecipeMessage msg:
if (!_prototypeManager.TryIndex(msg.ID, out LatheRecipePrototype recipe)) break;
queueMenu?.SetInfo(recipe);
_queueMenu?.SetInfo(recipe);
break;
case SharedLatheComponent.LatheStoppedProducingRecipeMessage _:
queueMenu?.ClearInfo();
_queueMenu?.ClearInfo();
break;
case SharedLatheComponent.LatheFullQueueMessage msg:
_queuedRecipes.Clear();
@@ -97,7 +97,7 @@ namespace Content.Client.GameObjects.Components.Research
if (!_prototypeManager.TryIndex(id, out LatheRecipePrototype recipePrototype)) break;
_queuedRecipes.Enqueue(recipePrototype);
}
queueMenu?.PopulateList();
_queueMenu?.PopulateList();
break;
}
}
@@ -106,8 +106,8 @@ namespace Content.Client.GameObjects.Components.Research
{
base.Dispose(disposing);
if (!disposing) return;
menu?.Dispose();
queueMenu?.Dispose();
_menu?.Dispose();
_queueMenu?.Dispose();
}
}
}

View File

@@ -1,5 +1,4 @@
using Content.Client.UserInterface;
using Content.Client.UserInterface.Stylesheets;
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
using Content.Shared.GameObjects.Components;
using Robust.Client.UserInterface;
@@ -14,21 +13,19 @@ namespace Content.Client.GameObjects.Components
[RegisterComponent]
public class StackComponent : SharedStackComponent, IItemStatus
{
[ViewVariables] public int Count { get; private set; }
[ViewVariables] public int MaxCount { get; private set; }
[ViewVariables(VVAccess.ReadWrite)] private bool _uiUpdateNeeded;
public Control MakeControl() => new StatusControl(this);
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
public override int Count
{
if (!(curState is StackComponentState cast))
return;
get => base.Count;
set
{
base.Count = value;
Count = cast.Count;
MaxCount = cast.MaxCount;
_uiUpdateNeeded = true;
_uiUpdateNeeded = true;
}
}
private sealed class StatusControl : Control

View File

@@ -43,8 +43,6 @@ namespace Content.Client.State
public override void Shutdown()
{
_playerManager.LocalPlayer.DetachEntity();
_inputManager.KeyBindStateChanged -= OnKeyBindStateChanged;
}

View File

@@ -0,0 +1,34 @@
using System.Data;
using Content.Client.GameObjects.Components.Observer;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.IoC;
namespace Content.Client.UserInterface
{
public class GhostGui : Control
{
public Button ReturnToBody = new Button(){Text = "Return to body"};
private GhostComponent _owner;
public GhostGui(GhostComponent owner)
{
IoCManager.InjectDependencies(this);
_owner = owner;
MouseFilter = MouseFilterMode.Ignore;
ReturnToBody.OnPressed += (args) => { owner.SendReturnToBodyMessage(); };
AddChild(ReturnToBody);
Update();
}
public void Update()
{
ReturnToBody.Disabled = !_owner.CanReturnToBody;
}
}
}

View File

@@ -4,6 +4,7 @@ using Content.Server.GameTicking;
using Content.Server.Interfaces.GameTicking;
using Content.Shared;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Map;
using Robust.Shared.Timing;
namespace Content.IntegrationTests
@@ -54,6 +55,10 @@ namespace Content.IntegrationTests
{
}
public GridCoordinates GetLateJoinSpawnPoint() => GridCoordinates.InvalidGrid;
public GridCoordinates GetJobSpawnPoint(string jobId) => GridCoordinates.InvalidGrid;
public GridCoordinates GetObserverSpawnPoint() => GridCoordinates.InvalidGrid;
public T AddGameRule<T>() where T : GameRule, new()
{
return new T();

View File

@@ -1,4 +1,5 @@
using Content.Server.Players;
using Content.Server.GameObjects.Components.Observer;
using Content.Server.Players;
using Robust.Server.Interfaces.Console;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Interfaces.GameObjects;
@@ -30,10 +31,14 @@ namespace Content.Server.Administration
}
else
{
var canReturn = mind.CurrentEntity != null && !mind.CurrentEntity.HasComponent<GhostComponent>();
var entityManager = IoCManager.Resolve<IEntityManager>();
var ghost = entityManager.SpawnEntity("AdminObserver", player.AttachedEntity.Transform.GridPosition);
mind.Visit(ghost);
if(canReturn)
mind.Visit(ghost);
else
mind.TransferTo(ghost);
ghost.GetComponent<GhostComponent>().CanReturnToBody = canReturn;
}
}
}

View File

@@ -1,4 +1,6 @@
using Content.Server.Interfaces.Chat;
using Content.Server.GameObjects.Components.Observer;
using Content.Server.Interfaces.Chat;
using Content.Server.Observer;
using Robust.Server.Interfaces.Console;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Enums;
@@ -24,7 +26,10 @@ namespace Content.Server.Chat
var message = string.Join(" ", args);
chat.EntitySay(player.AttachedEntity, message);
if (player.AttachedEntity.HasComponent<GhostComponent>())
chat.SendDeadChat(player, message);
else
chat.EntitySay(player.AttachedEntity, message);
}
}

View File

@@ -1,12 +1,17 @@
using System.Linq;
using Content.Server.GameObjects.Components.Observer;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Server.Interfaces.Chat;
using Content.Server.Observer;
using Content.Server.Players;
using Content.Shared.Chat;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
namespace Content.Server.Chat
{
@@ -20,6 +25,7 @@ namespace Content.Server.Chat
#pragma warning disable 649
[Dependency] private readonly IServerNetManager _netManager;
[Dependency] private readonly IPlayerManager _playerManager;
[Dependency] private readonly ILocalizationManager _localizationManager;
[Dependency] private readonly IMoMMILink _mommiLink;
#pragma warning restore 649
@@ -93,6 +99,18 @@ namespace Content.Server.Chat
_mommiLink.SendOOCMessage(player.SessionId.ToString(), message);
}
public void SendDeadChat(IPlayerSession player, string message)
{
var clients = _playerManager.GetPlayersBy(x => x.AttachedEntity != null && x.AttachedEntity.HasComponent<GhostComponent>()).Select(p => p.ConnectedClient);;
var msg = _netManager.CreateNetMessage<MsgChatMessage>();
msg.Channel = ChatChannel.Dead;
msg.Message = message;
msg.MessageWrap = $"{_localizationManager.GetString("DEAD")}: {player.AttachedEntity.Name}: {{0}}";
msg.SenderEntity = player.AttachedEntityUid.GetValueOrDefault();
_netManager.ServerSendToMany(msg, clients.ToList());
}
public void SendHookOOC(string sender, string message)
{
var msg = _netManager.CreateNetMessage<MsgChatMessage>();

View File

@@ -1,5 +1,6 @@
using System;
using Content.Server.GameObjects.Components.Nutrition;
using Content.Shared.Chemistry;
using Content.Shared.Interfaces.Chemistry;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Serialization;
@@ -14,8 +15,8 @@ namespace Content.Server.Chemistry.Metabolism
class DefaultDrink : IMetabolizable
{
//Rate of metabolism in units / second
private int _metabolismRate;
public int MetabolismRate => _metabolismRate;
private ReagentUnit _metabolismRate;
public ReagentUnit MetabolismRate => _metabolismRate;
//How much thirst is satiated when 1u of the reagent is metabolized
private float _hydrationFactor;
@@ -23,16 +24,16 @@ namespace Content.Server.Chemistry.Metabolism
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _metabolismRate, "rate", 1);
serializer.DataField(ref _metabolismRate, "rate", ReagentUnit.New(1));
serializer.DataField(ref _hydrationFactor, "nutrimentFactor", 30.0f);
}
//Remove reagent at set rate, satiate thirst if a ThirstComponent can be found
int IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime)
ReagentUnit IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime)
{
int metabolismAmount = (int)Math.Round(MetabolismRate * tickTime);
var metabolismAmount = MetabolismRate * tickTime;
if (solutionEntity.TryGetComponent(out ThirstComponent thirst))
thirst.UpdateThirst(metabolismAmount * HydrationFactor);
thirst.UpdateThirst(metabolismAmount.Float() * HydrationFactor);
//Return amount of reagent to be removed, remove reagent regardless of ThirstComponent presence
return metabolismAmount;

View File

@@ -1,8 +1,9 @@
using System;
using Content.Server.GameObjects.Components.Nutrition;
using Content.Server.GameObjects.Components.Nutrition;
using Content.Shared.Chemistry;
using Content.Shared.Interfaces.Chemistry;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
namespace Content.Server.Chemistry.Metabolism
@@ -14,8 +15,8 @@ namespace Content.Server.Chemistry.Metabolism
class DefaultFood : IMetabolizable
{
//Rate of metabolism in units / second
private int _metabolismRate;
public int MetabolismRate => _metabolismRate;
private ReagentUnit _metabolismRate;
public ReagentUnit MetabolismRate => _metabolismRate;
//How much hunger is satiated when 1u of the reagent is metabolized
private float _nutritionFactor;
@@ -23,16 +24,16 @@ namespace Content.Server.Chemistry.Metabolism
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _metabolismRate, "rate", 1);
serializer.DataField(ref _metabolismRate, "rate", ReagentUnit.New(1M));
serializer.DataField(ref _nutritionFactor, "nutrimentFactor", 30.0f);
}
//Remove reagent at set rate, satiate hunger if a HungerComponent can be found
int IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime)
ReagentUnit IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime)
{
int metabolismAmount = (int)Math.Round(MetabolismRate * tickTime);
var metabolismAmount = MetabolismRate * tickTime;
if (solutionEntity.TryGetComponent(out HungerComponent hunger))
hunger.UpdateFood(metabolismAmount * NutritionFactor);
hunger.UpdateFood(metabolismAmount.Float() * NutritionFactor);
//Return amount of reagent to be removed, remove reagent regardless of HungerComponent presence
return metabolismAmount;

View File

@@ -35,9 +35,9 @@ namespace Content.Server.Chemistry.ReactionEffects
serializer.DataField(ref _maxScale, "maxScale", 1);
}
public void React(IEntity solutionEntity, int intensity)
public void React(IEntity solutionEntity, decimal intensity)
{
float floatIntensity = intensity; //Use float to avoid truncation in scaling
float floatIntensity = (float)intensity;
if (solutionEntity == null)
return;
if(!solutionEntity.TryGetComponent(out SolutionComponent solution))

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using Content.Shared.Chemistry;
using Content.Shared.Interfaces;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Prototypes;
@@ -16,7 +17,7 @@ namespace Content.Server.Chemistry
private string _id;
private string _name;
private Dictionary<string, ReactantPrototype> _reactants;
private Dictionary<string, uint> _products;
private Dictionary<string, ReagentUnit> _products;
private List<IReactionEffect> _effects;
public string ID => _id;
@@ -28,7 +29,7 @@ namespace Content.Server.Chemistry
/// <summary>
/// Reagents created when the reaction occurs.
/// </summary>
public IReadOnlyDictionary<string, uint> Products => _products;
public IReadOnlyDictionary<string, ReagentUnit> Products => _products;
/// <summary>
/// Effects to be triggered when the reaction occurs.
/// </summary>
@@ -41,7 +42,7 @@ namespace Content.Server.Chemistry
serializer.DataField(ref _id, "id", string.Empty);
serializer.DataField(ref _name, "name", string.Empty);
serializer.DataField(ref _reactants, "reactants", new Dictionary<string, ReactantPrototype>());
serializer.DataField(ref _products, "products", new Dictionary<string, uint>());
serializer.DataField(ref _products, "products", new Dictionary<string, ReagentUnit>());
serializer.DataField(ref _effects, "effects", new List<IReactionEffect>());
}
}
@@ -51,13 +52,13 @@ namespace Content.Server.Chemistry
/// </summary>
public class ReactantPrototype : IExposeData
{
private int _amount;
private ReagentUnit _amount;
private bool _catalyst;
/// <summary>
/// Minimum amount of the reactant needed for the reaction to occur.
/// </summary>
public int Amount => _amount;
public ReagentUnit Amount => _amount;
/// <summary>
/// Whether or not the reactant is a catalyst. Catalysts aren't removed when a reaction occurs.
/// </summary>
@@ -65,7 +66,7 @@ namespace Content.Server.Chemistry
public void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _amount, "amount", 1);
serializer.DataField(ref _amount, "amount", ReagentUnit.New(1));
serializer.DataField(ref _catalyst, "catalyst", false);
}
}

View File

@@ -37,13 +37,13 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// attempt to inject it's entire contents upon use.
/// </summary>
[ViewVariables]
private int _transferAmount;
private ReagentUnit _transferAmount;
/// <summary>
/// Initial storage volume of the injector
/// </summary>
[ViewVariables]
private int _initialMaxVolume;
private ReagentUnit _initialMaxVolume;
/// <summary>
/// The state of the injector. Determines it's attack behavior. Containers must have the
@@ -62,22 +62,14 @@ namespace Content.Server.GameObjects.Components.Chemistry
{
base.ExposeData(serializer);
serializer.DataField(ref _injectOnly, "injectOnly", false);
serializer.DataField(ref _initialMaxVolume, "initialMaxVolume", 15);
serializer.DataField(ref _transferAmount, "transferAmount", 5);
serializer.DataField(ref _initialMaxVolume, "initialMaxVolume", ReagentUnit.New(15));
serializer.DataField(ref _transferAmount, "transferAmount", ReagentUnit.New(5));
}
public override void Initialize()
protected override void Startup()
{
base.Initialize();
//Create and setup internal storage
_internalContents = new SolutionComponent();
_internalContents.InitializeFromPrototype();
_internalContents.Init();
_internalContents.MaxVolume = _initialMaxVolume;
_internalContents.Owner = Owner; //Manually set owner to avoid crash when VV'ing this
base.Startup();
_internalContents = Owner.GetComponent<SolutionComponent>();
_internalContents.Capabilities |= SolutionCaps.Injector;
//Set _toggleState based on prototype
_toggleState = _injectOnly ? InjectorToggleMode.Inject : InjectorToggleMode.Draw;
}
@@ -165,7 +157,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
//Get transfer amount. May be smaller than _transferAmount if not enough room
int realTransferAmount = Math.Min(_transferAmount, targetBloodstream.EmptyVolume);
var realTransferAmount = ReagentUnit.Min(_transferAmount, targetBloodstream.EmptyVolume);
if (realTransferAmount <= 0)
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user,
@@ -193,7 +185,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
//Get transfer amount. May be smaller than _transferAmount if not enough room
int realTransferAmount = Math.Min(_transferAmount, targetSolution.EmptyVolume);
var realTransferAmount = ReagentUnit.Min(_transferAmount, targetSolution.EmptyVolume);
if (realTransferAmount <= 0)
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user,
@@ -221,7 +213,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
//Get transfer amount. May be smaller than _transferAmount if not enough room
int realTransferAmount = Math.Min(_transferAmount, targetSolution.CurrentVolume);
var realTransferAmount = ReagentUnit.Min(_transferAmount, targetSolution.CurrentVolume);
if (realTransferAmount <= 0)
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user,

View File

@@ -4,6 +4,7 @@ using System.Text;
using Content.Server.GameObjects.Components.Nutrition;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Shared.Chemistry;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
@@ -28,13 +29,13 @@ namespace Content.Server.GameObjects.Components.Chemistry
public override string Name => "Pourable";
private int _transferAmount;
private ReagentUnit _transferAmount;
/// <summary>
/// The amount of solution to be transferred from this solution when clicking on other solutions with it.
/// </summary>
[ViewVariables]
public int TransferAmount
public ReagentUnit TransferAmount
{
get => _transferAmount;
set => _transferAmount = value;
@@ -43,7 +44,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _transferAmount, "transferAmount", 5);
serializer.DataField(ref _transferAmount, "transferAmount", ReagentUnit.New(5.0M));
}
/// <summary>
@@ -69,7 +70,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
return false;
//Get transfer amount. May be smaller than _transferAmount if not enough room
int realTransferAmount = Math.Min(attackPourable.TransferAmount, targetSolution.EmptyVolume);
var realTransferAmount = ReagentUnit.Min(attackPourable.TransferAmount, targetSolution.EmptyVolume);
if (realTransferAmount <= 0) //Special message if container is full
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, eventArgs.User,

View File

@@ -30,7 +30,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(IAttackBy))]
public class ReagentDispenserComponent : SharedReagentDispenserComponent, IActivate, IAttackBy
public class ReagentDispenserComponent : SharedReagentDispenserComponent, IActivate, IAttackBy, ISolutionChange
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager;
@@ -42,7 +42,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
[ViewVariables] private string _packPrototypeId;
[ViewVariables] private bool HasBeaker => _beakerContainer.ContainedEntity != null;
[ViewVariables] private int DispenseAmount = 10;
[ViewVariables] private ReagentUnit _dispenseAmount = ReagentUnit.New(10);
[ViewVariables]
private SolutionComponent Solution => _beakerContainer.ContainedEntity.GetComponent<SolutionComponent>();
@@ -122,22 +122,22 @@ namespace Content.Server.GameObjects.Components.Chemistry
TryClear();
break;
case UiButton.SetDispenseAmount1:
DispenseAmount = 1;
_dispenseAmount = ReagentUnit.New(1);
break;
case UiButton.SetDispenseAmount5:
DispenseAmount = 5;
_dispenseAmount = ReagentUnit.New(5);
break;
case UiButton.SetDispenseAmount10:
DispenseAmount = 10;
_dispenseAmount = ReagentUnit.New(10);
break;
case UiButton.SetDispenseAmount25:
DispenseAmount = 25;
_dispenseAmount = ReagentUnit.New(25);
break;
case UiButton.SetDispenseAmount50:
DispenseAmount = 50;
_dispenseAmount = ReagentUnit.New(50);
break;
case UiButton.SetDispenseAmount100:
DispenseAmount = 100;
_dispenseAmount = ReagentUnit.New(100);
break;
case UiButton.Dispense:
if (HasBeaker)
@@ -161,7 +161,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
private bool PlayerCanUseDispenser(IEntity playerEntity)
{
//Need player entity to check if they are still able to use the dispenser
if (playerEntity == null)
if (playerEntity == null)
return false;
//Check if player can interact in their current state
if (!ActionBlockerSystem.CanInteract(playerEntity) || !ActionBlockerSystem.CanUse(playerEntity))
@@ -182,13 +182,13 @@ namespace Content.Server.GameObjects.Components.Chemistry
var beaker = _beakerContainer.ContainedEntity;
if (beaker == null)
{
return new ReagentDispenserBoundUserInterfaceState(false, 0, 0,
"", Inventory, Owner.Name, null, DispenseAmount);
return new ReagentDispenserBoundUserInterfaceState(false, ReagentUnit.New(0), ReagentUnit.New(0),
"", Inventory, Owner.Name, null, _dispenseAmount);
}
var solution = beaker.GetComponent<SolutionComponent>();
return new ReagentDispenserBoundUserInterfaceState(true, solution.CurrentVolume, solution.MaxVolume,
beaker.Name, Inventory, Owner.Name, solution.ReagentList.ToList(), DispenseAmount);
beaker.Name, Inventory, Owner.Name, solution.ReagentList.ToList(), _dispenseAmount);
}
private void UpdateUserInterface()
@@ -207,7 +207,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
return;
var beaker = _beakerContainer.ContainedEntity;
Solution.SolutionChanged -= HandleSolutionChangedEvent;
_beakerContainer.Remove(_beakerContainer.ContainedEntity);
UpdateUserInterface();
@@ -238,7 +237,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
if (!HasBeaker) return;
var solution = _beakerContainer.ContainedEntity.GetComponent<SolutionComponent>();
solution.TryAddReagent(Inventory[dispenseIndex].ID, DispenseAmount, out _);
solution.TryAddReagent(Inventory[dispenseIndex].ID, _dispenseAmount, out _);
UpdateUserInterface();
}
@@ -304,7 +303,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
else
{
_beakerContainer.Insert(activeHandEntity);
Solution.SolutionChanged += HandleSolutionChangedEvent;
UpdateUserInterface();
}
}
@@ -317,10 +315,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
return true;
}
private void HandleSolutionChangedEvent()
{
UpdateUserInterface();
}
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs) => UpdateUserInterface();
private void ClickSound()
{
@@ -329,5 +324,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
sound.Play("/Audio/machines/machine_switch.ogg", AudioParams.Default.WithVolume(-2f));
}
}
}
}

View File

@@ -1,27 +1,30 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using Content.Server.Chemistry;
using Content.Server.GameObjects.Components.Nutrition;
using Content.Server.Chemistry;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Chemistry;
using Content.Shared.Utility;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using System.Collections.Generic;
using System.Linq;
namespace Content.Server.GameObjects.Components.Chemistry
{
/// <summary>
/// Shared ECS component that manages a liquid solution of reagents.
/// ECS component that manages a liquid solution of reagents.
/// </summary>
[RegisterComponent]
internal class SolutionComponent : Shared.GameObjects.Components.Chemistry.SolutionComponent, IExamine
internal class SolutionComponent : SharedSolutionComponent, IExamine
{
#pragma warning disable 649
[Dependency] private readonly IPrototypeManager _prototypeManager;
@@ -31,27 +34,178 @@ namespace Content.Server.GameObjects.Components.Chemistry
private IEnumerable<ReactionPrototype> _reactions;
private AudioSystem _audioSystem;
private ChemistrySystem _chemistrySystem;
private SpriteComponent _spriteComponent;
private Solution _containedSolution = new Solution();
private ReagentUnit _maxVolume;
private SolutionCaps _capabilities;
private string _fillInitState;
private int _fillInitSteps;
private string _fillPathString = "Objects/Chemistry/fillings.rsi";
private ResourcePath _fillPath;
private SpriteSpecifier _fillSprite;
/// <summary>
/// The maximum volume of the container.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public ReagentUnit MaxVolume
{
get => _maxVolume;
set => _maxVolume = value; // Note that the contents won't spill out if the capacity is reduced.
}
/// <summary>
/// The total volume of all the of the reagents in the container.
/// </summary>
[ViewVariables]
public ReagentUnit CurrentVolume => _containedSolution.TotalVolume;
/// <summary>
/// The volume without reagents remaining in the container.
/// </summary>
[ViewVariables]
public ReagentUnit EmptyVolume => MaxVolume - CurrentVolume;
/// <summary>
/// The current blended color of all the reagents in the container.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public Color SubstanceColor { get; private set; }
/// <summary>
/// The current capabilities of this container (is the top open to pour? can I inject it into another object?).
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public SolutionCaps Capabilities
{
get => _capabilities;
set => _capabilities = value;
}
[ViewVariables]
public Solution Solution
{
get => _containedSolution;
set => _containedSolution = value;
}
public IReadOnlyList<Solution.ReagentQuantity> ReagentList => _containedSolution.Contents;
/// <summary>
/// Shortcut for Capabilities PourIn flag to avoid binary operators.
/// </summary>
public bool CanPourIn => (Capabilities & SolutionCaps.PourIn) != 0;
/// <summary>
/// Shortcut for Capabilities PourOut flag to avoid binary operators.
/// </summary>
public bool CanPourOut => (Capabilities & SolutionCaps.PourOut) != 0;
/// <summary>
/// Shortcut for Capabilities Injectable flag
/// </summary>
public bool Injectable => (Capabilities & SolutionCaps.Injectable) != 0;
/// <summary>
/// Shortcut for Capabilities Injector flag
/// </summary>
public bool Injector => (Capabilities & SolutionCaps.Injector) != 0;
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _maxVolume, "maxVol", ReagentUnit.New(0));
serializer.DataField(ref _containedSolution, "contents", _containedSolution);
serializer.DataField(ref _capabilities, "caps", SolutionCaps.None);
serializer.DataField(ref _fillInitState, "fillingState", "");
serializer.DataField(ref _fillInitSteps, "fillingSteps", 7);
}
public override void Initialize()
{
base.Initialize();
_audioSystem = _entitySystemManager.GetEntitySystem<AudioSystem>();
_chemistrySystem = _entitySystemManager.GetEntitySystem<ChemistrySystem>();
_reactions = _prototypeManager.EnumeratePrototypes<ReactionPrototype>();
}
protected override void Startup()
{
base.Startup();
Init();
RecalculateColor();
if (!string.IsNullOrEmpty(_fillInitState))
{
_spriteComponent = Owner.GetComponent<SpriteComponent>();
_fillPath = new ResourcePath(_fillPathString);
_fillSprite = new SpriteSpecifier.Rsi(_fillPath, _fillInitState + (_fillInitSteps - 1));
_spriteComponent.AddLayerWithSprite(_fillSprite);
UpdateFillIcon();
}
}
public void Init()
public void RemoveAllSolution()
{
_reactions = _prototypeManager.EnumeratePrototypes<ReactionPrototype>();
_audioSystem = _entitySystemManager.GetEntitySystem<AudioSystem>();
_containedSolution.RemoveAllSolution();
OnSolutionChanged(false);
}
public bool TryRemoveReagent(string reagentId, ReagentUnit quantity)
{
if (!ContainsReagent(reagentId, out var currentQuantity)) return false;
_containedSolution.RemoveReagent(reagentId, quantity);
OnSolutionChanged(false);
return true;
}
/// <summary>
/// Initializes the SolutionComponent if it doesn't have an owner
/// Attempt to remove the specified quantity from this solution
/// </summary>
public void InitializeFromPrototype()
/// <param name="quantity">Quantity of this solution to remove</param>
/// <returns>Whether or not the solution was successfully removed</returns>
public bool TryRemoveSolution(ReagentUnit quantity)
{
// Because Initialize needs an Owner, Startup isn't called, etc.
IoCManager.InjectDependencies(this);
_reactions = _prototypeManager.EnumeratePrototypes<ReactionPrototype>();
if (CurrentVolume == 0)
return false;
_containedSolution.RemoveSolution(quantity);
OnSolutionChanged(false);
return true;
}
public Solution SplitSolution(ReagentUnit quantity)
{
var solutionSplit = _containedSolution.SplitSolution(quantity);
OnSolutionChanged(false);
return solutionSplit;
}
protected void RecalculateColor()
{
if (_containedSolution.TotalVolume == 0)
{
SubstanceColor = Color.Transparent;
return;
}
Color mixColor = default;
var runningTotalQuantity = ReagentUnit.New(0);
foreach (var reagent in _containedSolution)
{
runningTotalQuantity += reagent.Quantity;
if(!_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto))
continue;
if (mixColor == default)
mixColor = proto.SubstanceColor;
mixColor = Color.InterpolateBetween(mixColor, proto.SubstanceColor,
(1 / runningTotalQuantity.Float()) * reagent.Quantity.Float());
}
SubstanceColor = mixColor;
}
/// <summary>
@@ -105,8 +259,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
if ((handSolutionComp.Capabilities & SolutionCaps.PourOut) == 0 || (component.Capabilities & SolutionCaps.PourIn) == 0)
return;
var transferQuantity = Math.Min(component.MaxVolume - component.CurrentVolume, handSolutionComp.CurrentVolume);
transferQuantity = Math.Min(transferQuantity, 10);
var transferQuantity = ReagentUnit.Min(component.MaxVolume - component.CurrentVolume, handSolutionComp.CurrentVolume, ReagentUnit.New(10));
// nothing to transfer
if (transferQuantity <= 0)
@@ -121,6 +274,10 @@ namespace Content.Server.GameObjects.Components.Chemistry
void IExamine.Examine(FormattedMessage message)
{
message.AddText(_loc.GetString("Contains:\n"));
if (ReagentList.Count == 0)
{
message.AddText("Nothing.\n");
}
foreach (var reagent in ReagentList)
{
if (_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto))
@@ -185,8 +342,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
if ((handSolutionComp.Capabilities & SolutionCaps.PourIn) == 0 || (component.Capabilities & SolutionCaps.PourOut) == 0)
return;
var transferQuantity = Math.Min(handSolutionComp.MaxVolume - handSolutionComp.CurrentVolume, component.CurrentVolume);
transferQuantity = Math.Min(transferQuantity, 10);
var transferQuantity = ReagentUnit.Min(handSolutionComp.MaxVolume - handSolutionComp.CurrentVolume, component.CurrentVolume, ReagentUnit.New(10));
// pulling from an empty container, pointless to continue
if (transferQuantity <= 0)
@@ -202,10 +358,11 @@ namespace Content.Server.GameObjects.Components.Chemistry
bool checkForNewReaction = false;
while (true)
{
//TODO: make a hashmap at startup and then look up reagents in the contents for a reaction
//Check the solution for every reaction
foreach (var reaction in _reactions)
{
if (SolutionValidReaction(reaction, out int unitReactions))
if (SolutionValidReaction(reaction, out var unitReactions))
{
PerformReaction(reaction, unitReactions);
checkForNewReaction = true;
@@ -223,11 +380,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
}
public bool TryAddReagent(string reagentId, int quantity, out int acceptedQuantity, bool skipReactionCheck = false, bool skipColor = false)
public bool TryAddReagent(string reagentId, ReagentUnit quantity, out ReagentUnit acceptedQuantity, bool skipReactionCheck = false, bool skipColor = false)
{
if (quantity > _maxVolume - _containedSolution.TotalVolume)
var toAcceptQuantity = MaxVolume - _containedSolution.TotalVolume;
if (quantity > toAcceptQuantity)
{
acceptedQuantity = _maxVolume - _containedSolution.TotalVolume;
acceptedQuantity = toAcceptQuantity;
if (acceptedQuantity == 0) return false;
}
else
@@ -241,13 +399,13 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
if(!skipReactionCheck)
CheckForReaction();
OnSolutionChanged();
OnSolutionChanged(skipColor);
return true;
}
public bool TryAddSolution(Solution solution, bool skipReactionCheck = false, bool skipColor = false)
{
if (solution.TotalVolume > (_maxVolume - _containedSolution.TotalVolume))
if (solution.TotalVolume > (MaxVolume - _containedSolution.TotalVolume))
return false;
_containedSolution.AddSolution(solution);
@@ -256,7 +414,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
if(!skipReactionCheck)
CheckForReaction();
OnSolutionChanged();
OnSolutionChanged(skipColor);
return true;
}
@@ -267,16 +425,16 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// <param name="reaction">The reaction whose reactants will be checked for in the solution.</param>
/// <param name="unitReactions">The number of times the reaction can occur with the given solution.</param>
/// <returns></returns>
private bool SolutionValidReaction(ReactionPrototype reaction, out int unitReactions)
private bool SolutionValidReaction(ReactionPrototype reaction, out ReagentUnit unitReactions)
{
unitReactions = int.MaxValue; //Set to some impossibly large number initially
unitReactions = ReagentUnit.MaxValue; //Set to some impossibly large number initially
foreach (var reactant in reaction.Reactants)
{
if (!ContainsReagent(reactant.Key, out int reagentQuantity))
if (!ContainsReagent(reactant.Key, out ReagentUnit reagentQuantity))
{
return false;
}
int currentUnitReactions = reagentQuantity / reactant.Value.Amount;
var currentUnitReactions = reagentQuantity / reactant.Value.Amount;
if (currentUnitReactions < unitReactions)
{
unitReactions = currentUnitReactions;
@@ -299,30 +457,88 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// <param name="solution">Solution to be reacted.</param>
/// <param name="reaction">Reaction to occur.</param>
/// <param name="unitReactions">The number of times to cause this reaction.</param>
private void PerformReaction(ReactionPrototype reaction, int unitReactions)
private void PerformReaction(ReactionPrototype reaction, ReagentUnit unitReactions)
{
//Remove non-catalysts
foreach (var reactant in reaction.Reactants)
{
if (!reactant.Value.Catalyst)
{
int amountToRemove = unitReactions * reactant.Value.Amount;
var amountToRemove = unitReactions * reactant.Value.Amount;
TryRemoveReagent(reactant.Key, amountToRemove);
}
}
//Add products
foreach (var product in reaction.Products)
{
TryAddReagent(product.Key, (int)(unitReactions * product.Value), out int acceptedQuantity, true);
TryAddReagent(product.Key, product.Value * unitReactions, out var acceptedQuantity, true);
}
//Trigger reaction effects
foreach (var effect in reaction.Effects)
{
effect.React(Owner, unitReactions);
effect.React(Owner, unitReactions.Decimal());
}
//Play reaction sound client-side
_audioSystem.Play("/Audio/effects/chemistry/bubbles.ogg", Owner.Transform.GridPosition);
}
/// <summary>
/// Check if the solution contains the specified reagent.
/// </summary>
/// <param name="reagentId">The reagent to check for.</param>
/// <param name="quantity">Output the quantity of the reagent if it is contained, 0 if it isn't.</param>
/// <returns>Return true if the solution contains the reagent.</returns>
public bool ContainsReagent(string reagentId, out ReagentUnit quantity)
{
foreach (var reagent in _containedSolution.Contents)
{
if (reagent.ReagentId == reagentId)
{
quantity = reagent.Quantity;
return true;
}
}
quantity = ReagentUnit.New(0);
return false;
}
public string GetMajorReagentId()
{
if (_containedSolution.Contents.Count == 0)
{
return "";
}
var majorReagent = _containedSolution.Contents.OrderByDescending(reagent => reagent.Quantity).First();;
return majorReagent.ReagentId;
}
protected void UpdateFillIcon()
{
if (string.IsNullOrEmpty(_fillInitState)) return;
var percentage = (CurrentVolume / MaxVolume).Double();
var level = ContentHelpers.RoundToLevels(percentage * 100, 100, _fillInitSteps);
//Transformed glass uses special fancy sprites so we don't bother
if (level == 0 || Owner.TryGetComponent<TransformableContainerComponent>(out var transformableContainerComponent)
&& transformableContainerComponent.Transformed)
{
_spriteComponent.LayerSetColor(1, Color.Transparent);
return;
}
_fillSprite = new SpriteSpecifier.Rsi(_fillPath, _fillInitState+level);
_spriteComponent.LayerSetSprite(1, _fillSprite);
_spriteComponent.LayerSetColor(1,SubstanceColor);
}
protected virtual void OnSolutionChanged(bool skipColor)
{
if (!skipColor)
RecalculateColor();
UpdateFillIcon();
_chemistrySystem.HandleSolutionChange(Owner);
}
}
}

View File

@@ -0,0 +1,90 @@
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Chemistry;
using ICSharpCode.SharpZipLib.Zip.Compression;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.GameObjects.Components.Chemistry
{
[RegisterComponent]
public class TransformableContainerComponent : Component, ISolutionChange
{
#pragma warning disable 649
[Dependency] private readonly IPrototypeManager _prototypeManager;
#pragma warning restore 649
public override string Name => "TransformableContainer";
private bool _transformed = false;
public bool Transformed { get => _transformed; }
private SpriteSpecifier _initialSprite;
private string _initialName;
private string _initialDescription;
private SpriteComponent _sprite;
private ReagentPrototype _currentReagent;
public override void Initialize()
{
base.Initialize();
_sprite = Owner.GetComponent<SpriteComponent>();
_initialSprite = new SpriteSpecifier.Rsi(new ResourcePath(_sprite.BaseRSIPath), "icon");
_initialName = Owner.Name;
_initialDescription = Owner.Description;
}
protected override void Startup()
{
base.Startup();
Owner.GetComponent<SolutionComponent>().Capabilities |= SolutionCaps.FitsInDispenser;;
}
public void CancelTransformation()
{
_currentReagent = null;
_transformed = false;
_sprite.LayerSetSprite(0, _initialSprite);
Owner.Name = _initialName;
Owner.Description = _initialDescription;
}
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs)
{
var solution = eventArgs.Owner.GetComponent<SolutionComponent>();
//Transform container into initial state when emptied
if (_currentReagent != null && solution.ReagentList.Count == 0)
{
CancelTransformation();
}
//the biggest reagent in the solution decides the appearance
var reagentId = solution.GetMajorReagentId();
//If biggest reagent didn't changed - don't change anything at all
if (_currentReagent != null && _currentReagent.ID == reagentId)
{
return;
}
//Only reagents with spritePath property can change appearance of transformable containers!
if (!string.IsNullOrWhiteSpace(reagentId) &&
_prototypeManager.TryIndex(reagentId, out ReagentPrototype proto) &&
!string.IsNullOrWhiteSpace(proto.SpriteReplacementPath))
{
var spriteSpec = new SpriteSpecifier.Rsi(new ResourcePath("Objects/Drinks/" + proto.SpriteReplacementPath),"icon");
_sprite.LayerSetSprite(0, spriteSpec);
Owner.Name = proto.Name + " glass";
Owner.Description = proto.Description;
_currentReagent = proto;
_transformed = true;
}
}
}
}

View File

@@ -0,0 +1,76 @@
using Content.Server.GameObjects.Components.Power;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces.GameTicking;
using Content.Shared.GameObjects.Components.Command;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.Interfaces.GameObjects;
using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.GameObjects.Components.Command
{
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
public class CommunicationsConsoleComponent : SharedCommunicationsConsoleComponent, IActivate
{
#pragma warning disable 649
[Dependency] private IEntitySystemManager _entitySystemManager;
#pragma warning restore 649
private BoundUserInterface _userInterface;
private PowerDeviceComponent _powerDevice;
private bool Powered => _powerDevice.Powered;
private RoundEndSystem RoundEndSystem => _entitySystemManager.GetEntitySystem<RoundEndSystem>();
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(CommunicationsConsoleUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
RoundEndSystem.OnRoundEndCountdownStarted += UpdateBoundInterface;
RoundEndSystem.OnRoundEndCountdownCancelled += UpdateBoundInterface;
RoundEndSystem.OnRoundEndCountdownFinished += UpdateBoundInterface;
}
private void UpdateBoundInterface()
{
_userInterface.SetState(new CommunicationsConsoleInterfaceState(RoundEndSystem.ExpectedCountdownEnd));
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage obj)
{
switch (obj.Message)
{
case CommunicationsConsoleCallEmergencyShuttleMessage _:
RoundEndSystem.RequestRoundEnd();
break;
case CommunicationsConsoleRecallEmergencyShuttleMessage _:
RoundEndSystem.CancelRoundEndCountdown();
break;
}
}
public void OpenUserInterface(IPlayerSession session)
{
_userInterface.Open(session);
}
void IActivate.Activate(ActivateEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
return;
if (!Powered)
{
return;
}
OpenUserInterface(actor.playerSession);
}
}
}

View File

@@ -5,6 +5,7 @@ using Content.Server.GameObjects.Components.Stack;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Shared.Construction;
using Content.Shared.GameObjects.Components;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.GameObjects;
@@ -14,7 +15,6 @@ using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.ViewVariables;
using static Content.Shared.Construction.ConstructionStepMaterial;
using static Content.Shared.Construction.ConstructionStepTool;
@@ -114,7 +114,7 @@ namespace Content.Server.GameObjects.Components.Construction
{
Sprite.AddLayerWithSprite(prototype.Icon);
}
}

View File

@@ -75,7 +75,13 @@ namespace Content.Server.GameObjects
{
if (damageType == DamageType.Total)
{
throw new ArgumentException("Cannot take damage for DamageType.Total");
foreach (DamageType e in Enum.GetValues(typeof(DamageType)))
{
if (e == damageType) continue;
TakeDamage(e, amount, source, sourceMob);
}
return;
}
InitializeDamageType(damageType);

View File

@@ -38,5 +38,6 @@ namespace Content.Server.GameObjects.Components.Markers
Unset = 0,
LateJoin,
Job,
Observer,
}
}

View File

@@ -34,29 +34,24 @@ namespace Content.Server.GameObjects.Components.Metabolism
/// Max volume of internal solution storage
/// </summary>
[ViewVariables]
private int _initialMaxVolume;
private ReagentUnit _initialMaxVolume;
/// <summary>
/// Empty volume of internal solution
/// </summary>
public int EmptyVolume => _internalSolution.EmptyVolume;
public ReagentUnit EmptyVolume => _internalSolution.EmptyVolume;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _initialMaxVolume, "maxVolume", 250);
serializer.DataField(ref _initialMaxVolume, "maxVolume", ReagentUnit.New(250));
}
public override void Initialize()
protected override void Startup()
{
base.Initialize();
//Create and setup internal solution storage
_internalSolution = new SolutionComponent();
_internalSolution.InitializeFromPrototype();
_internalSolution.Init();
base.Startup();
_internalSolution = Owner.GetComponent<SolutionComponent>();
_internalSolution.MaxVolume = _initialMaxVolume;
_internalSolution.Owner = Owner; //Manually set owner to avoid crash when VV'ing this
}
/// <summary>
@@ -98,7 +93,7 @@ namespace Content.Server.GameObjects.Components.Metabolism
//Run metabolism code for each reagent
foreach (var metabolizable in proto.Metabolism)
{
int reagentDelta = metabolizable.Metabolize(Owner, reagent.ReagentId, tickTime);
var reagentDelta = metabolizable.Metabolize(Owner, reagent.ReagentId, tickTime);
_internalSolution.TryRemoveReagent(reagent.ReagentId, reagentDelta);
}
}

View File

@@ -5,6 +5,7 @@ using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Nutrition;
using Content.Shared.Interfaces;
using Content.Shared.Maths;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
@@ -32,19 +33,16 @@ namespace Content.Server.GameObjects.Components.Nutrition
[ViewVariables]
private string _finishPrototype;
public int TransferAmount => _transferAmount;
public ReagentUnit TransferAmount => _transferAmount;
[ViewVariables]
private int _transferAmount = 2;
private ReagentUnit _transferAmount = ReagentUnit.New(2);
public int MaxVolume
public ReagentUnit MaxVolume
{
get => _contents.MaxVolume;
set => _contents.MaxVolume = value;
}
private Solution _initialContents; // This is just for loading from yaml
private int _maxVolume;
private bool _despawnOnFinish;
private bool _drinking;
@@ -56,60 +54,28 @@ namespace Content.Server.GameObjects.Components.Nutrition
{
return 0;
}
return Math.Max(1, _contents.CurrentVolume / _transferAmount);
return Math.Max(1, (int)Math.Ceiling((_contents.CurrentVolume / _transferAmount).Float()));
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _initialContents, "contents", null);
serializer.DataField(ref _maxVolume, "max_volume", 0);
serializer.DataField(ref _useSound, "use_sound", "/Audio/items/drink.ogg");
// E.g. cola can when done or clear bottle, whatever
// Currently this will enforce it has the same volume but this may change.
serializer.DataField(ref _despawnOnFinish, "despawn_empty", true);
// Currently this will enforce it has the same volume but this may change. - TODO: this should be implemented in a separate component
serializer.DataField(ref _despawnOnFinish, "despawn_empty", false);
serializer.DataField(ref _finishPrototype, "spawn_on_finish", null);
}
public override void Initialize()
{
base.Initialize();
if (_contents == null)
{
if (Owner.TryGetComponent(out SolutionComponent solutionComponent))
{
_contents = solutionComponent;
}
else
{
_contents = Owner.AddComponent<SolutionComponent>();
//Ensure SolutionComponent supports click transferring if custom one not set
_contents.Capabilities = SolutionCaps.PourIn
| SolutionCaps.PourOut
| SolutionCaps.Injectable;
var pourable = Owner.AddComponent<PourableComponent>();
pourable.TransferAmount = 5;
}
}
_drinking = false;
if (_maxVolume != 0)
_contents.MaxVolume = _maxVolume;
else
_contents.MaxVolume = _initialContents.TotalVolume;
_contents.SolutionChanged += HandleSolutionChangedEvent;
}
protected override void Startup()
{
base.Startup();
if (_initialContents != null)
{
_contents.TryAddSolution(_initialContents, true, true);
}
_initialContents = null;
_contents = Owner.GetComponent<SolutionComponent>();
_contents.Capabilities = SolutionCaps.PourIn
| SolutionCaps.PourOut
| SolutionCaps.Injectable;
_drinking = false;
Owner.TryGetComponent(out AppearanceComponent appearance);
_appearanceComponent = appearance;
_appearanceComponent?.SetData(SharedFoodComponent.FoodVisuals.MaxUses, MaxVolume);
@@ -149,7 +115,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
if (user.TryGetComponent(out StomachComponent stomachComponent))
{
_drinking = true;
var transferAmount = Math.Min(_transferAmount, _contents.CurrentVolume);
var transferAmount = ReagentUnit.Min(_transferAmount, _contents.CurrentVolume);
var split = _contents.SplitSolution(transferAmount);
if (stomachComponent.TryTransferSolution(split))
{
@@ -167,54 +133,6 @@ namespace Content.Server.GameObjects.Components.Nutrition
}
_drinking = false;
}
Finish(user);
}
/// <summary>
/// Trigger finish behavior in the drink if applicable.
/// Depending on the drink this will either delete it,
/// or convert it to another entity, like an empty variant.
/// </summary>
/// <param name="user">The entity that is using the drink</param>
public void Finish(IEntity user)
{
// Drink containers are mostly transient.
if (_drinking || !_despawnOnFinish || UsesLeft() > 0)
return;
var gridPos = Owner.Transform.GridPosition;
_contents.SolutionChanged -= HandleSolutionChangedEvent;
Owner.Delete();
if (_finishPrototype == null || user == null)
return;
var finisher = Owner.EntityManager.SpawnEntity(_finishPrototype, Owner.Transform.GridPosition);
if (user.TryGetComponent(out HandsComponent handsComponent) && finisher.TryGetComponent(out ItemComponent itemComponent))
{
if (handsComponent.CanPutInHand(itemComponent))
{
handsComponent.PutInHand(itemComponent);
return;
}
}
finisher.Transform.GridPosition = gridPos;
if (finisher.TryGetComponent(out DrinkComponent drinkComponent))
{
drinkComponent.MaxVolume = MaxVolume;
}
}
/// <summary>
/// Updates drink state when the solution is changed by something other
/// than this component. Without this some drinks won't properly delete
/// themselves without additional clicks/uses after them being emptied.
/// </summary>
private void HandleSolutionChangedEvent()
{
Finish(null);
}
}
}

View File

@@ -33,7 +33,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
[ViewVariables]
private SolutionComponent _contents;
[ViewVariables]
private int _transferAmount;
private ReagentUnit _transferAmount;
private Solution _initialContents; // This is just for loading from yaml
@@ -44,7 +44,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
serializer.DataField(ref _initialContents, "contents", null);
serializer.DataField(ref _useSound, "use_sound", "/Audio/items/eatfood.ogg");
// Default is transfer 30 units
serializer.DataField(ref _transferAmount, "transfer_amount", 5);
serializer.DataField(ref _transferAmount, "transfer_amount", ReagentUnit.New(5));
// E.g. empty chip packet when done
serializer.DataField(ref _finishPrototype, "spawn_on_finish", null);
}
@@ -78,7 +78,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
_initialContents = null;
if (_contents.CurrentVolume == 0)
{
_contents.TryAddReagent("chem.Nutriment", 5, out _);
_contents.TryAddReagent("chem.Nutriment", ReagentUnit.New(5), out _);
}
Owner.TryGetComponent(out AppearanceComponent appearance);
_appearanceComponent = appearance;
@@ -99,7 +99,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
{
return 0;
}
return Math.Max(1, _contents.CurrentVolume / _transferAmount);
return Math.Max(1, (int)Math.Ceiling((_contents.CurrentVolume / _transferAmount).Float()));
}
bool IUse.UseEntity(UseEntityEventArgs eventArgs)
@@ -130,7 +130,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
// TODO: Add putting food back in boxes here?
if (user.TryGetComponent(out StomachComponent stomachComponent))
{
var transferAmount = Math.Min(_transferAmount, _contents.CurrentVolume);
var transferAmount = ReagentUnit.Min(_transferAmount, _contents.CurrentVolume);
var split = _contents.SplitSolution(transferAmount);
if (stomachComponent.TryTransferSolution(split))
{

View File

@@ -27,7 +27,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
/// <summary>
/// Max volume of internal solution storage
/// </summary>
public int MaxVolume
public ReagentUnit MaxVolume
{
get => _stomachContents.MaxVolume;
set => _stomachContents.MaxVolume = value;
@@ -43,7 +43,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
/// Initial internal solution storage volume
/// </summary>
[ViewVariables]
private int _initialMaxVolume;
private ReagentUnit _initialMaxVolume;
/// <summary>
/// Time in seconds between reagents being ingested and them being transferred to <see cref="BloodstreamComponent"/>
@@ -64,26 +64,19 @@ namespace Content.Server.GameObjects.Components.Nutrition
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _initialMaxVolume, "maxVolume", 100);
serializer.DataField(ref _initialMaxVolume, "maxVolume", ReagentUnit.New(100));
serializer.DataField(ref _digestionDelay, "digestionDelay", 20);
}
public override void Initialize()
protected override void Startup()
{
base.Initialize();
//Doesn't use Owner.AddComponent<>() to avoid cross-contamination (e.g. with blood or whatever they holds other solutions)
_stomachContents = new SolutionComponent();
_stomachContents.InitializeFromPrototype();
_stomachContents = Owner.GetComponent<SolutionComponent>();
_stomachContents.MaxVolume = _initialMaxVolume;
_stomachContents.Owner = Owner; //Manually set owner to avoid crash when VV'ing this
//Ensure bloodstream in present
if (!Owner.TryGetComponent<BloodstreamComponent>(out _bloodstream))
{
Logger.Warning(_localizationManager.GetString(
"StomachComponent entity does not have a BloodstreamComponent, which is required for it to function. Owner entity name: {0}",
Owner.Name));
"StomachComponent entity does not have a BloodstreamComponent, which is required for it to function. Owner entity name: {0}",
Owner.Name));
}
}
@@ -141,10 +134,10 @@ namespace Content.Server.GameObjects.Components.Nutrition
private class ReagentDelta
{
public readonly string ReagentId;
public readonly int Quantity;
public readonly ReagentUnit Quantity;
public float Lifetime { get; private set; }
public ReagentDelta(string reagentId, int quantity)
public ReagentDelta(string reagentId, ReagentUnit quantity)
{
ReagentId = reagentId;
Quantity = quantity;

View File

@@ -0,0 +1,76 @@
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Players;
using Content.Shared.GameObjects.Components.Observer;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.ViewVariables;
using Timer = Robust.Shared.Timers.Timer;
namespace Content.Server.GameObjects.Components.Observer
{
[RegisterComponent]
public class GhostComponent : SharedGhostComponent, IActionBlocker
{
private bool _canReturnToBody = true;
[ViewVariables(VVAccess.ReadWrite)]
public bool CanReturnToBody
{
get => _canReturnToBody;
set
{
_canReturnToBody = value;
Dirty();
}
}
public override void Initialize()
{
base.Initialize();
Owner.EnsureComponent<VisibilityComponent>().Layer = (int)VisibilityFlags.Ghost;
}
public override ComponentState GetComponentState() => new GhostComponentState(CanReturnToBody);
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null,
IComponent component = null)
{
base.HandleMessage(message, netChannel, component);
switch (message)
{
case ReturnToBodyComponentMessage reenter:
if (!Owner.TryGetComponent(out IActorComponent actor) || !CanReturnToBody) break;
if (netChannel == null || netChannel == actor.playerSession.ConnectedClient)
{
actor.playerSession.ContentData().Mind.UnVisit();
}
break;
case PlayerAttachedMsg msg:
msg.NewPlayer.VisibilityMask |= (int)VisibilityFlags.Ghost;
Dirty();
break;
case PlayerDetachedMsg msg:
msg.OldPlayer.VisibilityMask &= ~(int)VisibilityFlags.Ghost;
Timer.Spawn(100, Owner.Delete);
break;
default:
break;
}
}
public bool CanInteract() => false;
public bool CanUse() => false;
public bool CanThrow() => false;
public bool CanDrop() => false;
public bool CanPickup() => false;
public bool CanEmote() => false;
public bool CanAttack() => false;
}
}

View File

@@ -1,7 +1,15 @@
using System;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Audio;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -24,9 +32,14 @@ namespace Content.Server.GameObjects.Components.Power
/// Component that represents a light bulb. Can be broken, or burned, which turns them mostly useless.
/// </summary>
[RegisterComponent]
public class LightBulbComponent : Component
public class LightBulbComponent : Component, ILand
{
#pragma warning disable 649
[Dependency] private readonly IPrototypeManager _prototypeManager;
[Dependency] private readonly IRobustRandom _random;
#pragma warning restore 649
/// <summary>
/// Invoked whenever the state of the light bulb changes.
/// </summary>
@@ -104,5 +117,18 @@ namespace Content.Server.GameObjects.Components.Power
base.Initialize();
UpdateColor();
}
public void Land(LandEventArgs eventArgs)
{
if (State == LightBulbState.Broken)
return;
var soundCollection = _prototypeManager.Index<SoundCollectionPrototype>("glassbreak");
var file = _random.Pick(soundCollection.PickFiles);
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AudioSystem>().Play(file, Owner);
State = LightBulbState.Broken;
}
}
}

View File

@@ -3,11 +3,9 @@ using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components;
using Content.Shared.Interfaces;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Reflection;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Serialization;
using Robust.Shared.Timers;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
@@ -23,34 +21,19 @@ namespace Content.Server.GameObjects.Components.Stack
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager;
#pragma warning restore 649
private const string SerializationCache = "stack";
private int _count = 50;
private int _maxCount = 50;
private bool _throwIndividually = false;
[ViewVariables(VVAccess.ReadWrite)]
public int Count
public override int Count
{
get => _count;
get => base.Count;
set
{
_count = value;
if (_count <= 0)
base.Count = value;
if (Count <= 0)
{
Owner.Delete();
}
Dirty();
}
}
[ViewVariables]
public int MaxCount
{
get => _maxCount;
private set
{
_maxCount = value;
Dirty();
}
}
@@ -65,12 +48,6 @@ namespace Content.Server.GameObjects.Components.Stack
}
}
[ViewVariables]
public int AvailableSpace => MaxCount - Count;
[ViewVariables]
public object StackType { get; private set; }
public void Add(int amount)
{
Count += amount;
@@ -91,42 +68,6 @@ namespace Content.Server.GameObjects.Components.Stack
return false;
}
public override void ExposeData(ObjectSerializer serializer)
{
serializer.DataFieldCached(ref _maxCount, "max", 50);
serializer.DataFieldCached(ref _count, "count", MaxCount);
if (!serializer.Reading)
{
return;
}
if (serializer.TryGetCacheData(SerializationCache, out object stackType))
{
StackType = stackType;
return;
}
if (serializer.TryReadDataFieldCached("stacktype", out string raw))
{
var refl = IoCManager.Resolve<IReflectionManager>();
if (refl.TryParseEnumReference(raw, out var @enum))
{
stackType = @enum;
}
else
{
stackType = raw;
}
}
else
{
stackType = Owner.Prototype.ID;
}
serializer.SetCacheData(SerializationCache, stackType);
StackType = stackType;
}
public bool AttackBy(AttackByEventArgs eventArgs)
{
if (eventArgs.AttackWith.TryGetComponent<StackComponent>(out var stack))
@@ -175,20 +116,5 @@ namespace Content.Server.GameObjects.Components.Stack
"There is [color=lightgray]1[/color] thing in the stack",
"There are [color=lightgray]{0}[/color] things in the stack.", Count, Count));
}
public override ComponentState GetComponentState()
{
return new StackComponentState(Count, MaxCount);
}
}
public enum StackType
{
Metal,
Glass,
Cable,
Ointment,
Brutepack,
FloorTileSteel
}
}

View File

@@ -5,23 +5,23 @@ namespace Content.Server.GameObjects.EntitySystems
{
public interface IActionBlocker
{
bool CanMove();
bool CanMove() => true;
bool CanInteract();
bool CanInteract() => true;
bool CanUse();
bool CanUse() => true;
bool CanThrow();
bool CanThrow() => true;
bool CanSpeak();
bool CanSpeak() => true;
bool CanDrop();
bool CanDrop() => true;
bool CanPickup();
bool CanPickup() => true;
bool CanEmote();
bool CanEmote() => true;
bool CanAttack();
bool CanAttack() => true;
}
public class ActionBlockerSystem : EntitySystem

View File

@@ -0,0 +1,42 @@
using System;
using System.Linq;
using JetBrains.Annotations;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.GameObjects.EntitySystems
{
/// <summary>
/// This interface gives components behavior on whether entities solution (implying SolutionComponent is in place) is changed
/// </summary>
public interface ISolutionChange
{
/// <summary>
/// Called when solution is mixed with some other solution, or when some part of the solution is removed
/// </summary>
void SolutionChanged(SolutionChangeEventArgs eventArgs);
}
public class SolutionChangeEventArgs : EventArgs
{
public IEntity Owner { get; set; }
}
[UsedImplicitly]
public class ChemistrySystem : EntitySystem
{
public void HandleSolutionChange(IEntity owner)
{
var eventArgs = new SolutionChangeEventArgs
{
Owner = owner,
};
var solutionChangeArgs = owner.GetAllComponents<ISolutionChange>().ToList();
foreach (var solutionChangeArg in solutionChangeArgs)
{
solutionChangeArg.SolutionChanged(eventArgs);
}
}
}
}

View File

@@ -3,12 +3,14 @@ using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.Components.Movement;
using Content.Server.GameObjects.Components.Sound;
using Content.Server.Interfaces.GameObjects.Components.Movement;
using Content.Server.Observer;
using Content.Shared.Audio;
using Content.Shared.GameObjects.Components.Inventory;
using Content.Shared.Maps;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.GameObjects;
using Robust.Server.Interfaces.Player;
using Robust.Server.Interfaces.Timing;
using Robust.Shared.Configuration;
@@ -25,6 +27,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
@@ -138,6 +141,7 @@ namespace Content.Server.GameObjects.EntitySystems
{
if (physics.LinearVelocity != Vector2.Zero)
physics.LinearVelocity = Vector2.Zero;
}
else
{
@@ -185,6 +189,11 @@ namespace Content.Server.GameObjects.EntitySystems
if (!TryGetAttachedComponent(session as IPlayerSession, out IMoverComponent moverComp))
return;
var owner = (session as IPlayerSession)?.AttachedEntity;
if (owner != null && owner.TryGetComponent(out SpeciesComponent species) && species.CurrentDamageState is DeadState)
new Ghost().Execute(null, (IPlayerSession)session, null);
moverComp.SetVelocityDirection(dir, state);
}

View File

@@ -0,0 +1,65 @@
using System;
using System.Threading;
using Content.Server.Interfaces.GameTicking;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Timer = Robust.Shared.Timers.Timer;
namespace Content.Server.GameObjects.EntitySystems
{
public class RoundEndSystem : EntitySystem
{
#pragma warning disable 649
[Dependency] private IGameTicker _gameTicker;
[Dependency] private IGameTiming _gameTiming;
#pragma warning restore 649
private CancellationTokenSource _roundEndCancellationTokenSource = new CancellationTokenSource();
public bool IsRoundEndCountdownStarted { get; private set; }
public TimeSpan RoundEndCountdownTime { get; set; } = TimeSpan.FromMinutes(4);
public TimeSpan? ExpectedCountdownEnd = null;
public delegate void RoundEndCountdownStarted();
public event RoundEndCountdownStarted OnRoundEndCountdownStarted;
public delegate void RoundEndCountdownCancelled();
public event RoundEndCountdownCancelled OnRoundEndCountdownCancelled;
public delegate void RoundEndCountdownFinished();
public event RoundEndCountdownFinished OnRoundEndCountdownFinished;
public void RequestRoundEnd()
{
if (IsRoundEndCountdownStarted)
return;
IsRoundEndCountdownStarted = true;
ExpectedCountdownEnd = _gameTiming.CurTime + RoundEndCountdownTime;
Timer.Spawn(RoundEndCountdownTime, EndRound, _roundEndCancellationTokenSource.Token);
OnRoundEndCountdownStarted?.Invoke();
}
public void CancelRoundEndCountdown()
{
if (!IsRoundEndCountdownStarted)
return;
IsRoundEndCountdownStarted = false;
_roundEndCancellationTokenSource.Cancel();
_roundEndCancellationTokenSource = new CancellationTokenSource();
ExpectedCountdownEnd = null;
OnRoundEndCountdownCancelled?.Invoke();
}
private void EndRound()
{
OnRoundEndCountdownFinished?.Invoke();
_gameTicker.EndRound();
}
}
}

View File

@@ -0,0 +1,10 @@
using System;
namespace Content.Server.GameObjects
{
[Flags]
public enum VisibilityFlags
{
Ghost = 2,
}
}

View File

@@ -13,6 +13,7 @@ using Content.Server.Mobs;
using Content.Server.Mobs.Roles;
using Content.Server.Players;
using Content.Shared;
using Content.Shared.Chat;
using Content.Shared.Jobs;
using Content.Shared.Preferences;
using Robust.Server.Interfaces.Maps;
@@ -124,6 +125,8 @@ namespace Content.Server.GameTicking
{
Logger.InfoS("ticker", "Restarting round!");
SendServerMessage("Restarting round...");
RunLevel = GameRunLevel.PreRoundLobby;
_resettingCleanup();
_preRoundSetup();
@@ -148,6 +151,8 @@ namespace Content.Server.GameTicking
DebugTools.Assert(RunLevel == GameRunLevel.PreRoundLobby);
Logger.InfoS("ticker", "Starting round!");
SendServerMessage("The round is starting now...");
RunLevel = GameRunLevel.InRound;
var preset = MakeGamePreset();
@@ -192,6 +197,14 @@ namespace Content.Server.GameTicking
_sendStatusToAll();
}
private void SendServerMessage(string message)
{
var msg = _netManager.CreateNetMessage<MsgChatMessage>();
msg.Channel = ChatChannel.Server;
msg.Message = message;
IoCManager.Resolve<IServerNetManager>().ServerSendToAll(msg);
}
private HumanoidCharacterProfile GetPlayerProfile(IPlayerSession p) =>
(HumanoidCharacterProfile) _prefsManager.GetPreferences(p.SessionId.Username).SelectedCharacter;
@@ -304,7 +317,7 @@ namespace Content.Server.GameTicking
private IEntity _spawnPlayerMob(Job job, bool lateJoin = true)
{
GridCoordinates coordinates = lateJoin ? _getLateJoinSpawnPoint() : _getJobSpawnPoint(job.Prototype.ID);
GridCoordinates coordinates = lateJoin ? GetLateJoinSpawnPoint() : GetJobSpawnPoint(job.Prototype.ID);
var entity = _entityManager.SpawnEntity(PlayerPrototypeName, coordinates);
if (entity.TryGetComponent(out InventoryComponent inventory))
{
@@ -330,11 +343,11 @@ namespace Content.Server.GameTicking
private IEntity _spawnObserverMob()
{
GridCoordinates coordinates = _getLateJoinSpawnPoint();
var coordinates = GetObserverSpawnPoint();
return _entityManager.SpawnEntity(ObserverPrototypeName, coordinates);
}
private GridCoordinates _getLateJoinSpawnPoint()
public GridCoordinates GetLateJoinSpawnPoint()
{
var location = _spawnPoint;
@@ -350,7 +363,7 @@ namespace Content.Server.GameTicking
return location;
}
private GridCoordinates _getJobSpawnPoint(string jobId)
public GridCoordinates GetJobSpawnPoint(string jobId)
{
var location = _spawnPoint;
@@ -367,6 +380,23 @@ namespace Content.Server.GameTicking
return location;
}
public GridCoordinates GetObserverSpawnPoint()
{
var location = _spawnPoint;
var possiblePoints = new List<GridCoordinates>();
foreach (var entity in _entityManager.GetEntities(new TypeEntityQuery(typeof(SpawnPointComponent))))
{
var point = entity.GetComponent<SpawnPointComponent>();
if (point.SpawnType == SpawnPointType.Observer)
possiblePoints.Add(entity.Transform.GridPosition);
}
if (possiblePoints.Count != 0) location = _robustRandom.Pick(possiblePoints);
return location;
}
/// <summary>
/// Cleanup that has to run to clear up anything from the previous round.
/// Stuff like wiping the previous map clean.

View File

@@ -18,6 +18,7 @@ namespace Content.Server.Interfaces.Chat
void EntityMe(IEntity source, string action);
void SendOOC(IPlayerSession player, string message);
void SendDeadChat(IPlayerSession player, string message);
void SendHookOOC(string sender, string message);
}

View File

@@ -8,6 +8,6 @@ namespace Content.Shared.Interfaces
/// </summary>
public interface IReactionEffect : IExposeData
{
void React(IEntity solutionEntity, int intensity);
void React(IEntity solutionEntity, decimal intensity);
}
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Content.Server.GameTicking;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Map;
using Robust.Shared.Timing;
namespace Content.Server.Interfaces.GameTicking
@@ -27,6 +28,10 @@ namespace Content.Server.Interfaces.GameTicking
void MakeJoinGame(IPlayerSession player);
void ToggleReady(IPlayerSession player, bool ready);
GridCoordinates GetLateJoinSpawnPoint();
GridCoordinates GetJobSpawnPoint(string jobId);
GridCoordinates GetObserverSpawnPoint();
// GameRule system.
T AddGameRule<T>() where T : GameRule, new();
void RemoveGameRule(GameRule rule);

View File

@@ -0,0 +1,74 @@
using Content.Server.GameObjects;
using Content.Server.GameObjects.Components.Observer;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces.GameTicking;
using Content.Server.Players;
using Content.Shared.GameObjects;
using Robust.Server.Interfaces.Console;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
namespace Content.Server.Observer
{
public class Ghost : IClientCommand
{
public string Command => "ghost";
public string Description => "Give up on life and become a ghost.";
public string Help => "ghost";
public void Execute(IConsoleShell shell, IPlayerSession player, string[] args)
{
if (player == null)
{
shell.SendText((IPlayerSession) null, "Nah");
return;
}
var mind = player.ContentData().Mind;
var canReturn = player.AttachedEntity != null;
var name = player.AttachedEntity?.Name ?? player.Name;
if (player.AttachedEntity != null && player.AttachedEntity.HasComponent<GhostComponent>())
return;
if (mind.VisitingEntity != null)
{
mind.UnVisit();
}
var position = player.AttachedEntity?.Transform.GridPosition ?? IoCManager.Resolve<IGameTicker>().GetObserverSpawnPoint();
if (canReturn && player.AttachedEntity.TryGetComponent(out SpeciesComponent species))
{
switch (species.CurrentDamageState)
{
case DeadState _:
canReturn = true;
break;
case CriticalState _:
canReturn = true;
if (!player.AttachedEntity.TryGetComponent(out DamageableComponent damageable)) break;
damageable.TakeDamage(DamageType.Total, 100); // TODO: Use airloss/oxyloss instead
break;
default:
canReturn = false;
break;
}
}
var entityManager = IoCManager.Resolve<IEntityManager>();
var ghost = entityManager.SpawnEntity("MobObserver", position);
ghost.Name = name;
var ghostComponent = ghost.GetComponent<GhostComponent>();
ghostComponent.CanReturnToBody = canReturn;
if(canReturn)
mind.Visit(ghost);
else
mind.TransferTo(ghost);
}
}
}

View File

@@ -1,4 +1,4 @@
using Content.Server.Cargo;
using Content.Server.Cargo;
using Content.Server.Chat;
using Content.Server.GameTicking;
using Content.Server.Interfaces;
@@ -7,7 +7,9 @@ using Content.Server.Interfaces.GameTicking;
using Content.Server.Preferences;
using Content.Server.Sandbox;
using Content.Server.Utility;
using Content.Shared.Chemistry;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.Chemistry;
using Robust.Shared.IoC;
namespace Content.Server

View File

@@ -6,7 +6,7 @@ namespace Content.Shared.Chat
/// Represents chat channels that the player can filter chat tabs by.
/// </summary>
[Flags]
public enum ChatChannel : byte
public enum ChatChannel : short
{
None = 0,
@@ -46,9 +46,14 @@ namespace Content.Shared.Chat
/// </summary>
Emotes = 64,
/// <summary>
/// Deadchat
/// </summary>
Dead = 128,
/// <summary>
/// Unspecified.
/// </summary>
Unspecified = 128,
Unspecified = 256,
}
}

View File

@@ -35,7 +35,7 @@ namespace Content.Shared.Chat
/// <summary>
/// The sending entity.
/// Only applies to <see cref="ChatChannel.Local"/> and <see cref="ChatChannel.Emotes"/>.
/// Only applies to <see cref="ChatChannel.Local"/>, <see cref="ChatChannel.Dead"/> and <see cref="ChatChannel.Emotes"/>.
/// </summary>
public EntityUid SenderEntity { get; set; }
@@ -48,6 +48,7 @@ namespace Content.Shared.Chat
switch (Channel)
{
case ChatChannel.Local:
case ChatChannel.Dead:
case ChatChannel.Emotes:
SenderEntity = buffer.ReadEntityUid();
break;
@@ -63,6 +64,7 @@ namespace Content.Shared.Chat
switch (Channel)
{
case ChatChannel.Local:
case ChatChannel.Dead:
case ChatChannel.Emotes:
buffer.Write(SenderEntity);
break;

View File

@@ -1,7 +1,7 @@
using System;
using Content.Shared.Interfaces.Chemistry;
using Content.Shared.Interfaces.Chemistry;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
namespace Content.Shared.Chemistry
@@ -10,18 +10,17 @@ namespace Content.Shared.Chemistry
class DefaultMetabolizable : IMetabolizable
{
//Rate of metabolism in units / second
private int _metabolismRate = 1;
public int MetabolismRate => _metabolismRate;
private decimal _metabolismRate = 1;
public decimal MetabolismRate => _metabolismRate;
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _metabolismRate, "rate", 1);
}
int IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime)
ReagentUnit IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime)
{
int metabolismAmount = (int)Math.Round(MetabolismRate * tickTime);
return metabolismAmount;
return ReagentUnit.New(MetabolismRate * (decimal)tickTime);
}
}
}

View File

@@ -21,6 +21,7 @@ namespace Content.Shared.Chemistry
private string _description;
private Color _substanceColor;
private List<IMetabolizable> _metabolism;
private string _spritePath;
public string ID => _id;
public string Name => _name;
@@ -29,6 +30,8 @@ namespace Content.Shared.Chemistry
//List of metabolism effects this reagent has, should really only be used server-side.
public List<IMetabolizable> Metabolism => _metabolism;
public string SpriteReplacementPath => _spritePath;
public ReagentPrototype()
{
IoCManager.InjectDependencies(this);
@@ -42,6 +45,7 @@ namespace Content.Shared.Chemistry
serializer.DataField(ref _name, "name", string.Empty);
serializer.DataField(ref _description, "desc", string.Empty);
serializer.DataField(ref _substanceColor, "color", Color.White);
serializer.DataField(ref _spritePath, "spritePath", string.Empty);
if (_moduleManager.IsServerModule)
serializer.DataField(ref _metabolism, "metabolism", new List<IMetabolizable> {new DefaultMetabolizable()});

View File

@@ -0,0 +1,225 @@
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Serialization;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
namespace Content.Shared.Chemistry
{
[Serializable]
public struct ReagentUnit : ISelfSerialize, IComparable<ReagentUnit>, IEquatable<ReagentUnit>
{
private int _value;
private static readonly int Shift = 2;
public static ReagentUnit MaxValue => new ReagentUnit(int.MaxValue);
private double ShiftDown()
{
return _value / Math.Pow(10, Shift);
}
private ReagentUnit(int value)
{
_value = value;
}
public static ReagentUnit New(int value)
{
return new ReagentUnit(value * (int) Math.Pow(10, Shift));
}
public static ReagentUnit New(decimal value)
{
return new ReagentUnit((int) Math.Round(value * (decimal) Math.Pow(10, Shift), MidpointRounding.AwayFromZero));
}
public static ReagentUnit New(float value)
{
return new ReagentUnit(FromFloat(value));
}
private static int FromFloat(float value)
{
return (int) Math.Round(value * (float) Math.Pow(10, Shift), MidpointRounding.AwayFromZero);
}
public static ReagentUnit New(double value)
{
return new ReagentUnit((int) Math.Round(value * Math.Pow(10, Shift), MidpointRounding.AwayFromZero));
}
public static ReagentUnit New(string value)
{
return New(FloatFromString(value));
}
private static float FloatFromString(string value)
{
return float.Parse(value, CultureInfo.InvariantCulture);
}
public static ReagentUnit operator +(ReagentUnit a) => a;
public static ReagentUnit operator -(ReagentUnit a) => new ReagentUnit(-a._value);
public static ReagentUnit operator +(ReagentUnit a, ReagentUnit b)
=> new ReagentUnit(a._value + b._value);
public static ReagentUnit operator -(ReagentUnit a, ReagentUnit b)
=> a + -b;
public static ReagentUnit operator *(ReagentUnit a, ReagentUnit b)
{
var aD = a.ShiftDown();
var bD = b.ShiftDown();
return New(aD * bD);
}
public static ReagentUnit operator *(ReagentUnit a, float b)
{
var aD = (float) a.ShiftDown();
return New(aD * b);
}
public static ReagentUnit operator *(ReagentUnit a, decimal b)
{
var aD = (decimal) a.ShiftDown();
return New(aD * b);
}
public static ReagentUnit operator *(ReagentUnit a, double b)
{
var aD = a.ShiftDown();
return New(aD * b);
}
public static ReagentUnit operator *(ReagentUnit a, int b)
{
return new ReagentUnit(a._value * b);
}
public static ReagentUnit operator /(ReagentUnit a, ReagentUnit b)
{
if (b._value == 0)
{
throw new DivideByZeroException();
}
var aD = a.ShiftDown();
var bD = b.ShiftDown();
return New(aD / bD);
}
public static bool operator <=(ReagentUnit a, int b)
{
return a.ShiftDown() <= b;
}
public static bool operator >=(ReagentUnit a, int b)
{
return a.ShiftDown() >= b;
}
public static bool operator ==(ReagentUnit a, int b)
{
return a.ShiftDown() == b;
}
public static bool operator !=(ReagentUnit a, int b)
{
return a.ShiftDown() != b;
}
public static bool operator <=(ReagentUnit a, ReagentUnit b)
{
return a._value <= b._value;
}
public static bool operator >=(ReagentUnit a, ReagentUnit b)
{
return a._value >= b._value;
}
public static bool operator <(ReagentUnit a, ReagentUnit b)
{
return a._value < b._value;
}
public static bool operator >(ReagentUnit a, ReagentUnit b)
{
return a._value > b._value;
}
public float Float()
{
return (float) ShiftDown();
}
public decimal Decimal()
{
return (decimal) ShiftDown();
}
public double Double()
{
return ShiftDown();
}
public int Int()
{
return (int) ShiftDown();
}
public static ReagentUnit Min(params ReagentUnit[] reagentUnits)
{
return reagentUnits.Min();
}
public static ReagentUnit Min(ReagentUnit a, ReagentUnit b)
{
return a < b ? a : b;
}
public override bool Equals(object obj)
{
return obj is ReagentUnit unit &&
_value == unit._value;
}
public override int GetHashCode()
{
return HashCode.Combine(_value);
}
public void Deserialize(string value)
{
_value = FromFloat(FloatFromString(value));
}
public override string ToString() => $"{ShiftDown().ToString(CultureInfo.InvariantCulture)}";
public string Serialize()
{
return ToString();
}
public bool Equals([AllowNull] ReagentUnit other)
{
return _value == other._value;
}
public int CompareTo([AllowNull] ReagentUnit other)
{
if(other._value > _value)
{
return -1;
}
if(other._value < _value)
{
return 1;
}
return 0;
}
}
}

View File

@@ -1,11 +1,13 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Interfaces.Chemistry;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Content.Shared.Chemistry
{
@@ -23,7 +25,7 @@ namespace Content.Shared.Chemistry
/// The calculated total volume of all reagents in the solution (ex. Total volume of liquid in beaker).
/// </summary>
[ViewVariables]
public int TotalVolume { get; private set; }
public ReagentUnit TotalVolume { get; private set; }
/// <summary>
/// Constructs an empty solution (ex. an empty beaker).
@@ -35,7 +37,7 @@ namespace Content.Shared.Chemistry
/// </summary>
/// <param name="reagentId">The prototype ID of the reagent to add.</param>
/// <param name="quantity">The quantity in milli-units.</param>
public Solution(string reagentId, int quantity)
public Solution(string reagentId, ReagentUnit quantity)
{
AddReagent(reagentId, quantity);
}
@@ -47,7 +49,7 @@ namespace Content.Shared.Chemistry
if (serializer.Reading)
{
TotalVolume = 0;
TotalVolume = ReagentUnit.New(0);
foreach (var reagent in _contents)
{
TotalVolume += reagent.Quantity;
@@ -60,9 +62,9 @@ namespace Content.Shared.Chemistry
/// </summary>
/// <param name="reagentId">The prototype ID of the reagent to add.</param>
/// <param name="quantity">The quantity in milli-units.</param>
public void AddReagent(string reagentId, int quantity)
public void AddReagent(string reagentId, ReagentUnit quantity)
{
if(quantity <= 0)
if (quantity <= 0)
return;
for (var i = 0; i < _contents.Count; i++)
@@ -85,7 +87,7 @@ namespace Content.Shared.Chemistry
/// </summary>
/// <param name="reagentId">The prototype ID of the reagent to add.</param>
/// <returns>The quantity in milli-units.</returns>
public int GetReagentQuantity(string reagentId)
public ReagentUnit GetReagentQuantity(string reagentId)
{
for (var i = 0; i < _contents.Count; i++)
{
@@ -93,10 +95,10 @@ namespace Content.Shared.Chemistry
return _contents[i].Quantity;
}
return 0;
return ReagentUnit.New(0);
}
public void RemoveReagent(string reagentId, int quantity)
public void RemoveReagent(string reagentId, ReagentUnit quantity)
{
if(quantity <= 0)
return;
@@ -129,12 +131,12 @@ namespace Content.Shared.Chemistry
/// Remove the specified quantity from this solution.
/// </summary>
/// <param name="quantity">The quantity of this solution to remove</param>
public void RemoveSolution(int quantity)
public void RemoveSolution(ReagentUnit quantity)
{
if(quantity <= 0)
return;
var ratio = (float)(TotalVolume - quantity) / TotalVolume;
var ratio = (TotalVolume - quantity).Decimal() / TotalVolume.Decimal();
if (ratio <= 0)
{
@@ -149,21 +151,21 @@ namespace Content.Shared.Chemistry
// quantity taken is always a little greedy, so fractional quantities get rounded up to the nearest
// whole unit. This should prevent little bits of chemical remaining because of float rounding errors.
var newQuantity = (int)Math.Floor(oldQuantity * ratio);
var newQuantity = oldQuantity * ratio;
_contents[i] = new ReagentQuantity(reagent.ReagentId, newQuantity);
}
TotalVolume = (int)Math.Floor(TotalVolume * ratio);
TotalVolume = TotalVolume * ratio;
}
public void RemoveAllSolution()
{
_contents.Clear();
TotalVolume = 0;
TotalVolume = ReagentUnit.New(0);
}
public Solution SplitSolution(int quantity)
public Solution SplitSolution(ReagentUnit quantity)
{
if (quantity <= 0)
return new Solution();
@@ -178,14 +180,14 @@ namespace Content.Shared.Chemistry
}
newSolution = new Solution();
var newTotalVolume = 0;
var ratio = (float)(TotalVolume - quantity) / TotalVolume;
var newTotalVolume = ReagentUnit.New(0M);
var ratio = (TotalVolume - quantity).Decimal() / TotalVolume.Decimal();
for (var i = 0; i < _contents.Count; i++)
{
var reagent = _contents[i];
var newQuantity = (int)Math.Floor(reagent.Quantity * ratio);
var newQuantity = reagent.Quantity * ratio;
var splitQuantity = reagent.Quantity - newQuantity;
_contents[i] = new ReagentQuantity(reagent.ReagentId, newQuantity);
@@ -193,7 +195,7 @@ namespace Content.Shared.Chemistry
newTotalVolume += splitQuantity;
}
TotalVolume = (int)Math.Floor(TotalVolume * ratio);
TotalVolume = TotalVolume * ratio;
newSolution.TotalVolume = newTotalVolume;
return newSolution;
@@ -228,7 +230,7 @@ namespace Content.Shared.Chemistry
public Solution Clone()
{
var volume = 0;
var volume = ReagentUnit.New(0);
var newSolution = new Solution();
for (var i = 0; i < _contents.Count; i++)
@@ -246,9 +248,9 @@ namespace Content.Shared.Chemistry
public readonly struct ReagentQuantity
{
public readonly string ReagentId;
public readonly int Quantity;
public readonly ReagentUnit Quantity;
public ReagentQuantity(string reagentId, int quantity)
public ReagentQuantity(string reagentId, ReagentUnit quantity)
{
ReagentId = reagentId;
Quantity = quantity;

View File

@@ -1,4 +1,5 @@
using System;
using Content.Shared.Chemistry;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
@@ -18,11 +19,11 @@ namespace Content.Shared.GameObjects.Components.Chemistry
[Serializable, NetSerializable]
protected sealed class InjectorComponentState : ComponentState
{
public int CurrentVolume { get; }
public int TotalVolume { get; }
public ReagentUnit CurrentVolume { get; }
public ReagentUnit TotalVolume { get; }
public InjectorToggleMode CurrentMode { get; }
public InjectorComponentState(int currentVolume, int totalVolume, InjectorToggleMode currentMode) : base(ContentNetIDs.REAGENT_INJECTOR)
public InjectorComponentState(ReagentUnit currentVolume, ReagentUnit totalVolume, InjectorToggleMode currentMode) : base(ContentNetIDs.REAGENT_INJECTOR)
{
CurrentVolume = currentVolume;
TotalVolume = totalVolume;

View File

@@ -26,8 +26,8 @@ namespace Content.Shared.GameObjects.Components.Chemistry
public class ReagentDispenserBoundUserInterfaceState : BoundUserInterfaceState
{
public readonly bool HasBeaker;
public readonly int BeakerCurrentVolume;
public readonly int BeakerMaxVolume;
public readonly ReagentUnit BeakerCurrentVolume;
public readonly ReagentUnit BeakerMaxVolume;
public readonly string ContainerName;
/// <summary>
/// A list of the reagents which this dispenser can dispense.
@@ -38,10 +38,10 @@ namespace Content.Shared.GameObjects.Components.Chemistry
/// </summary>
public readonly List<Solution.ReagentQuantity> ContainerReagents;
public readonly string DispenserName;
public readonly int SelectedDispenseAmount;
public readonly ReagentUnit SelectedDispenseAmount;
public ReagentDispenserBoundUserInterfaceState(bool hasBeaker, int beakerCurrentVolume, int beakerMaxVolume, string containerName,
List<ReagentDispenserInventoryEntry> inventory, string dispenserName, List<Solution.ReagentQuantity> containerReagents, int selectedDispenseAmount)
public ReagentDispenserBoundUserInterfaceState(bool hasBeaker, ReagentUnit beakerCurrentVolume, ReagentUnit beakerMaxVolume, string containerName,
List<ReagentDispenserInventoryEntry> inventory, string dispenserName, List<Solution.ReagentQuantity> containerReagents, ReagentUnit selectedDispenseAmount)
{
HasBeaker = hasBeaker;
BeakerCurrentVolume = beakerCurrentVolume;

View File

@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using Content.Shared.Chemistry;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Shared.GameObjects.Components.Chemistry
{
public class SharedSolutionComponent : Component
{
public override string Name => "Solution";
/// <inheritdoc />
public sealed override uint? NetID => ContentNetIDs.SOLUTION;
[Serializable, NetSerializable]
public class SolutionComponentState : ComponentState
{
public SolutionComponentState() : base(ContentNetIDs.SOLUTION) { }
}
/// <inheritdoc />
public override ComponentState GetComponentState()
{
return new SolutionComponentState();
}
/// <inheritdoc />
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
base.HandleComponentState(curState, nextState);
if(curState == null)
return;
var compState = (SolutionComponentState)curState;
//TODO: Make me work!
}
}
}

View File

@@ -1,238 +0,0 @@
using System;
using System.Collections.Generic;
using Content.Shared.Chemistry;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Shared.GameObjects.Components.Chemistry
{
public class SolutionComponent : Component
{
#pragma warning disable 649
[Dependency] private readonly IPrototypeManager _prototypeManager;
#pragma warning restore 649
[ViewVariables]
protected Solution _containedSolution = new Solution();
protected int _maxVolume;
private SolutionCaps _capabilities;
/// <summary>
/// Triggered when the solution contents change.
/// </summary>
public event Action SolutionChanged;
/// <summary>
/// The maximum volume of the container.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public int MaxVolume
{
get => _maxVolume;
set => _maxVolume = value; // Note that the contents won't spill out if the capacity is reduced.
}
/// <summary>
/// The total volume of all the of the reagents in the container.
/// </summary>
[ViewVariables]
public int CurrentVolume => _containedSolution.TotalVolume;
/// <summary>
/// The volume without reagents remaining in the container.
/// </summary>
[ViewVariables]
public int EmptyVolume => MaxVolume - CurrentVolume;
/// <summary>
/// The current blended color of all the reagents in the container.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public Color SubstanceColor { get; private set; }
/// <summary>
/// The current capabilities of this container (is the top open to pour? can I inject it into another object?).
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public SolutionCaps Capabilities
{
get => _capabilities;
set => _capabilities = value;
}
public IReadOnlyList<Solution.ReagentQuantity> ReagentList => _containedSolution.Contents;
/// <summary>
/// Shortcut for Capabilities PourIn flag to avoid binary operators.
/// </summary>
public bool CanPourIn => (Capabilities & SolutionCaps.PourIn) != 0;
/// <summary>
/// Shortcut for Capabilities PourOut flag to avoid binary operators.
/// </summary>
public bool CanPourOut => (Capabilities & SolutionCaps.PourOut) != 0;
/// <summary>
/// Shortcut for Capabilities Injectable flag
/// </summary>
public bool Injectable => (Capabilities & SolutionCaps.Injectable) != 0;
/// <summary>
/// Shortcut for Capabilities Injector flag
/// </summary>
public bool Injector => (Capabilities & SolutionCaps.Injector) != 0;
/// <inheritdoc />
public override string Name => "Solution";
/// <inheritdoc />
public sealed override uint? NetID => ContentNetIDs.SOLUTION;
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _maxVolume, "maxVol", 0);
serializer.DataField(ref _containedSolution, "contents", _containedSolution);
serializer.DataField(ref _capabilities, "caps", SolutionCaps.None);
}
/// <inheritdoc />
protected override void Startup()
{
base.Startup();
RecalculateColor();
}
/// <inheritdoc />
protected override void Shutdown()
{
base.Shutdown();
_containedSolution.RemoveAllSolution();
_containedSolution = new Solution();
}
public void RemoveAllSolution()
{
_containedSolution.RemoveAllSolution();
OnSolutionChanged();
}
public bool TryRemoveReagent(string reagentId, int quantity)
{
if (!ContainsReagent(reagentId, out var currentQuantity)) return false;
_containedSolution.RemoveReagent(reagentId, quantity);
OnSolutionChanged();
return true;
}
/// <summary>
/// Attempt to remove the specified quantity from this solution
/// </summary>
/// <param name="quantity">Quantity of this solution to remove</param>
/// <returns>Whether or not the solution was successfully removed</returns>
public bool TryRemoveSolution(int quantity)
{
if (CurrentVolume == 0)
return false;
_containedSolution.RemoveSolution(quantity);
OnSolutionChanged();
return true;
}
public Solution SplitSolution(int quantity)
{
var solutionSplit = _containedSolution.SplitSolution(quantity);
OnSolutionChanged();
return solutionSplit;
}
protected void RecalculateColor()
{
if(_containedSolution.TotalVolume == 0)
SubstanceColor = Color.White;
Color mixColor = default;
float runningTotalQuantity = 0;
foreach (var reagent in _containedSolution)
{
runningTotalQuantity += reagent.Quantity;
if(!_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto))
continue;
if (mixColor == default)
mixColor = proto.SubstanceColor;
mixColor = BlendRGB(mixColor, proto.SubstanceColor, reagent.Quantity / runningTotalQuantity);
}
}
private Color BlendRGB(Color rgb1, Color rgb2, float amount)
{
var r = (float)Math.Round(rgb1.R + (rgb2.R - rgb1.R) * amount, 1);
var g = (float)Math.Round(rgb1.G + (rgb2.G - rgb1.G) * amount, 1);
var b = (float)Math.Round(rgb1.B + (rgb2.B - rgb1.B) * amount, 1);
var alpha = (float)Math.Round(rgb1.A + (rgb2.A - rgb1.A) * amount, 1);
return new Color(r, g, b, alpha);
}
/// <inheritdoc />
public override ComponentState GetComponentState()
{
return new SolutionComponentState();
}
/// <inheritdoc />
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
base.HandleComponentState(curState, nextState);
if(curState == null)
return;
var compState = (SolutionComponentState)curState;
//TODO: Make me work!
}
[Serializable, NetSerializable]
public class SolutionComponentState : ComponentState
{
public SolutionComponentState() : base(ContentNetIDs.SOLUTION) { }
}
/// <summary>
/// Check if the solution contains the specified reagent.
/// </summary>
/// <param name="reagentId">The reagent to check for.</param>
/// <param name="quantity">Output the quantity of the reagent if it is contained, 0 if it isn't.</param>
/// <returns>Return true if the solution contains the reagent.</returns>
public bool ContainsReagent(string reagentId, out int quantity)
{
foreach (var reagent in _containedSolution.Contents)
{
if (reagent.ReagentId == reagentId)
{
quantity = reagent.Quantity;
return true;
}
}
quantity = 0;
return false;
}
protected virtual void OnSolutionChanged()
{
SolutionChanged?.Invoke();
}
}
}

View File

@@ -0,0 +1,49 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Command
{
public class SharedCommunicationsConsoleComponent : Component
{
public override string Name => "CommunicationsConsole";
}
[Serializable, NetSerializable]
public class CommunicationsConsoleInterfaceState : BoundUserInterfaceState
{
public readonly TimeSpan? ExpectedCountdownEnd;
public readonly bool CountdownStarted;
public CommunicationsConsoleInterfaceState(TimeSpan? expectedCountdownEnd = null)
{
ExpectedCountdownEnd = expectedCountdownEnd;
CountdownStarted = expectedCountdownEnd != null;
}
}
[Serializable, NetSerializable]
public class CommunicationsConsoleCallEmergencyShuttleMessage : BoundUserInterfaceMessage
{
public CommunicationsConsoleCallEmergencyShuttleMessage()
{
}
}
[Serializable, NetSerializable]
public class CommunicationsConsoleRecallEmergencyShuttleMessage : BoundUserInterfaceMessage
{
public CommunicationsConsoleRecallEmergencyShuttleMessage()
{
}
}
[Serializable, NetSerializable]
public enum CommunicationsConsoleUiKey
{
Key
}
}

View File

@@ -0,0 +1,29 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Observer
{
public class SharedGhostComponent : Component
{
public override string Name => "Ghost";
public override uint? NetID => ContentNetIDs.GHOST;
}
[Serializable, NetSerializable]
public class GhostComponentState : ComponentState
{
public bool CanReturnToBody { get; }
public GhostComponentState(bool canReturnToBody) : base(ContentNetIDs.GHOST)
{
CanReturnToBody = canReturnToBody;
}
}
[Serializable, NetSerializable]
public class ReturnToBodyComponentMessage : ComponentMessage
{
public ReturnToBodyComponentMessage() => Directed = true;
}
}

View File

@@ -1,16 +1,109 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Reflection;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Shared.GameObjects.Components
{
public abstract class SharedStackComponent : Component
{
private const string SerializationCache = "stack";
public sealed override string Name => "Stack";
public sealed override uint? NetID => ContentNetIDs.STACK;
private int _count;
private int _maxCount;
[ViewVariables(VVAccess.ReadWrite)]
public virtual int Count
{
get => _count;
set
{
_count = value;
if (_count <= 0)
{
Owner.Delete();
}
Dirty();
}
}
[ViewVariables]
public int MaxCount
{
get => _maxCount;
private set
{
_maxCount = value;
Dirty();
}
}
[ViewVariables] public int AvailableSpace => MaxCount - Count;
[ViewVariables] public object StackType { get; private set; }
public override void ExposeData(ObjectSerializer serializer)
{
serializer.DataFieldCached(ref _maxCount, "max", 50);
serializer.DataFieldCached(ref _count, "count", MaxCount);
if (!serializer.Reading)
{
return;
}
if (serializer.TryGetCacheData(SerializationCache, out object stackType))
{
StackType = stackType;
return;
}
if (serializer.TryReadDataFieldCached("stacktype", out string raw))
{
var refl = IoCManager.Resolve<IReflectionManager>();
if (refl.TryParseEnumReference(raw, out var @enum))
{
stackType = @enum;
}
else
{
stackType = raw;
}
}
else
{
stackType = Owner.Prototype.ID;
}
serializer.SetCacheData(SerializationCache, stackType);
StackType = stackType;
}
public override ComponentState GetComponentState()
{
return new StackComponentState(Count, MaxCount);
}
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
if (!(curState is StackComponentState cast))
{
return;
}
Count = cast.Count;
MaxCount = cast.MaxCount;
}
[Serializable, NetSerializable]
protected sealed class StackComponentState : ComponentState
private sealed class StackComponentState : ComponentState
{
public int Count { get; }
public int MaxCount { get; }
@@ -22,4 +115,14 @@ namespace Content.Shared.GameObjects.Components
}
}
}
public enum StackType
{
Metal,
Glass,
Cable,
Ointment,
Brutepack,
FloorTileSteel
}
}

View File

@@ -41,5 +41,6 @@
public const uint HANDHELD_LIGHT = 1036;
public const uint PAPER = 1037;
public const uint REAGENT_INJECTOR = 1038;
public const uint GHOST = 1039;
}
}

View File

@@ -19,6 +19,6 @@ namespace Content.Shared.Interfaces.Chemistry
/// <param name="reagentId">The reagent id</param>
/// <param name="tickTime">The time since the last metabolism tick in seconds.</param>
/// <returns>The amount of reagent to be removed. The metabolizing organ should handle removing the reagent.</returns>
int Metabolize(IEntity solutionEntity, string reagentId, float tickTime);
ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime);
}
}

View File

@@ -0,0 +1,155 @@
using Content.Shared.Chemistry;
using NUnit.Framework;
using System;
namespace Content.Tests.Shared.Chemistry
{
[TestFixture, TestOf(typeof(ReagentUnit))]
public class ReagentUnit_Tests
{
[Test]
[TestCase(1, "1")]
[TestCase(0, "0")]
[TestCase(-1, "-1")]
public void ReagentUnitIntegerTests(int value, string expected)
{
var result = ReagentUnit.New(value);
Assert.AreEqual(expected, $"{result}");
}
[Test]
[TestCase(1.001f, "1")]
[TestCase(0.999f, "1")]
public void ReagentUnitFloatTests(float value, string expected)
{
var result = ReagentUnit.New(value);
Assert.AreEqual(expected, $"{result}");
}
[Test]
[TestCase(1.001d, "1")]
[TestCase(0.999d, "1")]
public void ReagentUnitDoubleTests(double value, string expected)
{
var result = ReagentUnit.New(value);
Assert.AreEqual(expected, $"{result}");
}
[Test]
[TestCase("1.001", "1")]
[TestCase("0.999", "1")]
public void ReagentUnitDecimalTests(string valueAsString, string expected)
{
var value = decimal.Parse(valueAsString);
var result = ReagentUnit.New(value);
Assert.AreEqual(expected, $"{result}");
}
[Test]
[TestCase("1.005", "1.01")]
[TestCase("0.999", "1")]
public void ReagentUnitStringTests(string value, string expected)
{
var result = ReagentUnit.New(value);
Assert.AreEqual(expected, $"{result}");
}
[Test]
[TestCase(1.001f, 1.001f, "2")]
[TestCase(1.001f, 1.004f, "2")]
[TestCase(1f, 1.005f, "2.01")]
[TestCase(1f, 2.005f, "3.01")]
public void CalculusPlus(float aFloat, float bFloat, string expected)
{
var a = ReagentUnit.New(aFloat);
var b = ReagentUnit.New(bFloat);
var result = a + b;
Assert.AreEqual(expected, $"{result}");
}
[Test]
[TestCase(1.001f, 1.001f, "0")]
[TestCase(1.001f, 1.004f, "0")]
[TestCase(1f, 2.005f, "-1.01")]
public void CalculusMinus(float aFloat, float bFloat, string expected)
{
var a = ReagentUnit.New(aFloat);
var b = ReagentUnit.New(bFloat);
var result = a - b;
Assert.AreEqual(expected, $"{result}");
}
[Test]
[TestCase(1.001f, 3f, "0.33")]
[TestCase(0.999f, 3f, "0.33")]
[TestCase(2.1f, 3f, "0.7")]
public void CalculusDivision(float aFloat, float bFloat, string expected)
{
var a = ReagentUnit.New(aFloat);
var b = ReagentUnit.New(bFloat);
var result = a / b;
Assert.AreEqual(expected, $"{result}");
}
[Test]
[TestCase(1.001f, 0.999f, "1")]
[TestCase(0.999f, 3f, "3")]
public void CalculusMultiplication(float aFloat, float bFloat, string expected)
{
var a = ReagentUnit.New(aFloat);
var b = ReagentUnit.New(bFloat);
var result = a * b;
Assert.AreEqual(expected, $"{result}");
}
[Test]
[TestCase(0.995f, 100)]
[TestCase(1.005f, 101)]
[TestCase(2.005f, 201)]
public void FloatRoundingTest(float a, int expected)
{
var result = (int) Math.Round(a * (float) Math.Pow(10, 2), MidpointRounding.AwayFromZero);
Assert.AreEqual(expected, result);
}
[Test]
public void ReagentUnitMin()
{
var unorderedList = new[]
{
ReagentUnit.New(5),
ReagentUnit.New(3),
ReagentUnit.New(1),
ReagentUnit.New(2),
ReagentUnit.New(4),
};
var min = ReagentUnit.Min(unorderedList);
Assert.AreEqual(ReagentUnit.New(1), min);
}
[Test]
[TestCase(1, 0, false)]
[TestCase(0, 0, true)]
[TestCase(-1, 0, false)]
[TestCase(null, 0, true)]
[TestCase(1, 1, true)]
[TestCase(0, 1, false)]
[TestCase(-1, 1, false)]
[TestCase(null, 1, false)]
public void ReagentUnitEquals(int a, int b, bool expected)
{
var parameter = ReagentUnit.New(a);
var comparison = ReagentUnit.New(b);
Assert.AreEqual(comparison.Equals(parameter), parameter.Equals(comparison));
Assert.AreEqual(expected, comparison.Equals(parameter));
}
}
}

View File

@@ -10,19 +10,19 @@ namespace Content.Tests.Shared.Chemistry
public void AddReagentAndGetSolution()
{
var solution = new Solution();
solution.AddReagent("water", 1000);
solution.AddReagent("water", ReagentUnit.New(1000));
var quantity = solution.GetReagentQuantity("water");
Assert.That(quantity, Is.EqualTo(1000));
Assert.That(quantity.Int(), Is.EqualTo(1000));
}
[Test]
public void ConstructorAddReagent()
{
var solution = new Solution("water", 1000);
var solution = new Solution("water", ReagentUnit.New(1000));
var quantity = solution.GetReagentQuantity("water");
Assert.That(quantity, Is.EqualTo(1000));
Assert.That(quantity.Int(), Is.EqualTo(1000));
}
[Test]
@@ -31,223 +31,276 @@ namespace Content.Tests.Shared.Chemistry
var solution = new Solution();
var quantity = solution.GetReagentQuantity("water");
Assert.That(quantity, Is.EqualTo(0));
Assert.That(quantity.Int(), Is.EqualTo(0));
}
[Test]
public void AddLessThanZeroReagentReturnsZero()
{
var solution = new Solution("water", -1000);
var solution = new Solution("water", ReagentUnit.New(-1000));
var quantity = solution.GetReagentQuantity("water");
Assert.That(quantity, Is.EqualTo(0));
Assert.That(quantity.Int(), Is.EqualTo(0));
}
[Test]
public void AddingReagentsSumsProperly()
{
var solution = new Solution();
solution.AddReagent("water", 1000);
solution.AddReagent("water", 2000);
solution.AddReagent("water", ReagentUnit.New(1000));
solution.AddReagent("water", ReagentUnit.New(2000));
var quantity = solution.GetReagentQuantity("water");
Assert.That(quantity, Is.EqualTo(3000));
Assert.That(quantity.Int(), Is.EqualTo(3000));
}
[Test]
public void ReagentQuantitiesStayUnique()
{
var solution = new Solution();
solution.AddReagent("water", 1000);
solution.AddReagent("fire", 2000);
solution.AddReagent("water", ReagentUnit.New(1000));
solution.AddReagent("fire", ReagentUnit.New(2000));
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(1000));
Assert.That(solution.GetReagentQuantity("fire"), Is.EqualTo(2000));
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(1000));
Assert.That(solution.GetReagentQuantity("fire").Int(), Is.EqualTo(2000));
}
[Test]
public void TotalVolumeIsCorrect()
{
var solution = new Solution();
solution.AddReagent("water", 1000);
solution.AddReagent("fire", 2000);
solution.AddReagent("water", ReagentUnit.New(1000));
solution.AddReagent("fire", ReagentUnit.New(2000));
Assert.That(solution.TotalVolume, Is.EqualTo(3000));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(3000));
}
[Test]
public void CloningSolutionIsCorrect()
{
var solution = new Solution();
solution.AddReagent("water", 1000);
solution.AddReagent("fire", 2000);
solution.AddReagent("water", ReagentUnit.New(1000));
solution.AddReagent("fire", ReagentUnit.New(2000));
var newSolution = solution.Clone();
Assert.That(newSolution.GetReagentQuantity("water"), Is.EqualTo(1000));
Assert.That(newSolution.GetReagentQuantity("fire"), Is.EqualTo(2000));
Assert.That(newSolution.TotalVolume, Is.EqualTo(3000));
Assert.That(newSolution.GetReagentQuantity("water").Int(), Is.EqualTo(1000));
Assert.That(newSolution.GetReagentQuantity("fire").Int(), Is.EqualTo(2000));
Assert.That(newSolution.TotalVolume.Int(), Is.EqualTo(3000));
}
[Test]
public void RemoveSolutionRecalculatesProperly()
{
var solution = new Solution();
solution.AddReagent("water", 1000);
solution.AddReagent("fire", 2000);
solution.AddReagent("water", ReagentUnit.New(1000));
solution.AddReagent("fire", ReagentUnit.New(2000));
solution.RemoveReagent("water", 500);
solution.RemoveReagent("water", ReagentUnit.New(500));
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(500));
Assert.That(solution.GetReagentQuantity("fire"), Is.EqualTo(2000));
Assert.That(solution.TotalVolume, Is.EqualTo(2500));
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(500));
Assert.That(solution.GetReagentQuantity("fire").Int(), Is.EqualTo(2000));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(2500));
}
[Test]
public void RemoveLessThanOneQuantityDoesNothing()
{
var solution = new Solution("water", 100);
var solution = new Solution("water", ReagentUnit.New(100));
solution.RemoveReagent("water", -100);
solution.RemoveReagent("water", ReagentUnit.New(-100));
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(100));
Assert.That(solution.TotalVolume, Is.EqualTo(100));
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(100));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(100));
}
[Test]
public void RemoveMoreThanTotalRemovesAllReagent()
{
var solution = new Solution("water", 100);
var solution = new Solution("water", ReagentUnit.New(100));
solution.RemoveReagent("water", 1000);
solution.RemoveReagent("water", ReagentUnit.New(1000));
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(0));
Assert.That(solution.TotalVolume, Is.EqualTo(0));
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(0));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(0));
}
[Test]
public void RemoveNonExistReagentDoesNothing()
{
var solution = new Solution("water", 100);
var solution = new Solution("water", ReagentUnit.New(100));
solution.RemoveReagent("fire", 1000);
solution.RemoveReagent("fire", ReagentUnit.New(1000));
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(100));
Assert.That(solution.TotalVolume, Is.EqualTo(100));
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(100));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(100));
}
[Test]
public void RemoveSolution()
{
var solution = new Solution("water", 700);
var solution = new Solution("water", ReagentUnit.New(700));
solution.RemoveSolution(500);
solution.RemoveSolution(ReagentUnit.New(500));
//Check that edited solution is correct
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(200));
Assert.That(solution.TotalVolume, Is.EqualTo(200));
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(200));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(200));
}
[Test]
public void RemoveSolutionMoreThanTotalRemovesAll()
{
var solution = new Solution("water", 800);
var solution = new Solution("water", ReagentUnit.New(800));
solution.RemoveSolution(1000);
solution.RemoveSolution(ReagentUnit.New(1000));
//Check that edited solution is correct
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(0));
Assert.That(solution.TotalVolume, Is.EqualTo(0));
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(0));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(0));
}
[Test]
public void RemoveSolutionRatioPreserved()
{
var solution = new Solution();
solution.AddReagent("water", 1000);
solution.AddReagent("fire", 2000);
solution.AddReagent("water", ReagentUnit.New(1000));
solution.AddReagent("fire", ReagentUnit.New(2000));
solution.RemoveSolution(1500);
solution.RemoveSolution(ReagentUnit.New(1500));
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(500));
Assert.That(solution.GetReagentQuantity("fire"), Is.EqualTo(1000));
Assert.That(solution.TotalVolume, Is.EqualTo(1500));
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(500));
Assert.That(solution.GetReagentQuantity("fire").Int(), Is.EqualTo(1000));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(1500));
}
[Test]
public void RemoveSolutionLessThanOneDoesNothing()
{
var solution = new Solution("water", 800);
var solution = new Solution("water", ReagentUnit.New(800));
solution.RemoveSolution(-200);
solution.RemoveSolution(ReagentUnit.New(-200));
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(800));
Assert.That(solution.TotalVolume, Is.EqualTo(800));
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(800));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(800));
}
[Test]
public void SplitSolution()
{
var solution = new Solution();
solution.AddReagent("water", 1000);
solution.AddReagent("fire", 2000);
solution.AddReagent("water", ReagentUnit.New(1000));
solution.AddReagent("fire", ReagentUnit.New(2000));
var splitSolution = solution.SplitSolution(750);
var splitSolution = solution.SplitSolution(ReagentUnit.New(750));
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(750));
Assert.That(solution.GetReagentQuantity("fire"), Is.EqualTo(1500));
Assert.That(solution.TotalVolume, Is.EqualTo(2250));
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(750));
Assert.That(solution.GetReagentQuantity("fire").Int(), Is.EqualTo(1500));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(2250));
Assert.That(splitSolution.GetReagentQuantity("water"), Is.EqualTo(250));
Assert.That(splitSolution.GetReagentQuantity("fire"), Is.EqualTo(500));
Assert.That(splitSolution.TotalVolume, Is.EqualTo(750));
Assert.That(splitSolution.GetReagentQuantity("water").Int(), Is.EqualTo(250));
Assert.That(splitSolution.GetReagentQuantity("fire").Int(), Is.EqualTo(500));
Assert.That(splitSolution.TotalVolume.Int(), Is.EqualTo(750));
}
[Test]
public void SplitSolutionFractional()
{
var solution = new Solution();
solution.AddReagent("water", ReagentUnit.New(1));
solution.AddReagent("fire", ReagentUnit.New(2));
var splitSolution = solution.SplitSolution(ReagentUnit.New(1));
Assert.That(solution.GetReagentQuantity("water").Float(), Is.EqualTo(0.67f));
Assert.That(solution.GetReagentQuantity("fire").Float(), Is.EqualTo(1.33f));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(2));
Assert.That(splitSolution.GetReagentQuantity("water").Float(), Is.EqualTo(0.33f));
Assert.That(splitSolution.GetReagentQuantity("fire").Float(), Is.EqualTo(0.67f));
Assert.That(splitSolution.TotalVolume.Int(), Is.EqualTo(1));
}
[Test]
public void SplitSolutionFractionalOpposite()
{
var solution = new Solution();
solution.AddReagent("water", ReagentUnit.New(1));
solution.AddReagent("fire", ReagentUnit.New(2));
var splitSolution = solution.SplitSolution(ReagentUnit.New(2));
Assert.That(solution.GetReagentQuantity("water").Float(), Is.EqualTo(0.33f));
Assert.That(solution.GetReagentQuantity("fire").Float(), Is.EqualTo(0.67f));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(1));
Assert.That(splitSolution.GetReagentQuantity("water").Float(), Is.EqualTo(0.67f));
Assert.That(splitSolution.GetReagentQuantity("fire").Float(), Is.EqualTo(1.33f));
Assert.That(splitSolution.TotalVolume.Int(), Is.EqualTo(2));
}
[Test]
[TestCase(0.03f, 0.01f, 0.02f)]
[TestCase(0.03f, 0.02f, 0.01f)]
public void SplitSolutionTinyFractionalBigSmall(float initial, float reduce, float remainder)
{
var solution = new Solution();
solution.AddReagent("water", ReagentUnit.New(initial));
var splitSolution = solution.SplitSolution(ReagentUnit.New(reduce));
Assert.That(solution.GetReagentQuantity("water").Float(), Is.EqualTo(remainder));
Assert.That(solution.TotalVolume.Float(), Is.EqualTo(remainder));
Assert.That(splitSolution.GetReagentQuantity("water").Float(), Is.EqualTo(reduce));
Assert.That(splitSolution.TotalVolume.Float(), Is.EqualTo(reduce));
}
[Test]
public void SplitSolutionMoreThanTotalRemovesAll()
{
var solution = new Solution("water", 800);
var solution = new Solution("water", ReagentUnit.New(800));
var splitSolution = solution.SplitSolution(1000);
var splitSolution = solution.SplitSolution(ReagentUnit.New(1000));
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(0));
Assert.That(solution.TotalVolume, Is.EqualTo(0));
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(0));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(0));
Assert.That(splitSolution.GetReagentQuantity("water"), Is.EqualTo(800));
Assert.That(splitSolution.TotalVolume, Is.EqualTo(800));
Assert.That(splitSolution.GetReagentQuantity("water").Int(), Is.EqualTo(800));
Assert.That(splitSolution.TotalVolume.Int(), Is.EqualTo(800));
}
[Test]
public void SplitSolutionLessThanOneDoesNothing()
{
var solution = new Solution("water", 800);
var solution = new Solution("water", ReagentUnit.New(800));
var splitSolution = solution.SplitSolution(-200);
var splitSolution = solution.SplitSolution(ReagentUnit.New(-200));
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(800));
Assert.That(solution.TotalVolume, Is.EqualTo(800));
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(800));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(800));
Assert.That(splitSolution.GetReagentQuantity("water"), Is.EqualTo(0));
Assert.That(splitSolution.TotalVolume, Is.EqualTo(0));
Assert.That(splitSolution.GetReagentQuantity("water").Int(), Is.EqualTo(0));
Assert.That(splitSolution.TotalVolume.Int(), Is.EqualTo(0));
}
[Test]
public void AddSolution()
{
var solutionOne = new Solution();
solutionOne.AddReagent("water", 1000);
solutionOne.AddReagent("fire", 2000);
solutionOne.AddReagent("water", ReagentUnit.New(1000));
solutionOne.AddReagent("fire", ReagentUnit.New(2000));
var solutionTwo = new Solution();
solutionTwo.AddReagent("water", 500);
solutionTwo.AddReagent("earth", 1000);
solutionTwo.AddReagent("water", ReagentUnit.New(500));
solutionTwo.AddReagent("earth", ReagentUnit.New(1000));
solutionOne.AddSolution(solutionTwo);
Assert.That(solutionOne.GetReagentQuantity("water"), Is.EqualTo(1500));
Assert.That(solutionOne.GetReagentQuantity("fire"), Is.EqualTo(2000));
Assert.That(solutionOne.GetReagentQuantity("earth"), Is.EqualTo(1000));
Assert.That(solutionOne.TotalVolume, Is.EqualTo(4500));
Assert.That(solutionOne.GetReagentQuantity("water").Int(), Is.EqualTo(1500));
Assert.That(solutionOne.GetReagentQuantity("fire").Int(), Is.EqualTo(2000));
Assert.That(solutionOne.GetReagentQuantity("earth").Int(), Is.EqualTo(1000));
Assert.That(solutionOne.TotalVolume.Int(), Is.EqualTo(4500));
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -11,6 +11,7 @@
- ooc
- observe
- toggleready
- ghost
- Index: 50
Name: Moderator
@@ -26,6 +27,7 @@
- showtime
- observe
- toggleready
- ghost
- kick
- listplayers
- loc
@@ -44,6 +46,7 @@
- aghost
- observe
- toggleready
- ghost
- spawn
- delete
- tp
@@ -84,6 +87,7 @@
- aghost
- observe
- toggleready
- ghost
- spawn
- delete
- tp

View File

@@ -18,3 +18,7 @@
- chem.Ale
- chem.Wine
- chem.Ice
- chem.Beer
- chem.Vodka
- chem.Cognac
- chem.Kahlua

View File

@@ -37,3 +37,4 @@
- chem.K
- chem.Ra
- chem.Na
- chem.U

View File

@@ -176,3 +176,8 @@
- type: ComputerVisualizer2D
key: generic_key
screen: comm
- type: CommunicationsConsole
- type: UserInterface
interfaces:
- key: enum.CommunicationsConsoleUiKey.Key
type: CommunicationsConsoleBoundUserInterface

View File

@@ -19,3 +19,4 @@
- chem.Tea
- chem.Ice
- chem.H2O
- chem.Cream

File diff suppressed because it is too large Load Diff

View File

@@ -5,12 +5,6 @@
description: One sip of this and you just know you're gonna have a good time.
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleAbsinthe
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- type: Sprite
sprite: Objects/Drinks/absinthebottle.rsi
- type: Icon
@@ -23,12 +17,6 @@
description: A bottle of 46 proof Emeraldine Melon Liquor. Sweet and light.
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleAlcoClear
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- type: Sprite
sprite: Objects/Drinks/alco-green.rsi
- type: Icon
@@ -40,13 +28,12 @@
name: Magm-Ale
description: A true dorf's drink of choice.
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleAle
- type: Solution
maxVol: 80
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- ReagentId: chem.Ale
Quantity: 80
- type: Sprite
sprite: Objects/Drinks/alebottle.rsi
- type: Icon
@@ -59,12 +46,6 @@
description: A bottle filled with nothing
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleAlcoClear
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- type: Sprite
sprite: Objects/Drinks/bottleofnothing.rsi
- type: Icon
@@ -76,13 +57,12 @@
name: Cognac bottle
description: A sweet and strongly alchoholic drink, made after numerous distillations and years of maturing. You might as well not scream 'SHITCURITY' this time.
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleCognac
- type: Solution
maxVol: 80
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- ReagentId: chem.Cognac
Quantity: 80
- type: Sprite
sprite: Objects/Drinks/cognacbottle.rsi
- type: Icon
@@ -95,12 +75,6 @@
description: A bottle of high quality gin, produced in the New London Space Station.
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleGin
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- type: Sprite
sprite: Objects/Drinks/ginbottle.rsi
- type: Icon
@@ -113,12 +87,6 @@
description: 100 proof cinnamon schnapps, made for alcoholic teen girls on spring break.
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleGoldschlager
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- type: Sprite
sprite: Objects/Drinks/goldschlagerbottle.rsi
- type: Icon
@@ -130,13 +98,12 @@
name: Kahlua bottle
description: A widely known, Mexican coffee-flavoured liqueur. In production since 1936, HONK
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleKahlua
- type: Solution
maxVol: 80
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
Quantity: 80
- type: Sprite
sprite: Objects/Drinks/kahluabottle.rsi
- type: Icon
@@ -149,12 +116,6 @@
description: Silver laced tequilla, served in space night clubs across the galaxy.
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottlePatron
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- type: Sprite
sprite: Objects/Drinks/patronbottle.rsi
- type: Icon
@@ -167,12 +128,6 @@
description: What a delightful packaging for a surely high quality wine! The vintage must be amazing!
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottlePoisonWine
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- type: Sprite
sprite: Objects/Drinks/pwinebottle.rsi
- type: Icon
@@ -185,12 +140,6 @@
description: This isn't just rum, oh no. It's practically GRIFF in a bottle.
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleRum
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- type: Sprite
sprite: Objects/Drinks/rumbottle.rsi
- type: Icon
@@ -203,12 +152,6 @@
description: Made from premium petroleum distillates, pure thalidomide and other fine quality ingredients!
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleTequila
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- type: Sprite
sprite: Objects/Drinks/tequillabottle.rsi
- type: Icon
@@ -221,12 +164,6 @@
description: Sweet, sweet dryness~
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleVermouth
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- type: Sprite
sprite: Objects/Drinks/vermouthbottle.rsi
- type: Icon
@@ -238,13 +175,12 @@
name: Vodka bottle
description: Aah, vodka. Prime choice of drink AND fuel by Russians worldwide.
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleVodka
- type: Solution
maxVol: 80
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- ReagentId: chem.Vodka
Quantity: 80
- type: Sprite
sprite: Objects/Drinks/vodkabottle.rsi
- type: Icon
@@ -256,13 +192,12 @@
name: Uncle Git's special reserve
description: A premium single-malt whiskey, gently matured inside the tunnels of a nuclear shelter. TUNNEL WHISKEY RULES.
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleWhiskey
- type: Solution
maxVol: 80
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- ReagentId: chem.Whiskey
Quantity: 80
- type: Sprite
sprite: Objects/Drinks/whiskeybottle.rsi
- type: Icon
@@ -274,13 +209,12 @@
name: Doublebearded bearded special wine bottle
description: A faint aura of unease and asspainery surrounds the bottle.
components:
- type: Drink
max_volume: 10
spawn_on_finish: DrinkBottleWine
- type: Solution
maxVol: 80
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 10
- ReagentId: chem.Wine
Quantity: 80
- type: Sprite
sprite: Objects/Drinks/winebottle.rsi
- type: Icon

View File

@@ -30,11 +30,12 @@
name: Space cola
description: A refreshing beverage.
components:
- type: Drink
- type: Solution
maxVol: 20
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 4
- ReagentId: chem.Cola
Quantity: 20
- type: Sprite
sprite: Objects/Drinks/cola.rsi
- type: Icon
@@ -62,10 +63,6 @@
description: ''
components:
- type: Drink
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 4
- type: Sprite
sprite: Objects/Drinks/ice_tea_can.rsi
- type: Icon
@@ -93,10 +90,6 @@
description: You wanted ORANGE. It gave you Lemon Lime.
components:
- type: Drink
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 4
- type: Sprite
sprite: Objects/Drinks/lemon-lime.rsi
- type: Icon
@@ -124,10 +117,6 @@
description: ''
components:
- type: Drink
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 4
- type: Sprite
sprite: Objects/Drinks/purple_can.rsi
- type: Icon
@@ -155,10 +144,6 @@
description: Blows right through you like a space wind.
components:
- type: Drink
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 4
- type: Sprite
sprite: Objects/Drinks/space_mountain_wind.rsi
- type: Icon
@@ -186,10 +171,6 @@
description: Tastes like a hull breach in your mouth.
components:
- type: Drink
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 4
- type: Sprite
sprite: Objects/Drinks/space-up.rsi
- type: Icon
@@ -217,10 +198,6 @@
description: The taste of a star in liquid form. And, a bit of tuna...?
components:
- type: Drink
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 4
- type: Sprite
sprite: Objects/Drinks/starkist.rsi
- type: Icon
@@ -248,10 +225,6 @@
description: The MBO has advised crew members that consumption of Thirteen Loko may result in seizures, blindness, drunkeness, or even death. Please Drink Responsibly.
components:
- type: Drink
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 4
- type: Sprite
sprite: Objects/Drinks/thirteen_loko.rsi
- type: Icon

View File

@@ -5,8 +5,11 @@
name: Base cup
abstract: true
components:
- type: Solution
maxVol: 20
- type: Pourable
transferAmount: 5
- type: Drink
max_volume: 4
despawn_empty: false
- type: Sound
- type: Sprite
@@ -20,8 +23,8 @@
name: Golden cup
description: A golden cup
components:
- type: Drink
max_volume: 10
- type: Solution
maxVol: 10
- type: Sprite
sprite: Objects/Drinks/golden_cup.rsi
- type: Icon
@@ -33,8 +36,8 @@
name: Insulated pitcher
description: A stainless steel insulated pitcher. Everyone's best friend in the morning.
components:
- type: Drink
max_volume: 15
- type: Solution
maxVol: 15
- type: Sprite
sprite: Objects/Drinks/pitcher.rsi
state: icon-6
@@ -52,8 +55,8 @@
name: Mug
description: A plain white mug.
components:
- type: Drink
max_volume: 4
- type: Solution
maxVol: 10
- type: Sprite
sprite: Objects/Drinks/mug.rsi
state: icon-3
@@ -71,8 +74,8 @@
name: Mug Black
description: A sleek black mug.
components:
- type: Drink
max_volume: 4
- type: Solution
maxVol: 10
- type: Sprite
sprite: Objects/Drinks/mug_black.rsi
state: icon-3
@@ -90,8 +93,8 @@
name: Mug Blue
description: A blue and black mug.
components:
- type: Drink
max_volume: 4
- type: Solution
maxVol: 10
- type: Sprite
sprite: Objects/Drinks/mug_blue.rsi
state: icon-3
@@ -109,8 +112,8 @@
name: Mug Green
description: A pale green and pink mug.
components:
- type: Drink
max_volume: 4
- type: Solution
maxVol: 10
- type: Sprite
sprite: Objects/Drinks/mug_green.rsi
state: icon-3
@@ -128,8 +131,8 @@
name: Mug Heart
description: A white mug, it prominently features a red heart.
components:
- type: Drink
max_volume: 4
- type: Solution
maxVol: 10
- type: Sprite
sprite: Objects/Drinks/mug_heart.rsi
state: icon-3
@@ -147,8 +150,8 @@
name: Mug Metal
description: A metal mug. You're not sure which metal.
components:
- type: Drink
max_volume: 4
- type: Solution
maxVol: 10
- type: Sprite
sprite: Objects/Drinks/mug_metal.rsi
state: icon-3
@@ -166,8 +169,8 @@
name: Mug Moebius
description: A mug with a Moebius Laboratories logo on it. Not even your morning coffee is safe from corporate advertising.
components:
- type: Drink
max_volume: 4
- type: Solution
maxVol: 10
- type: Sprite
sprite: Objects/Drinks/mug_moebius.rsi
state: icon-3
@@ -185,8 +188,8 @@
name: "#1 mug"
description: "A white mug, it prominently features a #1."
components:
- type: Drink
max_volume: 4
- type: Solution
maxVol: 10
- type: Sprite
sprite: Objects/Drinks/mug_one.rsi
state: icon-3
@@ -204,8 +207,8 @@
name: Mug Rainbow
description: A rainbow mug. The colors are almost as blinding as a welder.
components:
- type: Drink
max_volume: 4
- type: Solution
maxVol: 10
- type: Sprite
sprite: Objects/Drinks/mug_rainbow.rsi
state: icon-3
@@ -223,8 +226,8 @@
name: Mug Red
description: A red and black mug.
components:
- type: Drink
max_volume: 4
- type: Solution
maxVol: 10
- type: Sprite
sprite: Objects/Drinks/mug_red.rsi
state: icon-3

View File

@@ -10,13 +10,12 @@
state: icon
- type: Icon
state: icon
- type: Solution
maxVol: 10
- type: Pourable
transferAmount: 5
- type: Drink
despawn_empty: false
max_volume: 10
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 0
# Containers
- type: entity
@@ -89,19 +88,6 @@
- type: Icon
sprite: Objects/TrashDrinks/ginbottle_empty.rsi
# Couldn't think of a nice place to put this
- type: entity
name: Empty glass
parent: DrinkBottleBase
id: DrinkEmptyGlass
components:
- type: Sprite
sprite: Objects/TrashDrinks/alebottle_empty.rsi
- type: Icon
sprite: Objects/TrashDrinks/alebottle_empty.rsi
- type: Solution
max_volume: 4
- type: entity
name: Goldschlager bottle
parent: DrinkBottleBase

View File

@@ -9,10 +9,11 @@
- type: Icon
texture: Objects/Chemistry/chemicals.rsi/beaker.png
- type: Solution
fillingState: beaker
maxVol: 50
caps: 27
- type: Pourable
transferAmount: 5
transferAmount: 5.0
- type: entity
name: Large Beaker
@@ -25,10 +26,11 @@
- type: Icon
texture: Objects/Chemistry/chemicals.rsi/beakerlarge.png
- type: Solution
fillingState: beakerlarge
maxVol: 100
caps: 27
- type: Pourable
transferAmount: 5
transferAmount: 5.0
- type: entity
name: Dropper
@@ -41,10 +43,12 @@
- type: Icon
texture: Objects/Chemistry/chemicals.rsi/dropper.png
- type: Solution
fillingState: dropper
fillingSteps: 2
maxVol: 5
caps: 19
- type: Pourable
transferAmount: 5
transferAmount: 5.0
- type: entity
name: Syringe
@@ -53,10 +57,12 @@
id: Syringe
components:
- type: Sprite
texture: Objects/Chemistry/chemicals.rsi/syringeproj.png
texture: Objects/Chemistry/syringe.rsi/0.png
- type: Icon
texture: Objects/Chemistry/chemicals.rsi/syringeproj.png
texture: Objects/Chemistry/syringe.rsi/0.png
- type: Solution
fillingState: syringe
fillingSteps: 5
maxVol: 15
caps: 19
- type: Injector

View File

@@ -14,11 +14,14 @@
- type: Hunger
- type: Thirst
# Organs
- type: Stomach
maxVolume: 100
digestionDelay: 20
- type: Solution
maxVol: 250
- type: Bloodstream
maxVolume: 250
max_volume: 100
- type: Stomach
max_volume: 250
digestionDelay: 20
- type: Inventory
- type: Constructor
@@ -147,8 +150,6 @@
hands:
- left
- right
# Organs
- type: Stomach
- type: Inventory
- type: Sprite

View File

@@ -15,3 +15,8 @@
- type: Examiner
DoRangeCheck: false
- type: IgnorePause
- type: Ghost
- type: Sprite
netsync: false
drawdepth: Mobs
texture: Mob/observer.png

View File

@@ -0,0 +1,103 @@
- type: reaction
id: react.ManlyDorf
reactants:
chem.Beer:
amount: 1
chem.Ale:
amount: 2
products:
chem.ManlyDorf: 3
- type: reaction
id: react.CubaLibre
reactants:
chem.Cola:
amount: 1
chem.Rum:
amount: 2
products:
chem.CubaLibre: 3
- type: reaction
id: react.IrishCream
reactants:
chem.Cream:
amount: 1
chem.Whiskey:
amount: 2
products:
chem.IrishCream: 3
- type: reaction
id: react.IrishCoffee
reactants:
chem.IrishCream:
amount: 2
chem.Coffee:
amount: 2
products:
chem.IrishCoffee: 4
- type: reaction
id: react.IrishCarBomb
reactants:
chem.IrishCream:
amount: 1
chem.Ale:
amount: 1
products:
chem.IrishCarBomb: 2
- type: reaction
id: react.B52
reactants:
chem.IrishCarBomb:
amount: 1
chem.Kahlua:
amount: 1
chem.Cognac:
amount: 1
products:
chem.B52: 3
- type: reaction
id: react.AtomicBomb
reactants:
chem.B52:
amount: 10
chem.U:
amount: 1
products:
chem.AtomicBomb: 11
- type: reaction
id: react.WhiskeyCola
reactants:
chem.Whiskey:
amount: 2
chem.Cola:
amount: 1
products:
chem.WhiskeyCola: 3
- type: reaction
id: react.SyndicateBomb
reactants:
chem.WhiskeyCola:
amount: 1
chem.Beer:
amount: 1
products:
chem.SyndicateBomb: 2
- type: reaction
id: react.Antifreeze
reactants:
chem.Vodka:
amount: 2
chem.Cream:
amount: 1
chem.Ice:
amount: 1
products:
chem.Antifreeze: 4

View File

@@ -2,6 +2,7 @@
id: chem.Nutriment
name: Nutriment
desc: Generic nutrition
color: "#664330"
metabolism:
- !type:DefaultFood
rate: 1
@@ -10,11 +11,13 @@
id: chem.H2SO4
name: Sulfuric Acid
desc: A highly corrosive, oily, colorless liquid.
color: "#BF8C00"
- type: reagent
id: chem.H2O
name: Water
desc: A tasty colorless liquid.
color: "#808080"
metabolism:
- !type:DefaultDrink
rate: 1
@@ -29,6 +32,7 @@
id: chem.Plasma
name: Plasma
desc: Funky, space-magic pixie dust. You probably shouldn't eat this, but we both know you will anyways.
color: "#500064"
- type: reagent
id: chem.Ethanol

View File

@@ -2,21 +2,126 @@
id: chem.Whiskey
name: Whiskey
desc: An alcoholic beverage made from fermented grain mash
color: "#664300"
spritePath: whiskeyglass.rsi
- type: reagent
id: chem.Ale
name: Ale
desc: A type of beer brewed using a warm fermentation method, resulting in a sweet, full-bodied and fruity taste.
color: "#664300"
spritePath: aleglass.rsi
- type: reagent
id: chem.Wine
name: Wine
desc: An alcoholic drink made from fermented grapes
color: "#7E4043"
spritePath: wineglass.rsi
- type: reagent
id: chem.Beer
name: Beer
desc: A cold pint of pale lager.
color: "#664300"
spritePath: beerglass.rsi
- type: reagent
id: chem.Vodka
name: Vodka
desc: The glass contain wodka. Xynta.
color: "#664300"
- type: reagent
id: chem.Kahlua
name: Kahlua
desc: A widely known, Mexican coffee-flavoured liqueur. In production since 1936!
color: "#664300"
spritePath: kahluaglass.rsi
- type: reagent
id: chem.Cognac
name: Cognac
desc: A sweet and strongly alcoholic drink, twice distilled and left to mature for several years. Classy as fornication.
color: "#AB3C05"
spritePath: cognacglass.rsi
- type: reagent
id: chem.ManlyDorf
name: Manly Dorf
desc: A dwarfy concoction made from ale and beer. Intended for stout dwarves only.
color: "#664300"
spritePath: manlydorfglass.rsi
- type: reagent
id: chem.CubaLibre
name: Cuba Libre
desc: A classic mix of rum and cola.
color: "#3E1B00"
spritePath: cubalibreglass.rsi
- type: reagent
id: chem.IrishCarBomb
name: Irish Car Bomb
desc: A troubling mixture of irish cream and ale.
color: "#2E6671"
spritePath: irishcarbomb.rsi
- type: reagent
id: chem.IrishCoffee
name: Irish Coffee
desc: Coffee served with irish cream. Regular cream just isn't the same!
color: "#664300"
spritePath: irishcoffeeglass.rsi
- type: reagent
id: chem.IrishCream
name: Irish Cream
desc: Whiskey-imbued cream. What else could you expect from the Irish.
color: "#664300"
spritePath: irishcreamglass.rsi
- type: reagent
id: chem.B52
name: B-52
desc: Coffee, irish cream, and cognac. You will get bombed.
color: "#664300"
spritePath: b52glass.rsi
- type: reagent
id: chem.AtomicBomb
name: Atomic Bomb
desc: Nuclear proliferation never tasted so good.
color: "#666300"
spritePath: atomicbombglass.rsi
- type: reagent
id: chem.WhiskeyCola
name: Whiskey Cola
desc: An innocent-looking mixture of cola and whiskey. Delicious.
color: "#3E1B00"
spritePath: whiskeycolaglass.rsi
- type: reagent
id: chem.SyndicateBomb
name: Syndicate Bomb
desc: Somebody set us up the bomb!
color: "#2E6671"
spritePath: syndicatebomb.rsi
- type: reagent
id: chem.Antifreeze
name: Antifreeze
desc: The ultimate refreshment.
color: "#664300"
spritePath: antifreeze.rsi
- type: reagent
id: chem.Cola
name: Cola
desc: A sweet, carbonated soft drink. Caffeine free.
color: "#100800"
metabolism:
- !type:DefaultDrink
rate: 1
@@ -25,6 +130,7 @@
id: chem.Coffee
name: Coffee
desc: A drink made from brewed coffee beans. Contains a moderate amount of caffeine.
color: "#664300"
metabolism:
- !type:DefaultDrink
rate: 1
@@ -33,6 +139,25 @@
id: chem.Tea
name: Tea
desc: A made by boiling leaves of the tea tree, Camellia sinensis.
color: "#101000"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: chem.Cream
name: Cream
desc: The fatty, still liquid part of milk. Why don't you mix this with sum scotch, eh?
color: "#DFD7AF"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: chem.Milk
name: Milk
desc: An opaque white liquid produced by the mammary glands of mammals.
color: "#DFDFDF"
metabolism:
- !type:DefaultDrink
rate: 1

View File

@@ -2,11 +2,13 @@
id: chem.H
name: Hydrogen
desc: A light, flammable gas.
color: "#808080"
- type: reagent
id: chem.O
name: Oxygen
desc: An oxidizing, colorless gas.
color: "#808080"
- type: reagent
id: chem.S
@@ -36,6 +38,7 @@
id: chem.N
name: Nitrogen
desc: A colorless, odorless unreactive gas. Highly stable.
color: "#808080"
- type: reagent
id: chem.Fe
@@ -47,6 +50,7 @@
id: chem.F
name: Fluorine
desc: A highly toxic pale yellow gas. Extremely reactive.
color: "#808080"
- type: reagent
id: chem.Si
@@ -95,3 +99,9 @@
name: Sodium
desc: A silvery-white alkali metal. Highly reactive in it's pure form.
color: "#c6c8cc"
- type: reagent
id: chem.U
name: Uranium
desc: A silvery-white metallic chemical element in the actinide series, weakly radioactive.
color: "#00ff06"

View File

@@ -0,0 +1,6 @@
- type: sound_collection
id: glassbreak
files:
- /Audio/effects/glassbreak1.ogg
- /Audio/effects/glassbreak2.ogg
- /Audio/effects/glassbreak3.ogg

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

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