mirror of
https://github.com/DeltaV-Station/Delta-v.git
synced 2026-09-30 16:17:27 +00:00
* Added the blood writing ability to revenants (cherry picked from commit 8157d155e1f7a3840992b54475c59031d2a75f25) * Added the revenant Animate action (cherry picked from commit 1b53520849a60538598cae641c6e598b1a5d7534) * Added Animate action sprite and fixed revenant action sprites being small (cherry picked from commit 5c88ce9b21cf517023cd136ddb8aaa361f72fbfe) * Revenants in stasis can now be crafted into sentient revenant plushies Revenants in stasis can now talk (cherry picked from commit af84083da6e17bb7671d9f00993dc6bcc0b0a583) * Revenant plushies and ectoplasm now talk with their trapped revenant's name (cherry picked from commit 855b25aeb7f29ed6ff2dcc0d4ab1e589ae7e2e16) * Added a yml nerf option in case revenant is too powerful (cherry picked from commit 6895a5ca929f32267dea1e326aee62302dc151b8) * Added a yml nerf option in case revenant is too powerful (cherry picked from commit 6895a5ca929f32267dea1e326aee62302dc151b8) * Passing over salt ore now reveals revenants (cherry picked from commit beea55885f98adc72e34751ba6f798bfadd8dd31) * Salt puddles now reveal revenants Hooray for EnsureTileReaction! (cherry picked from commit b69304747e3b5aa867c5afd8917d2c170c65253e) * Bibles now reveal revenants Localization strings added (cherry picked from commit 62f7ec604748697e5fecc942c4a5d14ff682c596) * Added the revenant Haunt action (cherry picked from commit fb692c4cab3f411c11a3004fa73f30122e0ff7f9) * Haunt witnesses now have an icon over their head on the revenant's client (cherry picked from commit 5d1b3c6b0af4508ebee7aa8738f7a5f3aa9e53ca) * Slight revenant nerfs and an additional escape hatch (cherry picked from commit 490e4c525b75b03502497bfa89827f915f6c7fb2) * Fixed revenant speech name, and other fixes This is a few changes clumped into one. I should've broken this one up, but I got overzealous. - Revenant's ectoplasm now properly uses TransformSpeakerNameEvent - Revenants crafted into plushies now use VoiceOverrideComponent instead of VoiceMaskComponent - Plushies now have a custom speaking noise - Moved Impstation-specific revenant code to the _Impstation subdirectory (cherry picked from commit a5e0b0c9383e4011e69edc0592c8a4cf40b06402) * Fixed revenant haunt witness sprite not disappearing (cherry picked from commit 0c06d4a8f48f6eee324cd944dbcb1fce3258eb58) * New artifact effectr: animate nearby items (like a revenant) (cherry picked from commit 6ba1b4d8b313eda9e5ff82246ed7425e80287d0f) * Revenant cleanups * Revenant Fixes * add audio * haunt fixes * salt fixes * grinder gamer * stasis * cleanup and comments * deltav ectoplasm * clean up imp comment * event weight and guidebook gaming * catalog * alert fix * window nerf * effectprobfake * mapinit * no braincells ops Signed-off-by: Lyndomen <49795619+Lyndomen@users.noreply.github.com> * add physics * remove phyiscs * add physics realer * bible --------- Signed-off-by: Lyndomen <49795619+Lyndomen@users.noreply.github.com> Co-authored-by: TGRCDev <tgrc@tgrc.dev> Co-authored-by: TheGrimbeeper <thegrimbeeper.11@gmail.com>
383 lines
17 KiB
C#
383 lines
17 KiB
C#
using Content.Server.Kitchen.Components;
|
|
using Content.Server.Power.Components;
|
|
using Content.Server.Power.EntitySystems;
|
|
using Content.Server.Stack;
|
|
using Content.Shared.Chemistry.EntitySystems;
|
|
using Content.Shared.Chemistry.Components;
|
|
using Content.Shared.Containers.ItemSlots;
|
|
using Content.Shared.Destructible;
|
|
using Content.Shared.FixedPoint;
|
|
using Content.Shared.Interaction;
|
|
using Content.Shared.Kitchen;
|
|
using Content.Shared.Kitchen.Components;
|
|
using Content.Shared.Popups;
|
|
using Content.Shared.Random;
|
|
using Content.Shared.Stacks;
|
|
using JetBrains.Annotations;
|
|
using Robust.Server.GameObjects;
|
|
using Robust.Shared.Audio;
|
|
using Robust.Shared.Audio.Systems;
|
|
using Robust.Shared.Containers;
|
|
using Robust.Shared.Timing;
|
|
using System.Linq;
|
|
using Content.Server.Jittering;
|
|
using Content.Shared.Jittering;
|
|
using Content.Shared.Power;
|
|
|
|
namespace Content.Server.Kitchen.EntitySystems
|
|
{
|
|
[UsedImplicitly]
|
|
internal sealed class ReagentGrinderSystem : EntitySystem
|
|
{
|
|
[Dependency] private readonly IGameTiming _timing = default!;
|
|
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainersSystem = default!;
|
|
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
|
|
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
|
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
|
|
[Dependency] private readonly StackSystem _stackSystem = default!;
|
|
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
|
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
|
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
|
[Dependency] private readonly RandomHelperSystem _randomHelper = default!;
|
|
[Dependency] private readonly JitteringSystem _jitter = default!;
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
SubscribeLocalEvent<ActiveReagentGrinderComponent, ComponentStartup>(OnActiveGrinderStart);
|
|
SubscribeLocalEvent<ActiveReagentGrinderComponent, ComponentRemove>(OnActiveGrinderRemove);
|
|
SubscribeLocalEvent<ReagentGrinderComponent, ComponentStartup>((uid, _, _) => UpdateUiState(uid));
|
|
SubscribeLocalEvent((EntityUid uid, ReagentGrinderComponent _, ref PowerChangedEvent _) => UpdateUiState(uid));
|
|
SubscribeLocalEvent<ReagentGrinderComponent, InteractUsingEvent>(OnInteractUsing);
|
|
|
|
SubscribeLocalEvent<ReagentGrinderComponent, EntInsertedIntoContainerMessage>(OnContainerModified);
|
|
SubscribeLocalEvent<ReagentGrinderComponent, EntRemovedFromContainerMessage>(OnContainerModified);
|
|
SubscribeLocalEvent<ReagentGrinderComponent, ContainerIsRemovingAttemptEvent>(OnEntRemoveAttempt);
|
|
|
|
SubscribeLocalEvent<ReagentGrinderComponent, ReagentGrinderToggleAutoModeMessage>(OnToggleAutoModeMessage);
|
|
SubscribeLocalEvent<ReagentGrinderComponent, ReagentGrinderStartMessage>(OnStartMessage);
|
|
SubscribeLocalEvent<ReagentGrinderComponent, ReagentGrinderEjectChamberAllMessage>(OnEjectChamberAllMessage);
|
|
SubscribeLocalEvent<ReagentGrinderComponent, ReagentGrinderEjectChamberContentMessage>(OnEjectChamberContentMessage);
|
|
}
|
|
|
|
private void OnToggleAutoModeMessage(Entity<ReagentGrinderComponent> entity, ref ReagentGrinderToggleAutoModeMessage message)
|
|
{
|
|
entity.Comp.AutoMode = (GrinderAutoMode) (((byte) entity.Comp.AutoMode + 1) % Enum.GetValues(typeof(GrinderAutoMode)).Length);
|
|
|
|
UpdateUiState(entity);
|
|
}
|
|
|
|
public override void Update(float frameTime)
|
|
{
|
|
base.Update(frameTime);
|
|
|
|
var query = EntityQueryEnumerator<ActiveReagentGrinderComponent, ReagentGrinderComponent>();
|
|
while (query.MoveNext(out var uid, out var active, out var reagentGrinder))
|
|
{
|
|
if (active.EndTime > _timing.CurTime)
|
|
continue;
|
|
|
|
reagentGrinder.AudioStream = _audioSystem.Stop(reagentGrinder.AudioStream);
|
|
RemCompDeferred<ActiveReagentGrinderComponent>(uid);
|
|
|
|
var inputContainer = _containerSystem.EnsureContainer<Container>(uid, SharedReagentGrinder.InputContainerId);
|
|
var outputContainer = _itemSlotsSystem.GetItemOrNull(uid, SharedReagentGrinder.BeakerSlotId);
|
|
if (outputContainer is null || !_solutionContainersSystem.TryGetFitsInDispenser(outputContainer.Value, out var containerSoln, out var containerSolution))
|
|
continue;
|
|
|
|
List<EntityUid> toDelete = new(); // Imp
|
|
List<(EntityUid, int)> toSet = new(); // Imp
|
|
|
|
foreach (var item in inputContainer.ContainedEntities.ToList())
|
|
{
|
|
var solution = active.Program switch
|
|
{
|
|
GrinderProgram.Grind => TryGrindSolution(item, (uid, reagentGrinder), inputContainer.ContainedEntities), // Imp
|
|
GrinderProgram.Juice => CompOrNull<ExtractableComponent>(item)?.JuiceSolution,
|
|
_ => null,
|
|
};
|
|
|
|
if (solution is null)
|
|
continue;
|
|
|
|
if (TryComp<StackComponent>(item, out var stack))
|
|
{
|
|
var totalVolume = solution.Volume * stack.Count;
|
|
if (totalVolume <= 0)
|
|
continue;
|
|
|
|
// Maximum number of items we can process in the stack without going over AvailableVolume
|
|
// We add a small tolerance, because floats are inaccurate.
|
|
var fitsCount = (int) (stack.Count * FixedPoint2.Min(containerSolution.AvailableVolume / totalVolume + 0.01, 1));
|
|
if (fitsCount <= 0)
|
|
continue;
|
|
|
|
// Make a copy of the solution to scale
|
|
// Otherwise we'll actually change the volume of the remaining stack too
|
|
var scaledSolution = new Solution(solution);
|
|
scaledSolution.ScaleSolution(fitsCount);
|
|
solution = scaledSolution;
|
|
|
|
// _stackSystem.SetCount(item, stack.Count - fitsCount); // Setting to 0 will QueueDel // Imp
|
|
toSet.Add((item, stack.Count - fitsCount)); // Imp
|
|
|
|
}
|
|
else
|
|
{
|
|
if (solution.Volume > containerSolution.AvailableVolume)
|
|
continue;
|
|
|
|
var dev = new DestructionEventArgs();
|
|
RaiseLocalEvent(item, dev);
|
|
|
|
// QueueDel(item); // Imp
|
|
toDelete.Add(item); // Imp
|
|
}
|
|
|
|
|
|
_solutionContainersSystem.TryAddSolution(containerSoln.Value, solution);
|
|
}
|
|
|
|
// Begin Imp
|
|
foreach (var item in toDelete)
|
|
Del(item);
|
|
foreach (var (item, amount) in toSet)
|
|
_stackSystem.SetCount(item, amount); // Setting to 0 will QueueDel
|
|
// End Imp
|
|
|
|
_userInterfaceSystem.ServerSendUiMessage(uid, ReagentGrinderUiKey.Key,
|
|
new ReagentGrinderWorkCompleteMessage());
|
|
|
|
UpdateUiState(uid);
|
|
}
|
|
}
|
|
|
|
private void OnActiveGrinderStart(Entity<ActiveReagentGrinderComponent> ent, ref ComponentStartup args)
|
|
{
|
|
_jitter.AddJitter(ent, -10, 100);
|
|
}
|
|
|
|
private void OnActiveGrinderRemove(Entity<ActiveReagentGrinderComponent> ent, ref ComponentRemove args)
|
|
{
|
|
RemComp<JitteringComponent>(ent);
|
|
}
|
|
|
|
private void OnEntRemoveAttempt(Entity<ReagentGrinderComponent> entity, ref ContainerIsRemovingAttemptEvent args)
|
|
{
|
|
if (HasComp<ActiveReagentGrinderComponent>(entity))
|
|
args.Cancel();
|
|
}
|
|
|
|
private void OnContainerModified(EntityUid uid, ReagentGrinderComponent reagentGrinder, ContainerModifiedMessage args)
|
|
{
|
|
UpdateUiState(uid);
|
|
|
|
var outputContainer = _itemSlotsSystem.GetItemOrNull(uid, SharedReagentGrinder.BeakerSlotId);
|
|
_appearanceSystem.SetData(uid, ReagentGrinderVisualState.BeakerAttached, outputContainer.HasValue);
|
|
|
|
if (reagentGrinder.AutoMode != GrinderAutoMode.Off && !HasComp<ActiveReagentGrinderComponent>(uid) && this.IsPowered(uid, EntityManager))
|
|
{
|
|
var program = reagentGrinder.AutoMode == GrinderAutoMode.Grind ? GrinderProgram.Grind : GrinderProgram.Juice;
|
|
DoWork(uid, reagentGrinder, program);
|
|
}
|
|
}
|
|
|
|
private void OnInteractUsing(Entity<ReagentGrinderComponent> entity, ref InteractUsingEvent args)
|
|
{
|
|
var heldEnt = args.Used;
|
|
var inputContainer = _containerSystem.EnsureContainer<Container>(entity.Owner, SharedReagentGrinder.InputContainerId);
|
|
|
|
if (!HasComp<ExtractableComponent>(heldEnt))
|
|
{
|
|
if (!HasComp<FitsInDispenserComponent>(heldEnt))
|
|
{
|
|
// This is ugly but we can't use whitelistFailPopup because there are 2 containers with different whitelists.
|
|
_popupSystem.PopupEntity(Loc.GetString("reagent-grinder-component-cannot-put-entity-message"), entity.Owner, args.User);
|
|
}
|
|
|
|
// Entity did NOT pass the whitelist for grind/juice.
|
|
// Wouldn't want the clown grinding up the Captain's ID card now would you?
|
|
// Why am I asking you? You're biased.
|
|
return;
|
|
}
|
|
|
|
if (args.Handled)
|
|
return;
|
|
|
|
// Cap the chamber. Don't want someone putting in 500 entities and ejecting them all at once.
|
|
// Maybe I should have done that for the microwave too?
|
|
if (inputContainer.ContainedEntities.Count >= entity.Comp.StorageMaxEntities)
|
|
return;
|
|
|
|
if (!_containerSystem.Insert(heldEnt, inputContainer))
|
|
return;
|
|
|
|
args.Handled = true;
|
|
}
|
|
|
|
private void UpdateUiState(EntityUid uid)
|
|
{
|
|
ReagentGrinderComponent? grinderComp = null;
|
|
if (!Resolve(uid, ref grinderComp))
|
|
return;
|
|
|
|
var inputContainer = _containerSystem.EnsureContainer<Container>(uid, SharedReagentGrinder.InputContainerId);
|
|
var outputContainer = _itemSlotsSystem.GetItemOrNull(uid, SharedReagentGrinder.BeakerSlotId);
|
|
Solution? containerSolution = null;
|
|
var isBusy = HasComp<ActiveReagentGrinderComponent>(uid);
|
|
var canJuice = false;
|
|
var canGrind = false;
|
|
|
|
if (outputContainer is not null
|
|
&& _solutionContainersSystem.TryGetFitsInDispenser(outputContainer.Value, out _, out containerSolution)
|
|
&& inputContainer.ContainedEntities.Count > 0)
|
|
{
|
|
canGrind = inputContainer.ContainedEntities.All(CanGrind);
|
|
canJuice = inputContainer.ContainedEntities.All(CanJuice);
|
|
}
|
|
|
|
var state = new ReagentGrinderInterfaceState(
|
|
isBusy,
|
|
outputContainer.HasValue,
|
|
this.IsPowered(uid, EntityManager),
|
|
canJuice,
|
|
canGrind,
|
|
grinderComp.AutoMode,
|
|
GetNetEntityArray(inputContainer.ContainedEntities.ToArray()),
|
|
containerSolution?.Contents.ToArray()
|
|
);
|
|
_userInterfaceSystem.SetUiState(uid, ReagentGrinderUiKey.Key, state);
|
|
}
|
|
|
|
private void OnStartMessage(Entity<ReagentGrinderComponent> entity, ref ReagentGrinderStartMessage message)
|
|
{
|
|
if (!this.IsPowered(entity.Owner, EntityManager) || HasComp<ActiveReagentGrinderComponent>(entity))
|
|
return;
|
|
|
|
DoWork(entity.Owner, entity.Comp, message.Program);
|
|
}
|
|
|
|
private void OnEjectChamberAllMessage(Entity<ReagentGrinderComponent> entity, ref ReagentGrinderEjectChamberAllMessage message)
|
|
{
|
|
var inputContainer = _containerSystem.EnsureContainer<Container>(entity.Owner, SharedReagentGrinder.InputContainerId);
|
|
|
|
if (HasComp<ActiveReagentGrinderComponent>(entity) || inputContainer.ContainedEntities.Count <= 0)
|
|
return;
|
|
|
|
ClickSound(entity);
|
|
foreach (var toEject in inputContainer.ContainedEntities.ToList())
|
|
{
|
|
_containerSystem.Remove(toEject, inputContainer);
|
|
_randomHelper.RandomOffset(toEject, 0.4f);
|
|
}
|
|
UpdateUiState(entity);
|
|
}
|
|
|
|
private void OnEjectChamberContentMessage(Entity<ReagentGrinderComponent> entity, ref ReagentGrinderEjectChamberContentMessage message)
|
|
{
|
|
if (HasComp<ActiveReagentGrinderComponent>(entity))
|
|
return;
|
|
|
|
var inputContainer = _containerSystem.EnsureContainer<Container>(entity.Owner, SharedReagentGrinder.InputContainerId);
|
|
var ent = GetEntity(message.EntityId);
|
|
|
|
if (_containerSystem.Remove(ent, inputContainer))
|
|
{
|
|
_randomHelper.RandomOffset(ent, 0.4f);
|
|
ClickSound(entity);
|
|
UpdateUiState(entity);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The wzhzhzh of the grinder. Processes the contents of the grinder and puts the output in the beaker.
|
|
/// </summary>
|
|
/// <param name="uid">The grinder itself</param>
|
|
/// <param name="reagentGrinder"></param>
|
|
/// <param name="program">Which program, such as grind or juice</param>
|
|
private void DoWork(EntityUid uid, ReagentGrinderComponent reagentGrinder, GrinderProgram program)
|
|
{
|
|
var inputContainer = _containerSystem.EnsureContainer<Container>(uid, SharedReagentGrinder.InputContainerId);
|
|
var outputContainer = _itemSlotsSystem.GetItemOrNull(uid, SharedReagentGrinder.BeakerSlotId);
|
|
|
|
// Do we have anything to grind/juice and a container to put the reagents in?
|
|
if (inputContainer.ContainedEntities.Count <= 0 || !HasComp<FitsInDispenserComponent>(outputContainer))
|
|
return;
|
|
|
|
SoundSpecifier? sound;
|
|
switch (program)
|
|
{
|
|
case GrinderProgram.Grind when inputContainer.ContainedEntities.All(CanGrind):
|
|
sound = reagentGrinder.GrindSound;
|
|
break;
|
|
case GrinderProgram.Juice when inputContainer.ContainedEntities.All(CanJuice):
|
|
sound = reagentGrinder.JuiceSound;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
var active = AddComp<ActiveReagentGrinderComponent>(uid);
|
|
active.EndTime = _timing.CurTime + reagentGrinder.WorkTime * reagentGrinder.WorkTimeMultiplier;
|
|
active.Program = program;
|
|
|
|
reagentGrinder.AudioStream = _audioSystem.PlayPvs(sound, uid,
|
|
AudioParams.Default.WithPitchScale(1 / reagentGrinder.WorkTimeMultiplier))?.Entity; //slightly higher pitched
|
|
_userInterfaceSystem.ServerSendUiMessage(uid, ReagentGrinderUiKey.Key,
|
|
new ReagentGrinderWorkStartedMessage(program));
|
|
}
|
|
|
|
private void ClickSound(Entity<ReagentGrinderComponent> reagentGrinder)
|
|
{
|
|
_audioSystem.PlayPvs(reagentGrinder.Comp.ClickSound, reagentGrinder.Owner, AudioParams.Default.WithVolume(-2f));
|
|
}
|
|
|
|
// Begin Imp
|
|
private Solution? TryGrindSolution(EntityUid uid, Entity<ReagentGrinderComponent> grinder, IReadOnlyList<EntityUid> contents)
|
|
{
|
|
if (TryComp<ExtractableComponent>(uid, out var extractable)
|
|
&& extractable.GrindableSolution is not null
|
|
&& _solutionContainersSystem.TryGetSolution(uid, extractable.GrindableSolution, out _, out var solution))
|
|
{
|
|
var ev = new GrindAttemptEvent(grinder, contents);
|
|
RaiseLocalEvent(uid, ev);
|
|
|
|
if (ev.Cancelled)
|
|
return null;
|
|
|
|
return solution;
|
|
}
|
|
else
|
|
return null;
|
|
}
|
|
// End Imp
|
|
|
|
private bool CanGrind(EntityUid uid)
|
|
{
|
|
var solutionName = CompOrNull<ExtractableComponent>(uid)?.GrindableSolution;
|
|
|
|
return solutionName is not null && _solutionContainersSystem.TryGetSolution(uid, solutionName, out _, out _);
|
|
}
|
|
|
|
private bool CanJuice(EntityUid uid)
|
|
{
|
|
return CompOrNull<ExtractableComponent>(uid)?.JuiceSolution is not null;
|
|
}
|
|
|
|
// Begin Imp Changes
|
|
public sealed partial class GrindAttemptEvent : CancellableEntityEventArgs
|
|
{
|
|
public Entity<ReagentGrinderComponent> Grinder;
|
|
public IReadOnlyList<EntityUid> Reagents;
|
|
|
|
public GrindAttemptEvent(Entity<ReagentGrinderComponent> grinder, IReadOnlyList<EntityUid> reagents)
|
|
{
|
|
Grinder = grinder;
|
|
Reagents = reagents;
|
|
}
|
|
}
|
|
// End Imp Changes
|
|
}
|
|
}
|