Emergent Sanitation Gameplay (#1378)
* Emergent Sanitation Gameplay * Fix the map * Address review * Mention if it's slippery in the description
This commit is contained in:
@@ -65,17 +65,16 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
{
|
||||
if (!InteractionChecks.InRangeUnobstructed(eventArgs)) return;
|
||||
|
||||
Solution solution;
|
||||
if (CurrentVolume <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//Solution solution;
|
||||
if (eventArgs.Target == null)
|
||||
{
|
||||
if (CurrentVolume <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Drop the liquid on the mop on to the ground I guess? Potentially change by design
|
||||
// Maybe even use a toggle mode instead of "Pickup" and "dropoff"
|
||||
solution = _contents.SplitSolution(CurrentVolume);
|
||||
SpillHelper.SpillAt(eventArgs.ClickLocation, solution, "PuddleSmear");
|
||||
// Drop the liquid on the mop on to the ground
|
||||
SpillHelper.SpillAt(eventArgs.ClickLocation, _contents.SplitSolution(CurrentVolume), "PuddleSmear");
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -88,18 +87,34 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
// - _pickupAmount,
|
||||
// - whatever's left in the puddle, or
|
||||
// - whatever we can still hold (whichever's smallest)
|
||||
var transferAmount = ReagentUnit.Min(ReagentUnit.New(5), puddleComponent.CurrentVolume, MaxVolume - CurrentVolume);
|
||||
var transferAmount = ReagentUnit.Min(ReagentUnit.New(5), puddleComponent.CurrentVolume, CurrentVolume);
|
||||
bool puddleCleaned = puddleComponent.CurrentVolume - transferAmount <= 0;
|
||||
|
||||
if (transferAmount == 0)
|
||||
{
|
||||
return;
|
||||
if(puddleComponent.EmptyHolder) //The puddle doesn't actually *have* reagents, for example vomit because there's no "vomit" reagent.
|
||||
{
|
||||
puddleComponent.Owner.Delete();
|
||||
transferAmount = ReagentUnit.Min(ReagentUnit.New(5), CurrentVolume);
|
||||
puddleCleaned = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
puddleComponent.SplitSolution(transferAmount);
|
||||
}
|
||||
|
||||
solution = puddleComponent.SplitSolution(transferAmount);
|
||||
// Probably don't recolor a mop? Could work, if we layered it maybe
|
||||
if (!_contents.TryAddSolution(solution, false, true))
|
||||
if (puddleCleaned) //After cleaning the puddle, make a new puddle with solution from the mop as a "wet floor". Then evaporate it slowly.
|
||||
{
|
||||
// I can't imagine why this would happen
|
||||
throw new InvalidOperationException();
|
||||
SpillHelper.SpillAt(eventArgs.ClickLocation, _contents.SplitSolution(transferAmount), "PuddleSmear");
|
||||
}
|
||||
else
|
||||
{
|
||||
_contents.SplitSolution(transferAmount);
|
||||
}
|
||||
|
||||
// Give some visual feedback shit's happening (for anyone who can't hear sound)
|
||||
|
||||
@@ -2,7 +2,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Content.Server.GameObjects.Components.Movement;
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Server.GameObjects.EntitySystems.Click;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -15,6 +17,7 @@ using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -27,7 +30,7 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
/// Puddle on a floor
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class PuddleComponent : Component
|
||||
public class PuddleComponent : Component, IExamine
|
||||
{
|
||||
// Current design: Something calls the SpillHelper.Spill, that will either
|
||||
// A) Add to an existing puddle at the location (normalised to tile-center) or
|
||||
@@ -44,12 +47,25 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IMapManager _mapManager;
|
||||
[Dependency] private readonly ILocalizationManager _loc;
|
||||
#pragma warning restore 649
|
||||
|
||||
public override string Name => "Puddle";
|
||||
|
||||
private CancellationTokenSource _evaporationToken;
|
||||
private ReagentUnit _evaporateThreshold; // How few <Solution Quantity> we can hold prior to self-destructing
|
||||
public ReagentUnit EvaporateThreshold
|
||||
{
|
||||
get => _evaporateThreshold;
|
||||
set => _evaporateThreshold = value;
|
||||
}
|
||||
private ReagentUnit _slipThreshold = ReagentUnit.New(3);
|
||||
public ReagentUnit SlipThreshold
|
||||
{
|
||||
get => _slipThreshold;
|
||||
set => _slipThreshold = value;
|
||||
}
|
||||
private bool _slippery = false;
|
||||
private float _evaporateTime;
|
||||
private string _spillSound;
|
||||
|
||||
@@ -78,6 +94,7 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
private ReagentUnit OverflowLeft => CurrentVolume - OverflowVolume;
|
||||
|
||||
private SolutionComponent _contents;
|
||||
public bool EmptyHolder => _contents.ReagentList.Count == 0;
|
||||
private int _spriteVariants;
|
||||
// Whether the underlying solution color should be used
|
||||
private bool _recolor;
|
||||
@@ -87,11 +104,12 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
{
|
||||
serializer.DataFieldCached(ref _spillSound, "spill_sound", "/Audio/Effects/Fluids/splat.ogg");
|
||||
serializer.DataField(ref _overflowVolume, "overflow_volume", ReagentUnit.New(20));
|
||||
serializer.DataField(ref _evaporateTime, "evaporate_time", 600.0f);
|
||||
serializer.DataField(ref _evaporateTime, "evaporate_time", 5.0f);
|
||||
// Long-term probably have this based on the underlying reagents
|
||||
serializer.DataField(ref _evaporateThreshold, "evaporate_threshold", ReagentUnit.New(2));
|
||||
serializer.DataField(ref _evaporateThreshold, "evaporate_threshold", ReagentUnit.New(20));
|
||||
serializer.DataField(ref _spriteVariants, "variants", 1);
|
||||
serializer.DataField(ref _recolor, "recolor", false);
|
||||
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
@@ -121,6 +139,16 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
_spriteComponent.LayerSetState(0, $"{baseName}-{randomVariant}"); // TODO: Remove hardcode
|
||||
_spriteComponent.Rotation = Angle.FromDegrees(robustRandom.Next(0, 359));
|
||||
// UpdateAppearance should get called soon after this so shouldn't need to call Dirty() here
|
||||
|
||||
UpdateStatus();
|
||||
}
|
||||
|
||||
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
if(_slippery)
|
||||
{
|
||||
message.AddText(_loc.GetString("It looks slippery."));
|
||||
}
|
||||
}
|
||||
|
||||
// Flow rate should probably be controlled globally so this is it for now
|
||||
@@ -174,12 +202,28 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateStatus()
|
||||
public void Evaporate()
|
||||
{
|
||||
// If UpdateStatus is getting called again it means more fluid has been updated so let's just wait
|
||||
_evaporationToken?.Cancel();
|
||||
_contents.SplitSolution(ReagentUnit.Min(ReagentUnit.New(1), _contents.CurrentVolume));
|
||||
if (CurrentVolume == 0)
|
||||
{
|
||||
Owner.Delete();
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateStatus();
|
||||
}
|
||||
}
|
||||
|
||||
if (CurrentVolume > _evaporateThreshold)
|
||||
public void UpdateStatus()
|
||||
{
|
||||
_evaporationToken?.Cancel();
|
||||
if(Owner.Deleted) return;
|
||||
|
||||
UpdateAppearance();
|
||||
UpdateSlip();
|
||||
|
||||
if (_evaporateThreshold == ReagentUnit.New(-1) || CurrentVolume > _evaporateThreshold)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -187,12 +231,26 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
_evaporationToken = new CancellationTokenSource();
|
||||
|
||||
// KYS to evaporate
|
||||
Timer.Spawn(TimeSpan.FromSeconds(_evaporateTime), CheckEvaporate, _evaporationToken.Token);
|
||||
Timer.Spawn(TimeSpan.FromSeconds(_evaporateTime), Evaporate, _evaporationToken.Token);
|
||||
}
|
||||
|
||||
private void UpdateSlip()
|
||||
{
|
||||
if ((_slipThreshold == ReagentUnit.New(-1) || CurrentVolume < _slipThreshold) && Owner.TryGetComponent(out SlipperyComponent existingSlipperyComponent))
|
||||
{
|
||||
Owner.RemoveComponent<SlipperyComponent>();
|
||||
_slippery = false;
|
||||
}
|
||||
else if (CurrentVolume >= _slipThreshold && !Owner.TryGetComponent(out SlipperyComponent newSlipperyComponent))
|
||||
{
|
||||
Owner.AddComponent<SlipperyComponent>();
|
||||
_slippery = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAppearance()
|
||||
{
|
||||
if (Owner.Deleted)
|
||||
if (Owner.Deleted || EmptyHolder)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#nullable enable
|
||||
using Content.Shared.Chemistry;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
|
||||
@@ -31,11 +32,11 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
/// <param name="gridCoordinates"></param>
|
||||
/// <param name="solution">Initial solution for the prototype</param>
|
||||
/// <param name="prototype">Prototype to use</param>
|
||||
internal static void SpillAt(GridCoordinates gridCoordinates, Solution solution, string prototype)
|
||||
internal static PuddleComponent? SpillAt(GridCoordinates gridCoordinates, Solution solution, string prototype)
|
||||
{
|
||||
if (solution.TotalVolume == 0)
|
||||
{
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
@@ -48,7 +49,7 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
var tileRef = mapGrid.GetTileRef(gridCoordinates);
|
||||
if (tileRef.Tile.IsEmpty)
|
||||
{
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get normalized co-ordinate for spill location and spill it in the centre
|
||||
@@ -78,11 +79,13 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
// Did we add to an existing puddle
|
||||
if (spilt)
|
||||
{
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
var puddle = serverEntityManager.SpawnEntity(prototype, spillGridCoords);
|
||||
puddle.GetComponent<PuddleComponent>().TryAddSolution(solution);
|
||||
var newPuddleComponent = puddle.GetComponent<PuddleComponent>();
|
||||
newPuddleComponent.TryAddSolution(solution);
|
||||
return newPuddleComponent;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.GameObjects.Components;
|
||||
using Content.Shared.GameObjects.Components.Inventory;
|
||||
using Content.Server.GameObjects.EntitySystems.Click;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Interaction;
|
||||
using Content.Server.Interfaces;
|
||||
@@ -23,7 +24,7 @@ using static Content.Shared.GameObjects.SharedInventoryComponent.ClientInventory
|
||||
namespace Content.Server.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class InventoryComponent : SharedInventoryComponent, IExAct
|
||||
public class InventoryComponent : SharedInventoryComponent, IExAct, IEffectBlocker
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
|
||||
@@ -46,6 +47,18 @@ namespace Content.Server.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
bool IEffectBlocker.CanSlip()
|
||||
{
|
||||
if(Owner.TryGetComponent(out InventoryComponent inventoryComponent) &&
|
||||
inventoryComponent.TryGetSlotItem(EquipmentSlotDefines.Slots.SHOES, out ItemComponent shoes)
|
||||
)
|
||||
{
|
||||
return EffectBlockerSystem.CanSlip(shoes.Owner);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
var slots = SlotContainers.Keys.ToList();
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Items.Storage.Fill
|
||||
{
|
||||
[RegisterComponent]
|
||||
internal sealed class CustodialClosetFillComponent : Component, IMapInit
|
||||
{
|
||||
public override string Name => "CustodialClosetFill";
|
||||
|
||||
void IMapInit.MapInit()
|
||||
{
|
||||
var storage = Owner.GetComponent<IStorageComponent>();
|
||||
var random = IoCManager.Resolve<IRobustRandom>();
|
||||
|
||||
void Spawn(string prototype)
|
||||
{
|
||||
storage.Insert(Owner.EntityManager.SpawnEntity(prototype, Owner.Transform.GridPosition));
|
||||
}
|
||||
|
||||
Spawn("MopItem");
|
||||
Spawn("MopBucket");
|
||||
Spawn("WetFloorSign");
|
||||
Spawn("WetFloorSign");
|
||||
Spawn("WetFloorSign");
|
||||
Spawn("TrashBag");
|
||||
Spawn("TrashBag");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ namespace Content.Server.GameObjects.Components.Markers
|
||||
}
|
||||
}
|
||||
|
||||
private void Spawn()
|
||||
public virtual void Spawn()
|
||||
{
|
||||
if (Chance != 1.0f && !_robustRandom.Prob(Chance))
|
||||
return;
|
||||
@@ -92,7 +92,7 @@ namespace Content.Server.GameObjects.Components.Markers
|
||||
_entityManager.SpawnEntity(_robustRandom.Pick(Prototypes), Owner.Transform.GridPosition);
|
||||
}
|
||||
|
||||
public void MapInit()
|
||||
public virtual void MapInit()
|
||||
{
|
||||
_gameTicker.OnRuleAdded += RuleAdded;
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Markers;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Interfaces.GameTicking;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Logger = Robust.Shared.Log.Logger;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Markers
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class TrashSpawnerComponent : ConditionalSpawnerComponent
|
||||
{
|
||||
public override string Name => "TrashSpawner";
|
||||
|
||||
#pragma warning disable 649
|
||||
[Dependency] private IEntityManager _entityManager;
|
||||
[Dependency] private IRobustRandom _robustRandom;
|
||||
#pragma warning restore 649
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public List<string> RarePrototypes { get; set; } = new List<string>();
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
private List<string> _gameRules = new List<string>();
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float RareChance { get; set; } = 0.05f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Offset { get; set; } = 0.2f;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
serializer.DataField(this, x => RarePrototypes, "rarePrototypes", new List<string>());
|
||||
serializer.DataField(this, x => RareChance, "rareChance", 0.05f);
|
||||
serializer.DataField(this, x => Offset, "offset", 0.2f);
|
||||
}
|
||||
public override void Spawn()
|
||||
{
|
||||
if (RarePrototypes.Count > 0 && (RareChance == 1.0f || _robustRandom.Prob(RareChance)))
|
||||
{
|
||||
_entityManager.SpawnEntity(_robustRandom.Pick(RarePrototypes), Owner.Transform.GridPosition);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Chance != 1.0f && !_robustRandom.Prob(Chance))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Prototypes.Count == 0)
|
||||
{
|
||||
Logger.Warning($"Prototype list in TrashSpawnComponent is empty! Entity: {Owner}");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!Owner.Deleted)
|
||||
{
|
||||
var random = IoCManager.Resolve<IRobustRandom>();
|
||||
|
||||
var x_negative = random.Prob(0.5f) ? -1 : 1;
|
||||
var y_negative = random.Prob(0.5f) ? -1 : 1;
|
||||
|
||||
var entity = _entityManager.SpawnEntity(_robustRandom.Pick(Prototypes), Owner.Transform.GridPosition);
|
||||
entity.Transform.LocalPosition += new Vector2(random.NextFloat() * Offset * x_negative, random.NextFloat() * Offset * y_negative);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void MapInit()
|
||||
{
|
||||
Spawn();
|
||||
Owner.Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Movement
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class NoSlipComponent : Component, IEffectBlocker
|
||||
{
|
||||
public override string Name => "NoSlip";
|
||||
|
||||
bool IEffectBlocker.CanSlip() => false;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.Timers;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.Throw;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.Containers;
|
||||
@@ -90,6 +91,9 @@ namespace Content.Server.GameObjects.Components.Movement
|
||||
if (percentage < IntersectPercentage)
|
||||
return;
|
||||
|
||||
if(!EffectBlockerSystem.CanSlip(collidedWith))
|
||||
return;
|
||||
|
||||
stun.Paralyze(5f);
|
||||
_slipped.Add(collidedWith.Uid);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user