Merge branch 'master' into prediction

This commit is contained in:
Pieter-Jan Briers
2020-04-18 14:07:02 +02:00
237 changed files with 4278 additions and 1948 deletions

View File

@@ -1,6 +1,7 @@
using Content.Server.Cargo;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.Cargo;
using Content.Server.GameObjects.Components.Power;
using Content.Shared.Prototypes.Cargo;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.Interfaces.GameObjects;
@@ -40,6 +41,9 @@ namespace Content.Server.GameObjects.Components.Cargo
private bool _requestOnly = false;
private PowerDeviceComponent _powerDevice;
private bool Powered => _powerDevice.Powered;
public override void Initialize()
{
base.Initialize();
@@ -47,6 +51,7 @@ namespace Content.Server.GameObjects.Components.Cargo
Orders = Owner.GetComponent<CargoOrderDatabaseComponent>();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(CargoConsoleUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
_galacticBankManager.AddComponent(this);
BankId = 0;
}
@@ -66,6 +71,8 @@ namespace Content.Server.GameObjects.Components.Cargo
var message = serverMsg.Message;
if (!Orders.ConnectedToDatabase)
return;
if (!Powered)
return;
switch (message)
{
case CargoConsoleAddOrderMessage msg:
@@ -119,6 +126,8 @@ namespace Content.Server.GameObjects.Components.Cargo
{
return;
}
if (!Powered)
return;
_userInterface.Open(actor.playerSession);
}

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

@@ -2,6 +2,7 @@
using System.Linq;
using Content.Server.GameObjects.Components.Sound;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.GameObjects.Components.Power;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects;
using Content.Shared.Chemistry;
@@ -29,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;
@@ -41,11 +42,16 @@ 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>();
///implementing PowerDeviceComponent
private PowerDeviceComponent _powerDevice;
private bool Powered => _powerDevice.Powered;
/// <summary>
/// Shows the serializer how to save/load this components yaml prototype.
/// </summary>
@@ -70,6 +76,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
_beakerContainer =
ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-reagentContainerContainer", Owner);
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
InitializeFromPrototype();
UpdateUserInterface();
@@ -115,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)
@@ -154,11 +161,14 @@ 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))
return false;
//Check if device is powered
if (!Powered)
return false;
return true;
}
@@ -172,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()
@@ -197,7 +207,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
return;
var beaker = _beakerContainer.ContainedEntity;
Solution.SolutionChanged -= HandleSolutionChangedEvent;
_beakerContainer.Remove(_beakerContainer.ContainedEntity);
UpdateUserInterface();
@@ -228,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();
}
@@ -251,6 +260,9 @@ namespace Content.Server.GameObjects.Components.Chemistry
return;
}
if (!Powered)
return;
var activeHandEntity = hands.GetActiveHand?.Owner;
if (activeHandEntity == null)
{
@@ -291,7 +303,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
else
{
_beakerContainer.Insert(activeHandEntity);
Solution.SolutionChanged += HandleSolutionChangedEvent;
UpdateUserInterface();
}
}
@@ -304,10 +315,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
return true;
}
private void HandleSolutionChangedEvent()
{
UpdateUserInterface();
}
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs) => UpdateUserInterface();
private void ClickSound()
{
@@ -316,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,180 @@ 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;
public bool NoExamine => (Capabilities & SolutionCaps.NoExamine) != 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 +261,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)
@@ -120,7 +275,16 @@ namespace Content.Server.GameObjects.Components.Chemistry
void IExamine.Examine(FormattedMessage message)
{
if (NoExamine)
{
return;
}
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 +349,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 +365,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 +387,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 +406,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 +421,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
if(!skipReactionCheck)
CheckForReaction();
OnSolutionChanged();
OnSolutionChanged(skipColor);
return true;
}
@@ -267,16 +432,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 +464,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

@@ -150,8 +150,8 @@ namespace Content.Server.GameObjects.Components
if(!entity.Transform.IsMapTransform)
continue;
// only items that can be stored in an inventory, or a player, can be eaten by a locker
if(!entity.HasComponent<StoreableComponent>() && !entity.HasComponent<IActorComponent>())
// only items that can be stored in an inventory, or a mob, can be eaten by a locker
if (!entity.HasComponent<StoreableComponent>() && !entity.HasComponent<SpeciesComponent>())
continue;
if (!AddToContents(entity))
@@ -207,7 +207,8 @@ namespace Content.Server.GameObjects.Components
private bool AddToContents(IEntity entity)
{
var collidableComponent = Owner.GetComponent<ICollidableComponent>();
if(entity.TryGetComponent<ICollidableComponent>(out var entityCollidableComponent))
ICollidableComponent entityCollidableComponent;
if (entity.TryGetComponent(out entityCollidableComponent))
{
if(MaxSize < entityCollidableComponent.WorldAABB.Size.X
|| MaxSize < entityCollidableComponent.WorldAABB.Size.Y)
@@ -247,6 +248,10 @@ namespace Content.Server.GameObjects.Components
}
Contents.Insert(entity);
entity.Transform.WorldPosition = worldPos;
if (entityCollidableComponent != null)
{
entityCollidableComponent.CollisionEnabled = false;
}
return true;
}
return false;
@@ -256,7 +261,13 @@ namespace Content.Server.GameObjects.Components
{
foreach (var contained in Contents.ContainedEntities.ToArray())
{
Contents.Remove(contained);
if(Contents.Remove(contained))
{
if (contained.TryGetComponent<ICollidableComponent>(out var entityCollidableComponent))
{
entityCollidableComponent.CollisionEnabled = true;
}
}
}
}

View File

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

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Medical;
using Content.Server.GameObjects.Components.Power;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.Container;
using Robust.Server.GameObjects.Components.UserInterface;
@@ -24,6 +25,10 @@ namespace Content.Server.GameObjects.Components.Medical
private readonly Vector2 _ejectOffset = new Vector2(-0.5f, 0f);
public bool IsOccupied => _bodyContainer.ContainedEntity != null;
///implementing PowerDeviceComponent
private PowerDeviceComponent _powerDevice;
private bool Powered => _powerDevice.Powered;
public override void Initialize()
{
base.Initialize();
@@ -31,6 +36,7 @@ namespace Content.Server.GameObjects.Components.Medical
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(MedicalScannerUiKey.Key);
_bodyContainer = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-bodyContainer", Owner);
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
UpdateUserInterface();
}
@@ -79,6 +85,8 @@ namespace Content.Server.GameObjects.Components.Medical
private void UpdateUserInterface()
{
if (!Powered)
return;
var newState = GetUserInterfaceState();
_userInterface.SetState(newState);
}
@@ -112,6 +120,8 @@ namespace Content.Server.GameObjects.Components.Medical
{
return;
}
if (!Powered)
return;
_userInterface.Open(actor.playerSession);
}

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

@@ -1,4 +1,5 @@
using Content.Server.Mobs;
using Content.Server.GameObjects.Components.Observer;
using Content.Server.Mobs;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.ViewVariables;
@@ -46,13 +47,31 @@ namespace Content.Server.GameObjects.Components.Mobs
Mind = value;
}
public override void OnRemove()
protected override void Shutdown()
{
base.OnRemove();
base.Shutdown();
if (HasMind)
{
Mind.TransferTo(null);
var visiting = Mind.VisitingEntity;
if (visiting != null)
{
if (visiting.TryGetComponent(out GhostComponent ghost))
{
ghost.CanReturnToBody = false;
}
Mind.TransferTo(visiting);
}
else
{
var ghost = Owner.EntityManager.SpawnEntity("MobObserver", Owner.Transform.GridPosition);
ghost.Name = Mind.CharacterName;
var ghostComponent = ghost.GetComponent<GhostComponent>();
ghostComponent.CanReturnToBody = false;
Mind.TransferTo(ghost);
}
}
}
}

View File

@@ -3,9 +3,12 @@ using System.Collections.Generic;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Movement;
using Content.Server.Observer;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Mobs;
using Robust.Server.GameObjects;
using Robust.Server.Interfaces.Player;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
@@ -15,7 +18,7 @@ using Robust.Shared.Serialization;
namespace Content.Server.GameObjects
{
[RegisterComponent]
public class SpeciesComponent : SharedSpeciesComponent, IActionBlocker, IOnDamageBehavior, IExAct
public class SpeciesComponent : SharedSpeciesComponent, IActionBlocker, IOnDamageBehavior, IExAct, IRelayMoveInput
{
/// <summary>
/// Damagestates are reached by reaching a certain damage threshold, they will block actions after being reached
@@ -198,6 +201,14 @@ namespace Content.Server.GameObjects
Owner.GetComponent<DamageableComponent>().TakeDamage(DamageType.Brute, bruteDamage, null);
Owner.GetComponent<DamageableComponent>().TakeDamage(DamageType.Heat, burnDamage, null);
}
void IRelayMoveInput.MoveInputPressed(IPlayerSession session)
{
if (CurrentDamageState is DeadState)
{
new Ghost().Execute(null, session, null);
}
}
}
/// <summary>

View File

@@ -56,18 +56,6 @@ namespace Content.Server.GameObjects.Components.Movement
serializer.DataField(ref _visionRadius, "vision", 8.0f);
}
/// <summary>
/// Movement speed (m/s) that the entity walks, before modifiers
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float BaseWalkSpeed { get; set; } = PlayerInputMoverComponent.DefaultBaseWalkSpeed;
/// <summary>
/// Movement speed (m/s) that the entity sprints, before modifiers
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float BaseSprintSpeed { get; set; } = PlayerInputMoverComponent.DefaultBaseSprintSpeed;
/// <summary>
/// Movement speed (m/s) that the entity walks, after modifiers
/// </summary>
@@ -76,14 +64,15 @@ namespace Content.Server.GameObjects.Components.Movement
{
get
{
float speed = BaseWalkSpeed;
if (Owner.TryGetComponent<MovementSpeedModifierComponent>(out MovementSpeedModifierComponent component))
if (Owner.TryGetComponent(out MovementSpeedModifierComponent component))
{
speed *= component.WalkSpeedModifier;
return component.CurrentWalkSpeed;
}
return speed;
return MovementSpeedModifierComponent.DefaultBaseWalkSpeed;
}
}
/// <summary>
/// Movement speed (m/s) that the entity walks, after modifiers
/// </summary>
@@ -92,12 +81,12 @@ namespace Content.Server.GameObjects.Components.Movement
{
get
{
float speed = BaseSprintSpeed;
if (Owner.TryGetComponent<MovementSpeedModifierComponent>(out MovementSpeedModifierComponent component))
if (Owner.TryGetComponent(out MovementSpeedModifierComponent component))
{
speed *= component.SprintSpeedModifier;
return component.CurrentSprintSpeed;
}
return speed;
return MovementSpeedModifierComponent.DefaultBaseSprintSpeed;
}
}

View File

@@ -1,14 +1,20 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Movement
{
[RegisterComponent]
public class MovementSpeedModifierComponent : Component
{
public const float DefaultBaseWalkSpeed = 4.0f;
public const float DefaultBaseSprintSpeed = 7.0f;
public override string Name => "MovementSpeedModifier";
private float _cachedWalkSpeedModifier = 1.0f;
[ViewVariables]
public float WalkSpeedModifier
{
get
@@ -18,6 +24,7 @@ namespace Content.Server.GameObjects.Components.Movement
}
}
private float _cachedSprintSpeedModifier;
[ViewVariables]
public float SprintSpeedModifier
{
get
@@ -27,6 +34,16 @@ namespace Content.Server.GameObjects.Components.Movement
}
}
[ViewVariables(VVAccess.ReadWrite)]
public float BaseWalkSpeed { get; set; }
[ViewVariables(VVAccess.ReadWrite)]
public float BaseSprintSpeed { get; set; }
[ViewVariables]
public float CurrentWalkSpeed => WalkSpeedModifier * BaseWalkSpeed;
[ViewVariables]
public float CurrentSprintSpeed => SprintSpeedModifier * BaseSprintSpeed;
/// <summary>
/// set to warn us that a component's movespeed modifier has changed
/// </summary>
@@ -37,6 +54,14 @@ namespace Content.Server.GameObjects.Components.Movement
_movespeedModifiersNeedRefresh = true;
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, p => p.BaseWalkSpeed, "baseWalkSpeed", 4);
serializer.DataField(this, p => p.BaseSprintSpeed, "baseSprintSpeed", 7);
}
/// <summary>
/// Recalculate movement speed with current modifiers, or return early if no change
/// </summary>

View File

@@ -22,9 +22,6 @@ namespace Content.Server.GameObjects.Components.Movement
[ComponentReference(typeof(IMoverComponent))]
public class PlayerInputMoverComponent : Component, IMoverComponent, ICollideSpecial
{
public const float DefaultBaseWalkSpeed = 4.0f;
public const float DefaultBaseSprintSpeed = 7.0f;
#pragma warning disable 649
[Dependency] private readonly IConfigurationManager _configurationManager;
#pragma warning restore 649
@@ -37,19 +34,6 @@ namespace Content.Server.GameObjects.Components.Movement
/// <inheritdoc />
public override string Name => "PlayerInputMover";
/// <summary>
/// Movement speed (m/s) that the entity walks, before modifiers
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float BaseWalkSpeed { get; set; } = DefaultBaseWalkSpeed;
/// <summary>
/// Movement speed (m/s) that the entity sprints, before modifiers
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float BaseSprintSpeed { get; set; } = DefaultBaseSprintSpeed;
/// <summary>
/// Movement speed (m/s) that the entity walks, after modifiers
/// </summary>
@@ -58,12 +42,12 @@ namespace Content.Server.GameObjects.Components.Movement
{
get
{
float speed = BaseWalkSpeed;
if (Owner.TryGetComponent<MovementSpeedModifierComponent>(out MovementSpeedModifierComponent component))
if (Owner.TryGetComponent(out MovementSpeedModifierComponent component))
{
speed *= component.WalkSpeedModifier;
return component.CurrentWalkSpeed;
}
return speed;
return MovementSpeedModifierComponent.DefaultBaseWalkSpeed;
}
}
/// <summary>
@@ -74,12 +58,12 @@ namespace Content.Server.GameObjects.Components.Movement
{
get
{
float speed = BaseSprintSpeed;
if (Owner.TryGetComponent<MovementSpeedModifierComponent>(out MovementSpeedModifierComponent component))
if (Owner.TryGetComponent(out MovementSpeedModifierComponent component))
{
speed *= component.SprintSpeedModifier;
return component.CurrentSprintSpeed;
}
return speed;
return MovementSpeedModifierComponent.DefaultBaseSprintSpeed;
}
}
@@ -104,12 +88,6 @@ namespace Content.Server.GameObjects.Components.Movement
/// </summary>
[ViewVariables] public bool DiagonalMovementEnabled => _configurationManager.GetCVar<bool>("game.diagonalmovement");
public override void Initialize()
{
base.Initialize();
_configurationManager.RegisterCVar("game.diagonalmovement", true, CVar.ARCHIVE);
}
/// <inheritdoc />
public override void OnAdd()
{
@@ -120,20 +98,6 @@ namespace Content.Server.GameObjects.Components.Movement
base.OnAdd();
}
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
//only save the base speeds - the current speeds are transient.
serializer.DataReadWriteFunction("wspd", DefaultBaseWalkSpeed, value => BaseWalkSpeed = value, () => BaseWalkSpeed);
serializer.DataReadWriteFunction("rspd", DefaultBaseSprintSpeed, value => BaseSprintSpeed = value, () => BaseSprintSpeed);
// The velocity and moving directions is usually set from player or AI input,
// so we don't want to save/load these derived fields.
}
/// <summary>
/// Toggles one of the four cardinal directions. Each of the four directions are
/// composed into a single direction vector, <see cref="VelocityDir"/>. Enabling

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();
Owner.Delete();
}
break;
case PlayerAttachedMsg msg:
msg.NewPlayer.VisibilityMask |= (int)VisibilityFlags.Ghost;
Dirty();
break;
case PlayerDetachedMsg msg:
msg.OldPlayer.VisibilityMask &= ~(int)VisibilityFlags.Ghost;
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

@@ -62,6 +62,9 @@ namespace Content.Server.GameObjects.Components.Research
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
{
if (!Powered)
return;
switch (message.Message)
{
case LatheQueueRecipeMessage msg:
@@ -87,13 +90,15 @@ namespace Content.Server.GameObjects.Components.Research
case LatheServerSyncMessage msg:
if (!Owner.TryGetComponent(out TechnologyDatabaseComponent database)
|| !Owner.TryGetComponent(out ProtolatheDatabaseComponent protoDatabase)) return;
|| !Owner.TryGetComponent(out ProtolatheDatabaseComponent protoDatabase)) return;
if(database.SyncWithServer())
if (database.SyncWithServer())
protoDatabase.Sync();
break;
}
}
internal bool Produce(LatheRecipePrototype recipe)

View File

@@ -46,6 +46,8 @@ namespace Content.Server.GameObjects.Components.Research
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
{
if (!Owner.TryGetComponent(out TechnologyDatabaseComponent database)) return;
if (!Powered)
return;
switch (message.Message)
{

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

@@ -47,6 +47,8 @@ namespace Content.Server.GameObjects.Components.VendingMachines
{
return;
}
if (!Powered)
return;
var wires = Owner.GetComponent<WiresComponent>();
if (wires.IsPanelOpen)
@@ -121,6 +123,9 @@ namespace Content.Server.GameObjects.Components.VendingMachines
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg)
{
if (!Powered)
return;
var message = serverMsg.Message;
switch (message)
{