using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Logging; using ForgeKit; using SideLoader; using SideLoader.Model; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("SkillKit")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0+c48b2eb6460f1b993d9ddb914cbdc5ff6f1d22c6")] [assembly: AssemblyProduct("SkillKit")] [assembly: AssemblyTitle("SkillKit")] [assembly: AssemblyMetadata("BuildStamp", "c48b2eb 2026-07-30")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.0.0")] [module: UnverifiableCode] namespace SkillKit; internal static class CastAnimEngine { private struct ParsedAnim { public string AnimName; public bool Valid; public SpellCastType Type; } private struct LastSync { public Character Player; public SpellCastType Type; public SpellCastModifier Modifier; public bool Primed; } private static readonly HashSet _warnedBadName = new HashSet(); private static readonly HashSet _warnedBowAttack = new HashSet(); private static readonly Dictionary _parsed = new Dictionary(); private static readonly Dictionary _lastSync = new Dictionary(); internal static void InvalidateConverged() { _lastSync.Clear(); } internal static void Sync(SkillSpec spec, Character player) { //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Invalid comparison between Unknown and I4 //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Invalid comparison between Unknown and I4 //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) if (spec.CastAnim == null) { return; } CastPick castPick = spec.CastAnim(player); if (!_parsed.TryGetValue(spec, out var value) || (object)value.AnimName != castPick.AnimName) { value.AnimName = castPick.AnimName; value.Valid = Enum.TryParse(castPick.AnimName, ignoreCase: true, out value.Type); _parsed[spec] = value; } if (!value.Valid) { if (_warnedBadName.Add(spec)) { ManualLogSource log = SkillRegistry.Log; if (log != null) { log.LogWarning((object)("[SKILLKIT] '" + spec.Label + "': '" + castPick.AnimName + "' isn't a real Character.SpellCastType — cast-sync skipped, the donor's baked cast will show instead.")); } } return; } _warnedBadName.Remove(spec); SpellCastType type = value.Type; SpellCastModifier modifier = castPick.Modifier; if ((Object)(object)((player != null) ? player.CurrentWeapon : null) != (Object)null && (int)player.CurrentWeapon.Type == 200 && (int)modifier == 2) { if (_warnedBowAttack.Add(spec)) { ManualLogSource log2 = SkillRegistry.Log; if (log2 != null) { log2.LogWarning((object)("[SKILLKIT] '" + spec.Label + "': CastModifier.Attack with a bow equipped crashes AttackInput's charge path (bug-13) — forcing Immobilized instead.")); } } modifier = (SpellCastModifier)0; } if (_lastSync.TryGetValue(spec, out var value2) && value2.Primed && (Object)(object)value2.Player == (Object)(object)player && value2.Type == type && value2.Modifier == modifier) { return; } var (num, num2) = SkillInstanceSync.Sync(spec.ItemId, player, (Item item) => Apply(item, type, modifier)); _lastSync[spec] = new LastSync { Player = player, Type = type, Modifier = modifier, Primed = true }; if (num > 0) { ManualLogSource log3 = SkillRegistry.Log; if (log3 != null) { log3.LogMessage((object)($"[SKILLKIT] cast-sync: '{spec.Label}' '{type}'/{modifier} " + $"stamped onto {num} object(s) (learned instances seen: {num2}).")); } } } private static bool Apply(Item item, SpellCastType type, SpellCastModifier modifier) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) if (item.m_activateEffectAnimType == type && item.CastModifier == modifier && item.AlternateAnimHasSkillID == -1) { return false; } item.m_activateEffectAnimType = type; item.CastModifier = modifier; item.AlternateAnimHasSkillID = -1; return true; } } public static class CastGuard { public static void ClearSoon(Character player) { if (!((Object)(object)player == (Object)null) && !((Object)(object)SkillRegistry.CoroutineHost == (Object)null)) { SkillRegistry.CoroutineHost.StartCoroutine(ClearAfter(player)); } } private static IEnumerator ClearAfter(Character player) { yield return null; yield return null; if ((Object)(object)player != (Object)null && player.IsCasting) { ManualLogSource log = SkillRegistry.Log; if (log != null) { log.LogMessage((object)"[SKILLKIT] self-clearing a lingering IsCasting after a kit skill (bug-16 self-heal)."); } player.SetCastData((SpellCastType)(-1), (GameObject)null); } } } public class DelegateEffect : Effect { public override void ActivateLocally(Character _affectedCharacter, object[] _infos) { Item val = (((Object)(object)((Effect)this).ParentItem != (Object)null) ? ((Effect)this).ParentItem : ((Component)this).GetComponentInParent()); SkillSpec skillSpec = (((Object)(object)val != (Object)null) ? SkillRegistry.Find(val.ItemID) : null); if (skillSpec == null) { ManualLogSource log = SkillRegistry.Log; if (log != null) { log.LogWarning((object)("[SKILLKIT] DelegateEffect fired on '" + ((Object)this).name + "' but no registered spec matches ItemID=" + (((Object)(object)val != (Object)null) ? val.ItemID.ToString() : "") + " — cast does nothing.")); } return; } try { if (skillSpec.OnCastResult != null) { CastResult castResult = skillSpec.OnCastResult(_affectedCharacter); if (castResult == CastResult.Refund) { SkillCooldowns.RefundNextFrame(skillSpec.ItemId, _affectedCharacter, "[" + skillSpec.Label + "]", "refused"); } } else { skillSpec.OnCast?.Invoke(_affectedCharacter); } } finally { if (skillSpec.ClearCastAfter) { CastGuard.ClearSoon(_affectedCharacter); } } } } internal static class DynamicIconEngine { private static readonly Dictionary> _cache = new Dictionary>(); private static readonly HashSet _slLinked = new HashSet(); internal static void InvalidateLinks() { _slLinked.Clear(); } internal static void Sync(SkillSpec spec, Character player) { if (spec.Icon?.Select == null) { return; } string text = spec.Icon.Select(); if (text == null) { return; } Sprite icon = Resolve(spec, text); if ((Object)(object)icon == (Object)null) { return; } RegisterStaticIconLink(spec, icon); var (num, num2) = SkillInstanceSync.Sync(spec.ItemId, player, (Item item) => Apply(item, icon)); if (num > 0) { ManualLogSource log = SkillRegistry.Log; if (log != null) { log.LogMessage((object)("[SKILLKIT] icon sync: '" + spec.Label + "' state '" + text.ToUpperInvariant() + "' " + $"stamped onto {num} object(s) (learned instances seen: {num2}).")); } } } private static bool Apply(Item item, Sprite icon) { bool flag = Converge.To((Func)(() => item.m_itemIcon), icon, (Action)delegate(Sprite v) { item.m_itemIcon = v; }, (Func)((Sprite a, Sprite b) => (Object)(object)a == (Object)(object)b)); flag |= Converge.To((Func)(() => item.HasDynamicQuickSlotIcon), true, (Action)delegate(bool v) { item.HasDynamicQuickSlotIcon = v; }, (Func)null); Skill skill = default(Skill); ref Skill reference = ref skill; Item obj = item; reference = (Skill)(object)((obj is Skill) ? obj : null); if (skill != null) { flag |= Converge.To((Func)(() => skill.SkillTreeIcon), icon, (Action)delegate(Sprite v) { skill.SkillTreeIcon = v; }, (Func)((Sprite a, Sprite b) => (Object)(object)a == (Object)(object)b)); } return flag; } private static void RegisterStaticIconLink(SkillSpec spec, Sprite icon) { if (spec.Icon.Sprites.Count > 1 || !_slLinked.Add(spec.ItemId)) { return; } ResourcesPrefabManager instance = ResourcesPrefabManager.Instance; Item val = ((instance != null) ? instance.GetItemPrefab(spec.ItemId) : null); if ((Object)(object)val == (Object)null) { _slLinked.Remove(spec.ItemId); return; } try { CustomItemVisuals.SetSpriteLink(val, icon, false); ManualLogSource log = SkillRegistry.Log; if (log != null) { log.LogMessage((object)($"[SKILLKIT] icon link: '{spec.Label}' (ItemID={spec.ItemId}) " + "registered its static icon with SideLoader — learn toast + fresh clones now resolve it.")); } } catch (Exception ex) { _slLinked.Remove(spec.ItemId); ManualLogSource log2 = SkillRegistry.Log; if (log2 != null) { log2.LogWarning((object)("[SKILLKIT] '" + spec.Label + "' SideLoader icon-link failed (will retry): " + ex.Message)); } } } private static Sprite Resolve(SkillSpec spec, string key) { if (!_cache.TryGetValue(spec, out var value)) { value = (_cache[spec] = new Dictionary(StringComparer.OrdinalIgnoreCase)); } if (value.TryGetValue(key, out var value2)) { return value2; } Sprite val = null; if (spec.Icon.Sprites.TryGetValue(key, out var value3)) { val = Load(spec, value3); } else { ManualLogSource log = SkillRegistry.Log; if (log != null) { log.LogWarning((object)("[SKILLKIT] '" + spec.Label + "' icon state '" + key + "' has no sprite mapping — keeping the current icon.")); } } value[key] = val; return val; } private static Sprite Load(SkillSpec spec, string name) { //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Expected O, but got Unknown //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) try { Assembly assembly = spec.ResourceAssembly ?? Assembly.GetExecutingAssembly(); string text = null; string[] manifestResourceNames = assembly.GetManifestResourceNames(); foreach (string text2 in manifestResourceNames) { if (text2.EndsWith(name, StringComparison.OrdinalIgnoreCase)) { text = text2; break; } } if (text == null) { ManualLogSource log = SkillRegistry.Log; if (log != null) { log.LogWarning((object)("[SKILLKIT] '" + spec.Label + "' embedded icon '" + name + "' missing from " + assembly.GetName().Name + " — keeping the current icon.")); } return null; } byte[] array; using (Stream stream = assembly.GetManifestResourceStream(text)) { using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); array = memoryStream.ToArray(); } Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false); if (!ImageConversion.LoadImage(val, array)) { ManualLogSource log2 = SkillRegistry.Log; if (log2 != null) { log2.LogWarning((object)("[SKILLKIT] '" + spec.Label + "' icon '" + name + "' failed to decode.")); } return null; } IconPin.Pin(val); return IconPin.Pin(Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f)); } catch (Exception ex) { ManualLogSource log3 = SkillRegistry.Log; if (log3 != null) { log3.LogWarning((object)("[SKILLKIT] '" + spec.Label + "' icon '" + name + "' load failed: " + ex.Message)); } return null; } } } public static class IconPin { public static Texture2D Pin(Texture2D tex) { if ((Object)(object)tex != (Object)null) { ((Object)tex).hideFlags = (HideFlags)61; } return tex; } public static Sprite Pin(Sprite sprite) { if ((Object)(object)sprite != (Object)null) { ((Object)sprite).hideFlags = (HideFlags)61; } return sprite; } } [BepInPlugin("cobalt.skillkit", "SkillKit", "0.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string GUID = "cobalt.skillkit"; public const string NAME = "SkillKit"; public const string VERSION = "0.1.0"; internal static ManualLogSource Log; private const float IconTickSeconds = 2f; private float _iconLast; internal void Awake() { Log = ((BaseUnityPlugin)this).Logger; SkillRegistry.Init(Log, (MonoBehaviour)(object)this); Log.LogMessage((object)"SkillKit 0.1.0 loaded."); Log.LogMessage((object)("[SKILLKIT] build " + BuildStamp.Read(((object)this).GetType().Assembly) + " @ " + ((object)this).GetType().Assembly.Location)); } internal void Update() { CharacterManager instance = CharacterManager.Instance; Character player = ((instance != null) ? instance.GetFirstLocalCharacter() : null); SkillRegistry.SyncCastAnims(player); if (Time.unscaledTime - _iconLast >= 2f) { _iconLast = Time.unscaledTime; CastAnimEngine.InvalidateConverged(); SkillRegistry.SyncIcons(player); SkillRegistry.SyncCooldowns(player); } } } public static class SkillClock { public static float Remaining(Skill skill) { if (!((Object)(object)skill == (Object)null)) { return Mathf.Max(0f, skill.m_remainingCooldownTime); } return 0f; } public static void SetRemaining(Skill skill, float seconds) { if (!((Object)(object)skill == (Object)null)) { skill.m_remainingCooldownTime = Mathf.Max(0f, seconds); skill.m_lastUpdateCooldownTime = -999f; skill.m_lastReceivedCooldownProgress = -1f; skill.m_cooldownModifierOnCast = 1f; if (seconds <= 0f) { skill.m_inProgress = false; } } } } public static class SkillCooldowns { public static int Sync(int itemId, Character player, float want) { want = Mathf.Max(0f, want); return SkillInstanceSync.Sync(itemId, player, delegate(Item item) { Skill val = (Skill)(object)((item is Skill) ? item : null); if (val == null || Mathf.Approximately(val.Cooldown, want)) { return false; } val.Cooldown = want; return true; }).changed; } public static void RefundNextFrame(int itemId, Character player, string tag, string reason) { MonoBehaviour coroutineHost = SkillRegistry.CoroutineHost; if (!((Object)(object)coroutineHost == (Object)null)) { coroutineHost.StartCoroutine(Refund(itemId, player, tag, reason)); } } private static IEnumerator Refund(int itemId, Character player, string tag, string reason) { yield return null; int item = SkillInstanceSync.Sync(itemId, player, delegate(Item val2) { Skill val = (Skill)(object)((val2 is Skill) ? val2 : null); if (val == null || !val.InCooldown()) { return false; } SkillClock.SetRemaining(val, 0f); return true; }).changed; ManualLogSource log = SkillRegistry.Log; if (log != null) { log.LogMessage((object)((item > 0) ? (tag + " " + reason + " — cooldown refunded.") : (tag + " " + reason + " — no running cooldown to refund (dev-verb path)."))); } } } public static class SkillInstanceSync { public delegate bool Apply(Item item); private static readonly Dictionary _prefabCache = new Dictionary(); internal static void InvalidatePrefabCache() { _prefabCache.Clear(); } private static Item ResolvePrefab(int itemId) { if (_prefabCache.TryGetValue(itemId, out var value) && (Object)(object)value != (Object)null) { return value; } ResourcesPrefabManager instance = ResourcesPrefabManager.Instance; Item val = ((instance != null) ? instance.GetItemPrefab(itemId) : null); if ((Object)(object)val != (Object)null) { _prefabCache[itemId] = val; } return val; } public static (int changed, int instances) Sync(int itemId, Character player, Apply apply) { int num = 0; int num2 = 0; Item val = ResolvePrefab(itemId); if ((Object)(object)val != (Object)null && apply(val)) { num++; } try { if ((Object)(object)player != (Object)null) { CharacterInventory inventory = player.Inventory; object obj; if (inventory == null) { obj = null; } else { CharacterSkillKnowledge skillKnowledge = inventory.SkillKnowledge; obj = ((skillKnowledge != null) ? ((CharacterKnowledge)skillKnowledge).GetLearnedItems() : null); } IList list = (IList)obj; if (list != null) { for (int i = 0; i < list.Count; i++) { if ((Object)(object)list[i] != (Object)null && list[i].ItemID == itemId) { num2++; if (apply(list[i])) { num++; } } } } QuickSlot[] array = player.QuickSlotMngr?.m_quickSlots; if (array != null) { for (int j = 0; j < array.Length; j++) { Item val2 = (((Object)(object)array[j] != (Object)null) ? array[j].ActiveItem : null); if (!((Object)(object)val2 == (Object)null) && val2.ItemID == itemId) { num2++; if (apply(val2)) { num++; } } } } } } catch (Exception) { } return (changed: num, instances: num2); } } public static class SkillRegistry { public const string ActivationEffectsHostName = "ActivationEffects"; internal static ManualLogSource Log; internal static MonoBehaviour CoroutineHost; private static readonly List _specs = new List(); private static bool _wired; private static readonly HashSet _warnedOnce = new HashSet(); private static string _wiredBy; private static readonly object PlayerWaitKey = new object(); private static void WarnOnce(string key, string message) { if (_warnedOnce.Add(key)) { ManualLogSource log = Log; if (log != null) { log.LogError((object)message); } } } public static void Init(ManualLogSource log, MonoBehaviour coroutineHost) { if (_wired) { if (log != null) { log.LogWarning((object)("[SKILLKIT] Init ignored — already wired by " + _wiredBy + "; log/host args discarded (the first caller owns the wiring for the process lifetime, so this mod's kit lines will appear under that one).")); } return; } _wired = true; _wiredBy = ((log != null) ? log.SourceName : null) ?? (((Object)(object)coroutineHost != (Object)null) ? ((object)coroutineHost).GetType().FullName : ""); Log = log; CoroutineHost = coroutineHost; SL.OnPacksLoaded += SetupAll; SceneManager.sceneLoaded += OnSceneLoaded; } [MethodImpl(MethodImplOptions.NoInlining)] public static void Register(SkillSpec spec) { if (spec == null || spec.ItemId == 0 || (spec.OnCast == null && spec.OnCastResult == null && !spec.Passive)) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)"[SKILLKIT] Register: spec needs at least ItemId + OnCast/OnCastResult (or Passive=true) — ignored."); } return; } if (spec != null && spec.OnCast != null && spec.OnCastResult != null) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)("[SKILLKIT] Register: '" + (spec.Label ?? spec.ItemId.ToString()) + "' sets BOTH OnCast and OnCastResult — OnCastResult wins; the plain OnCast will never run.")); } } if (!IdPool.ClaimShared(spec.ItemId, "skill '" + (spec.Label ?? spec.ItemId.ToString()) + "'", (Action)delegate(string m) { ManualLogSource log5 = Log; if (log5 != null) { log5.LogWarning((object)m); } })) { return; } if (Find(spec.ItemId) != null) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogWarning((object)$"[SKILLKIT] Register: ItemID={spec.ItemId} ('{spec.Label}') already registered — ignored."); } return; } if (spec.Label == null) { spec.Label = spec.ItemId.ToString(); } if (spec.Passive && (spec.OnCast != null || spec.OnCastResult != null || spec.CastAnim != null)) { ManualLogSource log4 = Log; if (log4 != null) { log4.LogWarning((object)("[SKILLKIT] Register: '" + spec.Label + "' is Passive but sets OnCast/OnCastResult/CastAnim — a passive never activates; all nulled.")); } spec.OnCast = null; spec.OnCastResult = null; spec.CastAnim = null; } if (spec.ResourceAssembly == null) { Assembly assembly = spec.OnCast?.Method.DeclaringType?.Assembly ?? spec.OnCastResult?.Method.DeclaringType?.Assembly ?? spec.Icon?.Select?.Method.DeclaringType?.Assembly; if (assembly == null || assembly == typeof(SkillRegistry).Assembly) { assembly = Assembly.GetCallingAssembly(); } spec.ResourceAssembly = assembly; } _specs.Add(spec); } public static SkillSpec Find(int itemId) { for (int i = 0; i < _specs.Count; i++) { if (_specs[i].ItemId == itemId) { return _specs[i]; } } return null; } private static void SetupAll() { SkillInstanceSync.InvalidatePrefabCache(); CastAnimEngine.InvalidateConverged(); DynamicIconEngine.InvalidateLinks(); foreach (SkillSpec spec in _specs) { try { SetupOne(spec); } catch (Exception arg) { ManualLogSource log = Log; if (log != null) { log.LogError((object)($"[SKILLKIT] SetupOne '{spec.Label}' (ItemID={spec.ItemId}) threw — " + $"the remaining skills still wire: {arg}")); } } } SyncAll(null); ReportBootResolution(); } private static void ReportBootResolution() { //IL_009b: Unknown result type (might be due to invalid IL or missing references) foreach (SkillSpec spec in _specs) { ResourcesPrefabManager instance = ResourcesPrefabManager.Instance; Item val = ((instance != null) ? instance.GetItemPrefab(spec.ItemId) : null); Skill val2 = (Skill)(object)((val is Skill) ? val : null); if (val2 != null) { ManualLogSource log = Log; if (log != null) { log.LogMessage((object)($"[SKILLKIT] boot-resolve: '{spec.Label}' ItemID={spec.ItemId} -> Skill '{((Item)val2).Name}' " + "type=" + ((object)val2).GetType().Name + " " + $"SaveType={((Item)val2).SaveType} IsPrefab={((Item)val2).IsPrefab} HasDynamicIcon={((Item)val2).HasDynamicQuickSlotIcon}.")); } } else { ManualLogSource log2 = Log; if (log2 != null) { log2.LogError((object)($"[SKILLKIT] boot-resolve: '{spec.Label}' ItemID={spec.ItemId} UNRESOLVED " + "(prefab=" + (((Object)(object)val == (Object)null) ? "null" : ((object)val).GetType().Name) + ") — the SL pack did not register this skill; a saved learn of it will be DROPPED on load. Check for a SideLoader parse error above.")); } } } } private static void SetupOne(SkillSpec spec) { //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Expected O, but got Unknown //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Expected O, but got Unknown Item itemPrefab = ResourcesPrefabManager.Instance.GetItemPrefab(spec.ItemId); Skill val = (Skill)(object)((itemPrefab is Skill) ? itemPrefab : null); if (val == null) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)($"[SKILLKIT] '{spec.Label}' (ItemID={spec.ItemId}) not found or not a Skill — " + "did the consumer's SideLoader pack XML fail to load? Check the log above for a SideLoader parse error.")); } return; } if (spec.Passive && !(val is PassiveSkill)) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogError((object)($"[SKILLKIT] '{spec.Label}' (ItemID={spec.ItemId}) donor is {((object)val).GetType().Name}, " + "not PassiveSkill — pick a plain stat passive like Fitness 8205040; skill left un-stripped and inert.")); } return; } List list = new List(); foreach (Transform item in ((Component)val).transform) { Transform val2 = item; list.Add(((Component)val2).gameObject); } foreach (GameObject item2 in list) { Object.DestroyImmediate((Object)(object)item2); } if (!spec.Passive) { GameObject val3 = new GameObject("ActivationEffects"); val3.transform.parent = ((Component)val).transform; val3.AddComponent(); } if ((Object)(object)((Item)val).ItemIcon != (Object)null) { val.SkillTreeIcon = ((Item)val).ItemIcon; } ManualLogSource log3 = Log; if (log3 != null) { log3.LogMessage((object)string.Format("[SKILLKIT] '{0}' (ItemID={1}) wired{2}.", spec.Label, spec.ItemId, spec.Passive ? " (passive)" : "")); } } public static void SyncCastAnims(Character player) { for (int i = 0; i < _specs.Count; i++) { try { CastAnimEngine.Sync(_specs[i], player); } catch (Exception arg) { WarnOnce("cast:" + _specs[i].Label, $"[SKILLKIT] cast-sync '{_specs[i].Label}' threw (warn-once): {arg}"); } } } public static void SyncIcons(Character player) { for (int i = 0; i < _specs.Count; i++) { try { DynamicIconEngine.Sync(_specs[i], player); } catch (Exception arg) { WarnOnce("icon:" + _specs[i].Label, $"[SKILLKIT] icon-sync '{_specs[i].Label}' threw (warn-once): {arg}"); } } } public static void SyncCooldowns(Character player) { for (int i = 0; i < _specs.Count; i++) { SkillSpec skillSpec = _specs[i]; if (skillSpec.CooldownSeconds == null) { continue; } try { float num = skillSpec.CooldownSeconds(); int num2 = SkillCooldowns.Sync(skillSpec.ItemId, player, num); if (num2 > 0) { ManualLogSource log = Log; if (log != null) { log.LogMessage((object)$"[SKILLKIT] '{skillSpec.Label}' cooldown stamped {num:F0}s on {num2} object(s)."); } } } catch (Exception arg) { WarnOnce("cd:" + skillSpec.Label, $"[SKILLKIT] cooldown-sync '{skillSpec.Label}' threw (warn-once): {arg}"); } } } public static void SyncAll(Character player) { SyncCastAnims(player); SyncIcons(player); SyncCooldowns(player); } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (!((Object)(object)CoroutineHost == (Object)null) && _specs.Count != 0) { CoroutineHost.StartCoroutine(Lifecycle.WhenPlayerReady((Func)delegate { CharacterManager instance = CharacterManager.Instance; return (instance == null) ? null : instance.GetFirstLocalCharacter(); }, (Action)delegate(Character player) { CastAnimEngine.InvalidateConverged(); SyncAll(player); ReportLearnedState(player); }, (Action)null, 30f, PlayerWaitKey)); } } private static void ReportLearnedState(Character player) { object obj; if (player == null) { obj = null; } else { CharacterInventory inventory = player.Inventory; obj = ((inventory != null) ? inventory.SkillKnowledge : null); } CharacterSkillKnowledge val = (CharacterSkillKnowledge)obj; if ((Object)(object)val == (Object)null || _specs.Count == 0) { return; } int num = 0; foreach (SkillSpec spec in _specs) { if (((CharacterKnowledge)val).IsItemLearned(spec.ItemId)) { num++; continue; } ManualLogSource log = Log; if (log != null) { log.LogMessage((object)($"[SKILLKIT] post-load: '{spec.Label}' ItemID={spec.ItemId} NOT in SkillKnowledge " + "(never learned, or a saved learn failed to restore).")); } } ManualLogSource log2 = Log; if (log2 != null) { log2.LogMessage((object)($"[SKILLKIT] post-load: {num}/{_specs.Count} registered skills present in " + "the player's SkillKnowledge (a saved learn that resolved at boot-resolve but is absent here = a restore/write gap).")); } } public static bool Learn(Character player, int itemId) { if ((Object)(object)player == (Object)null) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)"[SKILLKIT] Learn: no player."); } return false; } string arg = Find(itemId)?.Label ?? itemId.ToString(); if (((CharacterKnowledge)player.Inventory.SkillKnowledge).IsItemLearned(itemId)) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogMessage((object)$"[SKILLKIT] '{arg}' (ItemID={itemId}) already learned."); } return true; } Item obj = ItemManager.Instance.GenerateItemNetwork(itemId); Skill val = (Skill)(object)((obj is Skill) ? obj : null); if (val == null) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogWarning((object)$"[SKILLKIT] Learn: ItemID={itemId} didn't resolve to a Skill — did the SideLoader pack load?"); } return false; } player.Inventory.TryUnlockSkill(val); ManualLogSource log4 = Log; if (log4 != null) { log4.LogMessage((object)$"[SKILLKIT] learned '{((Item)val).Name}' (ItemID={itemId})."); } CastAnimEngine.InvalidateConverged(); SyncAll(player); return true; } public static bool Verify(Character player) { bool result = _specs.Count > 0; if (_specs.Count == 0) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)"[SKILLKIT] verify: no specs registered."); } } foreach (SkillSpec spec in _specs) { List list = new List(); ResourcesPrefabManager instance = ResourcesPrefabManager.Instance; Item val = ((instance != null) ? instance.GetItemPrefab(spec.ItemId) : null); Skill val2 = (Skill)(object)((val is Skill) ? val : null); if ((Object)(object)val2 == (Object)null) { list.Add("prefab missing or not a Skill"); } else if (spec.Passive) { if (!(val2 is PassiveSkill)) { list.Add("prefab is " + ((object)val2).GetType().Name + ", not PassiveSkill (bad donor — SetupOne left it un-stripped and inert)"); } } else { Transform val3 = ((Component)val2).transform.Find("ActivationEffects"); if ((Object)(object)val3 == (Object)null) { list.Add("no 'ActivationEffects' child (pack-load setup didn't run?)"); } else if ((Object)(object)((Component)val3).GetComponent() == (Object)null) { list.Add("effect host has no DelegateEffect"); } } if (spec.Icon != null) { if (spec.Icon.Select == null) { list.Add("Icon.Select is null"); } foreach (KeyValuePair sprite in spec.Icon.Sprites) { if (string.IsNullOrEmpty(sprite.Value)) { list.Add("icon state '" + sprite.Key + "' maps to an empty resource name"); } } if ((Object)(object)val2 != (Object)null && !((Item)val2).HasDynamicQuickSlotIcon) { list.Add("prefab HasDynamicQuickSlotIcon is false (icon sync never ran?)"); } } if ((Object)(object)player != (Object)null) { CharacterInventory inventory = player.Inventory; if ((Object)(object)((inventory != null) ? inventory.SkillKnowledge : null) != (Object)null && !((CharacterKnowledge)player.Inventory.SkillKnowledge).IsItemLearned(spec.ItemId)) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogMessage((object)("[SKILLKIT] verify note: '" + spec.Label + "' not learned by the current player (fine — prefab checks only).")); } } } if (list.Count == 0) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogMessage((object)$"[SKILLKIT] verify PASS: '{spec.Label}' (ItemID={spec.ItemId})."); } continue; } result = false; ManualLogSource log4 = Log; if (log4 != null) { log4.LogWarning((object)string.Format("[SKILLKIT] verify FAIL: '{0}' (ItemID={1}): {2}.", spec.Label, spec.ItemId, string.Join("; ", list))); } } return result; } public static void DumpLearned(Character player, string filter) { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)"[SKILLKIT] dump: no player."); } return; } IList learnedItems = ((CharacterKnowledge)player.Inventory.SkillKnowledge).GetLearnedItems(); int num = 0; foreach (Item item in learnedItems) { if (item is Skill && item.Name != null && (filter == null || item.Name.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0)) { num++; ManualLogSource log2 = Log; if (log2 != null) { log2.LogMessage((object)($"[SKILLKIT] learned: '{item.Name}' (ItemID={item.ItemID}) ActivateEffectAnimType={item.ActivateEffectAnimType} " + $"CastModifier={item.CastModifier} AlternateAnimHasSkillID={item.AlternateAnimHasSkillID}")); } } } if (num == 0) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogMessage((object)("[SKILLKIT] no learned skill matching '" + filter + "'.")); } } } public static void DumpPrefabs(string filter) { int num = 0; foreach (Item value in ResourcesPrefabManager.ITEM_PREFABS.Values) { if (value is Skill && value.Name != null && (filter == null || value.Name.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0)) { num++; ManualLogSource log = Log; if (log != null) { log.LogMessage((object)$"[SKILLKIT] prefab: '{value.Name}' (ItemID={value.ItemID}) type={((object)value).GetType().Name}"); } } } ManualLogSource log2 = Log; if (log2 != null) { log2.LogMessage((object)$"[SKILLKIT] {num} prefab match(es)."); } } public static void CastDump(Character player) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)"[SKILLKIT] castdump: no player."); } return; } GameObject castReceiver = player.CastReceiver; ManualLogSource log2 = Log; if (log2 != null) { log2.LogMessage((object)($"[SKILLKIT] cast state: IsCasting={player.IsCasting} CurrentSpellCast={player.CurrentSpellCast} " + "CastReceiver=" + (((Object)(object)castReceiver != (Object)null) ? ((Object)castReceiver).name : "") + " " + $"InLocomotion={player.InLocomotion} NextIsLocomotion={player.NextIsLocomotion}")); } } public static void CastClear(Character player) { if ((Object)(object)player == (Object)null) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)"[SKILLKIT] castclear: no player."); } return; } bool isCasting = player.IsCasting; player.SetCastData((SpellCastType)(-1), (GameObject)null); ManualLogSource log2 = Log; if (log2 != null) { log2.LogMessage((object)$"[SKILLKIT] SetCastData(NONE, null) — was IsCasting={isCasting}, now IsCasting={player.IsCasting}."); } } } public sealed class SkillSpec { public int ItemId; public string Label; public Action OnCast; public Func OnCastResult; public Func CooldownSeconds; public bool Passive; public bool ClearCastAfter = true; public DynamicIcon Icon; public Func CastAnim; public Assembly ResourceAssembly; } public enum CastResult { Landed, Refund, RefuseSilently } public sealed class DynamicIcon { public Func Select; public Dictionary Sprites = new Dictionary(StringComparer.OrdinalIgnoreCase); public static DynamicIcon Fixed(string spriteName) { return new DynamicIcon { Select = () => "fixed", Sprites = { ["fixed"] = spriteName } }; } } public struct CastPick { public string AnimName; public SpellCastModifier Modifier; public CastPick(string animName, SpellCastModifier modifier) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) AnimName = animName; Modifier = modifier; } } public static class SkillVerbs { public static void RegisterAll(CommandRegistry c, ManualLogSource log, Func player) { c.Register("castdump", "Dump the player's stuck-cast state (bug-16 observability).", (Action)delegate { SkillRegistry.CastDump(player()); }); c.Register("castclear", "Clear a wedged IsCasting (bug-16 recovery).", (Action)delegate { SkillRegistry.CastClear(player()); }); c.Register("skillverify", "SkillKit wire-check: prefab/effect/icon per registered spec.", (Action)delegate { SkillRegistry.Verify(player()); }); c.Register("skilldump", "Dump learned skills whose name contains a substring ('skilldump ').", (Action)delegate(string[] parts) { if (parts.Length < 2) { log.LogWarning((object)"[SKILLDUMP] usage: skilldump "); } else { SkillRegistry.DumpLearned(player(), Tail(parts)); } }); c.Register("skillitemdump", "Browse loadable Skill-type item prefabs ('skillitemdump [name]').", (Action)delegate(string[] parts) { SkillRegistry.DumpPrefabs(Tail(parts)); }); } private static string Tail(string[] parts) { if (parts == null || parts.Length <= 1) { return null; } return string.Join(" ", parts, 1, parts.Length - 1); } } public static class SlStatus { public static readonly string[] DefaultDonors = new string[3] { "Bleeding", "Burning", "Poisoned" }; public static ManualLogSource Log; public static Func DefaultIconLoader; private static readonly Dictionary _iconPng = new Dictionary(); private static readonly Dictionary> _iconLoader = new Dictionary>(); private static readonly Dictionary _iconCache = new Dictionary(); private static readonly HashSet _iconDead = new HashSet(); private static ManualLogSource L => Log ?? Plugin.Log; public static bool ResolveDonor(SlStatusSpec spec, out string donor) { donor = Array.Find(spec.DonorCandidates, (string id) => (Object)(object)ResourcesPrefabManager.Instance.GetStatusEffectPrefab(id) != (Object)null); if (donor == null) { ManualLogSource l = L; if (l != null) { l.LogWarning((object)(spec.Tag + " no donor status found (tried " + string.Join(", ", spec.DonorCandidates) + ") — " + spec.DisabledSuffix)); } return false; } return true; } public static bool Register(SlStatusSpec spec, string donor, out StatusEffect prefab) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Expected O, but got Unknown prefab = null; if (!IdPool.ClaimShared(spec.NumId, "status '" + spec.Id + "'", (Action)delegate(string m) { Plugin.Log.LogWarning((object)m); })) { return false; } SL_StatusEffect val = new SL_StatusEffect(); val.TargetStatusIdentifier = donor; val.StatusIdentifier = spec.Id; val.NewStatusID = spec.NumId; val.Name = spec.Name; val.Description = spec.Description; val.Lifespan = spec.Lifespan; val.Purgeable = false; val.DisplayedInHUD = true; val.IsHidden = false; val.IsMalusEffect = spec.IsMalus; val.ActionOnHit = (ActionsOnHit)0; val.PlayFXOnActivation = false; val.Tags = new string[0]; SL_StatusEffect val2 = val; if (spec.BindFamily != null) { val2.FamilyMode = (FamilyModes)0; val2.BindFamily = spec.BindFamily; } ((ContentTemplate)val2).ApplyTemplate(); prefab = ResourcesPrefabManager.Instance.GetStatusEffectPrefab(spec.Id); if ((Object)(object)prefab == (Object)null) { ManualLogSource l = L; if (l != null) { l.LogWarning((object)(spec.Tag + " '" + spec.Id + "' missing after ApplyTemplate (see SideLoader warnings above).")); } return false; } StripInheritedFx(prefab, donor, spec.Id, spec.Tag); _iconPng[spec.Id] = spec.IconPng; _iconLoader[spec.Id] = spec.IconLoader ?? DefaultIconLoader; Sprite val3 = LoadIcon(spec.Id, spec.IconPng, spec.Tag); if ((Object)(object)val3 != (Object)null) { _iconCache[spec.Id] = val3; StampIcon(prefab, val3); } return true; } public static void ResetIconRetries() { if (_iconDead.Count != 0) { ManualLogSource l = L; if (l != null) { l.LogMessage((object)$"[ICONS] retrying {_iconDead.Count} icon(s) that failed to load earlier this session."); } _iconDead.Clear(); } } private static Sprite LoadIcon(string id, string png, string tag) { if (!_iconLoader.TryGetValue(id, out var value)) { value = DefaultIconLoader; } if (value == null) { ManualLogSource l = L; if (l != null) { l.LogWarning((object)(tag + " '" + id + "': no icon loader set (SkillKit.SlStatus.DefaultIconLoader) — the status will show a blank slot.")); } return null; } return value(png, tag); } private static void StampIcon(StatusEffect status, Sprite icon) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown status.OverrideIcon = icon; status.m_defaultStatusIcon = new StatusTypeIcon(Tag.None) { Icon = icon }; } public static bool IconHeld(string id) { ResourcesPrefabManager instance = ResourcesPrefabManager.Instance; StatusEffect val = ((instance != null) ? instance.GetStatusEffectPrefab(id) : null); if ((Object)(object)val != (Object)null) { return (Object)(object)val.OverrideIcon != (Object)null; } return false; } public static bool EnsureIcon(string id, StatusEffect live, string tag) { if (!_iconPng.TryGetValue(id, out var value)) { return false; } if (_iconDead.Contains(id)) { return false; } ResourcesPrefabManager instance = ResourcesPrefabManager.Instance; StatusEffect val = ((instance != null) ? instance.GetStatusEffectPrefab(id) : null); bool flag = (Object)(object)val == (Object)null || (Object)(object)val.OverrideIcon != (Object)null; bool flag2 = (Object)(object)live == (Object)null || (Object)(object)live.OverrideIcon != (Object)null; if (flag && flag2) { return false; } if (!_iconCache.TryGetValue(id, out var value2) || (Object)(object)value2 == (Object)null) { value2 = LoadIcon(id, value, tag); if ((Object)(object)value2 == (Object)null) { _iconDead.Add(id); ManualLogSource l = L; if (l != null) { l.LogWarning((object)(tag + " '" + id + "': embedded icon '" + value + "' could not be loaded — giving up on it for this session (it will show the blank/donor slot). Retry with `reloadcfg` after a rebuild.")); } return false; } _iconCache[id] = value2; } if ((Object)(object)val != (Object)null) { StampIcon(val, value2); } if ((Object)(object)live != (Object)null) { StampIcon(live, value2); } return true; } public static string IconState(string id, StatusEffect live) { ResourcesPrefabManager instance = ResourcesPrefabManager.Instance; StatusEffect status = ((instance != null) ? instance.GetStatusEffectPrefab(id) : null); string text = id + ": prefab=" + Describe(status); if ((Object)(object)live != (Object)null) { text = text + ", live=" + Describe(live); } return text; } private static string Describe(StatusEffect status) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)status == (Object)null) { return ""; } Sprite overrideIcon = status.OverrideIcon; if ((Object)(object)overrideIcon == (Object)null) { return "use=False (OverrideIcon null/destroyed -> DONOR BADGE)"; } return string.Format("use=True tex={0} flags={1}", ((Object)(object)overrideIcon.texture == (Object)null) ? "null" : "ok", ((Object)overrideIcon).hideFlags); } public static void StripInheritedFx(StatusEffect prefab, string donor, string id, string tag) { //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)prefab == (Object)null) { return; } ResourcesPrefabManager instance = ResourcesPrefabManager.Instance; StatusEffect val = ((instance != null) ? instance.GetStatusEffectPrefab(donor) : null); if ((Object)(object)val != (Object)null && prefab == val) { ManualLogSource l = L; if (l != null) { l.LogWarning((object)(tag + " '" + id + "' IS the shared donor prefab '" + donor + "' — refusing to strip its FX (that would break vanilla " + donor + " for the whole game). The clone must have failed; see SideLoader warnings.")); } return; } if ((Object)(object)prefab.FXPrefab != (Object)null) { ManualLogSource l2 = L; if (l2 != null) { l2.LogMessage((object)(tag + " '" + id + "': dropped inherited FX prefab '" + ((Object)prefab.FXPrefab).name + "' (bug 27 — an unbound zero-area emitter, one log line per frame for the status's whole life).")); } } prefab.FXPrefab = null; prefab.FxInstantiation = (FXInstantiationTypes)0; prefab.SpecialFXPrefab = null; prefab.SpecialFxInstantiation = (FXInstantiationTypes)0; prefab.PlaySpecialFXOnStop = false; } public static StatusEffect Live(Character player, string id) { object obj; if (player == null) { obj = null; } else { StatusEffectManager statusEffectMngr = player.StatusEffectMngr; obj = ((statusEffectMngr != null) ? statusEffectMngr.GetStatusEffectOfName(id) : null); } StatusEffect val = (StatusEffect)obj; if (EnsureIcon(id, val, "[ICONS]")) { ManualLogSource l = L; if (l != null) { l.LogMessage((object)("[ICONS] re-stamped " + id + " — its icon had gone missing (the HUD would have been showing the donor badge instead).")); } } return val; } public static int StackCount(Character player, string id) { StatusEffect obj = Live(player, id); if (obj == null) { return 0; } return obj.StackCount; } public static void AddStack(Character player, string id) { if (player != null) { StatusEffectManager statusEffectMngr = player.StatusEffectMngr; if (statusEffectMngr != null) { statusEffectMngr.AddStatusEffect(id); } } } public static void Remove(Character player, string id) { if (player != null) { StatusEffectManager statusEffectMngr = player.StatusEffectMngr; if (statusEffectMngr != null) { statusEffectMngr.RemoveStatusWithIdentifierName(id); } } } public static void RefreshAll(Character player, string id, float durationSeconds) { StatusEffect val = Live(player, id); if (val?.m_statusStack == null) { return; } foreach (StatusData item in val.m_statusStack) { item.LifeSpan = durationSeconds; item.ResetLifeSpan(); } } public static void SetStacks(Character player, string id, int n, float durationSeconds, bool canAdd) { if ((Object)(object)((player != null) ? player.StatusEffectMngr : null) == (Object)null) { return; } if (n <= 0) { Remove(player, id); } else if (canAdd) { int num = 16; while (StackCount(player, id) < n && num-- > 0) { AddStack(player, id); } StatusEffect val = Live(player, id); while ((Object)(object)val != (Object)null && val.StackCount > n && num-- > 0) { val.RemoveOldestStack(); } RefreshAll(player, id, durationSeconds); } } public static void AddOrRefresh(Character player, string id, float durationSeconds) { if ((Object)(object)((player != null) ? player.StatusEffectMngr : null) == (Object)null) { return; } StatusEffect val = Live(player, id); if ((Object)(object)val != (Object)null) { if (val.StatusData != null) { val.StatusData.LifeSpan = durationSeconds; val.StatusData.ResetLifeSpan(); } } else { player.StatusEffectMngr.AddStatusEffect(id); } } public static void SyncDuration(string id, float durationSeconds) { ResourcesPrefabManager instance = ResourcesPrefabManager.Instance; StatusEffect val = ((instance != null) ? instance.GetStatusEffectPrefab(id) : null); if (val?.StatusData != null) { val.StatusData.LifeSpan = durationSeconds; } } } public class SlStatusSpec { public string Id; public int NumId; public string Name; public string Description; public float Lifespan; public string IconPng; public bool IsMalus; public string Tag; public string DisabledSuffix; public string[] DonorCandidates = SlStatus.DefaultDonors; public SL_StatusEffectFamily BindFamily; public Func IconLoader; }