* Fix usages of TryIndex()
Most usages of TryIndex() were using it incorrectly. Checking whether prototype IDs specified in prototypes actually existed before using them. This is not appropriate as it's just hiding bugs that should be getting caught by the YAML linter and other tools. (#39115)
This then resulted in TryIndex() getting modified to log errors (94f98073b0), which is incorrect as it causes false-positive errors in proper uses of the API: external data validation. (#39098)
This commit goes through and checks every call site of TryIndex() to see whether they were correct. Most call sites were replaced with the new Resolve(), which is suitable for these "defensive programming" use cases.
Fixes #39115
Breaking change: while doing this I noticed IdCardComponent and related systems were erroneously using ProtoId<AccessLevelPrototype> for job prototypes. This has been corrected.
* fix tests
---------
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
219 lines
7.6 KiB
C#
219 lines
7.6 KiB
C#
using System.Linq;
|
|
using Content.Shared.Access.Components;
|
|
using Content.Shared.Clothing.Components;
|
|
using Content.Shared.Contraband;
|
|
using Content.Shared.Inventory;
|
|
using Content.Shared.Inventory.Events;
|
|
using Content.Shared.Item;
|
|
using Content.Shared.Lock;
|
|
using Content.Shared.Tag;
|
|
using Content.Shared.Verbs;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Timing;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Content.Shared.Clothing.EntitySystems;
|
|
|
|
public abstract class SharedChameleonClothingSystem : EntitySystem
|
|
{
|
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
|
[Dependency] private readonly ClothingSystem _clothingSystem = default!;
|
|
[Dependency] private readonly ContrabandSystem _contraband = default!;
|
|
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
|
[Dependency] private readonly SharedItemSystem _itemSystem = default!;
|
|
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
|
[Dependency] private readonly TagSystem _tag = default!;
|
|
[Dependency] protected readonly IGameTiming _timing = default!;
|
|
[Dependency] private readonly LockSystem _lock = default!;
|
|
|
|
private static readonly SlotFlags[] IgnoredSlots =
|
|
{
|
|
SlotFlags.All,
|
|
SlotFlags.PREVENTEQUIP,
|
|
SlotFlags.NONE
|
|
};
|
|
private static readonly SlotFlags[] Slots = Enum.GetValues<SlotFlags>().Except(IgnoredSlots).ToArray();
|
|
|
|
private readonly Dictionary<SlotFlags, List<EntProtoId>> _data = new();
|
|
|
|
public readonly Dictionary<SlotFlags, List<string>> ValidVariants = new();
|
|
[Dependency] protected readonly SharedUserInterfaceSystem UI = default!;
|
|
|
|
private static readonly ProtoId<TagPrototype> WhitelistChameleonTag = "WhitelistChameleon";
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
SubscribeLocalEvent<ChameleonClothingComponent, GotEquippedEvent>(OnGotEquipped);
|
|
SubscribeLocalEvent<ChameleonClothingComponent, GotUnequippedEvent>(OnGotUnequipped);
|
|
SubscribeLocalEvent<ChameleonClothingComponent, GetVerbsEvent<InteractionVerb>>(OnVerb);
|
|
|
|
SubscribeLocalEvent<ChameleonClothingComponent, PrototypesReloadedEventArgs>(OnPrototypeReload);
|
|
PrepareAllVariants();
|
|
}
|
|
|
|
private void OnPrototypeReload(EntityUid uid, ChameleonClothingComponent component, PrototypesReloadedEventArgs args)
|
|
{
|
|
PrepareAllVariants();
|
|
}
|
|
|
|
private void OnGotEquipped(EntityUid uid, ChameleonClothingComponent component, GotEquippedEvent args)
|
|
{
|
|
component.User = args.Equipee;
|
|
}
|
|
|
|
private void OnGotUnequipped(EntityUid uid, ChameleonClothingComponent component, GotUnequippedEvent args)
|
|
{
|
|
component.User = null;
|
|
}
|
|
|
|
// 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, ChameleonClothingComponent component)
|
|
{
|
|
if (string.IsNullOrEmpty(component.Default) ||
|
|
!_proto.Resolve(component.Default, out EntityPrototype? proto))
|
|
return;
|
|
|
|
// world sprite icon
|
|
UpdateSprite(uid, proto);
|
|
|
|
// copy name and description, unless its an ID card
|
|
if (!HasComp<IdCardComponent>(uid))
|
|
{
|
|
var meta = MetaData(uid);
|
|
_metaData.SetEntityName(uid, proto.Name, meta);
|
|
_metaData.SetEntityDescription(uid, proto.Description, meta);
|
|
}
|
|
|
|
// item sprite logic
|
|
if (TryComp(uid, out ItemComponent? item) &&
|
|
proto.TryGetComponent(out ItemComponent? otherItem, Factory))
|
|
{
|
|
_itemSystem.CopyVisuals(uid, otherItem, item);
|
|
}
|
|
|
|
// clothing sprite logic
|
|
if (TryComp(uid, out ClothingComponent? clothing) &&
|
|
proto.TryGetComponent("Clothing", out ClothingComponent? otherClothing))
|
|
{
|
|
_clothingSystem.CopyVisuals(uid, otherClothing, clothing);
|
|
}
|
|
|
|
// appearance data logic
|
|
if (TryComp(uid, out AppearanceComponent? appearance) &&
|
|
proto.TryGetComponent("Appearance", out AppearanceComponent? appearanceOther))
|
|
{
|
|
_appearance.AppendData(appearanceOther, uid);
|
|
Dirty(uid, appearance);
|
|
}
|
|
|
|
// properly mark contraband
|
|
if (proto.TryGetComponent("Contraband", out ContrabandComponent? contra))
|
|
{
|
|
EnsureComp<ContrabandComponent>(uid, out var current);
|
|
_contraband.CopyDetails(uid, contra, current);
|
|
}
|
|
else
|
|
{
|
|
RemComp<ContrabandComponent>(uid);
|
|
}
|
|
}
|
|
|
|
private void OnVerb(Entity<ChameleonClothingComponent> ent, ref GetVerbsEvent<InteractionVerb> args)
|
|
{
|
|
if (!args.CanAccess || !args.CanInteract || _lock.IsLocked(ent.Owner))
|
|
return;
|
|
|
|
// Can't pass args from a ref event inside of lambdas
|
|
var user = args.User;
|
|
|
|
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 = () => UI.TryToggleUi(ent.Owner, ChameleonUiKey.Key, user)
|
|
});
|
|
}
|
|
|
|
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, SlotFlags chameleonSlot = SlotFlags.NONE, string? requiredTag = null)
|
|
{
|
|
// 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, WhitelistChameleonTag))
|
|
return false;
|
|
|
|
if (requiredTag != null && !_tag.HasTag(tag, requiredTag))
|
|
return false;
|
|
|
|
// check if it's valid clothing
|
|
if (!proto.TryGetComponent("Clothing", out ClothingComponent? clothing))
|
|
return false;
|
|
if (!clothing.Slots.HasFlag(chameleonSlot))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a list of valid chameleon targets for these slots.
|
|
/// </summary>
|
|
public IEnumerable<EntProtoId> GetValidTargets(SlotFlags slot, string? tag = null)
|
|
{
|
|
var validTargets = new List<EntProtoId>();
|
|
if (tag != null)
|
|
{
|
|
foreach (var proto in _data[slot])
|
|
{
|
|
if (IsValidTarget(_proto.Index(proto), slot, tag))
|
|
validTargets.Add(proto);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
validTargets = _data[slot];
|
|
}
|
|
|
|
return validTargets;
|
|
}
|
|
|
|
protected void PrepareAllVariants()
|
|
{
|
|
_data.Clear();
|
|
var prototypes = _proto.EnumeratePrototypes<EntityPrototype>();
|
|
|
|
foreach (var proto in prototypes)
|
|
{
|
|
// check if this is valid clothing
|
|
if (!IsValidTarget(proto))
|
|
continue;
|
|
if (!proto.TryGetComponent(out ClothingComponent? item, Factory))
|
|
continue;
|
|
|
|
// sort item by their slot flags
|
|
// one item can be placed in several buckets
|
|
foreach (var slot in Slots)
|
|
{
|
|
if (!item.Slots.HasFlag(slot))
|
|
continue;
|
|
|
|
if (!_data.ContainsKey(slot))
|
|
{
|
|
_data.Add(slot, new List<EntProtoId>());
|
|
}
|
|
_data[slot].Add(proto.ID);
|
|
}
|
|
}
|
|
}
|
|
}
|