using System; using System.Collections.Generic; using System.Reflection; using HG; using HG.Reflection; using JetBrains.Annotations; using UnityEngine; using UnityEngine.Networking; namespace RoR2.Items; public abstract class BaseItemBodyBehavior : MonoBehaviour { private struct ItemTypePair { public ItemIndex itemIndex; public Type behaviorType; } private struct NetworkContextSet { public ItemTypePair[] itemTypePairs; public FixedSizeArrayPool behaviorArraysPool; public void SetItemTypePairs(List itemTypePairs) { this.itemTypePairs = itemTypePairs.ToArray(); behaviorArraysPool = new FixedSizeArrayPool(this.itemTypePairs.Length); } } [MeansImplicitUse] [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public class ItemDefAssociationAttribute : HG.Reflection.SearchableAttribute { public Type behaviorTypeOverride; public bool useOnServer = true; public bool useOnClient = true; } public int stack; private static NetworkContextSet server; private static NetworkContextSet client; private static NetworkContextSet shared; private static CharacterBody earlyAssignmentBody = null; private static Dictionary, BaseItemBodyBehavior[]> bodyToItemBehaviors = new Dictionary, BaseItemBodyBehavior[]>(); public CharacterBody body { get; private set; } protected void Awake() { body = earlyAssignmentBody; earlyAssignmentBody = null; } [SystemInitializer(new Type[] { typeof(ItemCatalog) })] private static void Init() { List list = new List(); List list2 = new List(); List list3 = new List(); List list4 = new List(); HG.Reflection.SearchableAttribute.GetInstances(list4); Type typeFromHandle = typeof(BaseItemBodyBehavior); Type typeFromHandle2 = typeof(ItemDef); foreach (ItemDefAssociationAttribute item in list4) { if (!(item.target is MethodInfo methodInfo)) { Debug.LogError("ItemDefAssociationAttribute cannot be applied to object of type '" + item?.GetType().FullName + "'"); continue; } if (!methodInfo.IsStatic) { Debug.LogError("ItemDefAssociationAttribute cannot be applied to method " + methodInfo.DeclaringType.FullName + "." + methodInfo.Name + ": Method is not static."); continue; } Type type = item.behaviorTypeOverride ?? methodInfo.DeclaringType; if (!typeFromHandle.IsAssignableFrom(type)) { Debug.LogError("ItemDefAssociationAttribute cannot be applied to method " + methodInfo.DeclaringType.FullName + "." + methodInfo.Name + ": " + methodInfo.DeclaringType.FullName + " does not derive from " + typeFromHandle.FullName + "."); continue; } if (type.IsAbstract) { Debug.LogError("ItemDefAssociationAttribute cannot be applied to method " + methodInfo.DeclaringType.FullName + "." + methodInfo.Name + ": " + methodInfo.DeclaringType.FullName + " is an abstract type."); continue; } if (!typeFromHandle2.IsAssignableFrom(methodInfo.ReturnType)) { Debug.LogError(string.Format("{0} cannot be applied to method {1}.{2}: {3}.{4} returns type '{5}' instead of {6}.", "ItemDefAssociationAttribute", methodInfo.DeclaringType.FullName, methodInfo.Name, methodInfo.DeclaringType.FullName, methodInfo, methodInfo.ReturnType?.FullName ?? "void", typeFromHandle2.FullName)); continue; } if (methodInfo.GetGenericArguments().Length != 0) { Debug.LogError(string.Format("{0} cannot be applied to method {1}.{2}: {3}.{4} must take no arguments.", "ItemDefAssociationAttribute", methodInfo.DeclaringType.FullName, methodInfo.Name, methodInfo.DeclaringType.FullName, methodInfo)); continue; } ItemDef itemDef = (ItemDef)methodInfo.Invoke(null, Array.Empty()); if (!itemDef) { Debug.LogError(methodInfo.DeclaringType.FullName + "." + methodInfo.Name + " returned null."); continue; } if (itemDef.itemIndex < ItemIndex.Count) { Debug.LogError($"{methodInfo.DeclaringType.FullName}.{methodInfo.Name} returned an ItemDef that's not registered in the ItemCatalog. result={itemDef}"); continue; } if (item.useOnServer) { list.Add(new ItemTypePair { itemIndex = itemDef.itemIndex, behaviorType = type }); } if (item.useOnClient) { list2.Add(new ItemTypePair { itemIndex = itemDef.itemIndex, behaviorType = type }); } if (item.useOnServer || item.useOnClient) { list3.Add(new ItemTypePair { itemIndex = itemDef.itemIndex, behaviorType = type }); } } server.SetItemTypePairs(list); client.SetItemTypePairs(list2); shared.SetItemTypePairs(list3); CharacterBody.onBodyAwakeGlobal += OnBodyAwakeGlobal; CharacterBody.onBodyDestroyGlobal += OnBodyDestroyGlobal; CharacterBody.onBodyInventoryChangedGlobal += OnBodyInventoryChangedGlobal; } private static ref NetworkContextSet GetNetworkContext() { bool active = NetworkServer.active; bool active2 = NetworkClient.active; if (active) { if (active2) { return ref shared; } return ref server; } if (active2) { return ref client; } throw new InvalidOperationException("Neither server nor client is running."); } private static void OnBodyAwakeGlobal(CharacterBody body) { BaseItemBodyBehavior[] value = GetNetworkContext().behaviorArraysPool.Request(); bodyToItemBehaviors.Add(body, value); } private static void OnBodyDestroyGlobal(CharacterBody body) { BaseItemBodyBehavior[] array = bodyToItemBehaviors[body]; for (int i = 0; i < array.Length; i++) { UnityEngine.Object.Destroy(array[i]); } bodyToItemBehaviors.Remove(body); if (NetworkServer.active || NetworkClient.active) { GetNetworkContext().behaviorArraysPool.Return(array); } } private static void OnBodyInventoryChangedGlobal(CharacterBody body) { UpdateBodyItemBehaviorStacks(body); } private static void UpdateBodyItemBehaviorStacks(CharacterBody body) { ref NetworkContextSet networkContext = ref GetNetworkContext(); BaseItemBodyBehavior[] array = bodyToItemBehaviors[body]; ItemTypePair[] itemTypePairs = networkContext.itemTypePairs; Inventory inventory = body.inventory; if ((bool)inventory) { for (int i = 0; i < itemTypePairs.Length; i++) { ItemTypePair itemTypePair = itemTypePairs[i]; SetItemStack(body, ref array[i], itemTypePair.behaviorType, inventory.GetItemCount(itemTypePair.itemIndex)); } return; } for (int j = 0; j < itemTypePairs.Length; j++) { ref BaseItemBodyBehavior reference = ref array[j]; if ((object)reference != null) { UnityEngine.Object.Destroy(reference); reference = null; } } } private static void SetItemStack(CharacterBody body, ref BaseItemBodyBehavior behavior, Type behaviorType, int stack) { if ((object)behavior == null != stack <= 0) { if (stack <= 0) { UnityEngine.Object.Destroy(behavior); behavior = null; } else { earlyAssignmentBody = body; behavior = (BaseItemBodyBehavior)body.gameObject.AddComponent(behaviorType); earlyAssignmentBody = null; } } if ((object)behavior != null) { behavior.stack = stack; } } }