From 26b7b1f647ab0208b07825e72ad6a64148a3ae1f Mon Sep 17 00:00:00 2001 From: 0xF Date: Sat, 22 Aug 2026 23:24:48 +0200 Subject: [PATCH 1/2] Add client-side custom item creation via server RPCs - Items.cs: CreateItem(JSON.Object[, refresh]) builds ItemDefinition + ItemBlueprint at runtime on the client and registers them into ItemManager collections. Server API SendCreate/RemoveCustomItems send JSON arrays over RPC (StringRaw). defaultBlueprints maintained surgically via Add/RemoveDefaultBlueprint (no full bpList rebuild). Cleanup on disconnect via ClientDisconnect -> RemoveCustomItems. - UI.ServerImage.cs: icon sprites set by CRC through the existing image callback system (ItemDefinitionEntry entries, no polling). - CommunityEntity.cs: centralized ClientDisconnect hook + ParseEnum. - Items.Test.cs: citem test commands (customitem_test / customitem_endtest). --- CommunityEntity.Items.Test.cs | 50 ++++++ CommunityEntity.Items.cs | 282 ++++++++++++++++++++++++++++++ CommunityEntity.UI.ServerImage.cs | 46 ++++- CommunityEntity.UI.cs | 231 ++++++++++++------------ CommunityEntity.cs | 70 +++++--- 5 files changed, 530 insertions(+), 149 deletions(-) create mode 100644 CommunityEntity.Items.Test.cs create mode 100644 CommunityEntity.Items.cs diff --git a/CommunityEntity.Items.Test.cs b/CommunityEntity.Items.Test.cs new file mode 100644 index 0000000..083daa3 --- /dev/null +++ b/CommunityEntity.Items.Test.cs @@ -0,0 +1,50 @@ +using UnityEngine; + +#if SERVER +public class citem +{ + [ServerVar] + public static void customitem_test( ConsoleSystem.Arg args ) + { + var player = args.Player(); + if ( player == null ) return; + + var ingredients = new JSON.Array(); + ingredients.Add( new JSON.Object { ["shortname"] = "wood", ["amount"] = 100 } ); + ingredients.Add( new JSON.Object { ["shortname"] = "stones", ["amount"] = 50 } ); + + var obj = new JSON.Object + { + ["shortname"] = "custom_rpc_item", + ["displayName"] = "Custom RPC Item", + ["displayDescription"] = "Created via server RPC", + ["category"] = ItemCategory.Misc.ToString(), + ["stackable"] = 100, + ["rarity"] = Rarity.Uncommon.ToString(), + ["amountType"] = ItemDefinition.AmountType.Count.ToString(), + ["ingredients"] = ingredients, + ["craftTime"] = 5, + ["workbenchLevelRequired"] = 1, + ["amountToCreate"] = 1, + ["defaultBlueprint"] = true + }; + + var items = new JSON.Array(); + items.Add( obj ); + + CommunityEntity.ServerInstance.SendCreateCustomItems( player, items.ToString() ); + } + + [ServerVar] + public static void customitem_endtest( ConsoleSystem.Arg args ) + { + var player = args.Player(); + if ( player == null ) return; + + var array = new JSON.Array(); + array.Add( "custom_rpc_item" ); + + CommunityEntity.ServerInstance.SendRemoveCustomItems( player, array.ToString() ); + } +} +#endif \ No newline at end of file diff --git a/CommunityEntity.Items.cs b/CommunityEntity.Items.cs new file mode 100644 index 0000000..6672d0b --- /dev/null +++ b/CommunityEntity.Items.cs @@ -0,0 +1,282 @@ +using System.Collections.Generic; +using UnityEngine; + +public partial class CommunityEntity +{ +#if SERVER + public void SendCreateCustomItems(BasePlayer player, string json) + { + ClientRPC(RpcTarget.Player("RPC_CreateCustomItems", player), json); + } + + public void SendRemoveCustomItems(BasePlayer player, string json) + { + ClientRPC(RpcTarget.Player("RPC_RemoveCustomItems", player), json); + } +#endif + +#if CLIENT + private static readonly List CustomItems = new List(); + private static readonly Dictionary CustomItemsDict = new Dictionary(); + + public static ItemDefinition CreateItem(JSON.Object obj) => CreateItem(obj, refresh: true); + + public static ItemDefinition CreateItem(JSON.Object obj, bool refresh) + { + var shortname = obj.GetString("shortname", null); + if (string.IsNullOrEmpty(shortname)) + return null; + + ItemManager.Initialize(); + + int itemid = shortname.GetHashCode(); + if (ItemManager.itemDictionary.ContainsKey(itemid)) + { + Debug.LogError($"[CommunityEntity] Custom item shortname '{shortname}' hashes to existing itemid {itemid}; not registering."); + return null; + } + if (ItemManager.itemDictionaryByName.ContainsKey(shortname)) + { + Debug.LogError($"[CommunityEntity] Custom item shortname '{shortname}' already exists; not registering."); + return null; + } + + List ingredients = null; + var ingredientsArray = obj.GetArray("ingredients"); + if (ingredientsArray != null) + { + ingredients = new List(ingredientsArray.Length); + for (int i = 0; i < ingredientsArray.Length; i++) + { + var ingredient = ingredientsArray[i].Obj; + if (ingredient == null) + continue; + + var shortname = ingredient.GetString("shortname", null); + if (string.IsNullOrEmpty(shortname)) + continue; + + var definition = ItemManager.FindItemDefinition(shortname); + if (definition == null) + continue; + + ingredients.Add(new ItemAmount(definition, ingredient.GetFloat("amount", 1f))); + } + if (ingredients.Count == 0) + ingredients = null; + } + + var go = new GameObject("CustomItem_" + shortname); + var definition = go.AddComponent(); + definition.itemid = itemid; + definition.shortname = shortname; + definition.displayName = new Translate.Phrase(shortname, obj.GetString("displayName", shortname)); + definition.displayDescription = new Translate.Phrase(shortname + ".description", obj.GetString("displayDescription", "")); + definition.category = ParseEnum(obj.GetString("category", "Misc"), ItemCategory.Misc); + definition.stackable = obj.GetInt("stackable", 1); + definition.rarity = ParseEnum(obj.GetString("rarity", "Common"), Rarity.Common); + definition.amountType = ParseEnum(obj.GetString("amountType", "Count"), ItemDefinition.AmountType.Count); + definition.Initialize(ItemManager.itemList); + + ItemManager.itemList.Add(definition); + ItemManager.itemDictionary.Add(itemid, definition); + ItemManager.itemDictionaryByName.Add(shortname, definition); + + CustomItems.Add(definition); + CustomItemsDict[shortname] = definition; + + uint.TryParse(obj.GetString("iconSpriteCrc"), out var iconSpriteCrc); + if (iconSpriteCrc != 0 && ClientInstance != null) + ClientInstance.ApplyTextureToItem(definition, iconSpriteCrc); + + if (ingredients != null) + { + var bp = go.AddComponent(); + bp.ingredients = ingredients; + bp.time = obj.GetFloat("craftTime", 1f); + bp.workbenchLevelRequired = obj.GetInt("workbenchLevelRequired", 0); + bp.amountToCreate = obj.GetInt("amountToCreate", 1); + bp.defaultBlueprint = obj.GetBoolean("defaultBlueprint", false); + bp.userCraftable = obj.GetBoolean("userCraftable", true); + bp.isResearchable = obj.GetBoolean("isResearchable", true); + bp.scrapRequired = obj.GetInt("scrapRequired", 0); + + ItemManager.bpList.Add(bp); + ItemManager.itemToBlueprint.Add(definition, bp); + + for (int i = 0; i < ingredients.Count; i++) + { + var ingredient = ingredients[i]; + if (ingredient.itemDef == null) + continue; + + if (!ItemManager.ingredientToBlueprints.TryGetValue(ingredient.itemDef, out var list)) + { + list = new List(); + ItemManager.ingredientToBlueprints.Add(ingredient.itemDef, list); + } + list.Add(bp); + } + + if (bp.defaultBlueprint) + AddDefaultBlueprint(definition.itemid); + } + + if (refresh) + { + LocalPlayer.OnInventoryChanged(); + UIBlueprints.Refresh(); + } + + return definition; + } + + private static void AddDefaultBlueprint(int itemid) + { + var current = ItemManager.defaultBlueprints; + for (int i = 0; i < current.Length; i++) + { + if (current[i] == itemid) + return; + } + + var result = new int[current.Length + 1]; + current.CopyTo(result, 0); + result[current.Length] = itemid; + ItemManager.defaultBlueprints = result; + } + + private static void RemoveDefaultBlueprint(int itemid) + { + var current = ItemManager.defaultBlueprints; + int index = -1; + for (int i = 0; i < current.Length; i++) + { + if (current[i] == itemid) + { + index = i; + break; + } + } + if (index < 0) + return; + + var result = new int[current.Length - 1]; + for (int i = 0, j = 0; i < current.Length; i++) + { + if (i == index) + continue; + result[j++] = current[i]; + } + ItemManager.defaultBlueprints = result; + } + + [RPC_Client] + public void RPC_CreateCustomItems(RPCMessage rpc) + { + var json = rpc.read.StringRaw(); + if (string.IsNullOrEmpty(json)) + return; + + var array = JSON.Array.Parse(json); + if (array == null) + return; + + bool created = false; + for (int i = 0; i < array.Length; i++) + { + var obj = array[i].Obj; + if (obj == null) + continue; + + if (CreateItem(obj, refresh: false) != null) + created = true; + } + + if (created) + { + LocalPlayer.OnInventoryChanged(); + UIBlueprints.Refresh(); + } + } + + [RPC_Client] + public void RPC_RemoveCustomItems(RPCMessage rpc) + { + var json = rpc.read.StringRaw(); + if (string.IsNullOrEmpty(json)) + return; + + var array = JSON.Array.Parse(json); + if (array == null) + return; + + bool removed = false; + for (int i = 0; i < array.Length; i++) + { + var shortname = array[i].Str; + if (string.IsNullOrEmpty(shortname)) + continue; + + if (!CustomItemsDict.TryGetValue(shortname, out var definition)) + continue; + + CustomItemsDict.Remove(shortname); + RemoveCustomItem(definition); + removed = true; + } + + if (removed) + { + CustomItems.Clear(); + CustomItems.AddRange(CustomItemsDict.Values); + LocalPlayer.OnInventoryChanged(); + UIBlueprints.Refresh(); + } + } + + public static void RemoveCustomItems() + { + for (int i = 0; i < CustomItems.Count; i++) + RemoveCustomItem(CustomItems[i]); + + CustomItems.Clear(); + CustomItemsDict.Clear(); + } + + private static void RemoveCustomItem(ItemDefinition definition) + { + ItemManager.itemList.Remove(definition); + ItemManager.itemDictionary.Remove(definition.itemid); + if (!string.IsNullOrEmpty(definition.shortname)) + ItemManager.itemDictionaryByName.Remove(definition.shortname); + + var bp = definition.GetComponent(); + if (bp != null) + { + ItemManager.bpList.Remove(bp); + ItemManager.itemToBlueprint.Remove(definition); + + for (int j = 0; j < bp.ingredients.Count; j++) + { + var ingredient = bp.ingredients[j]; + if (ingredient.itemDef == null) + continue; + + if (ItemManager.ingredientToBlueprints.TryGetValue(ingredient.itemDef, out var list)) + { + list.Remove(bp); + if (list.Count == 0) + ItemManager.ingredientToBlueprints.Remove(ingredient.itemDef); + } + } + + if (bp.defaultBlueprint) + RemoveDefaultBlueprint(definition.itemid); + } + + if (definition.gameObject != null) + Object.Destroy(definition.gameObject); + } +#endif +} \ No newline at end of file diff --git a/CommunityEntity.UI.ServerImage.cs b/CommunityEntity.UI.ServerImage.cs index 54db659..a00d05c 100644 --- a/CommunityEntity.UI.ServerImage.cs +++ b/CommunityEntity.UI.ServerImage.cs @@ -16,12 +16,18 @@ public partial class CommunityEntity private class ImageRequest { public List Entries = new List(); + public List ItemDefinitionEntries = new List(); public struct Entry { public UnityEngine.UI.MaskableGraphic graphic; public Vector4? slice; } + + public struct ItemDefinitionEntry + { + public ItemDefinition definition; + } } private class CachedTexture @@ -107,7 +113,12 @@ public void CL_ReceiveFilePng( BaseEntity.RPCMessage msg ) { ApplyCachedTextureToImage(c, texture); } - + + foreach (var item in request.ItemDefinitionEntries) + { + ApplyCachedTextureToItem(item, texture); + } + // Remove request requestingTextureImages.Remove(textureID); } @@ -175,6 +186,39 @@ public Sprite GetOrRequestSprite(uint id, Vector4? slice = null) return null; } + public void ApplyTextureToItem( ItemDefinition definition, uint textureID ) + { + var texture = GetCachedTexture( textureID ); + if ( texture == null ) + { + var bytes = FileStorage.client.Get( textureID, FileStorage.Type.png, net.ID ); + if ( bytes != null ) + { + texture = StoreCachedTexture( textureID, bytes ); + } + else + { + var request = RequestImage(textureID, true); + request.ItemDefinitionEntries.Add(new ImageRequest.ItemDefinitionEntry() + { + definition = definition + }); + return; + } + } + + ApplyCachedTextureToItem( new ImageRequest.ItemDefinitionEntry() + { + definition = definition + }, texture ); + } + + private void ApplyCachedTextureToItem( ImageRequest.ItemDefinitionEntry entry, CachedTexture texture ) + { + if ( entry.definition != null ) + entry.definition.iconSprite = texture.GetOrCreateSprite(null); + } + public void ApplyTextureToImage( UnityEngine.UI.MaskableGraphic component, uint textureID, Vector4? slice = null ) { var texture = GetCachedTexture( textureID ); diff --git a/CommunityEntity.UI.cs b/CommunityEntity.UI.cs index b052f81..dc14049 100644 --- a/CommunityEntity.UI.cs +++ b/CommunityEntity.UI.cs @@ -258,10 +258,10 @@ T GetOrAddComponent() where T : Component if ( ShouldUpdateField( "imagetype" ) ) c.type = ParseEnum( obj.GetString( "imagetype", "Simple" ), UnityEngine.UI.Image.Type.Simple ); if( ShouldUpdateField( "fillCenter" ) ) - c.fillCenter = obj.GetBoolean("fillCenter", c.fillCenter); - if (obj.ContainsKey("ppuMultiplier")) - c.pixelsPerUnitMultiplier = obj.GetFloat("ppuMultiplier", 1f); - + c.fillCenter = obj.GetBoolean("fillCenter", c.fillCenter); + if (obj.ContainsKey("ppuMultiplier")) + c.pixelsPerUnitMultiplier = obj.GetFloat("ppuMultiplier", 1f); + if ( obj.ContainsKey( "png" ) && uint.TryParse( obj.GetString( "png" ), out var id ) ) { Vector4? slice = null; @@ -348,9 +348,9 @@ T GetOrAddComponent() where T : Component case "UnityEngine.UI.Button": { var c = GetOrAddComponent(); - HandleEnableState( obj, c ); - if (ShouldUpdateField("interactable")) - c.interactable = obj.GetBoolean("interactable", true); + HandleEnableState( obj, c ); + if (ShouldUpdateField("interactable")) + c.interactable = obj.GetBoolean("interactable", true); if ( obj.ContainsKey( "command" ) ) { var cmd = obj.GetString( "command" ); @@ -383,7 +383,7 @@ T GetOrAddComponent() where T : Component // Modify the color of the button when hovered // Have to grab colorBlock, modify then reassign var colors = c.colors; - + if (HasField("normalColor")) colors.normalColor = ColorEx.Parse(obj.GetString("normalColor", "1.0 1.0 1.0 1.0")); if (HasField("highlightedColor")) @@ -395,23 +395,23 @@ T GetOrAddComponent() where T : Component if (HasField("disabledColor")) colors.disabledColor = ColorEx.Parse(obj.GetString("disabledColor", "0.5 0.5 0.5 0.5")); if (HasField("colorMultiplier")) - colors.colorMultiplier = obj.GetFloat("colorMultiplier", 1.0f); - if (HasField("fadeDuration")) - { - colors.fadeDuration = 0f; - c.colors = colors; - colors.fadeDuration = obj.GetFloat("fadeDuration", 0.1f); - } - else if(!c.IsInteractable()) - { - var prevFadeDuration = colors.fadeDuration; - colors.fadeDuration = 0f; - c.colors = colors; - colors.fadeDuration = prevFadeDuration; - } - - c.colors = colors; - + colors.colorMultiplier = obj.GetFloat("colorMultiplier", 1.0f); + if (HasField("fadeDuration")) + { + colors.fadeDuration = 0f; + c.colors = colors; + colors.fadeDuration = obj.GetFloat("fadeDuration", 0.1f); + } + else if(!c.IsInteractable()) + { + var prevFadeDuration = colors.fadeDuration; + colors.fadeDuration = 0f; + c.colors = colors; + colors.fadeDuration = prevFadeDuration; + } + + c.colors = colors; + GraphicComponentCreated( img, obj ); break; @@ -446,9 +446,9 @@ T GetOrAddComponent() where T : Component var c = GetOrAddComponent(); HandleEnableState( obj, c ); - c.textComponent = t; - if (ShouldUpdateField("interactable")) - c.interactable = obj.GetBoolean("interactable", true); + c.textComponent = t; + if (ShouldUpdateField("interactable")) + c.interactable = obj.GetBoolean("interactable", true); if ( ShouldUpdateField( "characterLimit" ) ) c.characterLimit = obj.GetInt( "characterLimit", allowUpdate ? c.characterLimit : 0 ); @@ -683,22 +683,22 @@ T GetOrAddComponent() where T : Component c.ignoreLayout = obj.GetBoolean("ignoreLayout", false); break; - } - case "UnityEngine.UI.CanvasGroup": - { - var c = GetOrAddComponent(); - if (ShouldUpdateField("alpha")) - c.alpha = obj.GetFloat("alpha", 1f); - if (ShouldUpdateField("blocksRaycasts")) - c.blocksRaycasts = obj.GetBoolean("blocksRaycasts", true); - if (ShouldUpdateField("interactable")) - c.interactable = obj.GetBoolean("interactable", true); - if (obj.ContainsKey("fade")) - { - var fade = Vector2Ex.Parse(obj.GetString("fade", "0 1")); - StartCoroutine(FadeCanvasGroup(c, fade.y, fade.x)); - } - break; + } + case "UnityEngine.UI.CanvasGroup": + { + var c = GetOrAddComponent(); + if (ShouldUpdateField("alpha")) + c.alpha = obj.GetFloat("alpha", 1f); + if (ShouldUpdateField("blocksRaycasts")) + c.blocksRaycasts = obj.GetBoolean("blocksRaycasts", true); + if (ShouldUpdateField("interactable")) + c.interactable = obj.GetBoolean("interactable", true); + if (obj.ContainsKey("fade")) + { + var fade = Vector2Ex.Parse(obj.GetString("fade", "0 1")); + StartCoroutine(FadeCanvasGroup(c, fade.y, fade.x)); + } + break; } case "Draggable": { @@ -768,14 +768,14 @@ T GetOrAddComponent() where T : Component var c = GetOrAddComponent(); HandleEnableState( obj, c ); break; - } - case "UnityEngine.UI.Mask": - { - var c = GetOrAddComponent(); - HandleEnableState(obj, c); - if (ShouldUpdateField("showMaskGraphic")) - c.showMaskGraphic = obj.GetBoolean("showMaskGraphic", true); - break; + } + case "UnityEngine.UI.Mask": + { + var c = GetOrAddComponent(); + HandleEnableState(obj, c); + if (ShouldUpdateField("showMaskGraphic")) + c.showMaskGraphic = obj.GetBoolean("showMaskGraphic", true); + break; } case "UnityEngine.UI.ScrollView": { @@ -889,46 +889,46 @@ T GetOrAddComponent() where T : Component if (ShouldUpdateField("verticalNormalizedPosition")) scrollRect.verticalNormalizedPosition = obj.GetFloat("verticalNormalizedPosition", 1f); break; - } - case "Tooltip": - { - if (TooltipRef != null && TooltipAlwaysOnTopRef != null && TooltipAlwaysOnTopEmojiRef != null) - { - var c = GetOrAddComponent(); - HandleEnableState(obj, c); - if (ShouldUpdateField("tooltipType")) - { - var tooltipType = ParseEnum(obj.GetString("tooltipType", "Default"), TooltipType.Default); - c.TooltipObject = tooltipType switch - { - TooltipType.AlwaysOnTop => TooltipAlwaysOnTopRef, - TooltipType.AlwaysOnTopEmoji => TooltipAlwaysOnTopEmojiRef, - _ => TooltipRef - }; - } - if (ShouldUpdateField("offset")) - { - c.offset = Vector2Ex.Parse(obj.GetString("offset", "8 8")); - } - if (ShouldUpdateField("useCentre")) - { - c.useCentre = obj.GetBoolean("useCentre", false); - } - if (ShouldUpdateField("text")) - { - var text = obj.GetString("text", "Text").Replace("\\n", "\n"); - c.SetPhrase(new Translate.Phrase(null, text)); - } - if (ShouldUpdateField("delay")) - { - c.delayBeforeAppearing = ParseEnum(obj.GetString("delay", "Short"), Tooltip.DelayType.Short); - } - if (ShouldUpdateField("position")) - { - c.positionMode = ParseEnum(obj.GetString("position", "Auto"), TooltipContainer.PositionMode.Auto); - } - } - break; + } + case "Tooltip": + { + if (TooltipRef != null && TooltipAlwaysOnTopRef != null && TooltipAlwaysOnTopEmojiRef != null) + { + var c = GetOrAddComponent(); + HandleEnableState(obj, c); + if (ShouldUpdateField("tooltipType")) + { + var tooltipType = ParseEnum(obj.GetString("tooltipType", "Default"), TooltipType.Default); + c.TooltipObject = tooltipType switch + { + TooltipType.AlwaysOnTop => TooltipAlwaysOnTopRef, + TooltipType.AlwaysOnTopEmoji => TooltipAlwaysOnTopEmojiRef, + _ => TooltipRef + }; + } + if (ShouldUpdateField("offset")) + { + c.offset = Vector2Ex.Parse(obj.GetString("offset", "8 8")); + } + if (ShouldUpdateField("useCentre")) + { + c.useCentre = obj.GetBoolean("useCentre", false); + } + if (ShouldUpdateField("text")) + { + var text = obj.GetString("text", "Text").Replace("\\n", "\n"); + c.SetPhrase(new Translate.Phrase(null, text)); + } + if (ShouldUpdateField("delay")) + { + c.delayBeforeAppearing = ParseEnum(obj.GetString("delay", "Short"), Tooltip.DelayType.Short); + } + if (ShouldUpdateField("position")) + { + c.positionMode = ParseEnum(obj.GetString("position", "Auto"), TooltipContainer.PositionMode.Auto); + } + } + break; } } } @@ -1016,8 +1016,8 @@ private void BuildScrollbar(Scrollbar scrollbar, JSON.Object obj, bool vertical) rt.offsetMin = new Vector2(0f, -size); rt.offsetMax = Vector2.zero; } - } - + } + static IEnumerator FadeCanvasGroup(CanvasGroup group, float to, float duration) { float from = group.alpha; @@ -1033,7 +1033,7 @@ static IEnumerator FadeCanvasGroup(CanvasGroup group, float to, float duration) yield break; } group.alpha = to; - } + } // sets the transform to a sensible default private void FitParent(RectTransform transform){ @@ -1075,13 +1075,6 @@ private void ApplyPadding(LayoutGroup g, JSON.Object obj, Func Sho } } - private static T ParseEnum(string value, T defaultValue) - where T : struct, System.Enum - { - if ( string.IsNullOrWhiteSpace( value ) ) return defaultValue; - return System.Enum.TryParse( value, true, out var parsedValue ) ? parsedValue : defaultValue; - } - private void GraphicComponentCreated(UnityEngine.UI.Graphic c, JSON.Object obj) { if (obj.ContainsKey("fadeIn")) @@ -1154,29 +1147,29 @@ private Font LoadFont(string fontName) } return font; - } - + } + [RPC_Client] public void DestroyUI(RPCMessage msg) { DestroyPanel(msg.read.StringRaw()); - UpdateCanvasesVisibility(); - } - + UpdateCanvasesVisibility(); + } + [RPC_Client] public void DestroyUIs(RPCMessage msg) - { - using var destroyUIs = msg.read.Proto(); - - if (destroyUIs.list == null) - { - return; - } - - for (int i = 0; i < destroyUIs.list.Count; i++) - { - DestroyPanel(destroyUIs.list[i]); - } + { + using var destroyUIs = msg.read.Proto(); + + if (destroyUIs.list == null) + { + return; + } + + for (int i = 0; i < destroyUIs.list.Count; i++) + { + DestroyPanel(destroyUIs.list[i]); + } UpdateCanvasesVisibility(); } diff --git a/CommunityEntity.cs b/CommunityEntity.cs index 03787be..843a0f8 100644 --- a/CommunityEntity.cs +++ b/CommunityEntity.cs @@ -2,7 +2,7 @@ using ProtoBuf; using System.Collections.Generic; using UnityEngine; - + public partial class CommunityEntity : PointEntity { public static CommunityEntity ServerInstance = null; @@ -33,33 +33,45 @@ protected override void ClientInit(Entity info) { base.ClientInit(info); UpdateCanvasesVisibility(); - } -#endif - -#if SERVER - // This mainly exists for our ServerRPC overload generator so this specific overload can exist - public void SendDestroyUIs(BasePlayer player, List uiPanels) - { - using var destroyUi = Pool.Get(); - destroyUi.list = Pool.Get>(); - for(int i = 0; i < uiPanels.Count; i++) - { - destroyUi.list.Add(uiPanels[i]); - } - ClientRPC(RpcTarget.Player("DestroyUIs", player), destroyUi); - } - - // Added alternative overload; plugins can have a static array with all UIs they want to destroy predefined - public void SendDestroyUIs(BasePlayer player, string[] uiPanels) - { - using var destroyUi = Pool.Get(); - destroyUi.list = Pool.Get>(); - for (int i = 0; i < uiPanels.Length; i++) - { - destroyUi.list.Add(uiPanels[i]); - } - ClientRPC(RpcTarget.Player("DestroyUIs", player), destroyUi); - } + } + + private void ClientDisconnect() + { + RemoveCustomItems(); + } + + private static T ParseEnum(string value, T defaultValue) + where T : struct, System.Enum + { + if (string.IsNullOrWhiteSpace(value)) return defaultValue; + return System.Enum.TryParse(value, true, out var parsedValue) ? parsedValue : defaultValue; + } +#endif + +#if SERVER + // This mainly exists for our ServerRPC overload generator so this specific overload can exist + public void SendDestroyUIs(BasePlayer player, List uiPanels) + { + using var destroyUi = Pool.Get(); + destroyUi.list = Pool.Get>(); + for(int i = 0; i < uiPanels.Count; i++) + { + destroyUi.list.Add(uiPanels[i]); + } + ClientRPC(RpcTarget.Player("DestroyUIs", player), destroyUi); + } + + // Added alternative overload; plugins can have a static array with all UIs they want to destroy predefined + public void SendDestroyUIs(BasePlayer player, string[] uiPanels) + { + using var destroyUi = Pool.Get(); + destroyUi.list = Pool.Get>(); + for (int i = 0; i < uiPanels.Length; i++) + { + destroyUi.list.Add(uiPanels[i]); + } + ClientRPC(RpcTarget.Player("DestroyUIs", player), destroyUi); + } #endif - + } From d0f1ec2fc108a29c11b7bda3d3a3d976ec19cf95 Mon Sep 17 00:00:00 2001 From: 0xF Date: Sun, 23 Aug 2026 08:03:36 +0200 Subject: [PATCH 2/2] Allow itemid to be supplied via JSON, falling back to shortname.GetHashCode() --- CommunityEntity.Items.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CommunityEntity.Items.cs b/CommunityEntity.Items.cs index 6672d0b..01c14d7 100644 --- a/CommunityEntity.Items.cs +++ b/CommunityEntity.Items.cs @@ -29,10 +29,10 @@ public static ItemDefinition CreateItem(JSON.Object obj, bool refresh) ItemManager.Initialize(); - int itemid = shortname.GetHashCode(); + int itemid = obj.GetInt("itemid", shortname.GetHashCode()); if (ItemManager.itemDictionary.ContainsKey(itemid)) { - Debug.LogError($"[CommunityEntity] Custom item shortname '{shortname}' hashes to existing itemid {itemid}; not registering."); + Debug.LogError($"[CommunityEntity] Custom item shortname '{shortname}' itemid {itemid} already in use; not registering."); return null; } if (ItemManager.itemDictionaryByName.ContainsKey(shortname))