From 4a28f777298e768b687b0e0769abd291e95157ad Mon Sep 17 00:00:00 2001 From: Drakosha Date: Mon, 2 Feb 2026 22:38:53 +0100 Subject: [PATCH] Add VariantSelectionList for fast variant matching --- ...lockBehaviorShapeTexturesFromAttributes.cs | 79 ++++---- .../CollectibleBehaviorContainedTransform.cs | 15 +- .../CollectibleBehaviorHeldBagTyped.cs | 13 +- ...ibleBehaviorShapeTexturesFromAttributes.cs | 93 ++++----- .../Item/ItemShapeTexturesFromAttributes.cs | 119 +++++------ .../Utility/IShapeTexturesFromAttributes.cs | 6 +- .../Utility/ShapeOverlayHelper.cs | 4 +- .../Utility/Transforms.cs | 20 +- .../Utility/VariantExtensions.cs | 63 ++---- .../Utility/VariantSelectionList.cs | 185 ++++++++++++++++++ 10 files changed, 386 insertions(+), 211 deletions(-) create mode 100644 AttributeRenderingLibrary/Utility/VariantSelectionList.cs diff --git a/AttributeRenderingLibrary/BlockBehavior/BlockBehaviorShapeTexturesFromAttributes.cs b/AttributeRenderingLibrary/BlockBehavior/BlockBehaviorShapeTexturesFromAttributes.cs index 06c8bdf..a4d7c83 100644 --- a/AttributeRenderingLibrary/BlockBehavior/BlockBehaviorShapeTexturesFromAttributes.cs +++ b/AttributeRenderingLibrary/BlockBehavior/BlockBehaviorShapeTexturesFromAttributes.cs @@ -13,20 +13,20 @@ namespace AttributeRenderingLibrary; public class BlockBehaviorShapeTexturesFromAttributes(Block block) : StrongBlockBehavior(block), IBlockShapeTexturesFromAttributes, IContainedMeshSource { - public Dictionary> NameByType { get; protected set; } - public Dictionary> DescriptionByType { get; protected set; } - public Dictionary CollisionBoxesByType { get; protected set; } - public Dictionary SelectionBoxesByType { get; protected set; } - public Dictionary DropsByType { get; protected set; } - - public Dictionary shapeByType { get; protected set; } - public Dictionary shapeInventoryByType { get; protected set; } - public Dictionary> texturesByType { get; protected set; } + public VariantSelectionList> NameByType { get; protected set; } + public VariantSelectionList> DescriptionByType { get; protected set; } + public VariantSelectionList CollisionBoxesByType { get; protected set; } + public VariantSelectionList SelectionBoxesByType { get; protected set; } + public VariantSelectionList DropsByType { get; protected set; } + + public VariantSelectionList shapeByType { get; protected set; } + public VariantSelectionList shapeInventoryByType { get; protected set; } + public VariantSelectionList> texturesByType { get; protected set; } #region Extra shape overrides - public Dictionary ShapeIgnoreElementsByType { get; protected set; } - public Dictionary ShapeIgnoreElementsCombineByType { get; protected set; } - public Dictionary ShapeSelectiveElementsByType { get; protected set; } - public Dictionary ShapeSelectiveElementsCombineByType { get; protected set; } + public VariantSelectionList ShapeIgnoreElementsByType { get; protected set; } + public VariantSelectionList ShapeIgnoreElementsCombineByType { get; protected set; } + public VariantSelectionList ShapeSelectiveElementsByType { get; protected set; } + public VariantSelectionList ShapeSelectiveElementsCombineByType { get; protected set; } #endregion private ICoreClientAPI clientApi; @@ -44,40 +44,45 @@ public override void Initialize(JsonObject properties) { base.Initialize(properties); - if (properties != null) - { - NameByType = properties["name"].AsObject>>(); - DescriptionByType = properties["description"].AsObject>>(); - DropsByType = properties["drops"].AsObject>(); - - shapeByType = properties["shape"].AsObject>(); - shapeInventoryByType = properties["shapeInventory"].AsObject>(); - texturesByType = properties["textures"].AsObject>>(); + if (properties is not { Count: > 0 }) + return; - ShapeIgnoreElementsByType = properties["shapeIgnoreElements"].AsObject>(); - ShapeIgnoreElementsCombineByType = properties["shapeIgnoreElementsCombine"].AsObject>(); - ShapeSelectiveElementsByType = properties["shapeSelectiveElements"].AsObject>(); - ShapeSelectiveElementsCombineByType = properties["shapeSelectiveElementsCombine"].AsObject>(); + NameByType = VariantSelectionList>.LoadFrom(properties["name"]); + DescriptionByType = VariantSelectionList>.LoadFrom(properties["description"]); + DropsByType = VariantSelectionList.LoadFrom(properties["drops"]); - LoadAndResolveCollisionAndSelectionBoxes(properties); - } + shapeByType = VariantSelectionList.LoadFrom(properties["shape"]); + shapeInventoryByType = VariantSelectionList.LoadFrom(properties["shapeInventory"]); + texturesByType = VariantSelectionList>.LoadFrom(properties["textures"]); + + ShapeIgnoreElementsByType = VariantSelectionList.LoadFrom(properties["shapeIgnoreElements"]); + ShapeIgnoreElementsCombineByType = VariantSelectionList.LoadFrom(properties["shapeIgnoreElementsCombine"]); + ShapeSelectiveElementsByType = VariantSelectionList.LoadFrom(properties["shapeSelectiveElements"]); + ShapeSelectiveElementsCombineByType = VariantSelectionList.LoadFrom(properties["shapeSelectiveElementsCombine"]); + + LoadAndResolveCollisionAndSelectionBoxes(properties); } private void LoadAndResolveCollisionAndSelectionBoxes(JsonObject properties) { - Dictionary rawCollisionsAndSelections = properties["collisionSelectionBoxes"]?.AsObject>(); - Dictionary rawCollisions = properties["collisionBoxes"]?.AsObject>(); - Dictionary rawSelections = properties["selectionBoxes"]?.AsObject>(); - - if (rawCollisionsAndSelections?.Count > 0) + if (properties["collisionSelectionBoxes"] is { Token: not null } rawCollisionsAndSelections) { - CollisionBoxesByType = rawCollisionsAndSelections?.ToDictionary(x => x.Key, x => x.Value.ToCuboidf()); - SelectionBoxesByType = rawCollisionsAndSelections?.ToDictionary(x => x.Key, x => x.Value.ToCuboidf()); + var dict = rawCollisionsAndSelections + .AsObject>(); + CollisionBoxesByType = VariantSelectionList.LoadFrom(dict, cube => cube.ToCuboidf()); + SelectionBoxesByType = VariantSelectionList.LoadFrom(dict, cube => cube.ToCuboidf()); } else { - CollisionBoxesByType = rawCollisions?.ToDictionary(x => x.Key, x => x.Value.ToCuboidf()); - SelectionBoxesByType = rawSelections?.ToDictionary(x => x.Key, x => x.Value.ToCuboidf()); + if (properties["collisionBoxes"] is { Token: not null } rawCollisions) + CollisionBoxesByType = VariantSelectionList.LoadFrom(rawCollisions + .AsObject>(), + cube => cube.ToCuboidf()); + + if (properties["selectionBoxes"] is { Token: not null } rawSelections) + SelectionBoxesByType = VariantSelectionList.LoadFrom(rawSelections + .AsObject>(), + cube => cube.ToCuboidf()); } } diff --git a/AttributeRenderingLibrary/CollectibleBehavior/CollectibleBehaviorContainedTransform.cs b/AttributeRenderingLibrary/CollectibleBehavior/CollectibleBehaviorContainedTransform.cs index 68a1dfb..05908e7 100644 --- a/AttributeRenderingLibrary/CollectibleBehavior/CollectibleBehaviorContainedTransform.cs +++ b/AttributeRenderingLibrary/CollectibleBehavior/CollectibleBehaviorContainedTransform.cs @@ -14,13 +14,18 @@ namespace AttributeRenderingLibrary; public class CollectibleBehaviorContainedTransform(CollectibleObject collObj) : CollectibleBehavior(collObj), IContainedTransform { public Transforms BasicTransforms { get; protected set; } - public Dictionary> ExtraTransforms { get; protected set; } + public Dictionary> ExtraTransforms { get; protected set; } public override void Initialize(JsonObject properties) { base.Initialize(properties); - BasicTransforms = properties["transforms"].AsObject(); - ExtraTransforms = properties["extraTransforms"].AsObject>>()?.ToDictionary(x => x.Key.ToLowerInvariant(), x => x.Value); + BasicTransforms = Transforms.LoadFrom(properties["transforms"]); + ExtraTransforms = properties["extraTransforms"] + .AsObject>>() + ?.ToDictionary( + x => x.Key.ToLowerInvariant(), + x => VariantSelectionList.LoadFrom(x.Value) + ); } public override void OnBeforeRender(ICoreClientAPI capi, ItemStack itemstack, EnumItemRenderTarget target, ref ItemRenderInfo renderinfo) @@ -30,7 +35,7 @@ public override void OnBeforeRender(ICoreClientAPI capi, ItemStack itemstack, En public void ApplyOnBeforeRenderTransform(EnumItemRenderTarget target, Variants variants, ref ModelTransform transform) { - Dictionary basicTransformsByType = target switch + VariantSelectionList basicTransformsByType = target switch { EnumItemRenderTarget.Gui => BasicTransforms?.GuiTransform, EnumItemRenderTarget.HandTp => BasicTransforms?.TpHandTransform, @@ -54,7 +59,7 @@ ModelTransform IContainedTransform.GetTransform(BlockEntityDisplay be, string at if (ExtraTransforms != null && ExtraTransforms.Count > 0 - && ExtraTransforms.TryGetValue(attributeTransformCode, out Dictionary transformsByType) + && ExtraTransforms.TryGetValue(attributeTransformCode, out VariantSelectionList transformsByType) && Variants.FromStack(stack).FindByVariant(transformsByType, out ModelTransform transform)) { transform = transform.EnsureDefaultValues(); diff --git a/AttributeRenderingLibrary/CollectibleBehavior/CollectibleBehaviorHeldBagTyped.cs b/AttributeRenderingLibrary/CollectibleBehavior/CollectibleBehaviorHeldBagTyped.cs index 78a82be..53a202b 100644 --- a/AttributeRenderingLibrary/CollectibleBehavior/CollectibleBehaviorHeldBagTyped.cs +++ b/AttributeRenderingLibrary/CollectibleBehavior/CollectibleBehaviorHeldBagTyped.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json.Linq; using Vintagestory.API.Common; using Vintagestory.API.Datastructures; using Vintagestory.GameContent; @@ -8,17 +9,17 @@ namespace AttributeRenderingLibrary; public class CollectibleBehaviorHeldBagTyped(CollectibleObject collObj) : CollectibleBehaviorHeldBag(collObj) { - public Dictionary QuantitySlotsByType { get; protected set; } - public Dictionary SlotBgColorByType { get; protected set; } - public Dictionary StorageFlagsByType { get; protected set; } + public VariantSelectionList QuantitySlotsByType { get; protected set; } + public VariantSelectionList SlotBgColorByType { get; protected set; } + public VariantSelectionList StorageFlagsByType { get; protected set; } public override void Initialize(JsonObject properties) { base.Initialize(properties); - QuantitySlotsByType = properties["quantitySlots"].AsObject>(); - SlotBgColorByType = properties["slotBgColor"].AsObject>(); - StorageFlagsByType = properties["storageFlags"].AsObject>()?.ToDictionary(x => x.Key, x => (EnumItemStorageFlags)x.Value); + QuantitySlotsByType = VariantSelectionList.LoadFrom(properties["quantitySlots"]); + SlotBgColorByType = VariantSelectionList.LoadFrom(properties["slotBgColor"]); + StorageFlagsByType = VariantSelectionList.LoadFrom(properties["storageFlags"].Token as JObject, token => (EnumItemStorageFlags)token.Value()); } public override int GetQuantitySlots(ItemStack bagstack) diff --git a/AttributeRenderingLibrary/CollectibleBehavior/CollectibleBehaviorShapeTexturesFromAttributes.cs b/AttributeRenderingLibrary/CollectibleBehavior/CollectibleBehaviorShapeTexturesFromAttributes.cs index 27262ba..1fdab8c 100644 --- a/AttributeRenderingLibrary/CollectibleBehavior/CollectibleBehaviorShapeTexturesFromAttributes.cs +++ b/AttributeRenderingLibrary/CollectibleBehavior/CollectibleBehaviorShapeTexturesFromAttributes.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Text; +using Newtonsoft.Json.Linq; using Vintagestory.API.Client; using Vintagestory.API.Common; using Vintagestory.API.Common.Entities; @@ -12,34 +13,34 @@ namespace AttributeRenderingLibrary; public class CollectibleBehaviorShapeTexturesFromAttributes(CollectibleObject collObj) : CollectibleBehavior(collObj), IShapeTexturesFromAttributes, IContainedMeshSource, IContainedCustomName, IAttachableToEntity { - public Dictionary shapeByType { get; protected set; } - public Dictionary> texturesByType { get; protected set; } + public VariantSelectionList shapeByType { get; protected set; } + public VariantSelectionList> texturesByType { get; protected set; } - public Dictionary> NameByType { get; protected set; } - public Dictionary> DescriptionByType { get; protected set; } - public Dictionary> ContainedDescriptionByType { get; protected set; } - public Dictionary StorageFlagsByType { get; protected set; } + public VariantSelectionList> NameByType { get; protected set; } + public VariantSelectionList> DescriptionByType { get; protected set; } + public VariantSelectionList> ContainedDescriptionByType { get; protected set; } + public VariantSelectionList StorageFlagsByType { get; protected set; } #region Animations - public Dictionary HeldLeftReadyAnimationByType { get; protected set; } - public Dictionary HeldRightReadyAnimationByType { get; protected set; } + public VariantSelectionList HeldLeftReadyAnimationByType { get; protected set; } + public VariantSelectionList HeldRightReadyAnimationByType { get; protected set; } - public Dictionary HeldLeftTpIdleAnimationByType { get; protected set; } - public Dictionary HeldRightTpIdleAnimationByType { get; protected set; } + public VariantSelectionList HeldLeftTpIdleAnimationByType { get; protected set; } + public VariantSelectionList HeldRightTpIdleAnimationByType { get; protected set; } - public Dictionary HeldTpUseAnimationByType { get; protected set; } - public Dictionary HeldTpHitAnimationByType { get; protected set; } + public VariantSelectionList HeldTpUseAnimationByType { get; protected set; } + public VariantSelectionList HeldTpHitAnimationByType { get; protected set; } #endregion #region Extra shape overrides - public Dictionary ShapeIgnoreElementsByType { get; protected set; } - public Dictionary ShapeIgnoreElementsCombineByType { get; protected set; } - public Dictionary ShapeSelectiveElementsByType { get; protected set; } - public Dictionary ShapeSelectiveElementsCombineByType { get; protected set; } + public VariantSelectionList ShapeIgnoreElementsByType { get; protected set; } + public VariantSelectionList ShapeIgnoreElementsCombineByType { get; protected set; } + public VariantSelectionList ShapeSelectiveElementsByType { get; protected set; } + public VariantSelectionList ShapeSelectiveElementsCombineByType { get; protected set; } #endregion #region IAttachableToEntity - public Dictionary> AttachedShapeBySlotCodeByType { get; protected set; } - public Dictionary CategoryCodeByType { get; protected set; } - public Dictionary DisableElementsByType { get; protected set; } - public Dictionary KeepElementsByType { get; protected set; } + public VariantSelectionList> AttachedShapeBySlotCodeByType { get; protected set; } + public VariantSelectionList CategoryCodeByType { get; protected set; } + public VariantSelectionList DisableElementsByType { get; protected set; } + public VariantSelectionList KeepElementsByType { get; protected set; } private IAttachableToEntity iattr; #endregion @@ -54,36 +55,36 @@ public override void OnLoaded(ICoreAPI api) public override void Initialize(JsonObject properties) { base.Initialize(properties); + + if (properties is not { Count: > 0 }) + return; - if (properties != null) - { - shapeByType = properties["shape"].AsObject>(); - texturesByType = properties["textures"].AsObject>>(); + shapeByType = VariantSelectionList.LoadFrom(properties["shape"]); + texturesByType = VariantSelectionList>.LoadFrom(properties["textures"]); - ShapeIgnoreElementsByType = properties["shapeIgnoreElements"].AsObject>(); - ShapeIgnoreElementsCombineByType = properties["shapeIgnoreElementsCombine"].AsObject>(); - ShapeSelectiveElementsByType = properties["shapeSelectiveElements"].AsObject>(); - ShapeSelectiveElementsCombineByType = properties["shapeSelectiveElementsCombine"].AsObject>(); + ShapeIgnoreElementsByType = VariantSelectionList.LoadFrom(properties["shapeIgnoreElements"]); + ShapeIgnoreElementsCombineByType = VariantSelectionList.LoadFrom(properties["shapeIgnoreElementsCombine"]); + ShapeSelectiveElementsByType = VariantSelectionList.LoadFrom(properties["shapeSelectiveElements"]); + ShapeSelectiveElementsCombineByType = VariantSelectionList.LoadFrom(properties["shapeSelectiveElementsCombine"]); - NameByType = properties["name"].AsObject>>(); - DescriptionByType = properties["description"].AsObject>>(); - ContainedDescriptionByType = properties["containedDescription"].AsObject>>(); - StorageFlagsByType = properties["storageFlags"].AsObject>(); + NameByType = VariantSelectionList>.LoadFrom(properties["name"]); + DescriptionByType = VariantSelectionList>.LoadFrom(properties["description"]); + ContainedDescriptionByType = VariantSelectionList>.LoadFrom(properties["containedDescription"]); + StorageFlagsByType = VariantSelectionList.LoadFrom(properties["storageFlags"]); - HeldLeftReadyAnimationByType = properties["heldLeftReadyAnimation"].AsObject>(); - HeldRightReadyAnimationByType = properties["heldRightReadyAnimation"].AsObject>(); + HeldLeftReadyAnimationByType = VariantSelectionList.LoadFrom(properties["heldLeftReadyAnimation"]); + HeldRightReadyAnimationByType = VariantSelectionList.LoadFrom(properties["heldRightReadyAnimation"]); - HeldLeftTpIdleAnimationByType = properties["heldLeftTpIdleAnimation"].AsObject>(); - HeldRightTpIdleAnimationByType = properties["heldRightTpIdleAnimation"].AsObject>(); + HeldLeftTpIdleAnimationByType = VariantSelectionList.LoadFrom(properties["heldLeftTpIdleAnimation"]); + HeldRightTpIdleAnimationByType = VariantSelectionList.LoadFrom(properties["heldRightTpIdleAnimation"]); - HeldTpUseAnimationByType = properties["heldTpUseAnimation"].AsObject>(); - HeldTpHitAnimationByType = properties["heldTpHitAnimation"].AsObject>(); + HeldTpUseAnimationByType = VariantSelectionList.LoadFrom(properties["heldTpUseAnimation"]); + HeldTpHitAnimationByType = VariantSelectionList.LoadFrom(properties["heldTpHitAnimation"]); - AttachedShapeBySlotCodeByType = properties["STFA_attachableToEntity"]?["attachedShapeBySlotCode"].AsObject>>(); - CategoryCodeByType = properties["STFA_attachableToEntity"]?["categoryCode"].AsObject>(); - DisableElementsByType = properties["STFA_attachableToEntity"]?["disableElements"].AsObject>(); - KeepElementsByType = properties["STFA_attachableToEntity"]?["keepElements"].AsObject>(); - } + AttachedShapeBySlotCodeByType = VariantSelectionList>.LoadFrom(properties["STFA_attachableToEntity"]?["attachedShapeBySlotCode"]); + CategoryCodeByType = VariantSelectionList.LoadFrom(properties["STFA_attachableToEntity"]?["categoryCode"]); + DisableElementsByType = VariantSelectionList.LoadFrom(properties["STFA_attachableToEntity"]?["disableElements"]); + KeepElementsByType = VariantSelectionList.LoadFrom(properties["STFA_attachableToEntity"]?["keepElements"]); } public override void OnUnloaded(ICoreAPI api) @@ -342,7 +343,7 @@ public override EnumItemStorageFlags GetStorageFlags(ItemStack itemstack, ref En public override string GetHeldReadyAnimation(ItemSlot activeHotbarSlot, Entity forEntity, EnumHand hand, ref EnumHandling bhHandling) { - Dictionary animCodesByType = (hand == EnumHand.Left) ? HeldLeftReadyAnimationByType : HeldRightReadyAnimationByType; + VariantSelectionList animCodesByType = (hand == EnumHand.Left) ? HeldLeftReadyAnimationByType : HeldRightReadyAnimationByType; if (animCodesByType == null || animCodesByType.Count == 0) { @@ -360,7 +361,7 @@ public override string GetHeldReadyAnimation(ItemSlot activeHotbarSlot, Entity f public override string GetHeldTpIdleAnimation(ItemSlot activeHotbarSlot, Entity forEntity, EnumHand hand, ref EnumHandling bhHandling) { - Dictionary animCodesByType = (hand == EnumHand.Left) ? HeldLeftTpIdleAnimationByType : HeldRightTpIdleAnimationByType; + VariantSelectionList animCodesByType = (hand == EnumHand.Left) ? HeldLeftTpIdleAnimationByType : HeldRightTpIdleAnimationByType; if (animCodesByType == null || animCodesByType.Count == 0) { @@ -462,7 +463,7 @@ void IAttachableToEntity.CollectTextures(ItemStack stack, Shape shape, string te shape.Textures[textureCode] = texture.Baked.BakedName; } - Dictionary> texturesByType = []; + VariantSelectionList> texturesByType = null; if (stack.Collectible.GetCollectibleInterface() is IShapeTexturesFromAttributes STFA) { diff --git a/AttributeRenderingLibrary/Item/ItemShapeTexturesFromAttributes.cs b/AttributeRenderingLibrary/Item/ItemShapeTexturesFromAttributes.cs index 7eab0ea..10e8663 100644 --- a/AttributeRenderingLibrary/Item/ItemShapeTexturesFromAttributes.cs +++ b/AttributeRenderingLibrary/Item/ItemShapeTexturesFromAttributes.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Text; +using Newtonsoft.Json.Linq; using Vintagestory.API.Client; using Vintagestory.API.Common; using Vintagestory.API.Common.Entities; @@ -12,37 +13,37 @@ namespace AttributeRenderingLibrary; public class ItemShapeTexturesFromAttributes : Item, IShapeTexturesFromAttributes, IContainedMeshSource, IContainedCustomName, IAttachableToEntity { - public Dictionary shapeByType { get; protected set; } - public Dictionary> texturesByType { get; protected set; } - - public Dictionary> NameByType { get; protected set; } - public Dictionary> DescriptionByType { get; protected set; } - public Dictionary> ContainedDescriptionByType { get; protected set; } - public Dictionary StorageFlagsByType { get; protected set; } - public Dictionary DurabilityByType { get; protected set; } - public Dictionary AttackPowerByType { get; protected set; } - public Dictionary AttackRangeByType { get; protected set; } + public VariantSelectionList shapeByType { get; protected set; } + public VariantSelectionList> texturesByType { get; protected set; } + + public VariantSelectionList> NameByType { get; protected set; } + public VariantSelectionList> DescriptionByType { get; protected set; } + public VariantSelectionList> ContainedDescriptionByType { get; protected set; } + public VariantSelectionList StorageFlagsByType { get; protected set; } + public VariantSelectionList DurabilityByType { get; protected set; } + public VariantSelectionList AttackPowerByType { get; protected set; } + public VariantSelectionList AttackRangeByType { get; protected set; } #region Animations - public Dictionary HeldLeftReadyAnimationByType { get; protected set; } - public Dictionary HeldRightReadyAnimationByType { get; protected set; } + public VariantSelectionList HeldLeftReadyAnimationByType { get; protected set; } + public VariantSelectionList HeldRightReadyAnimationByType { get; protected set; } - public Dictionary HeldLeftTpIdleAnimationByType { get; protected set; } - public Dictionary HeldRightTpIdleAnimationByType { get; protected set; } + public VariantSelectionList HeldLeftTpIdleAnimationByType { get; protected set; } + public VariantSelectionList HeldRightTpIdleAnimationByType { get; protected set; } - public Dictionary HeldTpUseAnimationByType { get; protected set; } - public Dictionary HeldTpHitAnimationByType { get; protected set; } + public VariantSelectionList HeldTpUseAnimationByType { get; protected set; } + public VariantSelectionList HeldTpHitAnimationByType { get; protected set; } #endregion #region Extra shape overrides - public Dictionary ShapeIgnoreElementsByType { get; protected set; } - public Dictionary ShapeIgnoreElementsCombineByType { get; protected set; } - public Dictionary ShapeSelectiveElementsByType { get; protected set; } - public Dictionary ShapeSelectiveElementsCombineByType { get; protected set; } + public VariantSelectionList ShapeIgnoreElementsByType { get; protected set; } + public VariantSelectionList ShapeIgnoreElementsCombineByType { get; protected set; } + public VariantSelectionList ShapeSelectiveElementsByType { get; protected set; } + public VariantSelectionList ShapeSelectiveElementsCombineByType { get; protected set; } #endregion #region IAttachableToEntity - public Dictionary> AttachedShapeBySlotCodeByType { get; protected set; } - public Dictionary CategoryCodeByType { get; protected set; } - public Dictionary DisableElementsByType { get; protected set; } - public Dictionary KeepElementsByType { get; protected set; } + public VariantSelectionList> AttachedShapeBySlotCodeByType { get; protected set; } + public VariantSelectionList CategoryCodeByType { get; protected set; } + public VariantSelectionList DisableElementsByType { get; protected set; } + public VariantSelectionList KeepElementsByType { get; protected set; } private IAttachableToEntity iattr; #endregion @@ -63,38 +64,38 @@ public override void OnUnloaded(ICoreAPI api) public virtual void LoadTypes() { - if (Attributes != null) - { - shapeByType = Attributes["shape"].AsObject>(); - texturesByType = Attributes["textures"].AsObject>>(); - - ShapeIgnoreElementsByType = Attributes["shapeIgnoreElements"].AsObject>(); - ShapeIgnoreElementsCombineByType = Attributes["shapeIgnoreElementsCombine"].AsObject>(); - ShapeSelectiveElementsByType = Attributes["shapeSelectiveElements"].AsObject>(); - ShapeSelectiveElementsCombineByType = Attributes["shapeSelectiveElementsCombine"].AsObject>(); - - NameByType = Attributes["name"].AsObject>>(); - DescriptionByType = Attributes["description"].AsObject>>(); - ContainedDescriptionByType = Attributes["containedDescription"].AsObject>>(); - StorageFlagsByType = Attributes["storageFlags"].AsObject>(); - DurabilityByType = Attributes["durability"].AsObject>(); - AttackPowerByType = Attributes["attackPower"].AsObject>(); - AttackRangeByType = Attributes["attackRange"].AsObject>(); - - HeldLeftReadyAnimationByType = Attributes["heldLeftReadyAnimation"].AsObject>(); - HeldRightReadyAnimationByType = Attributes["heldRightReadyAnimation"].AsObject>(); - - HeldLeftTpIdleAnimationByType = Attributes["heldLeftTpIdleAnimation"].AsObject>(); - HeldRightTpIdleAnimationByType = Attributes["heldRightTpIdleAnimation"].AsObject>(); - - HeldTpUseAnimationByType = Attributes["heldTpUseAnimation"].AsObject>(); - HeldTpHitAnimationByType = Attributes["heldTpHitAnimation"].AsObject>(); - - AttachedShapeBySlotCodeByType = Attributes["STFA_attachableToEntity"]?["attachedShapeBySlotCode"].AsObject>>(); - CategoryCodeByType = Attributes["STFA_attachableToEntity"]?["categoryCode"].AsObject>(); - DisableElementsByType = Attributes["STFA_attachableToEntity"]?["disableElements"].AsObject>(); - KeepElementsByType = Attributes["STFA_attachableToEntity"]?["keepElements"].AsObject>(); - } + if (Attributes is not { Count: > 0 }) + return; + + shapeByType = VariantSelectionList.LoadFrom(Attributes["shape"]); + texturesByType = VariantSelectionList>.LoadFrom(Attributes["textures"]); + + ShapeIgnoreElementsByType = VariantSelectionList.LoadFrom(Attributes["shapeIgnoreElements"]); + ShapeIgnoreElementsCombineByType = VariantSelectionList.LoadFrom(Attributes["shapeIgnoreElementsCombine"]); + ShapeSelectiveElementsByType = VariantSelectionList.LoadFrom(Attributes["shapeSelectiveElements"]); + ShapeSelectiveElementsCombineByType = VariantSelectionList.LoadFrom(Attributes["shapeSelectiveElementsCombine"]); + + NameByType = VariantSelectionList>.LoadFrom(Attributes["name"]); + DescriptionByType = VariantSelectionList>.LoadFrom(Attributes["description"]); + ContainedDescriptionByType = VariantSelectionList>.LoadFrom(Attributes["containedDescription"]); + StorageFlagsByType = VariantSelectionList.LoadFrom(Attributes["storageFlags"].Token as JObject, token => (EnumItemStorageFlags)token.Value()); + DurabilityByType = VariantSelectionList.LoadFrom(Attributes["durability"]); + AttackPowerByType = VariantSelectionList.LoadFrom(Attributes["attackPower"]); + AttackRangeByType = VariantSelectionList.LoadFrom(Attributes["attackRange"]); + + HeldLeftReadyAnimationByType = VariantSelectionList.LoadFrom(Attributes["heldLeftReadyAnimation"]); + HeldRightReadyAnimationByType = VariantSelectionList.LoadFrom(Attributes["heldRightReadyAnimation"]); + + HeldLeftTpIdleAnimationByType = VariantSelectionList.LoadFrom(Attributes["heldLeftTpIdleAnimation"]); + HeldRightTpIdleAnimationByType = VariantSelectionList.LoadFrom(Attributes["heldRightTpIdleAnimation"]); + + HeldTpUseAnimationByType = VariantSelectionList.LoadFrom(Attributes["heldTpUseAnimation"]); + HeldTpHitAnimationByType = VariantSelectionList.LoadFrom(Attributes["heldTpHitAnimation"]); + + AttachedShapeBySlotCodeByType = VariantSelectionList>.LoadFrom(Attributes["STFA_attachableToEntity"]?["attachedShapeBySlotCode"]); + CategoryCodeByType = VariantSelectionList.LoadFrom(Attributes["STFA_attachableToEntity"]?["categoryCode"]); + DisableElementsByType = VariantSelectionList.LoadFrom(Attributes["STFA_attachableToEntity"]?["disableElements"]); + KeepElementsByType = VariantSelectionList.LoadFrom(Attributes["STFA_attachableToEntity"]?["keepElements"]); } public virtual MeshData GetOrCreateMesh(ItemStack itemstack, ITextureAtlasAPI targetAtlas) @@ -393,7 +394,7 @@ public override float GetAttackRange(IItemStack withItemStack) public override string GetHeldReadyAnimation(ItemSlot activeHotbarSlot, Entity forEntity, EnumHand hand) { - Dictionary animCodesByType = (hand == EnumHand.Left) ? HeldLeftReadyAnimationByType : HeldRightReadyAnimationByType; + VariantSelectionList animCodesByType = (hand == EnumHand.Left) ? HeldLeftReadyAnimationByType : HeldRightReadyAnimationByType; if (animCodesByType == null || animCodesByType.Count == 0) { @@ -410,7 +411,7 @@ public override string GetHeldReadyAnimation(ItemSlot activeHotbarSlot, Entity f public override string GetHeldTpIdleAnimation(ItemSlot activeHotbarSlot, Entity forEntity, EnumHand hand) { - Dictionary animCodesByType = (hand == EnumHand.Left) ? HeldLeftTpIdleAnimationByType : HeldRightTpIdleAnimationByType; + VariantSelectionList animCodesByType = (hand == EnumHand.Left) ? HeldLeftTpIdleAnimationByType : HeldRightTpIdleAnimationByType; if (animCodesByType == null || animCodesByType.Count == 0) { @@ -509,7 +510,7 @@ void IAttachableToEntity.CollectTextures(ItemStack stack, Shape shape, string te shape.Textures[textureCode] = texture.Baked.BakedName; } - Dictionary> texturesByType = []; + VariantSelectionList> texturesByType = null; if (stack.Collectible.GetCollectibleInterface() is IShapeTexturesFromAttributes STFA) { diff --git a/AttributeRenderingLibrary/Utility/IShapeTexturesFromAttributes.cs b/AttributeRenderingLibrary/Utility/IShapeTexturesFromAttributes.cs index 17c24b9..9bace32 100644 --- a/AttributeRenderingLibrary/Utility/IShapeTexturesFromAttributes.cs +++ b/AttributeRenderingLibrary/Utility/IShapeTexturesFromAttributes.cs @@ -6,11 +6,11 @@ namespace AttributeRenderingLibrary; public interface IShapeTexturesFromAttributes { - public Dictionary shapeByType { get; } - public Dictionary> texturesByType { get; } + public VariantSelectionList shapeByType { get; } + public VariantSelectionList> texturesByType { get; } } public interface IBlockShapeTexturesFromAttributes : IShapeTexturesFromAttributes { - public Dictionary DropsByType { get; } + public VariantSelectionList DropsByType { get; } } \ No newline at end of file diff --git a/AttributeRenderingLibrary/Utility/ShapeOverlayHelper.cs b/AttributeRenderingLibrary/Utility/ShapeOverlayHelper.cs index 8c45e8e..80f93b9 100644 --- a/AttributeRenderingLibrary/Utility/ShapeOverlayHelper.cs +++ b/AttributeRenderingLibrary/Utility/ShapeOverlayHelper.cs @@ -66,9 +66,9 @@ public static Dictionary AddOverlays(ICoreClientAPI clien /// The textures grouped by variant /// The texture codes that have been prefixed /// The texture prefix to use for prefixed codes - public static void BakeVariantTextures(ICoreClientAPI clientApi, UniversalShapeTextureSource textureSource, Variants variants, Dictionary> texturesByType, Dictionary prefixedTextureCodes = null, string overlayPrefix = "") + public static void BakeVariantTextures(ICoreClientAPI clientApi, UniversalShapeTextureSource textureSource, Variants variants, VariantSelectionList> texturesByType, Dictionary prefixedTextureCodes = null, string overlayPrefix = "") { - if (!variants.FindByVariant(texturesByType, out Dictionary variantTextures)) return; + if (!texturesByType.FindFirstByVariant(variants, out Dictionary variantTextures)) return; foreach((string textureCode, CompositeTexture texture) in variantTextures) { diff --git a/AttributeRenderingLibrary/Utility/Transforms.cs b/AttributeRenderingLibrary/Utility/Transforms.cs index a669d31..7116501 100644 --- a/AttributeRenderingLibrary/Utility/Transforms.cs +++ b/AttributeRenderingLibrary/Utility/Transforms.cs @@ -1,12 +1,20 @@ -using System.Collections.Generic; -using Vintagestory.API.Common; +using Vintagestory.API.Common; +using Vintagestory.API.Datastructures; namespace AttributeRenderingLibrary; public class Transforms { - public Dictionary GuiTransform { get; set; } - public Dictionary TpHandTransform { get; set; } - public Dictionary TpOffHandTransform { get; set; } - public Dictionary GroundTransform { get; set; } + public VariantSelectionList GuiTransform { get; set; } + public VariantSelectionList TpHandTransform { get; set; } + public VariantSelectionList TpOffHandTransform { get; set; } + public VariantSelectionList GroundTransform { get; set; } + + public static Transforms LoadFrom(JsonObject json) => new() + { + GuiTransform = VariantSelectionList.LoadFrom(json[nameof(GuiTransform)]), + TpHandTransform = VariantSelectionList.LoadFrom(json[nameof(TpHandTransform)]), + TpOffHandTransform = VariantSelectionList.LoadFrom(json[nameof(TpOffHandTransform)]), + GroundTransform = VariantSelectionList.LoadFrom(json[nameof(GroundTransform)]), + }; } diff --git a/AttributeRenderingLibrary/Utility/VariantExtensions.cs b/AttributeRenderingLibrary/Utility/VariantExtensions.cs index e6c7428..54eb9f2 100644 --- a/AttributeRenderingLibrary/Utility/VariantExtensions.cs +++ b/AttributeRenderingLibrary/Utility/VariantExtensions.cs @@ -4,79 +4,48 @@ using System.Text; using Vintagestory.API.Common; using Vintagestory.API.Config; -using Vintagestory.API.Util; namespace AttributeRenderingLibrary; public static class VariantExtensions { - /// - /// Similar to ByType, tries to match key (or multiple keys, if there is '::' separator used as AND operator) and give value behind it - /// - /// - /// - /// List of keys, including keys with '::' separator used as AND operator - /// - /// True, if value by key is found, otherwise false - public static bool FindByVariant(this Variants variants, Dictionary inDictionary, out T result) + public static bool FindByVariant(this Variants variants, VariantSelectionList inList, out T result) { + Core.Api?.World.FrameProfiler.Enter("attributerenderinglibrary.findbyvariant"); + result = default; - if (variants == null || inDictionary == null || inDictionary.Count == 0) + if (variants is null || inList is not { Count: > 0 }) { - Core.Api?.World.FrameProfiler.Mark("attributerenderinglibrary.findbyvariant"); + Core.Api?.World.FrameProfiler.Leave(); return false; } - List variantAsStringArray = variants.GetAsStringArray(); - foreach ((string key, T value) in inDictionary) + if (inList.FindFirstByVariant(variants, out result)) { - string[] keys = key.Contains("::") ? key.Split("::") : [key]; - if (keys.All(k => variantAsStringArray.Any(v => WildcardUtil.Match(k, v)))) - { - result = value; - Core.Api?.World.FrameProfiler.Mark("attributerenderinglibrary.findbyvariant"); - return true; - } + Core.Api?.World.FrameProfiler.Leave(); + return true; } - Core.Api?.World.FrameProfiler.Mark("attributerenderinglibrary.findbyvariant"); + Core.Api?.World.FrameProfiler.Leave(); return false; } - /// - /// Similar to FindByVariant, but returns multiple matching values - /// - /// - /// - /// List of keys, including keys with '::' separator used as AND operator - /// - /// True, if value by key is found, otherwise false - public static IEnumerable FindAllByVariant(this Variants variants, IDictionary inDictionary) + public static IEnumerable FindAllByVariant(this Variants variants, VariantSelectionList inList) { - if (variants == null || inDictionary == null || inDictionary.Count == 0) + Core.Api?.World.FrameProfiler.Enter("attributerenderinglibrary.findallbyvariant"); + if (variants is null || inList is not { Count: > 0 }) { - Core.Api?.World.FrameProfiler.Mark("attributerenderinglibrary.findbyvariant"); + Core.Api?.World.FrameProfiler.Leave(); yield break; } - List variantAsStringArray = variants.GetAsStringArray(); - foreach ((string key, T value) in inDictionary) + foreach (var entry in inList.FindAllByVariant(variants)) { - string[] keys = key.Contains("::") ? key.Split("::") : [key]; - if (keys.All(k => variantAsStringArray.Any(v => WildcardUtil.Match(k, v)))) - { - Core.Api?.World.FrameProfiler.Mark("attributerenderinglibrary.findbyvariant"); - yield return value; - } + yield return entry; } - Core.Api?.World.FrameProfiler.Mark("attributerenderinglibrary.findbyvariant"); - } - - public static bool IsTrue(this Variants variants, Dictionary inDictionary) - { - return variants != null && variants.FindByVariant(inDictionary, out bool result) && result; + Core.Api?.World.FrameProfiler.Leave(); } /// diff --git a/AttributeRenderingLibrary/Utility/VariantSelectionList.cs b/AttributeRenderingLibrary/Utility/VariantSelectionList.cs new file mode 100644 index 0000000..00ac739 --- /dev/null +++ b/AttributeRenderingLibrary/Utility/VariantSelectionList.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; +using Vintagestory.API.Datastructures; +using Vintagestory.API.Util; + +namespace AttributeRenderingLibrary; + +/// +/// This class is designed to store test-set + value pairs +/// +/// value type +public class VariantSelectionList +{ + /// + /// Cached pattern data for fast matching + /// + public struct TestPattern + { + public bool MatchAny; + public string Key; + public string Value; + + /// + /// Construct pattern from string + /// + /// string pattern, can be explicit (attr-val) or wildcard (attr-*, *-val, *, etc.) + /// constructed pattern + /// true on success + public static bool CreateFromString(string str, out TestPattern outPattern) + { + str = str.Trim(); + if (str == "*") + { + outPattern = new TestPattern + { + MatchAny = true, + }; + return true; + } + else + { + int separatorPos = str.IndexOf('-'); + if (separatorPos == -1) + { + outPattern = default; + return false; + } + + string key = str.Substring(0, separatorPos).Trim(); + string value = str.Substring(separatorPos + 1).Trim(); + + if (key.Length == 0 || value.Length == 0) + { + outPattern = default; + return false; + } + + outPattern = new TestPattern + { + Key = key, + Value = value, + }; + return true; + } + } + + /// + /// Test this pattern against variants + /// + /// variants to test against + /// true on any match + public bool Test(Variants variants) + { + if (MatchAny) + return true; + + bool wildcardKey = Key.Contains("*"); + bool wildcardValue = Value.Contains("*"); + + bool anyKey = wildcardKey && Key.Length == 1; + bool anyValue = wildcardValue && Value.Length == 1; + + bool TestValue(string pattern, string value) + { + if (anyValue) + return true; + if (wildcardValue) + return WildcardUtil.Match(pattern, value); + return pattern == value; + } + + if (anyKey) + { + foreach (var variant in variants.Elements) + { + if (TestValue(Value, variant.Value)) + return true; + } + } + if (wildcardKey) // Slowest option, check every entry + { + foreach (var variant in variants.Elements) + { + if (WildcardUtil.Match(Key, variant.Key)) + { + if (TestValue(Value, variant.Value)) + return true; + } + } + } + else // Faster option, value lookup + { + if (variants.Elements.TryGetValue(Key, out string variantValue)) + { + return TestValue(Value, variantValue); + } + } + + return false; + } + } + + public List> Entries { get; private init; } = new(); + public int Count => Entries.Count; + + public static VariantSelectionList LoadFrom(IDictionary dict) => LoadFrom(dict, item => item); + + public static VariantSelectionList LoadFrom(JObject obj) => LoadFrom(obj, item => item.ToObject()); + + public static VariantSelectionList LoadFrom(JsonObject obj) => LoadFrom(obj.Token as JObject); + + public static VariantSelectionList LoadFrom(IDictionary dict, Converter converter) + { + if (dict is not { Count: > 0 }) + return null; + + VariantSelectionList result = new(); + + foreach (var entry in dict) + { + var patternStrings = entry.Key.Split("::", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + List patterns = new(); + + foreach (var patternString in patternStrings) + { + if (!TestPattern.CreateFromString(patternString, out var pattern)) + throw new ArgumentException($"Failed to create test pattern {patternString}"); + + patterns.Add(pattern); + } + + result.Entries.Add(new KeyValuePair(patterns.ToArray(), converter(entry.Value))); + } + + return result; + } + + public bool FindFirstByVariant(Variants variants, out TItem outItem) + { + foreach (var entry in Entries) + { + if (entry.Key.All(pattern => pattern.Test(variants))) + { + outItem = entry.Value; + return true; + } + } + + outItem = default; + return false; + } + + public IEnumerable FindAllByVariant(Variants variants) + { + foreach (var entry in Entries) + { + if (entry.Key.All(pattern => pattern.Test(variants))) + { + yield return entry.Value; + } + } + } +}