Merge 79a7bf8765 into fd5b16abb3
|
|
@ -0,0 +1,78 @@
|
|||
using Content.Client.Clothing;
|
||||
using Content.Client.Items.Systems;
|
||||
using Content.Shared._Funkystation.Stains.Components;
|
||||
using Content.Shared._Funkystation.Stains.Systems;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Hands;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client._Funkystation.Stains;
|
||||
|
||||
public sealed class StainSystem : SharedStainSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = null!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solution = null!;
|
||||
[Dependency] private readonly SpriteSystem _sprite = null!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<StainableComponent, AppearanceChangeEvent>(OnAppearanceChanged);
|
||||
SubscribeLocalEvent<StainableComponent, GetEquipmentVisualsEvent>(OnEquipmentVisuals, after: [typeof(ClientClothingSystem)]);
|
||||
SubscribeLocalEvent<StainableComponent, GetInhandVisualsEvent>(OnInhandVisuals, after: [typeof(ItemSystem)]);
|
||||
}
|
||||
|
||||
private void OnAppearanceChanged(Entity<StainableComponent> ent, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
var spriteEnt = new Entity<SpriteComponent?>(ent.Owner, args.Sprite);
|
||||
|
||||
var layers = new List<int>(ent.Comp.RevealedLayers);
|
||||
layers.Sort((a, b) => b.CompareTo(a));
|
||||
|
||||
foreach (var layer in layers)
|
||||
{
|
||||
_sprite.RemoveLayer(spriteEnt, layer);
|
||||
}
|
||||
|
||||
ent.Comp.RevealedLayers.Clear();
|
||||
|
||||
foreach (var (_, layerData) in BuildVisuals(ent, ent.Comp.IconVisuals, "icon"))
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
ent.Comp.RevealedLayers.Add(args.Sprite.AddLayer(layerData));
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEquipmentVisuals(Entity<StainableComponent> ent, ref GetEquipmentVisualsEvent args)
|
||||
{
|
||||
if (ent.Comp.ClothingVisuals.TryGetValue(args.Slot, out var layers))
|
||||
args.Layers.AddRange(BuildVisuals(ent, layers, args.Slot));
|
||||
}
|
||||
|
||||
private void OnInhandVisuals(Entity<StainableComponent> ent, ref GetInhandVisualsEvent args)
|
||||
{
|
||||
if (ent.Comp.ItemVisuals.TryGetValue(args.Location.ToString(), out var layers))
|
||||
args.Layers.AddRange(BuildVisuals(ent, layers, args.Location.ToString()));
|
||||
}
|
||||
|
||||
private IEnumerable<(string, PrototypeLayerData)> BuildVisuals(Entity<StainableComponent> ent, List<PrototypeLayerData> templates, string prefix)
|
||||
{
|
||||
if (!_solution.TryGetSolution(ent.Owner, ent.Comp.SolutionName, out _, out var sol) || sol.Volume <= FixedPoint2.Zero)
|
||||
yield break;
|
||||
|
||||
var color = sol.GetColor(_prototypeManager);
|
||||
for (var i = 0; i < templates.Count; i++)
|
||||
{
|
||||
var layer = templates[i];
|
||||
layer.Color = color;
|
||||
yield return ($"stain-{prefix}-{i}", layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
using Content.Shared._Funkystation.WashingMachine;
|
||||
|
||||
namespace Content.Client._Funkystation.WashingMachine;
|
||||
|
||||
/// <summary>
|
||||
/// This only exists for client-side prediction.
|
||||
/// </summary>
|
||||
public sealed class WashingMachineSystem : SharedWashingMachineSystem;
|
||||
|
|
@ -38,5 +38,15 @@ namespace Content.Server.Construction.Components
|
|||
// TODO Force flush interaction queue before serializing to YAML.
|
||||
// Otherwise you can end up with entities stuck in invalid states (e.g., waiting for DoAfters).
|
||||
public readonly Queue<object> InteractionQueue = new();
|
||||
|
||||
// DeltaV Start - WashingMachine shouldn't keep people inside when it breaks to a previous node.
|
||||
/// <summary>
|
||||
/// These containers should not be handled by construction and should resume their normal behavior.
|
||||
/// </summary>
|
||||
/// <example>Washing machines shouldn't transfer their entityStorage container to the previous node when broken,
|
||||
/// but instead release their content.</example>
|
||||
[DataField]
|
||||
public HashSet<string>? ForbiddenContainers;
|
||||
// DeltaV End.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ namespace Content.Server.Construction
|
|||
/// the entity does not have a <see cref="ConstructionComponent"/>.</returns>
|
||||
public bool AddContainer(EntityUid uid, string container, ConstructionComponent? construction = null)
|
||||
{
|
||||
if (!Resolve(uid, ref construction))
|
||||
if (!Resolve(uid, ref construction)
|
||||
// DeltaV - Prohibit construction to manage specific containers, so it doesn't put contents into broken states.
|
||||
|| construction.ForbiddenContainers != null && construction.ForbiddenContainers.Contains(container))
|
||||
return false;
|
||||
|
||||
return construction.Containers.Add(container);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ using Content.Shared.IdentityManagement;
|
|||
using Content.Shared.Maps;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Slippery;
|
||||
using Content.Shared._Funkystation.Fluids; // Funky - Stainable Clothing.
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
|
@ -255,8 +256,7 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
|
|||
if (!_random.Prob(0.5f))
|
||||
return;
|
||||
|
||||
if (!_solutionContainerSystem.ResolveSolution(entity.Owner, entity.Comp.SolutionName, ref entity.Comp.Solution,
|
||||
out var solution))
|
||||
if (!_solutionContainerSystem.ResolveSolution(entity.Owner, entity.Comp.SolutionName, ref entity.Comp.Solution, out var solution)) // Funky
|
||||
return;
|
||||
|
||||
Popups.PopupEntity(Loc.GetString("puddle-component-slipped-touch-reaction", ("puddle", entity.Owner)),
|
||||
|
|
@ -265,6 +265,13 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
|
|||
// Take 15% of the puddle solution
|
||||
var splitSol = _solutionContainerSystem.SplitSolution(entity.Comp.Solution.Value, solution.Volume * 0.15f);
|
||||
Reactive.DoEntityReaction(args.Slipped, splitSol, ReactionMethod.Touch);
|
||||
// Funky Start - Stain clothing on slip.
|
||||
if (splitSol.Volume > 0)
|
||||
{
|
||||
var stainEv = new SpilledOnEvent(entity.Owner, splitSol.Clone());
|
||||
RaiseLocalEvent(args.Slipped, stainEv);
|
||||
}
|
||||
// Funky End - Stain clothing on slip.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -437,6 +444,10 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
|
|||
|
||||
targets.Add(owner);
|
||||
Reactive.DoEntityReaction(owner, splitSolution, ReactionMethod.Touch);
|
||||
|
||||
if (splitSolution.Volume > 0) // Funky - Stainable Clothing.
|
||||
RaiseLocalEvent(owner, new SpilledOnEvent(entity, splitSolution.Clone()));
|
||||
|
||||
Popups.PopupEntity(Loc.GetString("spill-land-spilled-on-other",
|
||||
("spillable", entity),
|
||||
("target", Identity.Entity(owner, EntityManager))),
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
using Robust.Shared.Containers;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.Nyanotrasen.Laundry;
|
||||
using Content.Shared.Storage;
|
||||
|
||||
namespace Content.Server.Nyanotrasen.Laundry;
|
||||
|
||||
// I just wanted the sprite to change states when it broke.
|
||||
|
||||
public sealed class LaundrySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SharedWashingMachineComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<SharedWashingMachineComponent, BreakageEventArgs>(OnBreak);
|
||||
SubscribeLocalEvent<SharedWashingMachineComponent, EntInsertedIntoContainerMessage>(OnContainerModified);
|
||||
SubscribeLocalEvent<SharedWashingMachineComponent, EntRemovedFromContainerMessage>(OnContainerModified);
|
||||
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, SharedWashingMachineComponent component, MapInitEvent args)
|
||||
{
|
||||
if (!_containerSystem.TryGetContainer(uid, "storagebase", out var container))
|
||||
return;
|
||||
|
||||
_appearanceSystem.SetData(uid, StorageVisuals.HasContents, container.ContainedEntities.Count > 0);
|
||||
}
|
||||
|
||||
private void OnBreak(EntityUid uid, SharedWashingMachineComponent component, BreakageEventArgs args)
|
||||
{
|
||||
_appearanceSystem.SetData(uid, WashingMachineVisualState.Broken, true);
|
||||
}
|
||||
|
||||
private void OnContainerModified(EntityUid uid, SharedWashingMachineComponent component, ContainerModifiedMessage args)
|
||||
{
|
||||
if (args.Container.ID == "storagebase")
|
||||
_appearanceSystem.SetData(uid, StorageVisuals.HasContents, args.Container.ContainedEntities.Count > 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using Content.Shared._Funkystation.Stains.Components;
|
||||
using Content.Shared._Funkystation.Stains.Systems;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Tag;
|
||||
|
||||
namespace Content.Server._Funkystation.Stains;
|
||||
|
||||
public sealed class StainSystem : SharedStainSystem
|
||||
{
|
||||
[Dependency] private readonly TagSystem _tag = null!;
|
||||
private static readonly string ScannableDnaTag = "DNASolutionScannable";
|
||||
|
||||
protected override void OnStained(Entity<StainableComponent> ent, Entity<SolutionComponent> solution)
|
||||
{
|
||||
base.OnStained(ent, solution);
|
||||
|
||||
_tag.AddTag(ent.Owner, ScannableDnaTag);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
using Content.Shared._Funkystation.WashingMachine;
|
||||
using Content.Shared._Funkystation.Stains.Components;
|
||||
using Content.Shared._Funkystation.Stains.Systems;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Storage.Components;
|
||||
using Content.Server.Forensics;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Damage.Systems;
|
||||
|
||||
namespace Content.Server._Funkystation.WashingMachine;
|
||||
|
||||
public sealed class WashingMachineSystem : SharedWashingMachineSystem
|
||||
{
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solution = null!;
|
||||
[Dependency] private readonly SharedStainSystem _stains = null!;
|
||||
[Dependency] private readonly ForensicsSystem _forensics = null!;
|
||||
[Dependency] private readonly DamageableSystem _damageable = null!;
|
||||
[Dependency] private readonly IRobustRandom _random = null!;
|
||||
[Dependency] private readonly ReactiveSystem _reactive = null!;
|
||||
|
||||
private static readonly SoundSpecifier HitSound = new SoundCollectionSpecifier("MetalThud");
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<WashingMachineComponent, MapInitEvent>(OnMapInit);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<WashingMachineComponent> machine, ref MapInitEvent args)
|
||||
{
|
||||
Appearance.SetData(machine.Owner, WashingMachineVisuals.State, machine.Comp.State);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
var query = EntityQueryEnumerator<WashingMachineComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (comp.State != WashingMachineState.Washing || comp.NextWashingStep > Timing.CurTime)
|
||||
continue;
|
||||
|
||||
if (Timing.CurTime >= comp.WashFinishTime)
|
||||
{
|
||||
FinishWash((uid, comp));
|
||||
continue;
|
||||
}
|
||||
|
||||
comp.NextWashingStep = Timing.CurTime + comp.WashingStepCooldown;
|
||||
ProcessWashingHazards((uid, comp));
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessWashingHazards(Entity<WashingMachineComponent> machine)
|
||||
{
|
||||
if (!TryComp<EntityStorageComponent>(machine, out var storage) || storage.Contents.ContainedEntities.Count == 0)
|
||||
return;
|
||||
|
||||
var reagentSpray = new Solution();
|
||||
reagentSpray.AddReagent(machine.Comp.SprayReagent, machine.Comp.ReagentSprayAmount);
|
||||
|
||||
// We store them in a hashset as gibbing will modify the collection and cause an error.
|
||||
var entitiesToWash = storage.Contents.ContainedEntities.ToHashSet();
|
||||
var doSpray = _random.Prob(machine.Comp.ReagentSprayChance);
|
||||
var hasHeavyItems = false;
|
||||
|
||||
foreach (var item in entitiesToWash)
|
||||
{
|
||||
_damageable.TryChangeDamage(item, machine.Comp.EntityBluntDamage, true);
|
||||
|
||||
if (doSpray)
|
||||
_reactive.DoEntityReaction(item, reagentSpray, ReactionMethod.Touch);
|
||||
|
||||
if (!hasHeavyItems && !HasComp<ClothingComponent>(item))
|
||||
hasHeavyItems = true;
|
||||
}
|
||||
|
||||
if (hasHeavyItems && _random.Prob(machine.Comp.ThumpSoundChance))
|
||||
Audio.PlayPvs(HitSound, machine);
|
||||
}
|
||||
|
||||
protected override bool TryStartWash(Entity<WashingMachineComponent> machine, EntityUid user)
|
||||
{
|
||||
if (!base.TryStartWash(machine, user))
|
||||
return false;
|
||||
|
||||
machine.Comp.AudioStream = Audio.PlayPvs(machine.Comp.WashLoopSound, machine.Owner)?.Entity;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void FinishWash(Entity<WashingMachineComponent> machine)
|
||||
{
|
||||
machine.Comp.State = WashingMachineState.Idle;
|
||||
machine.Comp.WashFinishTime = null;
|
||||
machine.Comp.NextWashAllowed = Timing.CurTime + machine.Comp.Cooldown;
|
||||
|
||||
Audio.Stop(machine.Comp.AudioStream);
|
||||
Audio.PlayPvs(machine.Comp.WashFinishedSound, machine);
|
||||
Appearance.SetData(machine, WashingMachineVisuals.State, WashingMachineState.Idle);
|
||||
|
||||
var hasHeavyItems = false;
|
||||
HashSet<EntityUid> items = new();
|
||||
if (TryComp<EntityStorageComponent>(machine, out var storage))
|
||||
{
|
||||
items = storage.Contents.ContainedEntities.ToHashSet();
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (!hasHeavyItems && !HasComp<ClothingComponent>(item))
|
||||
hasHeavyItems = true;
|
||||
|
||||
if (!TryComp<StainableComponent>(item, out var stain)
|
||||
|| !_solution.TryGetSolution(item, stain.SolutionName, out var sol))
|
||||
continue;
|
||||
|
||||
if (TryComp<ForensicsComponent>(machine, out var machineForensics))
|
||||
machineForensics.DNAs.UnionWith(_forensics.GetSolutionsDNA(sol.Value.Comp.Solution));
|
||||
|
||||
_solution.RemoveAllSolution(sol.Value);
|
||||
_stains.UpdateVisuals((item, stain));
|
||||
}
|
||||
}
|
||||
|
||||
var machineEv = new WashingMachineFinishedWashingEvent(items);
|
||||
RaiseLocalEvent(machine, machineEv);
|
||||
|
||||
var itemEv = new WashingMachineWashedEvent(machine, items);
|
||||
foreach (var item in items)
|
||||
{
|
||||
RaiseLocalEvent(item, itemEv);
|
||||
}
|
||||
|
||||
UpdateForensics((machine, machine), items);
|
||||
|
||||
if (hasHeavyItems && machine.Comp.SelfDamage.AnyPositive())
|
||||
{
|
||||
_damageable.TryChangeDamage(machine.Owner, machine.Comp.SelfDamage * machine.Comp.WashTime.TotalSeconds, ignoreResistances: true);
|
||||
}
|
||||
|
||||
Storage.OpenStorage(machine);
|
||||
Dirty(machine);
|
||||
}
|
||||
|
||||
private void UpdateForensics(Entity<WashingMachineComponent> machine, HashSet<EntityUid> items)
|
||||
{
|
||||
if (!TryComp<ForensicsComponent>(machine.Owner, out var forensics))
|
||||
return;
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (!TryComp<FiberComponent>(item, out var fiber))
|
||||
continue;
|
||||
|
||||
var fiberLocale = string.IsNullOrEmpty(fiber.FiberColor)
|
||||
? Loc.GetString("forensic-fibers", ("material", fiber.FiberMaterial))
|
||||
: Loc.GetString("forensic-fibers-colored", ("color", fiber.FiberColor), ("material", fiber.FiberMaterial));
|
||||
|
||||
forensics.Fibers.Add(fiberLocale + " ; " + fiber.Fiberprint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,11 +13,13 @@ using Content.Shared.Fluids;
|
|||
using Content.Shared.Forensics.Components;
|
||||
using Content.Shared.Gibbing;
|
||||
using Content.Shared.HealthExaminable;
|
||||
using Content.Shared.Inventory; // Funky - Stainable Clothing.
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Shared.Rejuvenate;
|
||||
using Content.Shared.StatusEffectNew;
|
||||
using Content.Shared._Funkystation.Fluids; // Funky - Stainable Clothing.
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
|
@ -40,6 +42,7 @@ public abstract class SharedBloodstreamSystem : EntitySystem
|
|||
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!; // Funky - Stainable Clothing.
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
@ -461,7 +464,22 @@ public abstract class SharedBloodstreamSystem : EntitySystem
|
|||
return true;
|
||||
|
||||
tempSolution.AddSolution(leakedBlood, PrototypeManager);
|
||||
// Funky Start - Stainable Clothing.
|
||||
var stainEv = new SpilledOnEvent(ent.Owner, tempSolution, ignoreBlockers: true);
|
||||
RaiseLocalEvent(ent.Owner, stainEv);
|
||||
|
||||
var xform = Transform(ent.Owner);
|
||||
foreach (var neighbor in _lookup.GetEntitiesInRange(xform.Coordinates, 1.5f))
|
||||
{
|
||||
if (neighbor == ent.Owner || !HasComp<InventoryComponent>(neighbor))
|
||||
continue;
|
||||
|
||||
RaiseLocalEvent(neighbor, new SpilledOnEvent(ent.Owner, tempSolution));
|
||||
|
||||
if (tempSolution.Volume <= 0)
|
||||
break;
|
||||
}
|
||||
// Funky End - Stainable Clothing.
|
||||
if (tempSolution.Volume > ent.Comp.BleedPuddleThreshold)
|
||||
{
|
||||
_puddle.TrySpillAt(ent.Owner, tempSolution, out _, sound: false);
|
||||
|
|
@ -522,7 +540,22 @@ public abstract class SharedBloodstreamSystem : EntitySystem
|
|||
tempSol.AddSolution(tempSolution, PrototypeManager);
|
||||
SolutionContainer.RemoveAllSolution(ent.Comp.TemporarySolution.Value);
|
||||
}
|
||||
// Funky Start - Stainable clothing.
|
||||
var stainEv = new SpilledOnEvent(ent.Owner, tempSol, ignoreBlockers: true);
|
||||
RaiseLocalEvent(ent.Owner, stainEv);
|
||||
|
||||
var xform = Transform(ent.Owner);
|
||||
foreach (var neighbor in _lookup.GetEntitiesInRange(xform.Coordinates, 1.5f))
|
||||
{
|
||||
if (neighbor == ent.Owner || !HasComp<InventoryComponent>(neighbor))
|
||||
continue;
|
||||
|
||||
RaiseLocalEvent(neighbor, new SpilledOnEvent(ent.Owner, tempSol));
|
||||
|
||||
if (tempSol.Volume <= 0)
|
||||
break;
|
||||
}
|
||||
// Funky End - Stainable Clothing.
|
||||
_puddle.TrySpillAt(ent, tempSol, out _);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ using Content.Shared.Spillable;
|
|||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Weapons.Melee;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Content.Shared._Funkystation.Fluids; // Funky - Stainable Clothing.
|
||||
using Robust.Shared.Player;
|
||||
|
||||
|
||||
|
|
@ -166,7 +167,14 @@ public abstract partial class SharedPuddleSystem
|
|||
continue;
|
||||
|
||||
var splitSolution = _solutionContainerSystem.SplitSolution(soln.Value, totalSplit / hitCount);
|
||||
|
||||
// Funky Start - Stainable Clothing.
|
||||
if (splitSolution.Volume > 0)
|
||||
{
|
||||
// TODO: Remove the .Clone() when splashing someone doesn't evaporate the reagent anymore.
|
||||
var stainEv = new SpilledOnEvent(entity.Owner, splitSolution.Clone());
|
||||
RaiseLocalEvent(hit, stainEv);
|
||||
}
|
||||
// Funky End - Stainable Clothing.
|
||||
AdminLogger.Add(LogType.MeleeHit,
|
||||
$"{ToPrettyString(args.User):actor} "
|
||||
+ $"splashed {SharedSolutionContainerSystem.ToPrettyString(splitSolution):solution} "
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using Content.Shared._DV.Overlays; // DeltaV
|
||||
using Content.Shared._DV.Psionics.Events; // DeltaV
|
||||
using Content.Shared._Funkystation.Fluids; // Funky - Stainable Clothing
|
||||
using Content.Shared.Armor;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Chat;
|
||||
|
|
@ -118,6 +119,7 @@ public partial class InventorySystem
|
|||
SubscribeLocalEvent<InventoryComponent, GetVerbsEvent<EquipmentVerb>>(OnGetEquipmentVerbs);
|
||||
SubscribeLocalEvent<InventoryComponent, GetVerbsEvent<InnateVerb>>(OnGetInnateVerbs);
|
||||
|
||||
SubscribeLocalEvent<InventoryComponent, SpilledOnEvent>(RelayInventoryEvent); // Funky - Stainable Clothing.
|
||||
}
|
||||
|
||||
protected void RefRelayInventoryEvent<T>(EntityUid uid, InventoryComponent component, ref T args) where T : IInventoryRelayEvent
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ using Content.Shared.Movement.Systems;
|
|||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared._Funkystation.Fluids; // Funky
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Network;
|
||||
|
|
@ -120,7 +121,10 @@ public sealed class VomitSystem : EntitySystem
|
|||
// Makes a vomit solution the size of 90% of the chemicals removed from the chemstream
|
||||
solution.AddReagent(new ReagentId(VomitPrototype, _bloodstream.GetEntityBloodData((uid, bloodStream))), vomitAmount);
|
||||
}
|
||||
|
||||
// Funky Start - Stainable Clothing.
|
||||
var stainEv = new SpilledOnEvent(uid, solution.Clone());
|
||||
RaiseLocalEvent(uid, stainEv);
|
||||
// Funky End - Stainable Clothing.
|
||||
if (_puddle.TrySpillAt(uid, solution, out var puddle, false))
|
||||
{
|
||||
_forensics.TransferDna(puddle, uid, false);
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Nyanotrasen.Laundry;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class SharedWashingMachineComponent : Component { } //Hi, I'm no coder but the word "partial" used to be "sealed" o3o
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum WashingMachineVisualState : byte
|
||||
{
|
||||
Broken,
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Trigger.Components.Effects;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._DV.Trigger.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Causes a spill on the entity when triggered.
|
||||
/// If targetUser is true, it'll spill on the user instead.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class SpillOnTriggerComponent : BaseXOnTriggerComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the solution which will be used to spill on trigger.
|
||||
/// </summary>
|
||||
[DataField(required: true, tag: "solution")]
|
||||
public string SolutionName;
|
||||
|
||||
/// <summary>
|
||||
/// The inventory slots that this spill will be relayed to.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SlotFlags TargetSlots = SlotFlags.WITHOUT_POCKET;
|
||||
|
||||
/// <summary>
|
||||
/// The inventory slots that this spill will be relayed to if the target is prone.
|
||||
/// If left null, it'll copy the normal TargetSlots.
|
||||
/// </summary>
|
||||
[DataField("proneTargetSlots")]
|
||||
private SlotFlags? _proneTargetSlots;
|
||||
|
||||
[ViewVariables]
|
||||
public SlotFlags ProneTargetSlots => _proneTargetSlots ??= TargetSlots;
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
using Content.Shared._DV.Trigger.Components.Effects;
|
||||
using Content.Shared.Trigger;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Trigger;
|
||||
|
||||
namespace Content.Shared._DV.Trigger.Systems;
|
||||
namespace Content.Shared._DV.Trigger.Systems.OnTriggerSystems;
|
||||
|
||||
public sealed class CreateHitmanCardOnTriggerSystem : EntitySystem
|
||||
{
|
||||
|
|
@ -3,7 +3,7 @@ using Content.Shared._DV.Trigger.Components.Effects;
|
|||
using Content.Shared.Trigger;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Shared._DV.Trigger.Systems;
|
||||
namespace Content.Shared._DV.Trigger.Systems.OnTriggerSystems;
|
||||
|
||||
public sealed class ShowTipOnTriggerSystem : EntitySystem
|
||||
{
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
using Content.Shared._DV.Trigger.Components;
|
||||
using Content.Shared._Funkystation.Fluids;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Trigger;
|
||||
|
||||
namespace Content.Shared._DV.Trigger.Systems.OnTriggerSystems;
|
||||
|
||||
public sealed class SpillOnTriggerSystem : XOnTriggerSystem<SpillOnTriggerComponent>
|
||||
{
|
||||
[Dependency] private SharedSolutionContainerSystem _solutionContainer = default!;
|
||||
|
||||
protected override void OnTrigger(Entity<SpillOnTriggerComponent> spiller, EntityUid target, ref TriggerEvent args)
|
||||
{
|
||||
if (!_solutionContainer.TryGetSolution(spiller.Owner, spiller.Comp.SolutionName, out var solution)
|
||||
|| solution.Value.Comp.Solution.Volume <= FixedPoint2.Zero)
|
||||
return;
|
||||
|
||||
var targetSlots = HasComp<KnockedDownComponent>(target)
|
||||
? spiller.Comp.ProneTargetSlots
|
||||
: spiller.Comp.TargetSlots;
|
||||
|
||||
var spilledEvent = new SpilledOnEvent(spiller, solution.Value.Comp.Solution, targetSlots);
|
||||
RaiseLocalEvent(target, spilledEvent);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Inventory;
|
||||
|
||||
namespace Content.Shared._Funkystation.Fluids;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a fluid is spilled on an entity
|
||||
/// </summary>
|
||||
public sealed class SpilledOnEvent(EntityUid source, Solution solution, SlotFlags slotFlags = SlotFlags.WITHOUT_POCKET, bool ignoreBlockers = false) : EntityEventArgs, IInventoryRelayEvent
|
||||
{
|
||||
public EntityUid Source = source;
|
||||
public Solution Solution = solution;
|
||||
public bool IgnoreBlockers = ignoreBlockers;
|
||||
|
||||
public SlotFlags TargetSlots => slotFlags;
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
using Content.Shared.Inventory;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._Funkystation.Stains.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Prevents entities equipped in specific slots underneath this item from getting stained
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class StainBlockerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// These are the slots protected from stains by the entity with the component.
|
||||
/// </summary>
|
||||
[DataField("slots", required: true)]
|
||||
public SlotFlags BlockedSlots;
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._Funkystation.Stains.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This lets clothing be stained by blood or other reagents that aren't water.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class StainableComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The solution name. Pretty self-describing.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string SolutionName = "stain";
|
||||
|
||||
/// <summary>
|
||||
/// How much units of reagents the solution can take.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public FixedPoint2 MaxStainVolume = FixedPoint2.New(5);
|
||||
|
||||
/// <summary>
|
||||
/// The amount of units that get added to the solution with every spill on it.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public FixedPoint2 SpillTransferAmount = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// The doafter duration for removing the reagent from the solution by wringing it onto the floor.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float WringDoAfterDuration = 15f;
|
||||
|
||||
[DataField]
|
||||
public Dictionary<string, List<PrototypeLayerData>> ClothingVisuals = new();
|
||||
|
||||
[DataField]
|
||||
public Dictionary<string, List<PrototypeLayerData>> ItemVisuals = new();
|
||||
|
||||
[DataField]
|
||||
public List<PrototypeLayerData> IconVisuals = new();
|
||||
|
||||
[ViewVariables]
|
||||
public HashSet<int> RevealedLayers = new();
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class WringStainDoAfterEvent : SimpleDoAfterEvent;
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
using Content.Shared._Funkystation.Fluids;
|
||||
using Content.Shared._Funkystation.Stains.Components;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Fluids;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared._Funkystation.Stains.Systems;
|
||||
|
||||
public abstract class SharedStainSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solution = null!;
|
||||
[Dependency] private readonly SharedItemSystem _item = null!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = null!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = null!;
|
||||
[Dependency] private readonly InventorySystem _inventory = null!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = null!;
|
||||
[Dependency] private readonly SharedPuddleSystem _puddle = null!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = null!;
|
||||
|
||||
private EntityQuery<StainBlockerComponent> _stainBlockerQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<StainableComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<StainableComponent, InventoryRelayedEvent<SpilledOnEvent>>(OnSpilledOn);
|
||||
SubscribeLocalEvent<StainableComponent, GetVerbsEvent<Verb>>(OnGetVerbs);
|
||||
SubscribeLocalEvent<StainableComponent, WringStainDoAfterEvent>(OnWring);
|
||||
SubscribeLocalEvent<StainableComponent, SolutionContainerChangedEvent>(OnSolutionChanged);
|
||||
|
||||
_stainBlockerQuery = GetEntityQuery<StainBlockerComponent>();
|
||||
}
|
||||
|
||||
private void OnSolutionChanged(Entity<StainableComponent> ent, ref SolutionContainerChangedEvent args)
|
||||
{
|
||||
if (args.SolutionId == ent.Comp.SolutionName)
|
||||
UpdateVisuals(ent);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<StainableComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
if (_solution.TryGetSolution(ent.Owner, ent.Comp.SolutionName, out var sol))
|
||||
_solution.SetCanReact(sol.Value, false);
|
||||
}
|
||||
|
||||
private void OnSpilledOn(Entity<StainableComponent> clothing, ref InventoryRelayedEvent<SpilledOnEvent> args)
|
||||
{
|
||||
if (!args.Args.IgnoreBlockers && IsStainBlocked(clothing))
|
||||
return;
|
||||
|
||||
if (!_solution.TryGetSolution(clothing.Owner, clothing.Comp.SolutionName, out var stainSolution))
|
||||
return;
|
||||
|
||||
var attemptedTransferAmount = FixedPoint2.Min(args.Args.Solution.Volume, clothing.Comp.SpillTransferAmount);
|
||||
var actualTransferAmount = FixedPoint2.Min(attemptedTransferAmount, stainSolution.Value.Comp.Solution.AvailableVolume);
|
||||
// Exit early if nothing can be transferred.
|
||||
if (actualTransferAmount == 0)
|
||||
return;
|
||||
|
||||
var split = args.Args.Solution.SplitSolution(actualTransferAmount);
|
||||
|
||||
for (var i = split.Contents.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (split.Contents[i].Reagent.Prototype == "Water")
|
||||
split.RemoveReagent(split.Contents[i].Reagent, split.Contents[i].Quantity);
|
||||
}
|
||||
|
||||
if (split.Volume > 0)
|
||||
{
|
||||
_solution.TryAddSolution(stainSolution.Value, split);
|
||||
UpdateVisuals(clothing);
|
||||
OnStained(clothing, stainSolution.Value);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnStained(Entity<StainableComponent> ent, Entity<SolutionComponent> solution) { }
|
||||
|
||||
private bool IsStainBlocked(Entity<StainableComponent> ent)
|
||||
{
|
||||
if (!_container.TryGetContainingContainer(ent.Owner, out var container) || !TryComp<InventoryComponent>(container.Owner, out var inv))
|
||||
return false;
|
||||
|
||||
if (!_inventory.TryGetSlot(container.Owner, container.ID, out var slotDef, inv))
|
||||
return false;
|
||||
|
||||
foreach (var slot in inv.Slots)
|
||||
{
|
||||
if (!_inventory.TryGetSlotEntity(container.Owner, slot.Name, out var slotEnt, inv))
|
||||
continue;
|
||||
|
||||
if (_stainBlockerQuery.TryComp(slotEnt, out var blocker) && blocker.BlockedSlots.HasFlag(slotDef.SlotFlags))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void UpdateVisuals(Entity<StainableComponent> ent)
|
||||
{
|
||||
_item.VisualsChanged(ent.Owner);
|
||||
|
||||
if (TryComp<AppearanceComponent>(ent.Owner, out var app))
|
||||
{
|
||||
var toggled = true;
|
||||
if (_appearance.TryGetData(ent.Owner, StainVisuals.Toggle, out bool current, app))
|
||||
toggled = !current;
|
||||
|
||||
_appearance.SetData(ent.Owner, StainVisuals.Toggle, toggled, app);
|
||||
}
|
||||
if (_container.TryGetContainingContainer(ent.Owner, out var container))
|
||||
{
|
||||
if (TryComp<AppearanceComponent>(container.Owner, out var wearerApp))
|
||||
{
|
||||
_appearance.QueueUpdate(container.Owner, wearerApp);
|
||||
|
||||
Dirty(container.Owner, wearerApp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGetVerbs(Entity<StainableComponent> ent, ref GetVerbsEvent<Verb> args)
|
||||
{
|
||||
if (!args.CanInteract || !args.CanAccess || args.Using != ent.Owner)
|
||||
return;
|
||||
|
||||
if (!_solution.TryGetSolution(ent.Owner, ent.Comp.SolutionName, out _, out var sol) || sol.Volume <= 0)
|
||||
return;
|
||||
|
||||
var user = args.User;
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Text = Loc.GetString("stain-verb-wring"),
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/bubbles.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, user, ent.Comp.WringDoAfterDuration, new WringStainDoAfterEvent(), ent.Owner)
|
||||
{
|
||||
BreakOnMove = true,
|
||||
BreakOnDamage = true,
|
||||
NeedHand = true
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void OnWring(Entity<StainableComponent> ent, ref WringStainDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled)
|
||||
return;
|
||||
args.Handled = true;
|
||||
|
||||
if (!_solution.TryGetSolution(ent.Owner, ent.Comp.SolutionName, out var solComp, out var sol))
|
||||
return;
|
||||
|
||||
var split = _solution.SplitSolution(solComp.Value, sol.Volume);
|
||||
UpdateVisuals(ent);
|
||||
|
||||
if (_puddle.TrySpillAt(args.User, split, out _))
|
||||
_popup.PopupEntity(Loc.GetString("stain-verb-wring-success"), args.User, args.User);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum StainVisuals : byte
|
||||
{
|
||||
Toggle,
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
using Content.Shared.Storage.Components;
|
||||
using Content.Shared.Storage.EntitySystems;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Shared._Funkystation.WashingMachine;
|
||||
|
||||
public abstract class SharedWashingMachineSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly IGameTiming Timing = null!;
|
||||
[Dependency] protected readonly SharedAudioSystem Audio = null!;
|
||||
[Dependency] private readonly SharedPowerReceiverSystem _power = null!;
|
||||
[Dependency] protected readonly SharedEntityStorageSystem Storage = null!;
|
||||
[Dependency] protected readonly SharedAppearanceSystem Appearance = null!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = null!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<WashingMachineComponent, StorageOpenAttemptEvent>(OnStorageOpenAttempt);
|
||||
SubscribeLocalEvent<WashingMachineComponent, ActivateInWorldEvent>(OnActivate, before: [typeof(SharedEntityStorageSystem)]);
|
||||
SubscribeLocalEvent<WashingMachineComponent, GetVerbsEvent<ActivationVerb>>(OnGetVerbs);
|
||||
}
|
||||
|
||||
private void OnStorageOpenAttempt(Entity<WashingMachineComponent> ent, ref StorageOpenAttemptEvent args)
|
||||
{
|
||||
if (ent.Comp.State != WashingMachineState.Idle)
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void OnActivate(Entity<WashingMachineComponent> ent, ref ActivateInWorldEvent args)
|
||||
{
|
||||
if (args.Handled || !args.Complex)
|
||||
return;
|
||||
|
||||
if (ent.Comp.State != WashingMachineState.Idle || !_power.IsPowered(ent.Owner) || Storage.IsOpen(ent.Owner))
|
||||
return;
|
||||
|
||||
if (!TryComp<EntityStorageComponent>(ent, out var storage) || storage.Contents.ContainedEntities.Count == 0)
|
||||
return;
|
||||
|
||||
if (Timing.CurTime < ent.Comp.NextWashAllowed)
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("washing-machine-cooldown"), ent.Owner, args.User);
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
TryStartWash(ent, args.User);
|
||||
}
|
||||
|
||||
private void OnGetVerbs(Entity<WashingMachineComponent> ent, ref GetVerbsEvent<ActivationVerb> args)
|
||||
{
|
||||
if (!args.CanInteract || !args.CanComplexInteract)
|
||||
return;
|
||||
|
||||
if (ent.Comp.State != WashingMachineState.Idle || !_power.IsPowered(ent.Owner) || Storage.IsOpen(ent.Owner))
|
||||
return;
|
||||
|
||||
if (!TryComp<EntityStorageComponent>(ent, out var storage) || storage.Contents.ContainedEntities.Count == 0)
|
||||
return;
|
||||
|
||||
var user = args.User;
|
||||
args.Verbs.Add(new ActivationVerb
|
||||
{
|
||||
Text = Loc.GetString("washing-machine-start"),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/Spare/poweronoff.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
if (Timing.CurTime < ent.Comp.NextWashAllowed)
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("washing-machine-cooldown"), ent.Owner, user);
|
||||
return;
|
||||
}
|
||||
TryStartWash(ent, user);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected virtual bool TryStartWash(Entity<WashingMachineComponent> ent, EntityUid user)
|
||||
{
|
||||
if (ent.Comp.State != WashingMachineState.Idle || !_power.IsPowered(ent.Owner) || Storage.IsOpen(ent.Owner))
|
||||
return false;
|
||||
|
||||
if (Timing.CurTime < ent.Comp.NextWashAllowed)
|
||||
return false;
|
||||
|
||||
if (!TryComp<EntityStorageComponent>(ent, out var storage) || storage.Contents.ContainedEntities.Count == 0)
|
||||
return false;
|
||||
|
||||
ent.Comp.State = WashingMachineState.Washing;
|
||||
ent.Comp.WashFinishTime = Timing.CurTime + ent.Comp.WashTime;
|
||||
ent.Comp.NextWashingStep = Timing.CurTime + ent.Comp.WashingStepCooldown;
|
||||
|
||||
Dirty(ent.Owner, ent.Comp);
|
||||
Appearance.SetData(ent.Owner, WashingMachineVisuals.State, WashingMachineState.Washing);
|
||||
|
||||
var items = storage.Contents.ContainedEntities.ToHashSet();
|
||||
|
||||
var machineEv = new WashingMachineStartedWashingEvent(items);
|
||||
RaiseLocalEvent(ent.Owner, machineEv);
|
||||
|
||||
var itemEv = new WashingMachineIsBeingWashed(ent.Owner, items);
|
||||
foreach (var item in items)
|
||||
{
|
||||
RaiseLocalEvent(item, itemEv);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._Funkystation.WashingMachine;
|
||||
|
||||
/// <summary>
|
||||
/// This defines a machine with entityStorage capable of cleaning reagent stains on clothing.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
|
||||
public sealed partial class WashingMachineComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The duration of the washing process that determines <see cref="WashFinishTime"/>.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan WashTime = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
/// When the washing process is finished.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField, AutoPausedField]
|
||||
public TimeSpan? WashFinishTime;
|
||||
|
||||
/// <summary>
|
||||
/// The cooldown after each washing step for the next one.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan WashingStepCooldown = TimeSpan.FromSeconds(1);
|
||||
|
||||
/// <summary>
|
||||
/// The next time when washing is calculated (Damaging entities, spraying with water, etc.)
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField, AutoPausedField]
|
||||
public TimeSpan? NextWashingStep;
|
||||
|
||||
/// <summary>
|
||||
/// The cooldown length after <see cref="WashFinishTime"/> to determine <see cref="NextWashAllowed"/>.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan Cooldown = TimeSpan.FromSeconds(6);
|
||||
|
||||
/// <summary>
|
||||
/// The time when the washing machine can wash again after finishing a load of laundry.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField, AutoPausedField]
|
||||
public TimeSpan? NextWashAllowed;
|
||||
|
||||
/// <summary>
|
||||
/// The sound the washing machine makes during the washing process.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier? WashLoopSound;
|
||||
|
||||
/// <summary>
|
||||
/// The sound the washing machine makes after it finishes.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier? WashFinishedSound;
|
||||
|
||||
/// <summary>
|
||||
/// The current State of the washing machine, used for appearance.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public WashingMachineState State = WashingMachineState.Idle;
|
||||
|
||||
/// <summary>
|
||||
/// The current audio being played.
|
||||
/// </summary>
|
||||
/// <remarks>We save it so we can stop the looping audio when the process finishes.</remarks>
|
||||
public EntityUid? AudioStream;
|
||||
|
||||
/// <summary>
|
||||
/// The chance of a thump sound to occur whenever something that isn't clothing is washed.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float ThumpSoundChance = 0.8f;
|
||||
|
||||
/// <summary>
|
||||
/// The reagent to spray on entities inside the active washing machine.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string SprayReagent = "Water";
|
||||
|
||||
/// <summary>
|
||||
/// The amount of reagent to spray on entities inside the active washing machine.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float ReagentSprayAmount = 10.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The chance to spray the reagent on entities inside per step.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float ReagentSprayChance = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The damage dealt to entities within an active washing machine every <see cref="WashingStepCooldown"/>.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public DamageSpecifier EntityBluntDamage = new()
|
||||
{
|
||||
DamageDict = new Dictionary<ProtoId<DamageTypePrototype>, FixedPoint2>
|
||||
{
|
||||
{ "Blunt", 6f },
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The damage done to the washing machine itself upon finishing the washing process, multiplied by <see cref="WashTime"/>.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public DamageSpecifier SelfDamage = new()
|
||||
{
|
||||
DamageDict = new Dictionary<ProtoId<DamageTypePrototype>, FixedPoint2>
|
||||
{
|
||||
{ "Blunt", 5f },
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum WashingMachineState : byte
|
||||
{
|
||||
Idle,
|
||||
Washing,
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum WashingMachineVisuals : byte
|
||||
{
|
||||
State
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
namespace Content.Shared._Funkystation.WashingMachine;
|
||||
|
||||
public sealed class WashingMachineIsBeingWashed : EntityEventArgs
|
||||
{
|
||||
public EntityUid WashingMachine;
|
||||
public HashSet<EntityUid> Items;
|
||||
|
||||
public WashingMachineIsBeingWashed(EntityUid washingMachine, HashSet<EntityUid> items)
|
||||
{
|
||||
WashingMachine = washingMachine;
|
||||
Items = items;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class WashingMachineStartedWashingEvent : EntityEventArgs
|
||||
{
|
||||
public HashSet<EntityUid> Items;
|
||||
|
||||
public WashingMachineStartedWashingEvent(HashSet<EntityUid> items)
|
||||
{
|
||||
Items = items;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class WashingMachineWashedEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid WashingMachine;
|
||||
public HashSet<EntityUid> Items;
|
||||
|
||||
public WashingMachineWashedEvent(EntityUid washingMachine, HashSet<EntityUid> items)
|
||||
{
|
||||
WashingMachine = washingMachine;
|
||||
Items = items;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class WashingMachineFinishedWashingEvent : EntityEventArgs
|
||||
{
|
||||
public HashSet<EntityUid> Items;
|
||||
|
||||
public WashingMachineFinishedWashingEvent(HashSet<EntityUid> items)
|
||||
{
|
||||
Items = items;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
washing-machine-start = Start washing machine
|
||||
washing-machine-cooldown = The tank is still draining.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
stain-verb-wring = Wring out clothes
|
||||
stain-verb-wring-success = You wring out the cloth, spilling liquid all over the floor.
|
||||
|
|
@ -250,4 +250,8 @@ BoxPerformer: BoxPerformerDV
|
|||
|
||||
# 2026-07-04
|
||||
SpawnPointMedicalBorg: SpawnPointBorg
|
||||
SpawnPointSecurityBorg: SpawnPointBorg
|
||||
SpawnPointSecurityBorg: SpawnPointBorg
|
||||
|
||||
# 2026-07-19
|
||||
WashingMachineBroken: WashingMachine
|
||||
WashingMachineFilledClothes: WashingMachine
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@
|
|||
reagents:
|
||||
- ReagentId: Fiber
|
||||
Quantity: 10
|
||||
stain: # Funky - Stainable Clothing.
|
||||
maxVol: 5
|
||||
- type: Tag
|
||||
tags:
|
||||
- ClothMade
|
||||
|
|
@ -27,6 +29,17 @@
|
|||
damageProtection:
|
||||
flatReductions:
|
||||
Heat: 5 # the average lightbulb only does around four damage!
|
||||
# Funky Start - Stainable Clothing.
|
||||
- type: Appearance
|
||||
- type: Stainable
|
||||
clothingVisuals:
|
||||
gloves:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: gloveblood
|
||||
iconVisuals:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: glovebloodicon
|
||||
# Funky End - Stainable Clothing.
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
|
|
|
|||
|
|
@ -20,10 +20,23 @@
|
|||
reagents:
|
||||
- ReagentId: Fiber
|
||||
Quantity: 10
|
||||
stain: # Funky - Clothing stains.
|
||||
maxVol: 5
|
||||
- type: Tag
|
||||
tags:
|
||||
- ClothMade
|
||||
- WhitelistChameleon
|
||||
# Funky Start - Clothing stains.
|
||||
- type: Appearance
|
||||
- type: Stainable
|
||||
clothingVisuals:
|
||||
head:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: helmetblood
|
||||
iconVisuals:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: helmetbloodicon
|
||||
# Funky End - Clothing stains.
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
|
|
@ -141,6 +154,8 @@
|
|||
- Snout
|
||||
- HeadTop
|
||||
- HeadSide
|
||||
- type: StainBlocker # Funky - Clothing stains.
|
||||
slots: [MASK]
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
|
|
@ -189,6 +204,19 @@
|
|||
- Snout
|
||||
- HeadTop
|
||||
- HeadSide
|
||||
# Funky Start - Clothing stains.
|
||||
- type: Appearance
|
||||
- type: Stainable
|
||||
clothingVisuals:
|
||||
head:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: helmetblood
|
||||
iconVisuals:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: helmetbloodicon
|
||||
- type: StainBlocker
|
||||
slots: [MASK]
|
||||
# Funky End - Clothing stains.
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
|
|
@ -276,3 +304,14 @@
|
|||
- Hair
|
||||
- HeadTop
|
||||
- HeadSide
|
||||
# Funky Start - Clothing stains.
|
||||
- type: Appearance
|
||||
- type: Stainable
|
||||
clothingVisuals:
|
||||
head:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: helmetblood
|
||||
iconVisuals:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: helmetbloodicon
|
||||
# Funky End - Clothing stains.
|
||||
|
|
|
|||
|
|
@ -11,6 +11,17 @@
|
|||
slots: [mask]
|
||||
- type: StaticPrice
|
||||
price: 25
|
||||
# Funky Start - Stainable Clothing.
|
||||
- type: Appearance
|
||||
- type: Stainable
|
||||
clothingVisuals:
|
||||
mask:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: maskblood
|
||||
iconVisuals:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: maskbloodicon
|
||||
# Funky End - Stainable Clothing.
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
|
|
@ -53,6 +64,8 @@
|
|||
reagents:
|
||||
- ReagentId: Fiber
|
||||
Quantity: 10
|
||||
stain: # Funky - Stainable Clothing
|
||||
maxVol: 5
|
||||
- type: Tag
|
||||
tags:
|
||||
- ClothMade
|
||||
|
|
|
|||
|
|
@ -25,6 +25,16 @@
|
|||
damageCoefficient: 0.75 # Decent at stopping disablers
|
||||
- type: ExplosionResistance
|
||||
damageCoefficient: 0.90
|
||||
# Funky Start - Stainable Clothing.
|
||||
- type: Stainable
|
||||
clothingVisuals:
|
||||
outerClothing:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: armorblood
|
||||
iconVisuals:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: armorbloodicon
|
||||
# Funky End - Stainable Clothing.
|
||||
|
||||
#Standard armor vest, allowed for security and bartenders
|
||||
- type: entity
|
||||
|
|
|
|||
|
|
@ -9,6 +9,28 @@
|
|||
- type: Sprite
|
||||
state: icon
|
||||
- type: AllowSuitStorage # DeltaV - allow suit storage for all outer clothing, no whitelist
|
||||
# Funky Start - Clothing stains.
|
||||
- type: Appearance
|
||||
- type: Stainable
|
||||
clothingVisuals:
|
||||
outerClothing:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: outerclothing
|
||||
iconVisuals:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: outerclothing
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
food:
|
||||
maxVol: 30
|
||||
reagents:
|
||||
- ReagentId: Fiber
|
||||
Quantity: 30
|
||||
stain:
|
||||
maxVol: 5
|
||||
- type: StainBlocker
|
||||
slots: [INNERCLOTHING]
|
||||
# Funky End - Clothing stains.
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
|
|
@ -27,6 +49,18 @@
|
|||
- type: Tag
|
||||
tags:
|
||||
- WhitelistChameleon
|
||||
# Funky Start - Clothing stains.
|
||||
- type: Stainable
|
||||
clothingVisuals:
|
||||
outerClothing:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: suitblood
|
||||
iconVisuals:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: suitbloodicon
|
||||
- type: StainBlocker
|
||||
slots: [INNERCLOTHING]
|
||||
# Funky End - Clothing stains.
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
|
|
@ -46,6 +80,16 @@
|
|||
type: StorageBoundUserInterface
|
||||
- type: StaticPrice
|
||||
price: 70
|
||||
# Funky Start - Clothing stains.
|
||||
- type: Stainable
|
||||
clothingVisuals:
|
||||
outerClothing:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: coatblood
|
||||
iconVisuals:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: coatbloodicon
|
||||
# Funky End - Clothing stains.
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
|
|
@ -67,6 +111,16 @@
|
|||
- state: icon-open
|
||||
map: ["foldedLayer"]
|
||||
visible: false
|
||||
# Funky Start - Clothing stains.
|
||||
- type: Stainable
|
||||
clothingVisuals:
|
||||
outerClothing:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: outerclothing
|
||||
iconVisuals:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: outerclothing
|
||||
# Funky End - Clothing stains.
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
|
|
@ -159,6 +213,8 @@
|
|||
- type: CosmicTransmutable # DeltaV
|
||||
transmutesTo: ClothingOuterHardsuitCosmicCult
|
||||
requiredGlyphType: CosmicGlyphWarding
|
||||
- type: StainBlocker # Funky - Clothing stains.
|
||||
slots: [INNERCLOTHING, FEET, GLOVES]
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
|
|
@ -181,9 +237,9 @@
|
|||
- type: HeldSpeedModifier
|
||||
- type: Item
|
||||
size: Huge
|
||||
- type: Tag
|
||||
- type: Tag # DeltaV - Harpy Wings with Hardsuit
|
||||
tags:
|
||||
- HidesHarpyWings # DeltaV: Used by harpies to help render their hardsuit sprites
|
||||
- HidesHarpyWings
|
||||
- type: ProtectedFromStepTriggers
|
||||
slots: WITHOUT_POCKET
|
||||
- type: DamageOnInteractProtection
|
||||
|
|
@ -197,6 +253,8 @@
|
|||
- type: CosmicTransmutable # DeltaV
|
||||
transmutesTo: ClothingOuterHardsuitCosmicCult
|
||||
requiredGlyphType: CosmicGlyphWarding
|
||||
- type: StainBlocker # Funky - Clothing stains.
|
||||
slots: [INNERCLOTHING, FEET, GLOVES]
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterBase
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@
|
|||
reagents:
|
||||
- ReagentId: Fiber
|
||||
Quantity: 30
|
||||
stain: # Funky - Stainable Clothing.
|
||||
maxVol: 5
|
||||
- type: Tag
|
||||
tags:
|
||||
- ClothMade
|
||||
|
|
@ -486,7 +488,7 @@
|
|||
sprite: _DV/Clothing/OuterClothing/WinterCoats/medical.rsi # DeltaV - Recolor to match department color
|
||||
# clothingVisuals:
|
||||
# outerClothing:
|
||||
# - state: MED-equipped-OUTERCLOTHING
|
||||
# - state: MED-equipped-OUTERCLOTHING
|
||||
- type: Armor
|
||||
modifiers:
|
||||
coefficients:
|
||||
|
|
|
|||
|
|
@ -19,12 +19,25 @@
|
|||
reagents:
|
||||
- ReagentId: Fiber
|
||||
Quantity: 10
|
||||
stain: # Funky - Stainable Clothing.
|
||||
maxVol: 5
|
||||
- type: Tag
|
||||
tags:
|
||||
- ClothMade
|
||||
- Recyclable
|
||||
- WhitelistChameleon
|
||||
- type: ProtectedFromStepTriggers
|
||||
# Funky Start - Stainable Clothing.
|
||||
- type: Appearance
|
||||
- type: Stainable
|
||||
clothingVisuals:
|
||||
shoes:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: shoeblood
|
||||
iconVisuals:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: shoebloodicon
|
||||
# Funky End - Stainable Clothing
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
|
|
|
|||
|
|
@ -26,11 +26,31 @@
|
|||
reagents:
|
||||
- ReagentId: Fiber
|
||||
Quantity: 30
|
||||
stain: # Funky - Clothing stains.
|
||||
maxVol: 5
|
||||
- type: Tag
|
||||
tags:
|
||||
- ClothMade
|
||||
- Recyclable
|
||||
- WhitelistChameleon
|
||||
# Funky Start - Clothing stains.
|
||||
- type: Appearance
|
||||
- type: Stainable
|
||||
clothingVisuals:
|
||||
jumpsuit:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: uniformblood
|
||||
itemVisuals:
|
||||
left:
|
||||
- sprite: Effects/Stains/jumpsuit.rsi
|
||||
state: inhand-right
|
||||
right:
|
||||
- sprite: Effects/Stains/jumpsuit.rsi
|
||||
state: inhand-left
|
||||
iconVisuals:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: uniformbloodicon
|
||||
# Funky End - Clothing stains.
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
|
|
@ -45,6 +65,23 @@
|
|||
- Skirt # Delta-V : Harpies can wear this
|
||||
- ClothMade # Delta-V : Moths can eat this
|
||||
- WhitelistChameleon # Delta-V : You can set Chameleon clothes to this.
|
||||
# Funky Start - Clothing stains.
|
||||
- type: Stainable
|
||||
clothingVisuals:
|
||||
jumpsuit:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: uniformblood
|
||||
itemVisuals:
|
||||
left:
|
||||
- sprite: Effects/Stains/jumpskirt.rsi
|
||||
state: inhand-right
|
||||
right:
|
||||
- sprite: Effects/Stains/jumppskirt.rsi
|
||||
state: inhand-left
|
||||
iconVisuals:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: uniformbloodicon
|
||||
# Funky End - Clothing stains.
|
||||
|
||||
|
||||
- type: entity
|
||||
|
|
@ -74,3 +111,20 @@
|
|||
- Skirt # Delta-V : Harpies can wear this
|
||||
- ClothMade # Delta-V : Moths can eat this
|
||||
- WhitelistChameleon # Delta-V : You can set Chameleon clothes to this.
|
||||
# Funky Start - Clothing stains.
|
||||
- type: Stainable
|
||||
clothingVisuals:
|
||||
jumpsuit:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: uniformblood
|
||||
itemVisuals:
|
||||
left:
|
||||
- sprite: Effects/Stains/jumpskirt.rsi
|
||||
state: inhand-right
|
||||
right:
|
||||
- sprite: Effects/Stains/jumpskirt.rsi
|
||||
state: inhand-left
|
||||
iconVisuals:
|
||||
- sprite: _Funkystation/Effects/blood.rsi
|
||||
state: uniformbloodicon
|
||||
# Funky End - Clothing stains.
|
||||
|
|
|
|||
|
|
@ -215,7 +215,21 @@
|
|||
- type: Tag
|
||||
tags:
|
||||
- DNASolutionScannable
|
||||
- type: PuddleFootPrints # DeltaV- Begin updates from EE Blood Puddle
|
||||
# DeltaV Start.
|
||||
- type: PuddleFootPrints # Begin updates from EE Blood Puddle
|
||||
# Clothing Stains
|
||||
- type: TriggerOnStepTrigger
|
||||
keyOut: spill
|
||||
- type: TriggerOnTimedCollide
|
||||
keyOut: spill
|
||||
- type: SpillOnTrigger
|
||||
keysIn:
|
||||
- spill
|
||||
targetUser: true
|
||||
solution: puddle
|
||||
targetSlots: FEET
|
||||
proneTargetSlots: WITHOUT_POCKET
|
||||
# DeltaV End - Clothing Stains.
|
||||
|
||||
- type: entity
|
||||
name: footstep
|
||||
|
|
|
|||
|
|
@ -1,206 +0,0 @@
|
|||
- type: entity
|
||||
id: WashingMachine
|
||||
parent: BaseStructureDynamic
|
||||
name: washing machine
|
||||
description: A machine that washes clothes with a spinning steel drum in a shiny frame.
|
||||
components:
|
||||
- type: Sprite
|
||||
noRot: true
|
||||
sprite: Nyanotrasen/Structures/Machines/washer.rsi
|
||||
layers:
|
||||
# GenericVisualizer doesn't have a way to deal with multiple conditions at
|
||||
# once. So have all the layers ready to go, but don't show them unless
|
||||
# they're relevant.
|
||||
#
|
||||
# If we actually get more complex interactions with washing machines later,
|
||||
# we can take a look at making this cleaner.
|
||||
- map: ["enum.WashingMachineVisualLayers.Normal"]
|
||||
state: "normal-base"
|
||||
- map: ["enum.WashingMachineVisualLayers.Broken"]
|
||||
state: "broken-base"
|
||||
visible: false
|
||||
- map: ["enum.WashingMachineVisualLayers.Contents"]
|
||||
state: "stuff"
|
||||
visible: false
|
||||
- map: ["enum.WashingMachineVisualLayers.NormalDoor"]
|
||||
state: "normal-closed"
|
||||
- map: ["enum.WashingMachineVisualLayers.BrokenDoor"]
|
||||
state: "broken-closed"
|
||||
visible: false
|
||||
- type: Appearance
|
||||
- type: GenericVisualizer
|
||||
visuals:
|
||||
enum.WashingMachineVisualState.Broken:
|
||||
enum.WashingMachineVisualLayers.Normal:
|
||||
True: { visible: false }
|
||||
False: { visible: true }
|
||||
enum.WashingMachineVisualLayers.NormalDoor:
|
||||
True: { visible: false }
|
||||
False: { visible: true }
|
||||
enum.WashingMachineVisualLayers.Broken:
|
||||
True: { visible: true }
|
||||
False: { visible: false }
|
||||
enum.WashingMachineVisualLayers.BrokenDoor:
|
||||
True: { visible: true }
|
||||
False: { visible: false }
|
||||
enum.StorageVisuals.HasContents:
|
||||
enum.WashingMachineVisualLayers.Contents:
|
||||
True: { visible: true }
|
||||
False: { visible: false }
|
||||
enum.StorageVisuals.Open:
|
||||
enum.WashingMachineVisualLayers.NormalDoor:
|
||||
True: { state: "normal-open" }
|
||||
False: { state: "normal-closed" }
|
||||
enum.WashingMachineVisualLayers.BrokenDoor:
|
||||
True: { state: "broken-open" }
|
||||
False: { state: "broken-closed" }
|
||||
- type: WashingMachine
|
||||
- type: InteractionOutline
|
||||
- type: Physics
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
fix1:
|
||||
shape:
|
||||
!type:PhysShapeAabb
|
||||
bounds: "-0.35,-0.25,0.35,0.49"
|
||||
density: 600
|
||||
mask:
|
||||
- MachineMask
|
||||
layer:
|
||||
- MachineLayer
|
||||
- type: Climbable
|
||||
delay: 1.6
|
||||
- type: Storage
|
||||
maxItemSize: Huge # DeltaV - changed to huge (so felinids can fit)
|
||||
grid:
|
||||
- 0,0,5,5
|
||||
storageOpenSound:
|
||||
path: /Audio/Nyanotrasen/Machines/washer_open.ogg
|
||||
storageCloseSound:
|
||||
path: /Audio/Nyanotrasen/Machines/washer_close.ogg
|
||||
- type: AllowsSleepInside # DeltaV - let felinids sleep inside washing machines
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
storagebase: !type:Container
|
||||
ents: []
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
enum.StorageUiKey.Key:
|
||||
type: StorageBoundUserInterface
|
||||
- type: UseDelay
|
||||
delay: 0.5
|
||||
- type: Repairable
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
damageModifierSet: Metallic
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 200
|
||||
behaviors:
|
||||
- !type:DoActsBehavior
|
||||
acts: ["Destruction"]
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 100
|
||||
behaviors:
|
||||
- !type:PlaySoundBehavior
|
||||
sound:
|
||||
collection: MetalBreak
|
||||
- !type:SpawnEntitiesBehavior
|
||||
spawn:
|
||||
SheetSteel1:
|
||||
min: 1
|
||||
max: 1
|
||||
- !type:DoActsBehavior
|
||||
acts: ["Destruction"]
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 50
|
||||
behaviors:
|
||||
- !type:PlaySoundBehavior
|
||||
sound:
|
||||
collection: GlassBreak
|
||||
- !type:EmptyAllContainersBehaviour
|
||||
- !type:SpawnEntitiesBehavior
|
||||
spawn:
|
||||
ShardGlass:
|
||||
min: 1
|
||||
max: 1
|
||||
- !type:DoActsBehavior
|
||||
acts: ["Breakage"]
|
||||
|
||||
- type: entity
|
||||
id: WashingMachineBroken
|
||||
parent: WashingMachine
|
||||
name: washing machine
|
||||
suffix: broken
|
||||
description: A shattered mess of glass and steel that won't be washing anything anytime soon. It looks dusty.
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- map: ["enum.WashingMachineVisualLayers.Broken"]
|
||||
state: "broken-base"
|
||||
- map: ["enum.WashingMachineVisualLayers.Contents"]
|
||||
state: "stuff"
|
||||
visible: false
|
||||
- map: ["enum.WashingMachineVisualLayers.BrokenDoor"]
|
||||
state: "broken-closed"
|
||||
- type: Appearance
|
||||
- type: GenericVisualizer
|
||||
visuals:
|
||||
enum.StorageVisuals.HasContents:
|
||||
enum.WashingMachineVisualLayers.Contents:
|
||||
True: { visible: true }
|
||||
False: { visible: false }
|
||||
enum.StorageVisuals.Open:
|
||||
enum.WashingMachineVisualLayers.BrokenDoor:
|
||||
True: { state: "broken-open" }
|
||||
False: { state: "broken-closed" }
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 150
|
||||
behaviors:
|
||||
- !type:DoActsBehavior
|
||||
acts: ["Destruction"]
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 50
|
||||
behaviors:
|
||||
- !type:PlaySoundBehavior
|
||||
sound:
|
||||
collection: MetalBreak
|
||||
- !type:SpawnEntitiesBehavior
|
||||
spawn:
|
||||
SheetSteel1:
|
||||
min: 1
|
||||
max: 1
|
||||
- !type:DoActsBehavior
|
||||
acts: ["Destruction"]
|
||||
|
||||
- type: entity
|
||||
id: WashingMachineFilledClothes
|
||||
parent: WashingMachine
|
||||
name: washing machine
|
||||
suffix: random clothes
|
||||
components:
|
||||
- type: EntityTableContainerFill
|
||||
containers:
|
||||
storagebase: !type:AllSelector
|
||||
children:
|
||||
- id: Soap
|
||||
prob: 0.3
|
||||
- id: ClothingOuterWinterCoatPlaid
|
||||
prob: 0.5
|
||||
- id: ClothingUniformMNKTracksuitBlack
|
||||
prob: 0.3
|
||||
- id: ClothingCostumeNaota
|
||||
prob: 0.2
|
||||
- id: ClothingHeadBandSkull
|
||||
prob: 0.2
|
||||
- id: ClothingNeckScarfStripedBlue
|
||||
prob: 0.3
|
||||
|
||||
|
|
@ -51,6 +51,7 @@
|
|||
- WeaponSprayNozzle
|
||||
- ClothingBackpackWaterTank
|
||||
- MegaSprayBottle
|
||||
- WashingMachineCircuitboard # Funky
|
||||
|
||||
- type: latheRecipePack
|
||||
id: Instruments
|
||||
|
|
|
|||
|
|
@ -168,6 +168,7 @@
|
|||
- MegaSprayBottle
|
||||
- BorgModuleAdvancedCleaning
|
||||
- TrashBagOfHolding # DeltaV
|
||||
- WashingMachineCircuitboard # Funky
|
||||
|
||||
# Begin DeltaV Removals - waste of points nobody makes, moved into civilian mechs
|
||||
#- type: technology
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
- type: cargoProduct
|
||||
id: ServiceWashingMachineKit
|
||||
icon:
|
||||
sprite: _Funkystation/Structures/Machines/washing_machine.rsi
|
||||
state: base
|
||||
product: CrateServiceWashingMachineSet
|
||||
cost: 1500
|
||||
category: cargoproduct-category-name-service
|
||||
group: market
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
- type: entity
|
||||
id: CrateServiceWashingMachineSet
|
||||
parent: CratePlastic
|
||||
name: DIY washing machine kit
|
||||
description: A Nanotrasen Commercial Model-C washing machine, disassembled and ready for shipping. Contains small parts that may be ingested by infants.
|
||||
components:
|
||||
- type: StorageFill
|
||||
contents:
|
||||
- id: SheetSteel1
|
||||
amount: 6
|
||||
- id: CableApcStack10
|
||||
- id: MicroManipulatorStockPart
|
||||
- id: WashingMachineCircuitboard
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
- type: entity
|
||||
parent: BaseMachineCircuitboard
|
||||
id: WashingMachineCircuitboard
|
||||
name: Nanotrasen Commercial Model-C washing machine board
|
||||
description: A machine printed circuit board for an industrial-grade washing machine.
|
||||
components:
|
||||
- type: MachineBoard
|
||||
prototype: WashingMachine
|
||||
stackRequirements:
|
||||
Steel: 1
|
||||
Manipulator: 1
|
||||
Cable: 1
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
- type: entity
|
||||
parent: BaseFlatpack
|
||||
id: FlatpackWashingMachine
|
||||
name: Nanotrasen Commercial Model-C washing machine flatpack
|
||||
description: An industrial-grade washing machine, mechanically compressed into a small flatpack.
|
||||
components:
|
||||
- type: Flatpack
|
||||
entity: WashingMachine
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
- type: entity
|
||||
id: WashingMachine
|
||||
parent: [ BaseMachinePowered, ConstructibleMachine ]
|
||||
name: Nanotrasen Commercial Model-C washing machine
|
||||
description: An industrial-grade washing machine designed to clean even the grimiest of uniforms. Keep hands, hard objects and small crewmembers away from drum during cycle.
|
||||
placement:
|
||||
mode: SnapgridCenter
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Funkystation/Structures/Machines/washing_machine.rsi
|
||||
snapCardinals: true
|
||||
layers:
|
||||
- state: base
|
||||
map: [ "enum.StorageVisualLayers.Base" ]
|
||||
- state: empty
|
||||
map: [ "content" ]
|
||||
- state: door-closed
|
||||
map: [ "enum.StorageVisualLayers.Door" ]
|
||||
- state: running
|
||||
map: [ "washing" ]
|
||||
visible: false
|
||||
- type: WashingMachine
|
||||
washLoopSound:
|
||||
path: /Audio/_Funkystation/Machines/washing_loop.ogg
|
||||
params:
|
||||
loop: true
|
||||
washFinishedSound:
|
||||
path: /Audio/_Funkystation/Machines/washing_open.ogg
|
||||
- type: EntityStorage
|
||||
isCollidableWhenOpen: false
|
||||
open: false
|
||||
capacity: 4
|
||||
- type: Forensics
|
||||
canDnaBeCleaned: false
|
||||
- type: PlaceableSurface
|
||||
isPlaceable: false
|
||||
- type: Appearance
|
||||
- type: GenericVisualizer
|
||||
visuals:
|
||||
enum.StorageVisuals.Open:
|
||||
content:
|
||||
True: { visible: false }
|
||||
False: { visible: true }
|
||||
enum.StorageVisuals.HasContents:
|
||||
content:
|
||||
True: { state: full }
|
||||
False: { state: empty }
|
||||
enum.WashingMachineVisuals.State:
|
||||
washing:
|
||||
Idle: { visible: false }
|
||||
Washing: { visible: true }
|
||||
Broken: { visible: false }
|
||||
- type: EntityStorageVisuals
|
||||
stateBaseClosed: base
|
||||
stateBaseOpen: base
|
||||
stateDoorOpen: door-open
|
||||
stateDoorClosed: door-closed
|
||||
- type: Machine
|
||||
board: WashingMachineCircuitboard
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
machine_board: !type:Container
|
||||
machine_parts: !type:Container
|
||||
entity_storage: !type:Container
|
||||
- type: Construction
|
||||
forbiddenContainers:
|
||||
- entity_storage
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
damageModifierSet: Metallic
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 50
|
||||
behaviors:
|
||||
- !type:PlaySoundBehavior
|
||||
sound:
|
||||
collection: MetalBreak
|
||||
- !type:ChangeConstructionNodeBehavior
|
||||
node: machineFrame
|
||||
- !type:DoActsBehavior
|
||||
acts: ["Destruction"]
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
## Service
|
||||
|
||||
# Washing machine
|
||||
- type: latheRecipe
|
||||
parent: BaseCircuitboardRecipe
|
||||
id: WashingMachineCircuitboard
|
||||
result: WashingMachineCircuitboard
|
||||
|
After Width: | Height: | Size: 629 B |
|
After Width: | Height: | Size: 541 B |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 607 B |
|
After Width: | Height: | Size: 601 B |
|
After Width: | Height: | Size: 767 B |
|
After Width: | Height: | Size: 943 B |
|
After Width: | Height: | Size: 382 B |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 793 B |
|
After Width: | Height: | Size: 284 B |
|
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/blob/master/icons/effects/blood.dmi and modified by Will-Oliver-Br",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "uniformblood",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "armorblood",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "helmetblood",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "suitblood",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "maskblood",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "shoeblood",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "coatblood",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "gloveblood",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "outerclothing",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "itemblood"
|
||||
},
|
||||
{
|
||||
"name": "glovebloodicon"
|
||||
},
|
||||
{
|
||||
"name": "coatbloodicon"
|
||||
},
|
||||
{
|
||||
"name": "shoebloodicon"
|
||||
},
|
||||
{
|
||||
"name": "maskbloodicon"
|
||||
},
|
||||
{
|
||||
"name": "suitbloodicon"
|
||||
},
|
||||
{
|
||||
"name": "helmetbloodicon"
|
||||
},
|
||||
{
|
||||
"name": "armorbloodicon"
|
||||
},
|
||||
{
|
||||
"name": "uniformbloodicon"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 864 B |
|
After Width: | Height: | Size: 671 B |
|
After Width: | Height: | Size: 413 B |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 673 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 539 B |
|
After Width: | Height: | Size: 488 B |
|
After Width: | Height: | Size: 339 B |
|
After Width: | Height: | Size: 390 B |
|
After Width: | Height: | Size: 176 B |
|
After Width: | Height: | Size: 261 B |
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "By AraiMaia for Funky Station",
|
||||
"size":
|
||||
{
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states":
|
||||
[{
|
||||
"name": "base"
|
||||
},
|
||||
{
|
||||
"name": "empty"
|
||||
},
|
||||
{
|
||||
"name": "door-open"
|
||||
},
|
||||
{
|
||||
"name": "door-closed"
|
||||
},
|
||||
{
|
||||
"name": "full"
|
||||
},
|
||||
{
|
||||
"name": "panel"
|
||||
},
|
||||
{
|
||||
"name": "running",
|
||||
"delays":
|
||||
[
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
}]
|
||||
}
|
||||
|
After Width: | Height: | Size: 347 B |
|
After Width: | Height: | Size: 1.2 KiB |