Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions Content.Client/DeadSpace/Kitchen/PlateVisualsSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// Мёртвый Космос, Licensed under custom terms with restrictions on public hosting and commercial use, full text: https://raw.githubusercontent.com/dead-space-server/space-station-14-fobos/master/LICENSE.TXT

using System.Linq;
using System.Numerics;
using Content.Client.Items.Systems;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.DeadSpace.Kitchen.Components;
using Content.Shared.Item;
using Robust.Client.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;

namespace Content.Client.DeadSpace.Kitchen;

public sealed class PlateVisualsSystem : EntitySystem
{
private const string WorldLayerPrefix = "plate-content-";

[Dependency] private readonly ItemSystem _itemSystem = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;

private readonly Dictionary<EntityUid, HashSet<string>> _worldLayerKeys = new();

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<PlateComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<PlateComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<PlateComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
SubscribeLocalEvent<PlateComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
SubscribeLocalEvent<PlateComponent, VisualsChangedEvent>(OnVisualsChanged);
}

private void OnStartup(Entity<PlateComponent> ent, ref ComponentStartup args)
{
UpdateWorldVisuals(ent);
}

private void OnShutdown(Entity<PlateComponent> ent, ref ComponentShutdown args)
{
ClearWorldLayers(ent.Owner);
_worldLayerKeys.Remove(ent.Owner);
}

private void OnEntInserted(Entity<PlateComponent> ent, ref EntInsertedIntoContainerMessage args)
{
if (args.Container.ID != ent.Comp.SlotId)
return;

UpdateWorldVisuals(ent);
}

private void OnEntRemoved(Entity<PlateComponent> ent, ref EntRemovedFromContainerMessage args)
{
if (args.Container.ID != ent.Comp.SlotId)
return;

UpdateWorldVisuals(ent);
}

private void OnVisualsChanged(Entity<PlateComponent> ent, ref VisualsChangedEvent args)
{
if (args.ContainerId != ent.Comp.SlotId)
return;

UpdateWorldVisuals(ent);
}

private void UpdateWorldVisuals(Entity<PlateComponent> ent)
{
if (!TryComp(ent.Owner, out SpriteComponent? sprite))
return;

ClearWorldLayers(ent.Owner, sprite);

var keys = new HashSet<string>();
if (TryGetContentSprite(ent.Owner, ent.Comp, out var contentSprite))
{
AddClonedLayers(ent.Owner,
sprite,
contentSprite,
ent.Comp.ContentOffset,
ent.Comp.ContentScale,
WorldLayerPrefix,
keys);
}

if (keys.Count > 0)
_worldLayerKeys[ent.Owner] = keys;

_itemSystem.VisualsChanged(ent.Owner);
}

private void ClearWorldLayers(EntityUid uid, SpriteComponent? sprite = null)
{
if (!_worldLayerKeys.Remove(uid, out var keys))
return;

if (!Resolve(uid, ref sprite, false))
return;

foreach (var key in keys)
{
_sprite.RemoveLayer((uid, sprite), key, false);
}
}

private bool TryGetContentSprite(EntityUid plate, PlateComponent component, out SpriteComponent contentSprite)
{
var content = _itemSlots.GetItemOrNull(plate, component.SlotId);

if (content != null &&
TryComp(content.Value, out SpriteComponent? sprite) &&
sprite != null)
{
contentSprite = sprite;
return true;
}

contentSprite = default!;
return false;
}

private void AddClonedLayers(EntityUid targetUid,
SpriteComponent targetSprite,
SpriteComponent sourceSprite,
Vector2 offset,
Vector2 scale,
string keyPrefix,
ISet<string> keySink)
{
var layerIndex = 0;
var sourceEntity = sourceSprite.Owner;
Entity<SpriteComponent?> target = (targetUid, targetSprite);

foreach (var i in Enumerable.Range(0, sourceSprite.AllLayers.Count()))
{
if (!_sprite.TryGetLayer((sourceEntity, sourceSprite), i, out var sourceLayer, false) ||
!sourceLayer.Visible ||
sourceLayer.Blank ||
sourceLayer.CopyToShaderParameters != null)
{
continue;
}

var clone = new SpriteComponent.Layer(sourceLayer, targetSprite);

var key = $"{keyPrefix}{layerIndex}";
var index = _sprite.AddLayer(target, clone);
_sprite.LayerMapSet(target, key, index);

if (clone.RSI == null && sourceLayer.ActualRsi != null)
{
_sprite.LayerSetRsi(clone, sourceLayer.ActualRsi, sourceLayer.State);
_sprite.LayerSetAnimationTime(clone, sourceLayer.AnimationTime);
}

_sprite.LayerSetOffset(clone, clone.Offset + offset);
_sprite.LayerSetScale(clone, clone.Scale * scale);
keySink.Add(key);
layerIndex++;
}
}
}
29 changes: 29 additions & 0 deletions Content.Shared/DeadSpace/Kitchen/Components/PlateComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Мёртвый Космос, Licensed under custom terms with restrictions on public hosting and commercial use, full text: https://raw.githubusercontent.com/dead-space-server/space-station-14-fobos/master/LICENSE.TXT

using System.Numerics;
using Content.Shared.Item;
using Robust.Shared.Prototypes;

namespace Content.Shared.DeadSpace.Kitchen.Components;

[RegisterComponent]
public sealed partial class PlateComponent : Component
{
[DataField("slotId")]
public string SlotId = "plate_slot";

[DataField("contentOffset")]
public Vector2 ContentOffset = Vector2.Zero;

[DataField("heldContentOffsetLeft")]
public Vector2 HeldContentOffsetLeft = Vector2.Zero;

[DataField("heldContentOffsetRight")]
public Vector2 HeldContentOffsetRight = Vector2.Zero;

[DataField("contentScale")]
public Vector2 ContentScale = Vector2.One;

[DataField("maxItemSize")]
public ProtoId<ItemSizePrototype> MaxItemSize = "Normal";
}
168 changes: 168 additions & 0 deletions Content.Shared/DeadSpace/Kitchen/SharedPlateSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Мёртвый Космос, Licensed under custom terms with restrictions on public hosting and commercial use, full text: https://raw.githubusercontent.com/dead-space-server/space-station-14-fobos/master/LICENSE.TXT

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.DeadSpace.Kitchen.Components;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Item;
using Content.Shared.Nutrition;
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Verbs;
using Content.Shared.Whitelist;
using Robust.Shared.Containers;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;

namespace Content.Shared.DeadSpace.Kitchen;

public sealed class SharedPlateSystem : EntitySystem
{
private const int EatAltVerbPriority = 10;

[Dependency] private readonly SharedContainerSystem _containers = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly IngestionSystem _ingestion = default!;
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
[Dependency] private readonly SharedItemSystem _item = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;

private readonly Dictionary<ProtoId<ItemSizePrototype>, EntityWhitelist> _sizeWhitelistCache = new();

public override void Initialize()
{
base.Initialize();

if (!_net.IsClient)
SubscribeLocalEvent<PlateComponent, MapInitEvent>(OnPlateMapInit);
SubscribeLocalEvent<PlateComponent, UseInHandEvent>(OnUseInHand, before: [typeof(ItemSlotsSystem)]);
SubscribeLocalEvent<PlateComponent, GetVerbsEvent<AlternativeVerb>>(OnGetAlternativeVerbs);
SubscribeLocalEvent<HandsComponent, AccessibleOverrideEvent>(OnAccessibleOverride);
SubscribeLocalEvent<HandsComponent, InRangeOverrideEvent>(OnInRangeOverride);
}

private void OnPlateMapInit(Entity<PlateComponent> ent, ref MapInitEvent args)
{
if (!TryComp<ItemSlotsComponent>(ent.Owner, out var itemSlots) ||
!_itemSlots.TryGetSlot(ent.Owner, ent.Comp.SlotId, out var slot, itemSlots))
{
return;
}

slot.Whitelist = GetSizeWhitelist(ent.Comp.MaxItemSize);
Dirty(ent.Owner, itemSlots);
}

private void OnUseInHand(Entity<PlateComponent> ent, ref UseInHandEvent args)
{
if (args.Handled)
return;

if (!TryGetEdibleContent(ent.Owner, ent.Comp, out _))
return;

TryUsePlateContent(ent.Owner, ent.Comp, args.User);
args.Handled = true;
}

private void OnGetAlternativeVerbs(Entity<PlateComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
{
if (args.Hands == null ||
!args.CanAccess ||
!args.CanInteract ||
_hands.IsHolding((args.User, args.Hands), ent.Owner))
{
return;
}

var content = _itemSlots.GetItemOrNull(ent.Owner, ent.Comp.SlotId);
if (content == null || !TryGetIngestionVerb(args.User, content.Value, out var verb) || verb == null)
return;

var user = args.User;
if (verb.Priority < EatAltVerbPriority)
verb.Priority = EatAltVerbPriority;
verb.Act = () => TryUsePlateContent(ent.Owner, ent.Comp, user);
args.Verbs.Add(verb);
}

private bool TryUsePlateContent(EntityUid plate, PlateComponent component, EntityUid user)
{
if (!TryGetEdibleContent(plate, component, out var content))
return false;

return _ingestion.TryIngest(user, user, content.Value);
}

private bool TryGetIngestionVerb(EntityUid user, EntityUid content, [NotNullWhen(true)] out AlternativeVerb? verb)
{
verb = null;
var type = _ingestion.GetEdibleType((content, CompOrNull<EdibleComponent>(content)));
return type != null && _ingestion.TryGetIngestionVerb(user, content, type.Value, out verb);
}

private bool TryGetEdibleContent(EntityUid plate, PlateComponent component, [NotNullWhen(true)] out EntityUid? content)
{
content = _itemSlots.GetItemOrNull(plate, component.SlotId);
return content != null && _ingestion.GetEdibleType((content.Value, CompOrNull<EdibleComponent>(content.Value))) != null;
}

private void OnAccessibleOverride(Entity<HandsComponent> ent, ref AccessibleOverrideEvent args)
{
if (!TryGetPlate(args.Target, out var plate) || !_interaction.CanAccess(ent.Owner, plate.Value))
return;

args.Handled = true;
args.Accessible = true;
}

private void OnInRangeOverride(Entity<HandsComponent> ent, ref InRangeOverrideEvent args)
{
if (!TryGetPlate(args.Target, out var plate) || !_interaction.InRangeUnobstructed(ent.Owner, plate.Value))
return;

args.Handled = true;
args.InRange = true;
}

private bool TryGetPlate(EntityUid target, [NotNullWhen(true)] out EntityUid? plate)
{
plate = null;

if (!_containers.TryGetContainingContainer(target, out var container) ||
!TryComp(container.Owner, out PlateComponent? plateComp) ||
container.ID != plateComp.SlotId)
{
return false;
}

plate = container.Owner;
return true;
}

private EntityWhitelist GetSizeWhitelist(ProtoId<ItemSizePrototype> maxItemSize)
{
if (_sizeWhitelistCache.TryGetValue(maxItemSize, out var whitelist))
return whitelist;

var maxSize = _item.GetSizePrototype(maxItemSize);
whitelist = new EntityWhitelist
{
Sizes = _prototype.EnumeratePrototypes<ItemSizePrototype>()
.Where(size => size <= maxSize)
.OrderBy(size => size.Weight)
.Select(size => (ProtoId<ItemSizePrototype>) size.ID)
.ToList(),
};

_sizeWhitelistCache[maxItemSize] = whitelist;
return whitelist;
}
}
Loading
Loading