Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Разработка печати-хамелеона #75

Closed
84 changes: 84 additions & 0 deletions Content.Client/Stories/ChameleonStamp/ChameleonStampSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System.Linq;
using Content.Shared.Stories.ChameleonStamp;
using Content.Shared.Inventory;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.Log;
using Content.Shared.Paper;

namespace Content.Client.Stories.ChameleonStamp
{
public sealed class ChameleonStampSystem : SharedChameleonStampSystem
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IComponentFactory _factory = default!;

private readonly List<string> _data = new List<string>();

public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ChameleonStampComponent, AfterAutoHandleStateEvent>(HandleState);
PrepareAllVariants();
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnProtoReloaded);
}

private void OnProtoReloaded(PrototypesReloadedEventArgs args)
{
if (args.WasModified<EntityPrototype>())
{
PrepareAllVariants();
}
}

private void HandleState(EntityUid uid, ChameleonStampComponent component, ref AfterAutoHandleStateEvent args)
{
Logger.Info($"Обработка состояния для сущности с UID: {uid}");
UpdateVisuals(uid, component);
}

protected override void UpdateSprite(EntityUid uid, EntityPrototype proto)
{
base.UpdateSprite(uid, proto);

if (TryComp(uid, out SpriteComponent? sprite)
&& proto.TryGetComponent(out SpriteComponent? otherSprite, _factory))
{
sprite.CopyFrom(otherSprite);
}
}

public IEnumerable<string> GetValidTargets()
{
var set = new HashSet<string>();

foreach (var proto in _data)
{
Logger.Info($"Добавление прототипа {proto} в список");
set.UnionWith(_data);
}
return set;
}

private void PrepareAllVariants()
{
_data.Clear();
var prototypes = _proto.EnumeratePrototypes<EntityPrototype>();

foreach (var proto in prototypes)
{
// проверка, является ли это допустимой одеждой
if (!IsValidTarget(proto))
{
continue;
}
if (!proto.TryGetComponent(out StampComponent? item, _factory))
{
continue;
}
_data.Add(proto.ID);
Logger.Info($"Добавлен прототип {proto.ID}");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Content.Client.Clothing.Systems;
using Content.Shared.Clothing.Components;
using Content.Client.Clothing.UI;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Content.Shared.Stories.ChameleonStamp;
namespace Content.Client.Stories.ChameleonStamp.UI;

[UsedImplicitly]
public sealed class ChameleonStampBoundUserInterface : BoundUserInterface
{
private readonly ChameleonStampSystem _chameleon;

[ViewVariables]
private ChameleonMenu? _menu;

public ChameleonStampBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
_chameleon = EntMan.System<ChameleonStampSystem>();
}

protected override void Open()
{
base.Open();

_menu = this.CreateWindow<ChameleonMenu>();
_menu.OnIdSelected += OnIdSelected;
}

protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (state is not ChameleonStampBoundUserInterfaceState st)
{
Logger.Info($"Проверка стейтов не пройдена.");
return;
}
Logger.Info($"Проверка стейтов пройдена.");
var targets = _chameleon.GetValidTargets();
_menu?.UpdateState(targets, st.SelectedId);
}

private void OnIdSelected(string selectedId)
{
SendMessage(new ChameleonStampPrototypeSelectedMessage(selectedId));
}
}
93 changes: 93 additions & 0 deletions Content.Server/Stories/ChameleonStamp/ChameleonStampSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using Content.Server.IdentityManagement;
using Content.Shared.IdentityManagement.Components;
using Content.Shared.Prototypes;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using Content.Shared.Stories.ChameleonStamp;

namespace Content.Server.Stories.ChameleonStamp;

public sealed class ChameleonStampSystem : SharedChameleonStampSystem
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly IdentitySystem _identity = default!;

public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ChameleonStampComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<ChameleonStampComponent, GetVerbsEvent<InteractionVerb>>(OnVerb);
SubscribeLocalEvent<ChameleonStampComponent, ChameleonStampPrototypeSelectedMessage>(OnSelected);
}

private void OnMapInit(EntityUid uid, ChameleonStampComponent component, MapInitEvent args)
{
SetSelectedPrototype(uid, component.Default, true, component);
}

private void OnVerb(EntityUid uid, ChameleonStampComponent component, GetVerbsEvent<InteractionVerb> args)
{
if (!args.CanAccess || !args.CanInteract || component.User != args.User)
return;

args.Verbs.Add(new InteractionVerb()
{
Text = Loc.GetString("chameleon-component-verb-text"),
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")),
Act = () => TryOpenUi(uid, args.User, component)
});
}

private void OnSelected(EntityUid uid, ChameleonStampComponent component, ChameleonStampPrototypeSelectedMessage args)
{
SetSelectedPrototype(uid, args.SelectedId, component: component);
}

private void TryOpenUi(EntityUid uid, EntityUid user, ChameleonStampComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
if (!TryComp(user, out ActorComponent? actor))
return;
_uiSystem.TryToggleUi(uid, ChameleonUiKey.Key, actor.PlayerSession);
}

private void UpdateUi(EntityUid uid, ChameleonStampComponent? component = null)
{
if (!Resolve(uid, ref component))
return;

var state = new ChameleonStampBoundUserInterfaceState(component.Default);
_uiSystem.SetUiState(uid, ChameleonUiKey.Key, state);
}

/// <summary>
/// Change chameleon items name, description and sprite to mimic other entity prototype.
/// </summary>
public void SetSelectedPrototype(EntityUid uid, string? protoId, bool forceUpdate = false,
ChameleonStampComponent? component = null)
{
if (!Resolve(uid, ref component, false))
return;

// check that wasn't already selected
// forceUpdate on component init ignores this check
if (component.Default == protoId && !forceUpdate)
return;

// make sure that it is valid change
if (string.IsNullOrEmpty(protoId) || !_proto.TryIndex(protoId, out EntityPrototype? proto))
return;
if (!IsValidTarget(proto))
return;
component.Default = protoId;
UpdateVisuals(uid, component);
UpdateUi(uid, component);
Dirty(uid, component);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using Content.Shared.Stories.ChameleonStamp;
using Content.Shared.Inventory;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;

namespace Content.Shared.Stories.ChameleonStamp;

/// <summary>
/// Allow players to change clothing sprite to any other clothing prototype.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
[Access(typeof(SharedChameleonStampSystem))]
public sealed partial class ChameleonStampComponent : Component
{
/// <summary>
/// EntityPrototype id that chameleon item is trying to mimic.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
[DataField(required: true), AutoNetworkedField]
public EntProtoId? Default;

/// <summary>
/// Current user that wears chameleon clothing.
/// </summary>
[ViewVariables]
public EntityUid? User;
}

[Serializable, NetSerializable]
public sealed class ChameleonStampBoundUserInterfaceState : BoundUserInterfaceState
{
public readonly string? SelectedId;

public ChameleonStampBoundUserInterfaceState(string? selectedId)
{
SelectedId = selectedId;
}
}

[Serializable, NetSerializable]
public sealed class ChameleonStampPrototypeSelectedMessage : BoundUserInterfaceMessage
{
public readonly string SelectedId;

public ChameleonStampPrototypeSelectedMessage(string selectedId)
{
SelectedId = selectedId;
}
}

[Serializable, NetSerializable]
public enum ChameleonUiKey : byte
{
Key
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using Content.Shared.Access.Components;
using Content.Shared.Stories.ChameleonStamp;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Content.Shared.Item;
using Content.Shared.Tag;
using Robust.Shared.Prototypes;
using Content.Shared.Paper;

namespace Content.Shared.Stories.ChameleonStamp;

public abstract class SharedChameleonStampSystem : EntitySystem
{
[Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly SharedItemSystem _itemSystem = default!;
[Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly SharedItemSystem _itemSys = default!;

public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ChameleonStampComponent, GotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<ChameleonStampComponent, GotUnequippedEvent>(OnGotUnequipped);
}

private void OnGotEquipped(EntityUid uid, ChameleonStampComponent component, GotEquippedEvent args)
{
component.User = args.Equipee;
}

private void OnGotUnequipped(EntityUid uid, ChameleonStampComponent component, GotUnequippedEvent args)
{
component.User = null;
}

public void CopyVisuals(EntityUid uid, StampComponent otherStamp, StampComponent? stamp = null)
{
if (!Resolve(uid, ref stamp))
return;

stamp.StampedColor = otherStamp.StampedColor;
stamp.StampedName = otherStamp.StampedName;
stamp.StampState = otherStamp.StampState;

_itemSys.VisualsChanged(uid);
Dirty(uid, stamp);
}

// Updates chameleon visuals and meta information.
// This function is called on a server after user selected new outfit.
// And after that on a client after state was updated.
// This 100% makes sure that server and client have exactly same data.
protected void UpdateVisuals(EntityUid uid, ChameleonStampComponent component)
{
if (string.IsNullOrEmpty(component.Default) ||
!_proto.TryIndex(component.Default, out EntityPrototype? proto))
return;

// world sprite icon
UpdateSprite(uid, proto);

// sta sprite logic
if (TryComp(uid, out StampComponent? stamp) &&
proto.TryGetComponent("Stamp", out StampComponent? otherStamp))
{
CopyVisuals(uid, otherStamp, stamp);
}
}

protected virtual void UpdateSprite(EntityUid uid, EntityPrototype proto) { }

/// <summary>
/// Check if this entity prototype is valid target for chameleon item.
/// </summary>
public bool IsValidTarget(EntityPrototype proto)
{
// check if entity is valid
if (proto.Abstract || proto.HideSpawnMenu)
return false;

// check if it is marked as valid chameleon target
if (!proto.TryGetComponent(out TagComponent? tag, _factory) || !_tag.HasTag(tag, "WhitelistChameleon"))
return false;

// check if it's valid clothing
if (!proto.TryGetComponent("Stamp", out StampComponent? stamp))
return false;

return true;
}
}
3 changes: 3 additions & 0 deletions Resources/Prototypes/Entities/Objects/Misc/rubber_stamp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
size: Tiny
- type: StealTarget
stealGroup: Stamp
- type: Tag
tags:
- WhitelistChameleon

- type: entity
name: alternate rubber stamp
Expand Down
Loading
Loading