Upload from upload_mods.ps1

This commit is contained in:
Nathaniel Cosford
2025-06-04 16:13:32 +09:30
commit 7345f42201
470 changed files with 51966 additions and 0 deletions

View File

@@ -0,0 +1,437 @@
using System.Collections.Generic;
using UniLinq;
using UnityEngine;
namespace KFCommonUtilityLib.Scripts.StaticManagers
{
public static class AnimationRiggingManager
{
//public class FpvTransformRef
//{
// public Animator fpvAnimator;
// public RigTargets targets;
// public ItemInventoryData invData;
// //public Transform muzzle;
// //public Transform muzzle2;
// //public bool isDoubleBarrel;
// public FpvTransformRef(RigTargets targets, ItemInventoryData invData)
// {
// this.targets = targets;
// this.invData = invData;
// //this.isDoubleBarrel = isDoubleBarrel;
// fpvAnimator = targets.itemFpv.GetComponentInChildren<Animator>();
// //if (isDoubleBarrel)
// //{
// // muzzle = targets.itemFpv.transform.FindInChildren("Muzzle_L");
// // muzzle2 = targets.itemFpv.transform.FindInChildren("Muzzle_R");
// //}
// //else
// // muzzle = targets.itemFpv.transform.FindInChilds("Muzzle");
// }
// public bool IsRanged(out ItemActionRanged.ItemActionDataRanged rangedData)
// {
// return (rangedData = invData.actionData[MultiActionManager.GetActionIndexForEntity(GameManager.Instance.World.GetPrimaryPlayer())] as ItemActionRanged.ItemActionDataRanged) != null;
// }
//}
public static bool IsHoldingRiggedWeapon(EntityPlayer player)
{
AnimationTargetsAbs targets = GetRigTargetsFromPlayer(player);
return targets && !targets.Destroyed;
}
//private static bool RigItemChangedThisFrame = false;
//private static readonly HashSet<int> hash_rig_items = new HashSet<int>();
private static readonly HashSet<int> hash_items_take_over_reload_time = new HashSet<int>();
private static readonly HashSet<string> hash_items_parse_later = new HashSet<string>();
private static readonly HashSet<string> hash_rig_names = new HashSet<string>();
private static readonly HashSet<int> hash_rig_changed_players = new HashSet<int>();
//patched to item xml parsing
//public static void AddRigItem(int itemId) => hash_rig_items.Add(itemId);
public static void Clear()
{
//hash_rig_items.Clear();
//RigItemChangedThisFrame = false;
hash_items_parse_later.Clear();
hash_items_take_over_reload_time.Clear();
hash_rig_names.Clear();
hash_rig_changed_players.Clear();
}
public static void AddReloadTimeTakeOverItem(string name)
{
hash_items_parse_later.Add(name);
}
public static void AddRigExcludeName(string name)
{
hash_rig_names.Add(name);
}
public static void RemoveRigExcludeName(string name)
{
hash_rig_names.Remove(name);
}
public static bool ShouldExcludeRig(string name)
{
return hash_rig_names.Contains(name);
}
public static string[] GetExcludeRigs()
{
return hash_rig_names.ToArray();
}
public static void ParseItemIDs()
{
foreach (var name in hash_items_parse_later)
{
hash_items_take_over_reload_time.Add(ItemClass.GetItemClass(name).Id);
//Log.Out($"parse item id: {name} {ItemClass.GetItemClass(name).Id}");
}
hash_items_parse_later.Clear();
}
public static bool IsReloadTimeTakeOverItem(int id)
{
return hash_items_take_over_reload_time.Contains(id);
}
public static AnimationTargetsAbs GetRigTargetsFromPlayer(EntityAlive player)
{
if (player is not EntityPlayer)
return null;
Transform holdingItemTransform = player.inventory?.GetHoldingItemTransform();
if (holdingItemTransform)
{
return holdingItemTransform.GetComponent<AnimationTargetsAbs>();
}
return null;
}
public static AnimationTargetsAbs GetRigTargetsFromInventory(Inventory inventory)
{
Transform holdingItemTransform = inventory?.GetHoldingItemTransform();
if (holdingItemTransform)
{
return holdingItemTransform.GetComponent<AnimationTargetsAbs>();
}
return null;
}
//public static bool IsRigItem(int itemId) => hash_rig_items.Contains(itemId);
public static void UpdatePlayerAvatar(AvatarController controller)
{
if (!controller?.Entity)
{
return;
}
AnimationTargetsAbs targets = GetRigTargetsFromPlayer(controller.Entity as EntityPlayer);
bool RigItemChangedThisFrame = hash_rig_changed_players.Remove(controller.Entity.entityId);
if (targets && !targets.Destroyed)
{
targets.UpdatePlayerAvatar(controller, RigItemChangedThisFrame);
}
controller.UpdateBool(AvatarController.isCrouchingHash, controller.entity.IsCrouching, true);
//if ((controller.Entity as EntityPlayerLocal).bFirstPersonView && targets && !targets.Destroyed && targets.itemFpv)
//{
// //workaround for animator bullshit
// if (!targets.itemFpv.gameObject.activeSelf)
// {
// Log.Out("Rigged weapon not active, enabling it...");
// targets.SetEnabled(true);
// }
// //vroid workaround
// //it seems to use a separate animator for vroid model and does not replace CharacterBody
// //controller.UpdateInt(AvatarController.weaponHoldTypeHash, -1, false);
// controller.FPSArms.Animator.Play("idle", 0, 0f);
// foreach (var hash in resetHashes)
// {
// AnimationRiggingPatches.VanillaResetTrigger(controller, hash, false);
// }
// //controller.FPSArms?.Animator?.SetInteger(AvatarController.weaponHoldTypeHash, -1);
// //controller.CharacterBody?.Animator?.SetInteger(AvatarController.weaponHoldTypeHash, -1);
//}
//if (RigItemChangedThisFrame)
//{
// Log.Out("Rigged weapon changed, resetting animator...");
// Transform modelRoot = controller.GetActiveModelRoot();
// //if (modelRoot && (!targets || targets.Destroyed) && modelRoot.GetComponentInChildren<Animator>().TryGetComponent<AnimationGraphBuilder>(out var builder))
// //{
// // builder.DestroyGraph(!targets || targets.Destroyed);
// //}
// if (controller is AvatarLocalPlayerController localPlayerController && localPlayerController.isFPV && localPlayerController.FPSArms != null)
// {
// if (localPlayerController.FPSArms.Animator.TryGetComponent<AnimationGraphBuilder>(out var builder))
// {
// builder.VanillaWrapper.Play("idle", 0, 0f);
// }
// else
// {
// localPlayerController.FPSArms.Animator.Play("idle", 0, 0f);
// }
// }
// controller.UpdateInt(AvatarController.weaponHoldTypeHash, -1, false);
//}
}
public static void OnClearInventorySlot(Inventory inv, int slot)
{
if (inv == null)
return;
Transform transform = inv.models[slot];
if (transform && transform.TryGetComponent<AnimationTargetsAbs>(out var targets) && !targets.Destroyed)
{
//RigItemChangedThisFrame = true;
targets.Destroy();
if (slot == inv.holdingItemIdx && inv.entity is EntityPlayer)
{
hash_rig_changed_players.Add(inv.entity.entityId);
}
}
}
//patched to EntityPlayerLocal.OnHoldingItemChanged and EntityAlive.OnHoldingItemIndexChanged
public static void OnHoldingItemIndexChanged(EntityPlayer player)
{
if (!player || player.inventory == null || player.inventory.m_LastDrawnHoldingItemIndex < 0 || player.inventory.m_LastDrawnHoldingItemIndex >= player.inventory.GetSlotCount())
return;
if (player.inventory.m_LastDrawnHoldingItemIndex == player.inventory.holdingItemIdx)
{
hash_rig_changed_players.Add(player.entityId);
return;
}
Transform lastHoldingTransform = player.inventory.models[player.inventory.m_LastDrawnHoldingItemIndex];
if (!lastHoldingTransform)
{
hash_rig_changed_players.Add(player.entityId);
return;
}
AnimationTargetsAbs targets = lastHoldingTransform.GetComponent<AnimationTargetsAbs>();
//if (targets && !targets.Destroyed && targets.IsAnimationSet)
//{
// //RigItemChangedThisFrame = true;
// hash_rig_changed_players.Add(player.entityId);
// return;
//}
targets = GetRigTargetsFromPlayer(player);
if (targets && !targets.Destroyed && targets.IsAnimationSet)
{
//RigItemChangedThisFrame = true;
hash_rig_changed_players.Add(player.entityId);
}
}
public static Transform GetAttachmentReferenceOverrideTransform(Transform transform, string transformPath, Entity entity)
{
if (transform == null || entity == null || !(entity is EntityAlive entityAlive) || entityAlive.inventory == null)
return null;
//Log.Out("TRYING TO REDIRECT TRANSFORM REFERENCE: " + transform.name + " CHILD: " + transformPath);
var targets = entityAlive.inventory.GetHoldingItemTransform()?.GetComponent<AttachmentReference>();
if (targets != null && targets.attachmentReference != null)
{
var redirected = GameUtils.FindDeepChild(targets.attachmentReference, transform.name) ?? targets.attachmentReference;
//Log.Out("REDIRECTING TRANSFORM REFERENCE TO " + redirected.name);
var find = GameUtils.FindDeepChild(redirected, transformPath);
if (find != null)
//Log.Out("FOUND REDIRECTED CHILD: " + find.name + " PARENT: " + find.parent.name);
return find;
}
return GameUtils.FindDeepChild(transform, transformPath);
}
public static Transform GetAttachmentReferenceOverrideTransformActive(Transform transform, string transformPath, Entity entity)
{
if (transform == null || entity == null || !(entity is EntityAlive entityAlive) || entityAlive.inventory == null)
return null;
//Log.Out("TRYING TO REDIRECT TRANSFORM REFERENCE: " + transform.name + " CHILD: " + transformPath);
var targets = entityAlive.inventory.GetHoldingItemTransform()?.GetComponent<AttachmentReference>();
if (targets != null && targets.attachmentReference != null)
{
var redirected = GameUtils.FindDeepChildActive(targets.attachmentReference, transform.name) ?? targets.attachmentReference;
//Log.Out("REDIRECTING TRANSFORM REFERENCE TO " + redirected.name);
var find = GameUtils.FindDeepChildActive(redirected, transformPath);
if (find != null)
//Log.Out("FOUND REDIRECTED CHILD: " + find.name + " PARENT: " + find.parent.name);
return find;
}
return GameUtils.FindDeepChildActive(transform, transformPath);
}
//public static Transform GetMuzzleOverrideFPV(Transform muzzle, bool isLocalFpv)
//{
// if (!isLocalFpv || fpvTransformRef == null)
// return muzzle;
// if (fpvTransformRef.IsRanged(out var rangedData))
// {
// return rangedData.muzzle;
// }
// return muzzle;
//}
//public static Transform GetMuzzle2OverrideFPV(Transform muzzle2, bool isLocalFpv)
//{
// if (!isLocalFpv || fpvTransformRef == null)
// return muzzle2;
// if (fpvTransformRef.IsRanged(out var rangedData))
// {
// return rangedData.muzzle2;
// }
// return muzzle2;
//}
public static Transform GetTransformOverrideByName(Transform itemModel, string name, bool onlyActive = true)
{
if (itemModel == null)
return null;
if (!itemModel.TryGetComponent<AnimationTargetsAbs>(out var targets) || targets.Destroyed)
{
if (string.IsNullOrEmpty(name))
return itemModel;
return onlyActive ? GameUtils.FindDeepChildActive(itemModel, name) : GameUtils.FindDeepChild(itemModel, name);
}
Transform targetRoot = targets.ItemCurrentOrDefault;
if (string.IsNullOrEmpty(name))
return targetRoot;
return onlyActive ? GameUtils.FindDeepChildActive(targetRoot, name) : GameUtils.FindDeepChild(targetRoot, name);
}
public static Transform GetAddPartTransformOverride(Transform itemModel, string name, bool onlyActive = true)
{
return GetTransformOverrideByName(itemModel, name, onlyActive) ?? itemModel;
}
//patched to ItemActionRanged.ItemActionEffect
public static bool SpawnFpvParticles(bool isLocalFpv, ItemActionData _actionData, string particlesMuzzleFire, string particlesMuzzleFireFpv, string particlesMuzzleSmoke, string particlesMuzzleSmokeFpv)
{
if (!isLocalFpv || !GetRigTargetsFromInventory(_actionData.invData.holdingEntity.inventory))
return false;
var itemActionDataRanged = _actionData as ItemActionRanged.ItemActionDataRanged;
EntityPlayerLocal player = GameManager.Instance.World.GetPrimaryPlayer();
if (itemActionDataRanged.muzzle != null)
{
if (particlesMuzzleFire != null)
{
Transform fire = GameManager.Instance.SpawnParticleEffectClientForceCreation(new ParticleEffect(particlesMuzzleFireFpv != null ? particlesMuzzleFireFpv : particlesMuzzleFire, Vector3.zero, 1f, Color.clear, null, null, false), player.entityId, true);
if (fire != null)
{
fire.transform.localPosition = Vector3.zero;
//fire.transform.localEulerAngles = Vector3.zero;
if (itemActionDataRanged.IsDoubleBarrel && itemActionDataRanged.invData.itemValue.Meta == 0)
fire.transform.SetParent(itemActionDataRanged.muzzle2, false);
else
fire.transform.SetParent(itemActionDataRanged.muzzle, false);
Utils.SetLayerRecursively(fire.gameObject, 10, null);
//fire.transform.localPosition = Vector3.zero;
//fire.transform.localEulerAngles = Vector3.zero;
//fire.transform.localScale = Vector3.one;
foreach (var particle in fire.GetComponentsInChildren<ParticleSystem>())
{
particle.gameObject.SetActive(true);
particle.Clear();
particle.Play();
}
var temp = fire.gameObject.GetOrAddComponent<TemporaryMuzzleFlash>();
temp.life = 5;
//temp.Restart();
if (fire.TryGetComponent<LODGroup>(out var lod))
lod.enabled = false;
//Log.Out($"barrel position: {fire.transform.parent.parent.position}/{fire.transform.parent.parent.localPosition}, muzzle position: {fire.transform.parent.position}/{fire.transform.parent.localPosition}, particle position: {fire.transform.position}");
//Log.Out($"particles: {string.Join("\n", fire.GetComponentsInChildren<ParticleSystem>().Select(ps => ps.name + " active: " + ps.gameObject.activeInHierarchy + " layer: " + ps.gameObject.layer + " position: " + ps.transform.position))}");
}
}
if (particlesMuzzleSmoke != null && itemActionDataRanged.muzzle != null)
{
float num = GameManager.Instance.World.GetLightBrightness(World.worldToBlockPos(itemActionDataRanged.muzzle.transform.position)) / 2f;
Color clear = Color.clear;
Transform smoke = GameManager.Instance.SpawnParticleEffectClientForceCreation(new ParticleEffect(particlesMuzzleSmokeFpv != null ? particlesMuzzleSmokeFpv : particlesMuzzleSmoke, Vector3.zero, num, clear, null, null, false), player.entityId, true);
if (smoke != null)
{
smoke.transform.localPosition = Vector3.zero;
//smoke.transform.localEulerAngles = Vector3.zero;
Utils.SetLayerRecursively(smoke.gameObject, 10, null);
smoke.transform.SetParent(itemActionDataRanged.muzzle, false);
//smoke.transform.localPosition = Vector3.zero;
//smoke.transform.localEulerAngles = Vector3.zero;
//smoke.transform.localScale = Vector3.one;
foreach (var particle in smoke.GetComponentsInChildren<ParticleSystem>())
{
particle.gameObject.SetActive(true);
particle.Clear();
particle.Play();
}
var temp = smoke.gameObject.GetOrAddComponent<TemporaryMuzzleFlash>();
temp.life = 5;
//temp.Restart();
if (smoke.TryGetComponent<LODGroup>(out var lod))
lod.enabled = false;
}
}
}
return true;
}
//public static void SetTrigger(int _pid, EntityPlayer player)
//{
// AnimationTargetsAbs targets = GetRigTargetsFromPlayer(player);
// if (targets && !targets.Destroyed && targets.ItemAnimator)
// {
// targets.ItemAnimator.SetTrigger(_pid);
// //Log.Out($"setting trigger {_pid}");
// }
//}
//public static void ResetTrigger(int _pid, EntityPlayer player)
//{
// AnimationTargetsAbs targets = GetRigTargetsFromPlayer(player);
// if (targets && !targets.Destroyed && targets.ItemAnimator)
// {
// targets.ItemAnimator.ResetTrigger(_pid);
// //Log.Out($"resetting trigger {_pid}");
// }
//}
//public static void SetFloat(int _pid, float _value, EntityPlayer player)
//{
// AnimationTargetsAbs targets = GetRigTargetsFromPlayer(player);
// if (targets&& !targets.Destroyed && targets.ItemAnimator)
// {
// targets.ItemAnimator.SetFloat(_pid, _value);
// //Log.Out($"setting float {_pid}");
// }
//}
//public static void SetBool(int _pid, bool _value, EntityPlayer player)
//{
// AnimationTargetsAbs targets = GetRigTargetsFromPlayer(player);
// if (targets && !targets.Destroyed && targets.ItemAnimator)
// {
// targets.ItemAnimator.SetBool(_pid, _value);
// //Log.Out($"setting bool {_pid}");
// }
//}
//public static void SetInt(int _pid, int _value, EntityPlayer player)
//{
// AnimationTargetsAbs targets = GetRigTargetsFromPlayer(player);
// if (targets && !targets.Destroyed && targets.ItemAnimator)
// {
// targets.ItemAnimator.SetInteger(_pid, _value);
// //Log.Out($"setting int {_pid}");
// }
//}
}
}

View File

@@ -0,0 +1,117 @@
using System.Collections.Generic;
namespace KFCommonUtilityLib.Scripts.StaticManagers
{
public interface IBackgroundInventoryUpdater
{
int Index { get; }
bool OnUpdate(ItemInventoryData invData);
}
public static class BackgroundInventoryUpdateManager
{
private static readonly Dictionary<int, List<IBackgroundInventoryUpdater>[]> dict_updaters = new Dictionary<int, List<IBackgroundInventoryUpdater>[]>();
private static readonly Dictionary<int, List<IBackgroundInventoryUpdater>[]> dict_disabled = new Dictionary<int, List<IBackgroundInventoryUpdater>[]>();
public static void Cleanup()
{
dict_updaters.Clear();
dict_disabled.Clear();
}
public static void RegisterUpdater(EntityAlive entity, int slot, IBackgroundInventoryUpdater updater)
{
//do not handle remote entity update
if (entity == null || entity.isEntityRemote)
return;
Inventory inv = entity.inventory;
if (inv == null || slot < 0 || slot >= inv.GetSlotCount())
return;
if (!dict_updaters.TryGetValue(entity.entityId, out var arr_updaters))
{
arr_updaters = new List<IBackgroundInventoryUpdater>[inv.GetSlotCount()];
dict_updaters[entity.entityId] = arr_updaters;
}
if (arr_updaters[slot] == null)
{
arr_updaters[slot] = new List<IBackgroundInventoryUpdater>();
}
int lastIndex = arr_updaters[slot].FindIndex(u => u.Index == updater.Index);
if (lastIndex >= 0)
{
//replace old updater, this happens on inventory initialization when player enters game
arr_updaters[slot][lastIndex] = updater;
}
else
{
arr_updaters[slot].Add(updater);
}
}
public static void DisableUpdater(EntityAlive entity)
{
if (dict_updaters.TryGetValue(entity.entityId, out var updater))
{
dict_updaters.Remove(entity.entityId);
dict_disabled.Add(entity.entityId, updater);
}
}
public static void EnableUpdater(EntityAlive entity)
{
if (dict_disabled.TryGetValue(entity.entityId, out var updaters))
{
dict_disabled.Remove(entity.entityId);
dict_updaters.Add(entity.entityId, updaters);
}
}
public static void UnregisterUpdater(EntityAlive entity)
{
dict_updaters.Remove(entity.entityId);
}
public static void UnregisterUpdater(EntityAlive entity, int slot)
{
if (dict_updaters.TryGetValue(entity.entityId, out var arr_updaters) && arr_updaters != null)
{
arr_updaters[slot] = null;
}
}
public static void Update(EntityAlive entity)
{
if (!entity.isEntityRemote && dict_updaters.TryGetValue(entity.entityId, out var arr_updaters) && arr_updaters != null)
{
Inventory inv = entity.inventory;
int slotCount = inv.GetSlotCount();
var prevInvData = entity.MinEventContext.ItemInventoryData;
var prevItemValue = entity.MinEventContext.ItemValue;
var prevActionData = entity.MinEventContext.ItemActionData;
bool invChanged = false;
for (int i = 0; i < slotCount; i++)
{
if (arr_updaters[i] != null)
{
foreach (var updater in arr_updaters[i])
{
if (updater != null)
{
invChanged |= updater.OnUpdate(inv.GetItemDataInSlot(i));
}
}
}
}
entity.MinEventContext.ItemInventoryData = prevInvData;
entity.MinEventContext.ItemActionData = prevActionData;
entity.MinEventContext.ItemValue = prevItemValue;
if (invChanged)
{
entity.inventory.CallOnToolbeltChangedInternal();
}
}
}
}
}

View File

@@ -0,0 +1,215 @@
using System;
using System.Collections.Generic;
using UniLinq;
namespace KFCommonUtilityLib.Scripts.StaticManagers
{
public static class CustomEffectEnumManager
{
private static event Action OnInitDefault;
private static event Action OnInitFinal;
private static event Action OnPrintResult;
//call this in InitMod
public static void RegisterEnumType<T>(bool requestMin = false, int requestedMin = 0, bool requestMax = false, int requestedMax = int.MaxValue) where T : struct, Enum
{
if (EnumHolder<T>.Registered)
return;
EnumHolder<T>.Registered = true;
EnumHolder<T>.RequestMinMax(requestMin, requestedMin, requestMax, requestedMax);
OnInitDefault += EnumHolder<T>.InitDefault;
OnInitFinal += EnumHolder<T>.InitFinal;
OnPrintResult += EnumHolder<T>.PrintResult;
}
//hooked to GameAwake
public static void InitDefault()
{
OnInitDefault?.Invoke();
}
//patched to GameManager.StartGame prefix
public static void InitFinal()
{
OnInitFinal?.Invoke();
}
public static void PrintResults()
{
OnPrintResult?.Invoke();
}
//only call these from callbacks hooked to ModEvents.GameStartDone and cache the results for future usage
//patched to PassiveEffect.ParsePassiveEffect and MinEventActionBase.ParseXmlAttribute
public static T RegisterOrGetEnum<T>(string name, bool ignoreCase = false) where T : struct, Enum
{
if (!EnumHolder<T>.Registered)
{
throw new Exception($"Enum not registered: {typeof(T).Name}");
}
return EnumHolder<T>.RegisterOrGetEnum(name, ignoreCase);
}
//public static PassiveEffects RegisterOrGetPassive(string passive)
//{
// if (!dict_final_passive.TryGetValue(passive, out var value))
// {
// if (dict_final_passive.Count >= byte.MaxValue)
// throw new OverflowException("Passive effect count exceeds limit 255!");
// value = (PassiveEffects)(byte)dict_final_passive.Count;
// dict_final_passive.Add(passive, value);
// }
// return value;
//}
////patched to MinEventActionBase.ParseXmlAttribute
//public static MinEventTypes RegisterOrGetTrigger(string trigger)
//{
// if (!dict_final_trigger.TryGetValue(trigger, out var value))
// {
// value = (MinEventTypes)dict_final_trigger.Count;
// dict_final_trigger.Add(trigger, value);
// }
// return value;
//}
private static class EnumHolder<T> where T : struct, Enum
{
private static int max, min;
private static readonly TypeCode typecode;
private static readonly Dictionary<string, T> dict_default_enums = new Dictionary<string, T>();
private static readonly Dictionary<string, T> dict_default_enums_lower = new Dictionary<string, T>();
private static readonly LinkedList<(int start, int end)> link_default_holes = new LinkedList<(int start, int end)>();
private static Dictionary<string, T> dict_final_enums = new Dictionary<string, T>();
private static Dictionary<string, T> dict_final_enums_lower = new Dictionary<string, T>();
private static LinkedList<(int start, int end)> link_final_holes = new LinkedList<(int start, int end)>();
public static bool Registered { get; set; } = false;
private static bool DefaultInited { get; set; } = false;
static EnumHolder()
{
Type underlying = Enum.GetUnderlyingType(typeof(T));
typecode = Type.GetTypeCode(underlying);
switch (typecode)
{
case TypeCode.Byte:
min = 0;
max = byte.MaxValue;
break;
case TypeCode.SByte:
min = sbyte.MinValue;
max = sbyte.MaxValue;
break;
case TypeCode.Int16:
min = short.MinValue;
max = short.MaxValue;
break;
case TypeCode.UInt16:
min = 0;
max = ushort.MaxValue;
break;
case TypeCode.Int32:
case TypeCode.Int64:
min = int.MinValue;
max = int.MaxValue;
break;
case TypeCode.UInt32:
case TypeCode.UInt64:
min = 0;
max = int.MaxValue;
break;
default:
throw new Exception($"Invalid underlying type for enum {typeof(T).Name}");
}
}
public static void RequestMinMax(bool requestMin, int requestedMin, bool requestMax, int requestedMax)
{
if (requestMin && requestedMin >= min)
{
min = requestedMin;
}
if (requestMax && requestedMax <= max)
{
max = requestedMax;
}
}
public static void InitDefault()
{
if (DefaultInited)
return;
dict_default_enums.Clear();
dict_default_enums_lower.Clear();
link_default_holes.Clear();
var total = Enum.GetNames(typeof(T)).Length;
var enums = Enum.GetValues(typeof(T)).Cast<T>().ToArray();
for (int i = 0; i < total; i++)
{
string name = enums[i].ToString();
dict_default_enums.Add(name, enums[i]);
dict_default_enums_lower.Add(name.ToLower(), enums[i]);
}
var values = enums.Select(e => Convert.ToInt32(e)).OrderBy(i => i).ToArray();
int nextHole = min;
foreach (var value in values)
{
if (nextHole < value)
{
link_default_holes.AddLast((nextHole, Math.Min(value - 1, max)));
if (value >= max)
{
nextHole = max;
break;
}
nextHole = value + 1;
}
else if (nextHole == value)
{
if (value >= max)
{
break;
}
nextHole++;
}
}
if (nextHole <= max && values[values.Length - 1] < max)
{
link_default_holes.AddLast((nextHole, max));
}
DefaultInited = true;
}
public static void InitFinal()
{
dict_final_enums = new Dictionary<string, T>(dict_default_enums);
dict_final_enums_lower = new Dictionary<string, T>(dict_default_enums_lower);
link_final_holes = new LinkedList<(int start, int end)>(link_default_holes);
}
public static void PrintResult()
{
//Log.Out($"{typeof(T).Name}:\n" + string.Join("\n", dict_final_enums.Select(p => $"name: {p.Key} value: {p.Value}")));
}
public static T RegisterOrGetEnum(string passive, bool ignoreCase = false)
{
if (!(ignoreCase ? dict_final_enums_lower : dict_final_enums).TryGetValue(ignoreCase ? passive.ToLower() : passive, out var value))
{
if (link_final_holes.Count == 0)
throw new OverflowException($"Enum count exceeds limit {max}!");
(int start, int end) = link_final_holes.First.Value;
link_final_holes.RemoveFirst();
value = (T)Enum.ToObject(typeof(T), Convert.ChangeType(start, typecode));
dict_final_enums.Add(passive, value);
dict_final_enums_lower.Add(passive.ToLower(), value);
if (start < end)
{
start++;
link_final_holes.AddFirst((start, end));
}
}
return value;
}
}
}
}

View File

@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using UniLinq;
namespace KFCommonUtilityLib.Scripts.StaticManagers
{
public static class DelayLoadModuleManager
{
private static readonly List<(string mod, List<string> dlls)> list_delay_load = new List<(string mod, List<string> dlls)>();
private static readonly List<Assembly> loaded = new List<Assembly>();
public static void RegisterDelayloadDll(string modName, string dllNameWithoutExtension)
{
List<string> dlls;
int index = list_delay_load.FindIndex(p => p.mod == modName);
if (index < 0)
{
dlls = new List<string>() { dllNameWithoutExtension };
list_delay_load.Add((modName, dlls));
return;
}
dlls = list_delay_load[index].dlls;
dlls.Add(dllNameWithoutExtension);
}
public static void DelayLoad()
{
Assembly assembly = Assembly.GetAssembly(typeof(DelayLoadModuleManager));
Mod mod = ModManager.GetModForAssembly(assembly);
string delayLoadFolder = mod.Path + "/DelayLoad";
ModuleManagers.AddAssemblySearchPath(delayLoadFolder);
foreach (var pair in list_delay_load)
{
if (ModManager.GetLoadedAssemblies().Any(a => a.GetName().Name == pair.mod))
{
foreach (var dll in pair.dlls)
{
try
{
string assPath = Path.GetFullPath(delayLoadFolder + $"/{dll}.dll");
Assembly patch = Assembly.LoadFrom(assPath);
if (Path.GetFullPath(patch.Location).Equals(assPath, StringComparison.OrdinalIgnoreCase))
{
foreach (var type in patch.GetTypes())
{
if (typeof(IModApi).IsAssignableFrom(type))
{
IModApi modApi = (IModApi)Activator.CreateInstance(type);
modApi.InitMod(mod);
Log.Out(string.Concat($"[DELAYLOAD] Initialized code in {dll}.dll"));
}
}
mod.allAssemblies.Add(patch);
}
}
catch (Exception ex)
{
Log.Error($"[DELAYLOAD] Failed loading DLL {dll}.dll");
Log.Exception(ex);
}
}
}
}
//if (ModManager.GetLoadedAssemblies().FirstOrDefault(a => a.GetName().Name == "FullautoLauncher") != null)
//{
// try
// {
// string assPath = Path.GetFullPath(delayLoadFolder + "/FullautoLauncherAnimationRiggingCompatibilityPatch.dll");
// Assembly patch = Assembly.LoadFrom(assPath);
// if (Path.GetFullPath(patch.Location).Equals(assPath, StringComparison.OrdinalIgnoreCase))
// {
// Type apiType = typeof(IModApi);
// foreach (var type in patch.GetTypes())
// {
// if (apiType.IsAssignableFrom(type))
// {
// IModApi modApi = (IModApi)Activator.CreateInstance(type);
// modApi.InitMod(mod);
// Log.Out(string.Concat("[DELAYLOAD] Initialized code in FullautoLauncherAnimationRiggingCompatibilityPatch.dll"));
// }
// }
// }
// }
// catch (Exception ex)
// {
// Log.Error("[DELAYLOAD] Failed loading DLL FullautoLauncherAnimationRiggingCompatibilityPatch.dll");
// Log.Exception(ex);
// }
//}
}
}
}

View File

@@ -0,0 +1,937 @@
using HarmonyLib;
using KFCommonUtilityLib.Harmony;
using KFCommonUtilityLib.Scripts.Attributes;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Security.Permissions;
using System.Text;
using UniLinq;
using UnityEngine;
using UnityEngine.Scripting;
using FieldAttributes = Mono.Cecil.FieldAttributes;
using MethodAttributes = Mono.Cecil.MethodAttributes;
using MethodBody = Mono.Cecil.Cil.MethodBody;
using TypeAttributes = Mono.Cecil.TypeAttributes;
namespace KFCommonUtilityLib.Scripts.StaticManagers
{
//public static class AssemblyLocator
//{
// private static Dictionary<string, Assembly> assemblies;
// public static void Init()
// {
// assemblies = new Dictionary<string, Assembly>();
// foreach (var assembly in ModManager.GetLoadedAssemblies())
// {
// assemblies.Add(assembly.FullName, assembly);
// }
// AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler(CurrentDomain_AssemblyLoad);
// AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
// }
// private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
// {
// assemblies.TryGetValue(args.Name, out Assembly assembly);
// if (assembly != null)
// Log.Out($"RESOLVING ASSEMBLY {assembly.FullName}");
// else
// Log.Error($"RESOLVING ASSEMBBLY {args.Name} FAILED!");
// return assembly;
// }
// private static void CurrentDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args)
// {
// Assembly assembly = args.LoadedAssembly;
// assemblies[assembly.FullName] = assembly;
// Log.Out($"LOADING ASSEMBLY {assembly.FullName}");
// }
//}
public interface IModuleContainerFor<out T> where T : class
{
T Instance { get; }
}
public class MethodPatchInfo
{
public readonly MethodDefinition Method;
public Instruction PrefixBegin;
public Instruction PostfixBegin;
public Instruction PostfixEnd;
public MethodPatchInfo(MethodDefinition mtddef, Instruction postfixEnd, Instruction prefixBegin)
{
Method = mtddef;
PostfixEnd = postfixEnd;
PrefixBegin = prefixBegin;
}
}
public static class ItemActionModuleManager
{
private static readonly List<Assembly> list_created = new List<Assembly>();
private static AssemblyDefinition workingAssembly = null;
private static DefaultAssemblyResolver resolver;
private static ModuleAttributes moduleAttributes;
private static ModuleCharacteristics moduleCharacteristics;
private static readonly Dictionary<string, List<(string typename, int indexOfAction)>> dict_replacement_mapping = new Dictionary<string, List<(string typename, int indexOfAction)>>();
internal static void ClearOutputFolder()
{
Mod self = ModManager.GetMod("CommonUtilityLib");
string path = Path.Combine(self.Path, "AssemblyOutput");
if (Directory.Exists(path))
Array.ForEach(Directory.GetFiles(path), File.Delete);
else
Directory.CreateDirectory(path);
}
internal static void InitNew()
{
dict_replacement_mapping.Clear();
workingAssembly?.Dispose();
if (resolver == null)
{
resolver = new DefaultAssemblyResolver();
resolver.AddSearchDirectory(Path.Combine(Application.dataPath, "Managed"));
foreach (var mod in ModManager.GetLoadedMods())
{
resolver.AddSearchDirectory(mod.Path);
}
AssemblyDefinition assdef_main = AssemblyDefinition.ReadAssembly($"{Application.dataPath}/Managed/Assembly-CSharp.dll", new ReaderParameters() { AssemblyResolver = resolver });
moduleAttributes = assdef_main.MainModule.Attributes;
moduleCharacteristics = assdef_main.MainModule.Characteristics;
Log.Out("Reading Attributes from assembly: " + assdef_main.FullName);
}
string assname = "ItemActionModule" + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
workingAssembly = AssemblyDefinition.CreateAssembly(new AssemblyNameDefinition(assname,
new Version(0, 0, 0, 0)),
assname + ".dll",
new ModuleParameters()
{
Kind = ModuleKind.Dll,
AssemblyResolver = resolver,
Architecture = TargetArchitecture.I386,
Runtime = TargetRuntime.Net_4_0,
});
workingAssembly.MainModule.Attributes = moduleAttributes;
workingAssembly.MainModule.Characteristics = moduleCharacteristics;
//write security attributes so that calling non-public patch methods from this assembly is allowed
Mono.Cecil.SecurityAttribute sattr_permission = new Mono.Cecil.SecurityAttribute(workingAssembly.MainModule.ImportReference(typeof(SecurityPermissionAttribute)));
Mono.Cecil.CustomAttributeNamedArgument caarg_SkipVerification = new Mono.Cecil.CustomAttributeNamedArgument(nameof(SecurityPermissionAttribute.SkipVerification), new CustomAttributeArgument(workingAssembly.MainModule.TypeSystem.Boolean, true));
sattr_permission.Properties.Add(caarg_SkipVerification);
SecurityDeclaration sdec = new SecurityDeclaration(Mono.Cecil.SecurityAction.RequestMinimum);
sdec.SecurityAttributes.Add(sattr_permission);
workingAssembly.SecurityDeclarations.Add(sdec);
Log.Out("======Init New======");
}
internal static void CheckItem(ItemClass item)
{
for (int i = 0; i < item.Actions.Length; i++)
{
ItemAction itemAction = item.Actions[i];
if (itemAction != null && itemAction.Properties.Values.TryGetValue("ItemActionModules", out string str_modules))
{
string[] modules = str_modules.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
Type itemActionType = itemAction.GetType();
Type[] moduleTypes = modules.Select(s => ReflectionHelpers.GetTypeWithPrefix("ActionModule", s.Trim()))
.Where(t => t.GetCustomAttribute<TypeTargetAttribute>().BaseType.IsAssignableFrom(itemActionType)).ToArray();
string typename = CreateTypeName(itemActionType, moduleTypes);
//Log.Out(typename);
if (!TryFindType(typename, out _) && !TryFindInCur(typename, out _))
PatchType(itemActionType, moduleTypes);
if (!dict_replacement_mapping.TryGetValue(item.Name, out var list))
{
list = new List<(string typename, int indexOfAction)>();
dict_replacement_mapping.Add(item.Name, list);
}
list.Add((typename, i));
}
}
}
internal static void FinishAndLoad()
{
//output assembly
Log.Out("======Finish and Load======");
Mod self = ModManager.GetMod("CommonUtilityLib");
if (self == null)
{
Log.Warning("Failed to get mod!");
self = ModManager.GetModForAssembly(typeof(ItemActionModuleManager).Assembly);
}
if (self != null && workingAssembly != null && workingAssembly.MainModule.Types.Count > 1)
{
Log.Out("Assembly is valid!");
using (MemoryStream ms = new MemoryStream())
{
try
{
workingAssembly.Write(ms);
}
catch (Exception)
{
new ConsoleCmdShutdown().Execute(new List<string>(), new CommandSenderInfo());
}
DirectoryInfo dirInfo = Directory.CreateDirectory(Path.Combine(self.Path, "AssemblyOutput"));
string filename = Path.Combine(dirInfo.FullName, workingAssembly.Name.Name + ".dll");
Log.Out("Output Assembly: " + filename);
using (FileStream fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Write))
{
ms.WriteTo(fs);
}
Assembly newAssembly = Assembly.LoadFile(filename);
list_created.Add(newAssembly);
}
}
//replace item actions
if (list_created.Count > 0)
{
foreach (var pair in dict_replacement_mapping)
{
ItemClass item = ItemClass.GetItemClass(pair.Key, true);
foreach ((string typename, int indexOfAction) in pair.Value)
{
if (TryFindType(typename, out Type itemActionType))
{
//Log.Out($"Replace ItemAction {item.Actions[indexOfAction].GetType().FullName} with {itemActionType.FullName}");
ItemAction itemActionPrev = item.Actions[indexOfAction];
item.Actions[indexOfAction] = (ItemAction)Activator.CreateInstance(itemActionType);
item.Actions[indexOfAction].ActionIndex = indexOfAction;
item.Actions[indexOfAction].item = item;
item.Actions[indexOfAction].ExecutionRequirements = itemActionPrev.ExecutionRequirements;
item.Actions[indexOfAction].ReadFrom(itemActionPrev.Properties);
}
}
}
}
//cleanup
workingAssembly?.Dispose();
workingAssembly = null;
dict_replacement_mapping.Clear();
}
private static void PatchType(Type itemActionType, params Type[] moduleTypes)
{
if (workingAssembly == null)
return;
//Get assembly module
ModuleDefinition module = workingAssembly.MainModule;
TypeReference typeref_interface = module.ImportReference(typeof(IModuleContainerFor<>));
//Prepare type info
TypeTargetAttribute[] arr_attr_modules = moduleTypes.Select(t => t.GetCustomAttribute<TypeTargetAttribute>()).ToArray();
TypeReference[] arr_typeref_modules = moduleTypes.Select(t => module.ImportReference(t)).ToArray();
Type[] arr_type_data = arr_attr_modules.Select(a => a.DataType).ToArray();
TypeReference[] arr_typeref_data = arr_type_data.Select(a => a != null ? module.ImportReference(a) : null).ToArray();
//Find ItemActionData subtype
MethodInfo mtdinf_create_data = null;
{
Type type_itemActionRoot = typeof(ItemAction);
Type type_itemActionBase = itemActionType;
while (typeof(ItemAction).IsAssignableFrom(type_itemActionBase))
{
mtdinf_create_data = type_itemActionBase.GetMethod(nameof(ItemAction.CreateModifierData), BindingFlags.Public | BindingFlags.Instance);
if (mtdinf_create_data != null)
break;
mtdinf_create_data = mtdinf_create_data.GetBaseDefinition();
}
}
//Create new ItemAction
TypeReference typeref_itemAction = module.ImportReference(itemActionType);
TypeDefinition typedef_newAction = new TypeDefinition(null, CreateTypeName(itemActionType, moduleTypes), TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.Public | TypeAttributes.Sealed, typeref_itemAction);
for (int i = 0; i < arr_typeref_modules.Length; i++)
{
}
typedef_newAction.CustomAttributes.Add(new CustomAttribute(module.ImportReference(typeof(PreserveAttribute).GetConstructor(Array.Empty<Type>()))));
module.Types.Add(typedef_newAction);
//Create new ItemActionData
//Find CreateModifierData
MethodDefinition mtddef_create_data = module.ImportReference(mtdinf_create_data).Resolve();
//ItemActionData subtype is the return type of CreateModifierData
TypeReference typeref_actiondata = ((MethodReference)mtddef_create_data.Body.Instructions[mtddef_create_data.Body.Instructions.Count - 2].Operand).DeclaringType;
//Get type by assembly qualified name since it might be from mod assembly
Type type_itemActionData = Type.GetType(Assembly.CreateQualifiedName(typeref_actiondata.Module.Assembly.Name.Name, typeref_actiondata.FullName));
TypeDefinition typedef_newActionData = new TypeDefinition(null, CreateTypeName(type_itemActionData, arr_type_data), TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.NestedPublic | TypeAttributes.Sealed, module.ImportReference(typeref_actiondata));
typedef_newActionData.CustomAttributes.Add(new CustomAttribute(module.ImportReference(typeof(PreserveAttribute).GetConstructor(Array.Empty<Type>()))));
typedef_newAction.NestedTypes.Add(typedef_newActionData);
//Create fields
FieldDefinition[] arr_flddef_modules = new FieldDefinition[moduleTypes.Length];
FieldDefinition[] arr_flddef_data = new FieldDefinition[moduleTypes.Length];
for (int i = 0; i < moduleTypes.Length; i++)
{
//Create ItemAction field
Type type_module = moduleTypes[i];
FieldDefinition flddef_module = new FieldDefinition(CreateFieldName(type_module), FieldAttributes.Public, arr_typeref_modules[i]);
typedef_newAction.Fields.Add(flddef_module);
arr_flddef_modules[i] = flddef_module;
TypeReference typeref_module = arr_typeref_modules[i];
MakeContainerFor(module, typeref_interface, typedef_newAction, type_module, flddef_module, typeref_module);
//Create ItemActionData field
if (arr_typeref_data[i] != null)
{
TypeReference typeref_data = arr_typeref_data[i];
Type type_data = arr_type_data[i];
FieldDefinition flddef_data = new FieldDefinition(CreateFieldName(type_data), FieldAttributes.Public, typeref_data);
typedef_newActionData.Fields.Add(flddef_data);
arr_flddef_data[i] = flddef_data;
MakeContainerFor(module, typeref_interface, typedef_newActionData, type_data, flddef_data, typeref_data);
}
}
//Create ItemAction constructor
MethodDefinition mtddef_ctor = new MethodDefinition(".ctor", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, module.TypeSystem.Void);
var il = mtddef_ctor.Body.GetILProcessor();
il.Append(il.Create(OpCodes.Ldarg_0));
il.Append(il.Create(OpCodes.Call, module.ImportReference(itemActionType.GetConstructor(Array.Empty<Type>()))));
il.Append(il.Create(OpCodes.Nop));
for (int i = 0; i < arr_flddef_modules.Length; i++)
{
il.Append(il.Create(OpCodes.Ldarg_0));
il.Append(il.Create(OpCodes.Newobj, module.ImportReference(moduleTypes[i].GetConstructor(Array.Empty<Type>()))));
il.Append(il.Create(OpCodes.Stfld, arr_flddef_modules[i]));
il.Append(il.Create(OpCodes.Nop));
}
il.Append(il.Create(OpCodes.Ret));
typedef_newAction.Methods.Add(mtddef_ctor);
//Create ItemActionData constructor
MethodDefinition mtddef_ctor_data = new MethodDefinition(".ctor", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, module.TypeSystem.Void);
mtddef_ctor_data.Parameters.Add(new ParameterDefinition("_inventoryData", Mono.Cecil.ParameterAttributes.None, module.ImportReference(typeof(ItemInventoryData))));
mtddef_ctor_data.Parameters.Add(new ParameterDefinition("_indexInEntityOfAction", Mono.Cecil.ParameterAttributes.None, module.TypeSystem.Int32));
FieldReference fldref_invdata_item = module.ImportReference(typeof(ItemInventoryData).GetField(nameof(ItemInventoryData.item)));
FieldReference fldref_item_actions = module.ImportReference(typeof(ItemClass).GetField(nameof(ItemClass.Actions)));
il = mtddef_ctor_data.Body.GetILProcessor();
il.Append(il.Create(OpCodes.Ldarg_0));
il.Append(il.Create(OpCodes.Ldarg_1));
il.Append(il.Create(OpCodes.Ldarg_2));
il.Append(il.Create(OpCodes.Call, module.ImportReference(type_itemActionData.GetConstructor(new Type[] { typeof(ItemInventoryData), typeof(int) }))));
il.Append(il.Create(OpCodes.Nop));
for (int i = 0; i < arr_flddef_data.Length; i++)
{
if (arr_type_data[i] == null)
continue;
il.Append(il.Create(OpCodes.Ldarg_0));
il.Append(il.Create(OpCodes.Ldarg_1));
il.Append(il.Create(OpCodes.Ldarg_2));
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldfld, fldref_invdata_item);
il.Emit(OpCodes.Ldfld, fldref_item_actions);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Ldelem_Ref);
il.Emit(OpCodes.Castclass, typedef_newAction);
il.Emit(OpCodes.Ldfld, arr_flddef_modules[i]);
il.Append(il.Create(OpCodes.Newobj, module.ImportReference(arr_type_data[i].GetConstructor(new Type[] { typeof(ItemInventoryData), typeof(int), moduleTypes[i] }))));
il.Append(il.Create(OpCodes.Stfld, arr_flddef_data[i]));
il.Append(il.Create(OpCodes.Nop));
}
il.Append(il.Create(OpCodes.Ret));
typedef_newActionData.Methods.Add(mtddef_ctor_data);
//Create ItemAction.CreateModifierData override
MethodDefinition mtddef_create_modifier_data = new MethodDefinition(nameof(ItemAction.CreateModifierData), MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.ReuseSlot, module.ImportReference(typeof(ItemActionData)));
mtddef_create_modifier_data.Parameters.Add(new ParameterDefinition("_invData", Mono.Cecil.ParameterAttributes.None, module.ImportReference(typeof(ItemInventoryData))));
mtddef_create_modifier_data.Parameters.Add(new ParameterDefinition("_indexInEntityOfAction", Mono.Cecil.ParameterAttributes.None, module.TypeSystem.Int32));
il = mtddef_create_modifier_data.Body.GetILProcessor();
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Newobj, mtddef_ctor_data);
il.Emit(OpCodes.Ret);
typedef_newAction.Methods.Add(mtddef_create_modifier_data);
//<derived method name, method patch info>
Dictionary<string, MethodPatchInfo> dict_overrides = new Dictionary<string, MethodPatchInfo>();
//<derived method name, transpiler stub methods in inheritance order>
Dictionary<string, List<(MethodDefinition mtddef_target, MethodReference mtdref_original, MethodReference mtdref_harmony, List<MethodInfo> list_mtdinf_patches)>> dict_transpilers = new Dictionary<string, List<(MethodDefinition mtddef_target, MethodReference mtdref_original, MethodReference mtdref_harmony, List<MethodInfo> list_mtdinf_patches)>>();
//<derived method name, <module type name, local variable>>
Dictionary<string, Dictionary<string, VariableDefinition>> dict_all_states = new Dictionary<string, Dictionary<string, VariableDefinition>>();
//Get all transpilers and clone original methods
for (int i = 0; i < moduleTypes.Length; i++)
{
Type moduleType = moduleTypes[i];
const BindingFlags searchFlags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
foreach (var mtd in moduleType.GetMethods(searchFlags))
{
var attr = mtd.GetCustomAttribute<MethodTargetTranspilerAttribute>();
if (attr != null)
{
string id = attr.GetTargetMethodIdentifier();
if (!dict_transpilers.TryGetValue(id, out var list_transpilers))
{
list_transpilers = new List<(MethodDefinition mtddef_target, MethodReference mtdref_original, MethodReference mtdref_harmony, List<MethodInfo> list_mtdinf_patches)> { };
dict_transpilers[id] = list_transpilers;
}
int maxLevels = 0;
Type nextType = itemActionType;
while (attr.PreferredType.IsAssignableFrom(nextType))
{
maxLevels++;
if (list_transpilers.Count < maxLevels)
{
var mtdinfo_cur = AccessTools.Method(nextType, attr.TargetMethod, attr.Params);
var mtdinfo_harmony = ItemActionModulePatch.GetRealMethod(mtdinfo_cur, true);
//transpilers on undeclared methods are invalid
var mtdref_original = mtdinfo_cur.DeclaringType.Equals(nextType) ? module.ImportReference(mtdinfo_cur) : null;
var mtdref_harmony = mtdref_original != null ? module.ImportReference(mtdinfo_harmony) : null;
var mtddef_copy = mtdref_harmony?.Resolve()?.CloneToModuleAsStatic(module.ImportReference(mtdinfo_cur.DeclaringType), module) ?? null;
list_transpilers.Add((mtddef_copy, mtdref_original, mtdref_harmony, new List<MethodInfo>()));
}
nextType = nextType.BaseType;
}
list_transpilers[maxLevels - 1].list_mtdinf_patches.Add(mtd);
}
}
}
//apply transpilers and replace method calls on base methods with patched ones
Dictionary<string, MethodDefinition> dict_replacers = new Dictionary<string, MethodDefinition>();
foreach (var pair in dict_transpilers)
{
//the top copy to call in the override method
MethodDefinition mtddef_override_copy = null;
MethodReference mtdref_override_base = null;
for (int i = pair.Value.Count - 1; i >= 0; i--)
{
var mtddef_target = pair.Value[i].mtddef_target;
var mtdref_original = pair.Value[i].mtdref_harmony;
if (mtddef_target != null)
{
foreach (var patch in pair.Value[i].list_mtdinf_patches)
{
mtddef_target.Body.SimplifyMacros();
patch.Invoke(null, new object[] { mtddef_target.Body, module });
mtddef_target.Body.OptimizeMacros();
}
if (i < pair.Value.Count - 1)
{
//find first available base method
MethodDefinition mtddef_base_target = null;
MethodReference mtdref_base_original = null;
for (int j = i + 1; j < pair.Value.Count; j++)
{
mtddef_base_target = pair.Value[j].mtddef_target;
mtdref_base_original = pair.Value[j].mtdref_original;
if (mtddef_base_target != null)
{
break;
}
}
//replace calls to the base
if (mtddef_base_target != null)
{
foreach (var ins in mtddef_target.Body.Instructions)
{
if (ins.OpCode == OpCodes.Call && ((MethodReference)ins.Operand).FullName.Equals(mtdref_base_original.FullName))
{
Log.Out($"replacing call to {mtdref_base_original.FullName} to {mtddef_base_target.FullName}");
ins.Operand = mtddef_base_target;
}
}
}
}
//the iteration is reversed so make sure we grab the latest method
if (mtddef_target != null)
{
mtddef_override_copy = mtddef_target;
mtdref_override_base = mtdref_original;
}
//add patched copy to the class
typedef_newAction.Methods.Add(mtddef_target);
}
}
//create the method override that calls the patched copy
GetOrCreateOverride(dict_overrides, pair.Key, mtdref_override_base, module, mtddef_override_copy);
}
//Apply Postfixes first so that Prefixes can jump to the right instruction
for (int i = 0; i < moduleTypes.Length; i++)
{
Type moduleType = moduleTypes[i];
Dictionary<string, MethodOverrideInfo> dict_targets = GetMethodOverrideTargets<MethodTargetPostfixAttribute>(itemActionType, moduleType, module);
string moduleID = CreateFieldName(moduleType);
foreach (var pair in dict_targets)
{
MethodDefinition mtddef_root = module.ImportReference(pair.Value.mtdinf_base.GetBaseDefinition()).Resolve();
MethodDefinition mtddef_target = module.ImportReference(pair.Value.mtdinf_target).Resolve();
MethodPatchInfo mtdpinf_derived = GetOrCreateOverride(dict_overrides, pair.Key, pair.Value.mtdref_base, module);
MethodDefinition mtddef_derived = mtdpinf_derived.Method;
if (!dict_all_states.TryGetValue(pair.Key, out var dict_states))
{
dict_states = new Dictionary<string, VariableDefinition>();
dict_all_states.Add(pair.Key, dict_states);
}
var list_inst_pars = MatchArguments(mtddef_root, mtdpinf_derived, mtddef_target, arr_flddef_modules[i], arr_flddef_data[i], module, itemActionType, typedef_newActionData, true, dict_states, moduleID);
//insert invocation
il = mtddef_derived.Body.GetILProcessor();
foreach (var ins in list_inst_pars)
{
il.InsertBefore(mtdpinf_derived.PostfixEnd, ins);
}
il.InsertBefore(mtdpinf_derived.PostfixEnd, il.Create(OpCodes.Call, module.ImportReference(mtddef_target)));
if (mtdpinf_derived.PostfixBegin == null)
mtdpinf_derived.PostfixBegin = list_inst_pars[0];
}
}
//Apply Prefixes
for (int i = moduleTypes.Length - 1; i >= 0; i--)
{
Type moduleType = moduleTypes[i];
Dictionary<string, MethodOverrideInfo> dict_targets = GetMethodOverrideTargets<MethodTargetPrefixAttribute>(itemActionType, moduleType, module);
string moduleID = CreateFieldName(moduleType);
foreach (var pair in dict_targets)
{
MethodDefinition mtddef_root = module.ImportReference(pair.Value.mtdinf_base.GetBaseDefinition()).Resolve();
MethodDefinition mtddef_target = module.ImportReference(pair.Value.mtdinf_target).Resolve();
MethodPatchInfo mtdpinf_derived = GetOrCreateOverride(dict_overrides, pair.Key, pair.Value.mtdref_base, module);
MethodDefinition mtddef_derived = mtdpinf_derived.Method;
dict_all_states.TryGetValue(pair.Key, out var dict_states);
var list_inst_pars = MatchArguments(mtddef_root, mtdpinf_derived, mtddef_target, arr_flddef_modules[i], arr_flddef_data[i], module, itemActionType, typedef_newActionData, false, dict_states, moduleID);
//insert invocation
il = mtdpinf_derived.Method.Body.GetILProcessor();
Instruction ins_insert = mtdpinf_derived.PrefixBegin;
foreach (var ins in list_inst_pars)
{
il.InsertBefore(ins_insert, ins);
}
il.InsertBefore(ins_insert, il.Create(OpCodes.Call, module.ImportReference(mtddef_target)));
il.InsertBefore(ins_insert, il.Create(OpCodes.Brfalse_S, mtdpinf_derived.PostfixBegin ?? mtdpinf_derived.PostfixEnd));
}
}
foreach (var pair in dict_all_states)
{
var dict_states = pair.Value;
if (dict_states.Count > 0)
{
Log.Error($"__state variable count does not match in prefixes and postfixes for {pair.Key}! check following modules:\n" + string.Join("\n", dict_states.Keys));
throw new Exception();
}
}
//Add all overrides to new type
foreach (var mtd in dict_overrides.Values)
{
typedef_newAction.Methods.Add(mtd.Method);
//Log.Out($"Add method override to new action: {mtd.Method.Name}");
}
}
private static void MakeContainerFor(ModuleDefinition module, TypeReference typeref_interface, TypeDefinition typedef_container, Type type_module, FieldDefinition flddef_module, TypeReference typeref_module)
{
typedef_container.Interfaces.Add(new InterfaceImplementation(typeref_interface.MakeGenericInstanceType(typeref_module)));
PropertyDefinition propdef_instance = new PropertyDefinition("Instance", Mono.Cecil.PropertyAttributes.None, typeref_module);
MethodDefinition mtddef_instance_getter = new MethodDefinition("get_Instance", MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Final, typeref_module);
mtddef_instance_getter.Overrides.Add(module.ImportReference(AccessTools.Method(typeof(IModuleContainerFor<>).MakeGenericType(type_module), "get_Instance")));
typedef_container.Methods.Add(mtddef_instance_getter);
mtddef_instance_getter.Body = new Mono.Cecil.Cil.MethodBody(mtddef_instance_getter);
var generator = mtddef_instance_getter.Body.GetILProcessor();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldfld, flddef_module);
generator.Emit(OpCodes.Ret);
propdef_instance.GetMethod = mtddef_instance_getter;
typedef_container.Properties.Add(propdef_instance);
}
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="itemActionType"></param>
/// <param name="moduleType"></param>
/// <param name="module"></param>
/// <returns></returns>
private static Dictionary<string, MethodOverrideInfo> GetMethodOverrideTargets<T>(Type itemActionType, Type moduleType, ModuleDefinition module) where T : Attribute, IMethodTarget
{
Dictionary<string, MethodOverrideInfo> dict_overrides = new Dictionary<string, MethodOverrideInfo>();
const BindingFlags searchFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
foreach (var mtd in moduleType.GetMethods(searchFlags))
{
IMethodTarget attr = mtd.GetCustomAttribute<T>();
if (attr != null && (attr.PreferredType == null || attr.PreferredType.IsAssignableFrom(itemActionType)))
{
string id = attr.GetTargetMethodIdentifier();
MethodInfo mtdinf_base = AccessTools.Method(itemActionType, attr.TargetMethod, attr.Params);
if (mtdinf_base == null || !mtdinf_base.IsVirtual || mtdinf_base.IsFinal)
{
Log.Error($"Method not found: {attr.TargetMethod}");
continue;
}
MethodReference mtdref_base = module.ImportReference(mtdinf_base);
//Find preferred patch
if (dict_overrides.TryGetValue(id, out var pair))
{
if (attr.PreferredType == null)
continue;
//cur action type is sub or same class of cur preferred type
//cur preferred type is sub class of previous preferred type
//means cur preferred type is closer to the action type in inheritance hierachy than the previous one
if (attr.PreferredType.IsAssignableFrom(itemActionType) && (pair.prefType == null || attr.PreferredType.IsSubclassOf(pair.prefType)))
{
dict_overrides[id] = new MethodOverrideInfo(mtd, mtdinf_base, mtdref_base, attr.PreferredType);
}
}
else
{
dict_overrides[id] = new MethodOverrideInfo(mtd, mtdinf_base, mtdref_base, attr.PreferredType);
}
//Log.Out($"Add method override: {id} for {mtdref_base.FullName}/{mtdinf_base.Name}, action type: {itemActionType.Name}");
}
else
{
//Log.Out($"No override target found or preferred type not match on {mtd.Name}");
}
}
return dict_overrides;
}
/// <summary>
/// Get or create override MethodDefinition of mtdref_base.
/// </summary>
/// <param name="dict_overrides"></param>
/// <param name="id"></param>
/// <param name="mtdref_base"></param>
/// <param name="module"></param>
/// <returns></returns>
private static MethodPatchInfo GetOrCreateOverride(Dictionary<string, MethodPatchInfo> dict_overrides, string id, MethodReference mtdref_base, ModuleDefinition module, MethodDefinition mtddef_base_override = null)
{
//if (mtddef_base.FullName == "CreateModifierData")
// throw new MethodAccessException($"YOU SHOULD NOT MANUALLY MODIFY CreateModifierData!");
if (dict_overrides.TryGetValue(id, out var mtdpinf_derived))
{
return mtdpinf_derived;
}
//when overriding, retain attributes of base but make sure to remove the 'new' keyword which presents if you are overriding the root method
MethodDefinition mtddef_derived = new MethodDefinition(mtdref_base.Name, (mtdref_base.Resolve().Attributes | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.ReuseSlot) & ~MethodAttributes.NewSlot, module.ImportReference(mtdref_base.ReturnType));
//Log.Out($"Create method override: {id} for {mtdref_base.FullName}");
foreach (var par in mtdref_base.Parameters)
{
ParameterDefinition pardef = new ParameterDefinition(par.Name, par.Attributes, module.ImportReference(par.ParameterType));
if (par.HasConstant)
pardef.Constant = par.Constant;
mtddef_derived.Parameters.Add(pardef);
}
mtddef_derived.Body.Variables.Clear();
mtddef_derived.Body.InitLocals = true;
mtddef_derived.Body.Variables.Add(new VariableDefinition(module.TypeSystem.Boolean));
bool hasReturnVal = mtddef_derived.ReturnType.MetadataType != MetadataType.Void;
if (hasReturnVal)
{
mtddef_derived.Body.Variables.Add(new VariableDefinition(module.ImportReference(mtdref_base.ReturnType)));
}
var il = mtddef_derived.Body.GetILProcessor();
if (hasReturnVal)
{
il.Emit(OpCodes.Ldloca_S, mtddef_derived.Body.Variables[1]);
il.Emit(OpCodes.Initobj, module.ImportReference(mtddef_derived.ReturnType));
}
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Stloc_S, mtddef_derived.Body.Variables[0]);
Instruction prefixBegin = il.Create(OpCodes.Ldc_I4_1);
il.Append(prefixBegin);
il.Emit(OpCodes.Stloc_S, mtddef_derived.Body.Variables[0]);
il.Emit(OpCodes.Ldarg_0);
for (int i = 0; i < mtddef_derived.Parameters.Count; i++)
{
var par = mtddef_derived.Parameters[i];
il.Emit(par.ParameterType.IsByReference ? OpCodes.Ldarga_S : OpCodes.Ldarg_S, par);
}
il.Emit(OpCodes.Call, mtddef_base_override ?? module.ImportReference(mtdref_base));
if (hasReturnVal)
{
il.Emit(OpCodes.Stloc_S, mtddef_derived.Body.Variables[1]);
il.Emit(OpCodes.Ldloc_S, mtddef_derived.Body.Variables[1]);
}
il.Emit(OpCodes.Ret);
mtdpinf_derived = new MethodPatchInfo(mtddef_derived, mtddef_derived.Body.Instructions[mtddef_derived.Body.Instructions.Count - (hasReturnVal ? 2 : 1)], prefixBegin);
dict_overrides.Add(id, mtdpinf_derived);
return mtdpinf_derived;
}
/// <summary>
/// Create a List<Instruction> that loads all arguments required to call the method onto stack.
/// </summary>
/// <param name="mtddef_derived">The override method.</param>
/// <param name="mtddef_target">The patch method to be called.</param>
/// <param name="flddef_module">The injected module field.</param>
/// <param name="flddef_data">The injected data field.</param>
/// <param name="module">The assembly's main module.</param>
/// <param name="itemActionType">The base ItemAction type.</param>
/// <returns></returns>
/// <exception cref="MissingFieldException"></exception>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
private static List<Instruction> MatchArguments(MethodDefinition mtddef_root, MethodPatchInfo mtdpinf_derived, MethodDefinition mtddef_target, FieldDefinition flddef_module, FieldDefinition flddef_data, ModuleDefinition module, Type itemActionType, TypeDefinition typedef_newactiondata, bool isPostfix, Dictionary<string, VariableDefinition> dict_states, string moduleID)
{
var mtddef_derived = mtdpinf_derived.Method;
var il = mtddef_derived.Body.GetILProcessor();
//Match parameters
List<Instruction> list_inst_pars = new List<Instruction>();
list_inst_pars.Add(il.Create(OpCodes.Ldarg_0));
list_inst_pars.Add(il.Create(OpCodes.Ldfld, flddef_module));
foreach (var par in mtddef_target.Parameters)
{
if (par.Name.StartsWith("___"))
{
//___ means non public fields
string str_fldname = par.Name.Substring(3);
FieldDefinition flddef_target = module.ImportReference(itemActionType.GetField(str_fldname, BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic)).Resolve();
if (flddef_target == null)
throw new MissingFieldException($"Field with name \"{str_fldname}\" not found! Patch method: {mtddef_target.DeclaringType.FullName}.{mtddef_target.Name}");
if (flddef_target.IsStatic)
{
list_inst_pars.Add(il.Create((par.ParameterType.IsByReference) ? OpCodes.Ldsflda : OpCodes.Ldsfld, module.ImportReference(flddef_target)));
}
else
{
list_inst_pars.Add(il.Create(OpCodes.Ldarg_0));
list_inst_pars.Add(il.Create(par.ParameterType.IsByReference ? OpCodes.Ldflda : OpCodes.Ldfld, module.ImportReference(flddef_target)));
}
}
else if (!MatchSpecialParameters(par, flddef_data, mtddef_target, mtdpinf_derived, typedef_newactiondata, list_inst_pars, il, module, isPostfix, dict_states, moduleID))
{
//match param by name
int index = -1;
for (int j = 0; j < mtddef_root.Parameters.Count; j++)
{
if (mtddef_root.Parameters[j].Name == par.Name)
{
index = mtddef_root.Parameters[j].Index;
break;
}
}
if (index < 0)
throw new ArgumentException($"Parameter \"{par.Name}\" not found! Patch method: {mtddef_target.DeclaringType.FullName}.{mtddef_target.Name}");
try
{
//Log.Out($"Match Parameter {par.Name} to {mtddef_derived.Parameters[index].Name}/{mtddef_root.Parameters[index].Name} index: {index}");
}
catch (ArgumentOutOfRangeException e)
{
Log.Error($"index {index} parameter {par.Name}" +
$"root pars: {{{string.Join(",", mtddef_root.Parameters.Select(p => p.Name + "/" + p.Index).ToArray())}}}" +
$"derived pars: {{{string.Join(",", mtddef_derived.Parameters.Select(p => p.Name + "/" + p.Index).ToArray())}}}");
throw e;
}
if (!mtddef_derived.Parameters[index].ParameterType.IsByReference)
{
list_inst_pars.Add(il.Create(par.ParameterType.IsByReference ? OpCodes.Ldarga_S : OpCodes.Ldarg_S, mtddef_derived.Parameters[index]));
}
else
{
list_inst_pars.Add(il.Create(OpCodes.Ldarg_S, mtddef_derived.Parameters[index]));
if (!par.ParameterType.IsByReference)
{
list_inst_pars.Add(il.Create(OpCodes.Ldind_Ref));
}
}
}
}
return list_inst_pars;
}
private static bool MatchSpecialParameters(ParameterDefinition par, FieldDefinition flddef_data, MethodDefinition mtddef_target, MethodPatchInfo mtdpinf_derived, TypeDefinition typedef_newactiondata, List<Instruction> list_inst_pars, ILProcessor il, ModuleDefinition module, bool isPostfix, Dictionary<string, VariableDefinition> dict_states, string moduleID)
{
MethodDefinition mtddef_derived = mtdpinf_derived.Method;
switch (par.Name)
{
//load injected data instance
case "__customData":
if (flddef_data == null)
throw new ArgumentNullException($"No Injected ItemActionData in {mtddef_target.DeclaringType.FullName}!");
int index = -1;
for (int j = 0; j < mtddef_derived.Parameters.Count; j++)
{
if (mtddef_derived.Parameters[j].ParameterType.Name == "ItemActionData")
{
index = j;
break;
}
}
if (index < 0)
throw new ArgumentException($"ItemActionData is not present in target method! Patch method: {mtddef_target.DeclaringType.FullName}.{mtddef_target.Name}");
list_inst_pars.Add(il.Create(OpCodes.Ldarg_S, mtddef_derived.Parameters[index]));
list_inst_pars.Add(il.Create(OpCodes.Castclass, typedef_newactiondata));
list_inst_pars.Add(il.Create(par.ParameterType.IsByReference ? OpCodes.Ldflda : OpCodes.Ldfld, flddef_data));
break;
//load ItemAction instance
case "__instance":
list_inst_pars.Add(il.Create(OpCodes.Ldarg_0));
break;
//load return value
case "__result":
list_inst_pars.Add(il.Create(par.ParameterType.IsByReference ? OpCodes.Ldloca_S : OpCodes.Ldloc_S, mtddef_derived.Body.Variables[1]));
break;
//for postfix only, indicates whether original method is executed
case "__runOriginal":
if (par.ParameterType.IsByReference)
throw new ArgumentException($"__runOriginal is readonly! Patch method: {mtddef_target.DeclaringType.FullName}.{mtddef_target.Name}");
list_inst_pars.Add(il.Create(OpCodes.Ldloc_S, mtddef_derived.Body.Variables[0]));
break;
case "__state":
if (dict_states == null)
{
throw new ArgumentNullException($"__state is found in prefix but no matching postfix exists! Patch method: {mtddef_target.DeclaringType.FullName}.{mtddef_target.Name}");
}
if (!isPostfix && !dict_states.TryGetValue(moduleID, out var vardef))
{
throw new KeyNotFoundException($"__state is found in prefix but not found in corresponding postfix! Patch method: {mtddef_target.DeclaringType.FullName}.{mtddef_target.Name}");
}
if (par.IsOut && isPostfix)
{
throw new ArgumentException($"__state is marked as out parameter in postfix! Patch method: {mtddef_target.DeclaringType.FullName}.{mtddef_target.Name}");
}
if (!par.IsOut && !isPostfix)
{
throw new ArgumentException($"__state is not marked as out in prefix! Patch method: {mtddef_target.DeclaringType.FullName}.{mtddef_target.Name}");
}
if (isPostfix)
{
vardef = new VariableDefinition(module.ImportReference(par.ParameterType));
mtddef_derived.Body.Variables.Add(vardef);
dict_states.Add(moduleID, vardef);
var ins = mtddef_derived.Body.Instructions[0];
il.InsertBefore(ins, il.Create(OpCodes.Ldloca_S, vardef));
il.InsertBefore(ins, il.Create(OpCodes.Initobj, module.ImportReference(par.ParameterType)));
list_inst_pars.Add(il.Create(OpCodes.Ldloc_S, vardef));
}
else
{
vardef = dict_states[moduleID];
dict_states.Remove(moduleID);
list_inst_pars.Add(il.Create(OpCodes.Ldloca_S, vardef));
}
break;
default:
return false;
}
return true;
}
/// <summary>
/// Check if type is already generated in previous assemblies.
/// </summary>
/// <param name="name">Full type name.</param>
/// <param name="type">The retrieved type, null if not found.</param>
/// <returns>true if found.</returns>
private static bool TryFindType(string name, out Type type)
{
type = null;
foreach (var assembly in list_created)
{
type = assembly.GetType(name, false);
if (type != null)
return true;
}
return false;
}
/// <summary>
/// Check if type is already generated in current working assembly definition.
/// </summary>
/// <param name="name">Full type name.</param>
/// <param name="typedef">The retrieved type definition, null if not found.</param>
/// <returns>true if found.</returns>
private static bool TryFindInCur(string name, out TypeDefinition typedef)
{
typedef = workingAssembly?.MainModule.GetType(name);
return typedef != null;
}
public static string CreateFieldName(Type moduleType)
{
return (moduleType.FullName + "_" + moduleType.Assembly.GetName().Name).ReplaceInvalidChar();
}
public static string CreateFieldName(TypeReference moduleType)
{
return (moduleType.FullName + "_" + moduleType.Module.Assembly.Name.Name).ReplaceInvalidChar();
}
public static string CreateTypeName(Type itemActionType, params Type[] moduleTypes)
{
string typeName = itemActionType.FullName + "_" + itemActionType.Assembly.GetName().Name;
foreach (Type type in moduleTypes)
{
if (type != null)
typeName += "__" + type.FullName + "_" + type.Assembly.GetName().Name;
}
typeName = typeName.ReplaceInvalidChar();
return typeName;
}
public static string CreateTypeName(TypeReference itemActionType, params TypeReference[] moduleTypes)
{
string typeName = itemActionType.FullName + "_" + itemActionType.Module.Assembly.Name.Name;
foreach (TypeReference type in moduleTypes)
{
if (type != null)
typeName += "__" + type.FullName + "_" + type.Module.Assembly.Name.Name;
}
typeName = typeName.ReplaceInvalidChar();
return typeName;
}
private static string ReplaceInvalidChar(this string self)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < self.Length; i++)
{
char c = self[i];
if (!char.IsLetterOrDigit(c) && c != '_')
{
sb.Append('_');
}
else
{
sb.Append(c);
}
}
return sb.ToString();
}
}
internal struct MethodOverrideInfo
{
public MethodInfo mtdinf_target;
public MethodInfo mtdinf_base;
public MethodReference mtdref_base;
public Type prefType;
public MethodOverrideInfo(MethodInfo mtdinf_target, MethodInfo mtdinf_base, MethodReference mtddef_base, Type prefType)
{
this.mtdinf_target = mtdinf_target;
this.mtdinf_base = mtdinf_base;
this.mtdref_base = mtddef_base;
this.prefType = prefType;
}
}
}

View File

@@ -0,0 +1,257 @@
using KFCommonUtilityLib.Scripts.Utilities;
using System;
using System.Collections.Generic;
using UniLinq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace KFCommonUtilityLib.Scripts.StaticManagers
{
/// <summary>
/// only used for item modifier tags.
/// </summary>
public static class LocalItemTagsManager
{
public static bool CanInstall(FastTags<TagGroup.Global> itemTags, ItemClassModifier modClass)
{
return modClass != null && (modClass.InstallableTags.IsEmpty || itemTags.Test_AnySet(modClass.InstallableTags)) && (modClass.DisallowedTags.IsEmpty || !itemTags.Test_AnySet(modClass.DisallowedTags));
}
public static bool CanStay(FastTags<TagGroup.Global> itemTags, ItemClassModifier modClass)
{
Log.Out($"mod class is null {modClass is null}");
if (modClass != null)
{
Log.Out($"installable {modClass.InstallableTags.IsEmpty || itemTags.Test_AnySet(modClass.InstallableTags)}, disallowed {modClass.DisallowedTags.IsEmpty || !itemTags.Test_AnySet(modClass.DisallowedTags)}");
}
return modClass == null || ((modClass.InstallableTags.IsEmpty || itemTags.Test_AnySet(modClass.InstallableTags)) && (modClass.DisallowedTags.IsEmpty || !itemTags.Test_AnySet(modClass.DisallowedTags)));
}
public static bool CanInstallMod(this ItemValue itemValue, ItemClassModifier modToInstall)
{
if (modToInstall == null)
{
return false;
}
FastTags<TagGroup.Global> tags_after_install = GetTagsAsIfInstalled(itemValue, modToInstall);
if (itemValue.CosmeticMods != null)
{
foreach (var cosValue in itemValue.CosmeticMods)
{
if (cosValue == null || cosValue.IsEmpty())
{
continue;
}
ItemClassModifier cosClass = cosValue.ItemClass as ItemClassModifier;
if (cosClass == null)
{
continue;
}
if (!tags_after_install.Test_AnySet(cosClass.InstallableTags) || tags_after_install.Test_AnySet(cosClass.DisallowedTags))
{
return false;
}
}
}
if (itemValue.Modifications != null)
{
foreach (var modValue in itemValue.Modifications)
{
if (modValue == null || modValue.IsEmpty())
{
continue;
}
ItemClassModifier modClass = modValue.ItemClass as ItemClassModifier;
if (modClass == null)
{
continue;
}
if (!tags_after_install.Test_AnySet(modClass.InstallableTags) || tags_after_install.Test_AnySet(modClass.DisallowedTags))
{
return false;
}
}
}
return true;
}
public static bool CanSwapMod(this ItemValue itemValue, ItemValue modToSwap, ItemClassModifier modToInstall)
{
if (modToInstall == null)
{
return false;
}
FastTags<TagGroup.Global> tags_after_swap = GetTagsAsIfSwapped(itemValue, modToSwap, modToInstall);
if (itemValue.CosmeticMods != null)
{
foreach (var cosValue in itemValue.CosmeticMods)
{
if (cosValue == null || cosValue.IsEmpty() || cosValue == modToSwap)
{
continue;
}
ItemClassModifier cosClass = cosValue.ItemClass as ItemClassModifier;
if (cosClass == null)
{
continue;
}
if (!tags_after_swap.Test_AnySet(cosClass.InstallableTags) || tags_after_swap.Test_AnySet(cosClass.DisallowedTags))
{
return false;
}
}
}
if (itemValue.Modifications != null)
{
foreach (var modValue in itemValue.Modifications)
{
if (modValue == null || modValue.IsEmpty() || modValue == modToSwap)
{
continue;
}
ItemClassModifier modClass = modValue.ItemClass as ItemClassModifier;
if (modClass == null)
{
continue;
}
if (!tags_after_swap.Test_AnySet(modClass.InstallableTags) || tags_after_swap.Test_AnySet(modClass.DisallowedTags))
{
return false;
}
}
}
return true;
}
public static FastTags<TagGroup.Global> GetTags(ItemValue itemValue)
{
var str = string.Join(",", itemValue.GetPropertyOverrides("ItemTagsAppend"));
FastTags<TagGroup.Global> tagsToAdd = string.IsNullOrEmpty(str) ? FastTags<TagGroup.Global>.none : FastTags<TagGroup.Global>.Parse(str);
str = string.Join(",", itemValue.GetPropertyOverrides("ItemTagsRemove"));
FastTags<TagGroup.Global> tagsToRemove = string.IsNullOrEmpty(str) ? FastTags<TagGroup.Global>.none : FastTags<TagGroup.Global>.Parse(str);
return (itemValue.ItemClass.ItemTags | tagsToAdd).Remove(tagsToRemove);
}
public static FastTags<TagGroup.Global> GetTagsAsIfNotInstalled(ItemValue itemValue, ItemValue modValue)
{
var str = string.Join(",", itemValue.GetPropertyOverridesWithoutMod(modValue, "ItemTagsAppend"));
FastTags<TagGroup.Global> tagsToAdd = string.IsNullOrEmpty(str) ? FastTags<TagGroup.Global>.none : FastTags<TagGroup.Global>.Parse(str);
str = string.Join(",", itemValue.GetPropertyOverridesWithoutMod(modValue, "ItemTagsRemove"));
FastTags<TagGroup.Global> tagsToRemove = string.IsNullOrEmpty(str) ? FastTags<TagGroup.Global>.none : FastTags<TagGroup.Global>.Parse(str);
return (itemValue.ItemClass.ItemTags | tagsToAdd).Remove(tagsToRemove);
}
public static FastTags<TagGroup.Global> GetTagsAsIfInstalled(ItemValue itemValue, ItemClassModifier modClass)
{
string itemName = itemValue.ItemClass.GetItemName();
string val = "";
var str = string.Join(",", itemValue.GetPropertyOverrides("ItemTagsAppend"));
if (modClass.GetPropertyOverride("ItemTagsAppend", itemName, ref val))
{
str = string.Join(",", str, val);
}
FastTags<TagGroup.Global> tagsToAdd = string.IsNullOrEmpty(str) ? FastTags<TagGroup.Global>.none : FastTags<TagGroup.Global>.Parse(str);
str = string.Join(",", itemValue.GetPropertyOverrides("ItemTagsRemove"));
if (modClass.GetPropertyOverride("ItemTagsRemove", itemName, ref val))
{
str = string.Join(",", str, val);
}
FastTags<TagGroup.Global> tagsToRemove = string.IsNullOrEmpty(str) ? FastTags<TagGroup.Global>.none : FastTags<TagGroup.Global>.Parse(str);
return (itemValue.ItemClass.ItemTags | tagsToAdd).Remove(tagsToRemove);
}
public static FastTags<TagGroup.Global> GetTagsAsIfSwapped(ItemValue itemValue, ItemValue modValue, ItemClassModifier modClass)
{
string itemName = itemValue.ItemClass.GetItemName();
string val = "";
var str = string.Join(",", itemValue.GetPropertyOverridesWithoutMod(modValue, "ItemTagsAppend"));
if (modClass.GetPropertyOverride("ItemTagsAppend", itemName, ref val))
{
str = string.Join(",", str, val);
}
FastTags<TagGroup.Global> tagsToAdd = string.IsNullOrEmpty(str) ? FastTags<TagGroup.Global>.none : FastTags<TagGroup.Global>.Parse(str);
str = string.Join(",", itemValue.GetPropertyOverridesWithoutMod(modValue, "ItemTagsRemove"));
if (modClass.GetPropertyOverride("ItemTagsRemove", itemName, ref val))
{
str = string.Join(",", str, val);
}
FastTags<TagGroup.Global> tagsToRemove = string.IsNullOrEmpty(str) ? FastTags<TagGroup.Global>.none : FastTags<TagGroup.Global>.Parse(str);
return (itemValue.ItemClass.ItemTags | tagsToAdd).Remove(tagsToRemove);
}
public static IEnumerable<string> GetPropertyOverrides(this ItemValue self, string _propertyName)
{
if (self == null || (self.Modifications.Length == 0 && self.CosmeticMods.Length == 0))
{
yield break;
}
string _value = "";
string itemName = self.ItemClass.GetItemName();
for (int i = 0; i < self.Modifications.Length; i++)
{
ItemValue itemValue = self.Modifications[i];
if (itemValue != null && itemValue.ItemClass is ItemClassModifier itemClassModifier && itemClassModifier.GetPropertyOverride(_propertyName, itemName, ref _value))
{
yield return _value;
}
}
_value = "";
for (int j = 0; j < self.CosmeticMods.Length; j++)
{
ItemValue itemValue2 = self.CosmeticMods[j];
if (itemValue2 != null && itemValue2.ItemClass is ItemClassModifier itemClassModifier2 && itemClassModifier2.GetPropertyOverride(_propertyName, itemName, ref _value))
{
yield return _value;
}
}
}
public static IEnumerable<string> GetPropertyOverridesWithoutMod(this ItemValue self, ItemValue mod, string _propertyName)
{
if (self == null || (self.Modifications.Length == 0 && self.CosmeticMods.Length == 0))
{
yield break;
}
string _value = "";
string itemName = self.ItemClass.GetItemName();
for (int i = 0; i < self.Modifications.Length; i++)
{
ItemValue itemValue = self.Modifications[i];
if (itemValue != null && itemValue != mod && itemValue.ItemClass is ItemClassModifier itemClassModifier && itemClassModifier.GetPropertyOverride(_propertyName, itemName, ref _value))
{
yield return _value;
}
}
_value = "";
for (int j = 0; j < self.CosmeticMods.Length; j++)
{
ItemValue itemValue2 = self.CosmeticMods[j];
if (itemValue2 != null && itemValue2 != mod && itemValue2.ItemClass is ItemClassModifier itemClassModifier2 && itemClassModifier2.GetPropertyOverride(_propertyName, itemName, ref _value))
{
yield return _value;
}
}
}
}
}

View File

@@ -0,0 +1,600 @@
using KFCommonUtilityLib.Scripts.ConsoleCmd;
using KFCommonUtilityLib.Scripts.Utilities;
using System;
using System.Collections.Generic;
using UniLinq;
using UnityEngine;
namespace KFCommonUtilityLib.Scripts.StaticManagers
{
//concept: maintain an entityID-AltActionIndice mapping on both server and client
//and get the correct action before calling ItemAction.*
//always set MinEventParams.itemActionData
//done: set meta and ammoindex on switching mode, keep current mode in metadata
//should take care of accuracy updating
//partially done: should support shared meta
//alt actions should be considered primary, redirect index == 0 to custom method
//redirect ItemClass.Actions[0] to custom method
//however, player input handling is redirected to action0 so that alternative module can dispatch it to correct action.
//patch GameManager.updateSendClientPlayerPositionToServer to sync data, so that mode change always happens after holding item change
public struct MultiActionIndice
{
public const int MAX_ACTION_COUNT = 3;
public unsafe fixed sbyte indices[MAX_ACTION_COUNT];
public unsafe fixed sbyte metaIndice[MAX_ACTION_COUNT];
public readonly byte modeCount;
public unsafe MultiActionIndice(ItemClass item)
{
ItemAction[] actions = item.Actions;
indices[0] = 0;
metaIndice[0] = 0;
byte last = 1;
for (sbyte i = 3; i < actions.Length && last < MAX_ACTION_COUNT; i++)
{
if (actions[i] != null)
{
indices[last] = i;
if (actions[i].Properties.Values.TryGetValue("ShareMetaWith", out string str) && sbyte.TryParse(str, out sbyte shareWith))
{
metaIndice[last] = shareWith;
}
else
{
metaIndice[last] = i;
}
last++;
}
}
modeCount = last;
for (; last < MAX_ACTION_COUNT; last++)
{
indices[last] = -1;
metaIndice[last] = -1;
}
}
public unsafe int GetActionIndexForMode(int mode)
{
return indices[mode];
}
public unsafe int GetMetaIndexForMode(int mode)
{
return metaIndice[mode];
}
public unsafe int GetMetaIndexForActionIndex(int actionIndex)
{
return metaIndice[GetModeForAction(actionIndex)];
}
public int GetModeForAction(int actionIndex)
{
int mode = -1;
for (int i = 0; i < MultiActionIndice.MAX_ACTION_COUNT; i++)
{
unsafe
{
if (indices[i] == actionIndex)
{
mode = i;
break;
}
}
}
return mode;
}
}
//MultiActionMapping instance should be changed on ItemAction.StartHolding, so we only need to send curIndex.
public class MultiActionMapping
{
public const string STR_MULTI_ACTION_INDEX = "MultiActionIndex";
public readonly MultiActionIndice indices;
private int slotIndex;
private int curIndex;
private int lastDisplayMode = -1;
private readonly bool[] unlocked;
private ActionModuleAlternative.AlternativeData altData;
public EntityAlive entity;
public string toggleSound;
public ItemValue ItemValue
{
get
{
var res = entity.inventory.GetItem(slotIndex).itemValue;
if (res.IsEmpty())
{
return null;
}
return res;
}
}
public int SlotIndex => slotIndex;
/// <summary>
/// when set CurIndex from local input, also set manager to dirty to update the index on other clients
/// </summary>
public int CurMode
{
get => curIndex;
set
{
unsafe
{
if (value < 0)
value = 0;
else
{
while (value < MultiActionIndice.MAX_ACTION_COUNT)
{
if (unlocked[value])
break;
value++;
}
//mostly for CurIndex++, cycle through available indices
if (value >= MultiActionIndice.MAX_ACTION_COUNT || indices.indices[value] == -1)
value = 0;
}
if (curIndex == value)
return;
SaveMeta();
//load current meta and ammo index from metadata
curIndex = value;
ReadMeta();
entity.emodel?.avatarController?.UpdateInt(MultiActionUtils.ExecutingActionIndexHash, CurActionIndex, false);
//altData.OverrideMuzzleTransform(curIndex);
}
}
}
//for ItemClass.Actions access
public int CurActionIndex => indices.GetActionIndexForMode(curIndex);
//for meta saving on mode switch only?
public int CurMetaIndex => indices.GetMetaIndexForMode(curIndex);
public int ModeCount => indices.modeCount;
//mapping object is created on StartHolding
//we set the curIndex field instead of the property, according to following situations:
//1. it's a newly created ItemValue, meta and ammo index belongs to action0, no saving is needed;
//2. it's an existing ItemValue, meta and ammo index is set to its action index, still saving is unnecessary.
internal MultiActionMapping(ActionModuleAlternative.AlternativeData altData, MultiActionIndice indices, EntityAlive entity, ItemValue itemValueTemp, string toggleSound, int slotIndex, bool[] unlocked)
{
this.altData = altData;
this.indices = indices;
this.entity = entity;
this.slotIndex = slotIndex;
this.unlocked = unlocked;
object res = itemValueTemp.GetMetadata(STR_MULTI_ACTION_INDEX);
if (res is false || res is null)
{
itemValueTemp.SetMetadata(STR_MULTI_ACTION_INDEX, 0, TypedMetadataValue.TypeTag.Integer);
curIndex = 0;
}
else
{
curIndex = (int)res;
ReadMeta();
}
unsafe
{
for (int i = 0; i < MultiActionIndice.MAX_ACTION_COUNT; i++)
{
int metaIndex = indices.metaIndice[i];
if (metaIndex < 0)
break;
if (!itemValueTemp.HasMetadata(MultiActionUtils.ActionMetaNames[metaIndex]))
{
itemValueTemp.SetMetadata(MultiActionUtils.ActionMetaNames[metaIndex], 0, TypedMetadataValue.TypeTag.Integer);
}
#if DEBUG
else
{
Log.Out($"{MultiActionUtils.ActionMetaNames[metaIndex]}: {itemValueTemp.GetMetadata(MultiActionUtils.ActionMetaNames[metaIndex]).ToString()}");
}
#endif
if (!itemValueTemp.HasMetadata(MultiActionUtils.ActionSelectedAmmoNames[metaIndex]))
{
itemValueTemp.SetMetadata(MultiActionUtils.ActionSelectedAmmoNames[metaIndex], 0, TypedMetadataValue.TypeTag.Integer);
}
#if DEBUG
else
{
Log.Out($"{MultiActionUtils.ActionSelectedAmmoNames[metaIndex]}: {itemValueTemp.GetMetadata(MultiActionUtils.ActionSelectedAmmoNames[metaIndex]).ToString()}");
}
#endif
}
}
this.toggleSound = toggleSound;
entity.emodel?.avatarController?.UpdateInt(MultiActionUtils.ExecutingActionIndexHash, CurActionIndex, false);
#if DEBUG
Log.Out($"MultiAction mode {curIndex}, meta {itemValueTemp.Meta}, ammo index {itemValueTemp.SelectedAmmoTypeIndex}\n {StackTraceUtility.ExtractStackTrace()}");
#endif
}
public void SaveMeta(ItemValue _itemValue = null)
{
//save previous meta and ammo index to metadata
int curMetaIndex = CurMetaIndex;
ItemValue itemValue = _itemValue ?? ItemValue;
if (itemValue == null)
return;
ItemAction[] actions = itemValue.ItemClass.Actions;
if (CurActionIndex < 0 || CurActionIndex >= actions.Length)
return;
ItemActionAttack itemActionAttack = actions[CurActionIndex] as ItemActionAttack;
if (itemActionAttack == null)
return;
if (ConsoleCmdReloadLog.LogInfo)
{
Log.Out($"Saving meta for item {itemValue.ItemClass.Name}");
}
itemValue.SetMetadata(MultiActionUtils.ActionMetaNames[curMetaIndex], itemValue.Meta, TypedMetadataValue.TypeTag.Integer);
itemValue.SetMetadata(MultiActionUtils.ActionSelectedAmmoNames[curMetaIndex], (int)itemValue.SelectedAmmoTypeIndex, TypedMetadataValue.TypeTag.Integer);
if (itemValue.SelectedAmmoTypeIndex > itemActionAttack.MagazineItemNames.Length)
{
Log.Error($"SAVING META ERROR: AMMO INDEX LARGER THAN AMMO ITEM COUNT!\n{StackTraceUtility.ExtractStackTrace()}");
}
if (ConsoleCmdReloadLog.LogInfo)
{
ConsoleCmdMultiActionItemValueDebug.LogMeta(itemValue);
Log.Out($"Save Meta stacktrace:\n{StackTraceUtility.ExtractStackTrace()}");
}
}
public void ReadMeta(ItemValue _itemValue = null)
{
int curMetaIndex = CurMetaIndex;
ItemValue itemValue = _itemValue ?? ItemValue;
if (itemValue == null)
return;
itemValue.SetMetadata(STR_MULTI_ACTION_INDEX, curIndex, TypedMetadataValue.TypeTag.Integer);
object res = itemValue.GetMetadata(MultiActionUtils.ActionMetaNames[curMetaIndex]);
if (res is false || res is null)
{
itemValue.SetMetadata(MultiActionUtils.ActionMetaNames[curMetaIndex], 0, TypedMetadataValue.TypeTag.Integer);
itemValue.Meta = 0;
}
else
{
itemValue.Meta = (int)res;
}
res = itemValue.GetMetadata(MultiActionUtils.ActionSelectedAmmoNames[curMetaIndex]);
if (res is false || res is null)
{
itemValue.SetMetadata(MultiActionUtils.ActionSelectedAmmoNames[curMetaIndex], 0, TypedMetadataValue.TypeTag.Integer);
itemValue.SelectedAmmoTypeIndex = 0;
}
else
{
itemValue.SelectedAmmoTypeIndex = (byte)(int)res;
}
if (ConsoleCmdReloadLog.LogInfo)
{
ConsoleCmdMultiActionItemValueDebug.LogMeta(itemValue);
Log.Out($"Read Meta stacktrace:\n{StackTraceUtility.ExtractStackTrace()}");
}
}
public int SetupRadial(XUiC_Radial _xuiRadialWindow, EntityPlayerLocal _epl)
{
_xuiRadialWindow.ResetRadialEntries();
int preSelectedIndex = -1;
string[] magazineItemNames = ((ItemActionAttack)_epl.inventory.holdingItem.Actions[CurActionIndex]).MagazineItemNames;
bool[] disableStates = CommonUtilityPatch.GetUnusableItemEntries(magazineItemNames, _epl, CurActionIndex);
for (int i = 0; i < magazineItemNames.Length; i++)
{
ItemClass ammoClass = ItemClass.GetItemClass(magazineItemNames[i], false);
if (ammoClass != null && (!_epl.isHeadUnderwater || ammoClass.UsableUnderwater) && !disableStates[i])
{
int ammoCount = _xuiRadialWindow.xui.PlayerInventory.GetItemCount(ammoClass.Id);
bool isCurrentUsing = _epl.inventory.holdingItemItemValue.SelectedAmmoTypeIndex == i;
_xuiRadialWindow.CreateRadialEntry(i, ammoClass.GetIconName(), (ammoCount > 0) ? "ItemIconAtlas" : "ItemIconAtlasGreyscale", ammoCount.ToString(), ammoClass.GetLocalizedItemName(), isCurrentUsing);
if (isCurrentUsing)
{
preSelectedIndex = i;
}
}
}
return preSelectedIndex;
}
public bool CheckDisplayMode()
{
if (lastDisplayMode == CurMode)
{
return false;
}
else
{
lastDisplayMode = CurMode;
return true;
}
}
public bool IsActionUnlocked(int actionIndex)
{
if (actionIndex >= MultiActionIndice.MAX_ACTION_COUNT || actionIndex < 0)
return false;
return unlocked[actionIndex];
}
}
public static class MultiActionManager
{
//clear on game load
private static readonly Dictionary<int, MultiActionMapping> dict_mappings = new Dictionary<int, MultiActionMapping>();
private static readonly Dictionary<int, MultiActionIndice> dict_indice = new Dictionary<int, MultiActionIndice>();
private static readonly Dictionary<int, FastTags<TagGroup.Global>[]> dict_item_action_exclude_tags = new Dictionary<int, FastTags<TagGroup.Global>[]>();
private static readonly Dictionary<int, int[][]> dict_item_action_exclude_mod_property = new Dictionary<int, int[][]>();
private static readonly Dictionary<int, int[][]> dict_item_action_exclude_mod_passive = new Dictionary<int, int[][]>();
private static readonly Dictionary<int, int[][]> dict_item_action_exclude_mod_trigger = new Dictionary<int, int[][]>();
//should set to true when:
//mode switch input received;
//start holding new multi action weapon.?
//if true, send local curIndex to other clients in updateSendClientPlayerPositionToServer.
public static bool LocalModeChanged { get; set; }
public static void PostloadCleanup()
{
dict_mappings.Clear();
dict_indice.Clear();
}
public static void PreloadCleanup()
{
dict_item_action_exclude_tags.Clear();
dict_item_action_exclude_mod_property.Clear();
dict_item_action_exclude_mod_passive.Clear();
dict_item_action_exclude_mod_trigger.Clear();
}
public static void ParseItemActionExcludeTagsAndModifiers(ItemClass item)
{
if (item == null)
return;
FastTags<TagGroup.Global>[] tags = null;
int[][] properties = null, passives = null, triggers = null;
for (int i = 0; i < item.Actions.Length; i++)
{
if (item.Actions[i] != null)
{
if (item.Actions[i].Properties.Values.TryGetValue("ExcludeTags", out string str))
{
if (tags == null)
{
tags = new FastTags<TagGroup.Global>[ItemClass.cMaxActionNames];
dict_item_action_exclude_tags.Add(item.Id, tags);
}
tags[i] = FastTags<TagGroup.Global>.Parse(str);
}
if (item.Actions[i].Properties.Values.TryGetValue("ExcludeMods", out str))
{
if (properties == null)
{
properties = new int[ItemClass.cMaxActionNames][];
dict_item_action_exclude_mod_property.Add(item.Id, properties);
}
properties[i] = str.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Where(s => !string.IsNullOrEmpty(s))
.Select(s => ItemClass.GetItemClass(s, false))
.Where(_item => _item != null)
.Select(_item => _item.Id)
.ToArray();
//Log.Out($"EXCLUDE PROPERTIES FROM ITEM {item.Name} ITEMID {item.Id} ACTION {i} : {string.Join(" ", properties[i])}");
}
if (item.Actions[i].Properties.Values.TryGetValue("ExcludePassives", out str))
{
if (passives == null)
{
passives = new int[ItemClass.cMaxActionNames][];
dict_item_action_exclude_mod_passive.Add(item.Id, passives);
}
passives[i] = str.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Where(s => !string.IsNullOrEmpty(s))
.Select(s => ItemClass.GetItemClass(s, false))
.Where(_item => _item != null)
.Select(_item => _item.Id)
.ToArray();
//Log.Out($"EXCLUDE PASSIVES FROM ITEM {item.Name} ITEMID {item.Id} ACTION {i} : {string.Join(" ", passives[i])}");
}
if (item.Actions[i].Properties.Values.TryGetValue("ExcludeTriggers", out str))
{
if (triggers == null)
{
triggers = new int[ItemClass.cMaxActionNames][];
dict_item_action_exclude_mod_trigger.Add(item.Id, triggers);
}
triggers[i] = str.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Where(s => !string.IsNullOrEmpty(s))
.Select(s => ItemClass.GetItemClass(s, false))
.Where(_item => _item != null)
.Select(_item => _item.Id)
.ToArray();
//Log.Out($"EXCLUDE TRIGGERS FROM ITEM {item.Name} ITEMID {item.Id} ACTION {i} : {string.Join(" ", triggers[i])}");
}
}
}
}
public static void ModifyItemTags(ItemValue itemValue, ItemActionData actionData, ref FastTags<TagGroup.Global> tags)
{
if (itemValue == null || actionData == null || !dict_item_action_exclude_tags.TryGetValue(itemValue.type, out var arr_tags))
{
return;
}
tags = tags.Remove(arr_tags[actionData.indexInEntityOfAction]);
}
public static bool ShouldExcludeProperty(int itemId, int modId, int actionIndex)
{
return dict_item_action_exclude_mod_property.TryGetValue(itemId, out var arr_exclude) && arr_exclude[actionIndex] != null && Array.IndexOf(arr_exclude[actionIndex], modId) >= 0;
}
public static bool ShouldExcludePassive(int itemId, int modId, int actionIndex)
{
return dict_item_action_exclude_mod_passive.TryGetValue(itemId, out var arr_exclude) && arr_exclude[actionIndex] != null && Array.IndexOf(arr_exclude[actionIndex], modId) >= 0;
}
public static bool ShouldExcludeTrigger(int itemId, int modId, int actionIndex)
{
return dict_item_action_exclude_mod_trigger.TryGetValue(itemId, out var arr_exclude) && arr_exclude[actionIndex] != null && Array.IndexOf(arr_exclude[actionIndex], modId) >= 0;
}
public static void UpdateLocalMetaSave(int playerID)
{
if (dict_mappings.TryGetValue(playerID, out MultiActionMapping mapping))
{
mapping?.SaveMeta();
}
}
public static MultiActionIndice GetActionIndiceForItemID(int itemID)
{
if (dict_indice.TryGetValue(itemID, out MultiActionIndice indice))
return indice;
ItemClass item = ItemClass.GetForId(itemID);
if (item == null)
return indice;
indice = new MultiActionIndice(item);
dict_indice[itemID] = indice;
return indice;
}
public static void ToggleLocalActionIndex(EntityPlayerLocal player)
{
if (player == null || !dict_mappings.TryGetValue(player.entityId, out MultiActionMapping mapping))
return;
if (mapping.ModeCount <= 1 || player.inventory.IsHoldingItemActionRunning())
return;
int prevMode = mapping.CurMode;
mapping.CurMode++;
if (prevMode != mapping.CurMode)
{
FireToggleModeEvent(player, mapping);
player.inventory.CallOnToolbeltChangedInternal();
}
}
public static void FireToggleModeEvent(EntityPlayerLocal player, MultiActionMapping mapping)
{
player.PlayOneShot(mapping.toggleSound);
LocalModeChanged = true;
player.MinEventContext.ItemActionData = player.inventory.holdingItemData.actionData[mapping.CurActionIndex];
player.FireEvent(CustomEnums.onSelfItemSwitchMode);
}
public static void SetMappingForEntity(int entityID, MultiActionMapping mapping)
{
dict_mappings[entityID] = mapping;
//Log.Out($"current item index mapping: {((mapping == null || mapping.itemValue == null) ? "null" : mapping.itemValue.ItemClass.Name)}");
}
public static bool SetModeForEntity(int entityID, int mode)
{
if (dict_mappings.TryGetValue(entityID, out MultiActionMapping mapping) && mapping != null)
{
int prevMode = mapping.CurMode;
mapping.CurMode = mode;
return prevMode != mapping.CurMode;
}
return false;
}
public static int GetModeForEntity(int entityID)
{
if (!dict_mappings.TryGetValue(entityID, out MultiActionMapping mapping) || mapping == null)
return 0;
return mapping.CurMode;
}
public static int GetActionIndexForEntity(EntityAlive entity)
{
if (entity == null || !dict_mappings.TryGetValue(entity.entityId, out var mapping) || mapping == null)
return 0;
return mapping.CurActionIndex;
}
public static int GetMetaIndexForEntity(int entityID)
{
if (!dict_mappings.TryGetValue(entityID, out var mapping) || mapping == null)
return 0;
return mapping.CurMetaIndex;
}
public static int GetMetaIndexForActionIndex(int entityID, int actionIndex)
{
if (!dict_mappings.TryGetValue(entityID, out var mapping) || mapping == null)
{
return actionIndex;
}
int mode = mapping.indices.GetModeForAction(actionIndex);
if (mode > 0)
{
unsafe
{
return mapping.indices.metaIndice[mode];
}
}
return actionIndex;
}
public static MultiActionMapping GetMappingForEntity(int entityID)
{
dict_mappings.TryGetValue(entityID, out var mapping);
return mapping;
}
internal static float inputCD = 0;
internal static void UpdateLocalInput(EntityPlayerLocal player, PlayerActionsLocal localActions, bool isUIOpen, float _dt)
{
if (inputCD > 0)
{
inputCD = Math.Max(0, inputCD - _dt);
}
if (isUIOpen || inputCD > 0 || player.emodel.IsRagdollActive || player.IsDead() || player.AttachedToEntity != null)
{
return;
}
if (PlayerActionKFLib.Instance.ToggleActionMode && PlayerActionKFLib.Instance.ToggleActionMode.WasPressed)
{
var mapping = GetMappingForEntity(player.entityId);
if (mapping == null)
{
return;
}
if (player.inventory.IsHoldingItemActionRunning())
{
return;
}
if (localActions.Reload.WasPressed || localActions.PermanentActions.Reload.WasPressed)
{
inputCD = 0.1f;
return;
}
player.inventory.Execute(mapping.CurActionIndex, true, localActions);
localActions.Primary.ClearInputState();
ToggleLocalActionIndex(player);
}
}
}
}

View File

@@ -0,0 +1,370 @@
using CameraShake;
using GearsAPI.Settings.Global;
using UnityEngine;
namespace KFCommonUtilityLib.Scripts.StaticManagers
{
public static class RecoilManager
{
private enum RecoilState
{
None,
Recoil,
Return
}
private static RecoilState state;
private static float lastKickTime = 0f;
//private static float recoilScaledDelta = 0f;
//private static float returnScaledDelta = 0f;
private static Vector2 targetRotationXY = Vector2.zero;
private static Vector2 targetReturnXY = Vector2.zero;
private static Vector3 returnSpeedCur = Vector3.zero;
private static Vector2 totalRotationXY = Vector2.zero;
private static Vector3 totalReturnCur = Vector3.zero;
private static EntityPlayerLocal player;
//Gears options
private static bool enableCap = false;
private static bool enableDynamicCap = false;
private static bool enableSoftCap = false;
private static bool enablePreRecoilCompensation = false;
private static float maxRecoilAngle = 15;
private static int maxDynamicRecoilCapShots = 6;
private static float recoilCapRemain = 1f;
private static float recoilCompensationSensitivityMultiplier = 0f;
private const float DEFAULT_SNAPPINESS_PISTOL = 6f;
private const float DEFAULT_SNAPPINESS_RIFLE = 3.6f;
private const float DEFAULT_SNAPPINESS_SHOTGUN = 6f;
private const float DEFAULT_RETURN_SPEED_PISTOL = 8f;
private const float DEFAULT_RETURN_SPEED_RIFLE = 4f;
private const float DEFAULT_RETURN_SPEED_SHOTGUN = 4f;
private static readonly FastTags<TagGroup.Global> PistolTag = FastTags<TagGroup.Global>.Parse("pistol");
private static readonly FastTags<TagGroup.Global> ShotgunTag = FastTags<TagGroup.Global>.Parse("shotgun");
private static void ClearData()
{
state = RecoilState.None;
//recoilScaledDelta = 0;
returnSpeedCur = Vector3.zero;
targetRotationXY = Vector2.zero;
targetReturnXY = Vector2.zero;
totalRotationXY = Vector2.zero;
totalReturnCur = Vector3.zero;
lastKickTime = 0f;
}
public static void InitRecoilSettings(IModGlobalSettings settings)
{
var capSetting = settings.GetTab("RecoilSettings").GetCategory("Capping");
var recoilCompensationSetting = capSetting.GetSetting("RecoilCompensationSensitivityMultiplier") as ISliderGlobalSetting;
recoilCompensationSensitivityMultiplier = float.Parse(recoilCompensationSetting.CurrentValue);
recoilCompensationSetting.OnSettingChanged += (setting, newValue) => recoilCompensationSensitivityMultiplier = float.Parse(newValue);
var preRecoilCompensationSetting = capSetting.GetSetting("EnablePreRecoilCompensation") as ISwitchGlobalSetting;
enablePreRecoilCompensation = preRecoilCompensationSetting.CurrentValue == "Enable";
preRecoilCompensationSetting.OnSettingChanged += (setting, newValue) => enablePreRecoilCompensation = newValue == "Enable";
var enableCapSetting = capSetting.GetSetting("EnableCap") as ISwitchGlobalSetting;
enableCap = enableCapSetting.CurrentValue == "Enable";
enableCapSetting.OnSettingChanged += (setting, newValue) =>
{
enableCap = newValue == "Enable";
UpdateSettingState(setting.Category);
};
var recoilRemainSetting = capSetting.GetSetting("RecoilRemain") as ISliderGlobalSetting;
recoilCapRemain = float.Parse(recoilRemainSetting.CurrentValue);
recoilRemainSetting.OnSettingChanged += (setting, newValue) => recoilCapRemain = float.Parse(newValue);
var enableSoftCapSetting = capSetting.GetSetting("EnableSoftCap") as ISwitchGlobalSetting;
enableSoftCap = enableSoftCapSetting.CurrentValue == "Enable";
enableSoftCapSetting.OnSettingChanged += (setting, newValue) => enableSoftCap = newValue == "Enable";
var maxRecoilAngleSetting = capSetting.GetSetting("MaxRecoilAngle") as ISliderGlobalSetting;
maxRecoilAngle = float.Parse(maxRecoilAngleSetting.CurrentValue);
maxRecoilAngleSetting.OnSettingChanged += (setting, newValue) => maxRecoilAngle = float.Parse(newValue);
var enableDynamicCapSetting = capSetting.GetSetting("EnableDynamicCap") as ISwitchGlobalSetting;
enableDynamicCap = enableDynamicCapSetting.CurrentValue == "Enable";
enableDynamicCapSetting.OnSettingChanged += (setting, newValue) =>
{
enableDynamicCap = newValue == "Enable";
UpdateSettingState(setting.Category);
};
var maxDynamicRecoilCapShotsSetting = capSetting.GetSetting("MaxDynamicRecoilCapShots") as ISliderGlobalSetting;
maxDynamicRecoilCapShots = int.Parse(maxDynamicRecoilCapShotsSetting.CurrentValue);
maxDynamicRecoilCapShotsSetting.OnSettingChanged += (setting, newValue) => maxDynamicRecoilCapShots = int.Parse(newValue);
UpdateSettingState(capSetting);
}
private static void UpdateSettingState(IGlobalModSettingsCategory category)
{
category.GetSetting("EnableCap").Enabled = true;
category.GetSetting("RecoilRemain").Enabled = enableCap;
category.GetSetting("EnableSoftCap").Enabled = enableCap;
category.GetSetting("MaxRecoilAngle").Enabled = enableCap && !enableDynamicCap;
category.GetSetting("EnableDynamicCap").Enabled = enableCap;
category.GetSetting("MaxDynamicRecoilCapShots").Enabled = enableCap && enableDynamicCap;
}
public static void InitPlayer(EntityPlayerLocal _player)
{
ClearData();
player = _player;
player.cameraTransform.AddMissingComponent<CameraShaker>();
}
public static void Cleanup()
{
ClearData();
player = null;
}
private static float shakeFreq = 20;
private static int shakeBounce = 5;
public static void AddRecoil(Vector2 recoilRangeHor, Vector2 recoilRangeVer)
{
if (player == null) { return; }
state = RecoilState.Recoil;
//recoilScaledDelta = 0;
returnSpeedCur = Vector3.zero;
//returnScaledDelta = 0;
float cap = 0f;
if (enableCap)
{
if (enableDynamicCap)
{
cap = Mathf.Abs(recoilRangeVer.y) * maxDynamicRecoilCapShots;
}
else
{
cap = maxRecoilAngle;
}
}
float cameraShakeStrength = EffectManager.GetValue(CustomEnums.RecoilCameraShakeStrength, player.inventory.holdingItemItemValue, 0.12f, player);
float targetRotationX = player.rand.RandomRange(recoilRangeVer.x, recoilRangeVer.y);
float targetRotationY = player.rand.RandomRange(recoilRangeHor.x, recoilRangeHor.y);
if (enableCap)
{
if (Mathf.Abs(totalRotationXY.x) >= Mathf.Abs(cap))
{
targetRotationX *= recoilCapRemain;
}
else if (enableSoftCap)
{
targetRotationX *= Mathf.Lerp(recoilCapRemain, 1f, 1 - Mathf.InverseLerp(0, Mathf.Abs(cap), Mathf.Abs(totalRotationXY.x)));
//targetRotationX *= Mathf.Lerp(recoilCapRemain, 1f, Mathf.Cos(Mathf.PI * .5f * Mathf.InverseLerp(0, Mathf.Abs(cap), Mathf.Abs(totalRotationXY.x))));
}
}
if (!player.AimingGun)
{
targetRotationXY += new Vector2(targetRotationX, targetRotationY) * 2f;
totalRotationXY += new Vector2(targetRotationX, targetRotationY) * 2f;
CameraShaker.Presets.ShortShake3D(cameraShakeStrength * 1.2f, shakeFreq, shakeBounce);
}
else
{
targetRotationXY += new Vector2(targetRotationX, targetRotationY);
totalRotationXY += new Vector2(targetRotationX, targetRotationY);
CameraShaker.Presets.ShortShake3D(cameraShakeStrength, shakeFreq, shakeBounce);
}
lastKickTime = Time.time;
//if (enableCap)
//{
// float totalRotationXCapped = Mathf.Clamp(totalRotationXY.x, -cap, cap);
// targetRotationXY.x = Mathf.Clamp(targetRotationXY.x + totalRotationXCapped - totalRotationXY.x, -cap, cap);
// totalRotationXY.x = totalRotationXCapped;
//}
}
public static float CompensateX(float movedX)
{
if (!enablePreRecoilCompensation)
{
if (targetReturnXY.x * movedX < 0)
targetReturnXY.x = Mathf.Max(0, targetReturnXY.x + movedX);
return movedX;
}
float targetX = targetRotationXY.x;
float returnX = targetReturnXY.x;
float res = Compensate(movedX, player.movementInput.rotation.x, ref targetX, ref returnX);
targetRotationXY.x = targetX;
targetReturnXY.x = returnX;
return res;
}
public static float CompensateY(float movedY)
{
if (!enablePreRecoilCompensation)
{
if (targetReturnXY.y * movedY < 0)
targetReturnXY.y = Mathf.Max(0, targetReturnXY.y + movedY);
return movedY;
}
float targetY = targetRotationXY.y;
float returnY = targetReturnXY.y;
float res = Compensate(movedY, player.movementInput.rotation.y, ref targetY, ref returnY);
targetRotationXY.y = targetY;
targetReturnXY.y = returnY;
return res;
}
private static float Compensate(float moved, float original, ref float targetRotation, ref float targetReturn)
{
float dsScale = 1;
if (player.AimingGun)
{
dsScale = Mathf.Lerp(1, 1 / PlayerMoveController.Instance.mouseZoomSensitivity, recoilCompensationSensitivityMultiplier);
//dsScale = 1 / GamePrefs.GetFloat(EnumGamePrefs.OptionsZoomSensitivity);
//if (player.inventory.holdingItemData.actionData[1] is IModuleContainerFor<ActionModuleDynamicSensitivity.DynamicSensitivityData> dsDataContainer && dsDataContainer.Instance.activated)
//{
// dsScale *= Mathf.Sqrt(dsDataContainer.Instance.ZoomRatio);
//}
}
float modified = moved * dsScale - original;
float target = ApplyOppositeCompensation(targetRotation, modified, out modified);
modified /= dsScale;
float compensated = target - targetRotation;
//if (compensated < 0)
//{
// Log.Out($"compensated {compensated} prev {targetRotation} cur {target}");
//}
targetRotation = target;
float @return = targetReturn + (modified * targetReturn < 0 ? modified : 0);
//Log.Out($"return {@return} targetReturn {targetReturn} compensated {compensated} modified {modified}");
if (@return * targetReturn > 0)
{
targetReturn = @return;
}
else
{
targetReturn = 0;
}
return original + modified;
}
public static void ApplyRecoil()
{
if (player == null)
return;
if (state == RecoilState.Recoil)
{
//Log.Out($"target rotation {targetRotationXY}");
//if (targetRotationXY.sqrMagnitude <= 1e-6)
//{
// targetRotationXY = Vector3.zero;
// recoilScaledDelta = 1;
// returnSpeedCur = Vector3.zero;
// state = RecoilState.Return;
// return;
//}
//returnScaledDelta = 0;
FastTags<TagGroup.Global> actionTags = player.inventory.holdingItemItemValue.ItemClass.ItemTags;
MultiActionManager.ModifyItemTags(player.inventory.holdingItemItemValue, player.inventory.holdingItemData.actionData[MultiActionManager.GetActionIndexForEntity(player)], ref actionTags);
float snappinessDefault;
if (actionTags.Test_AnySet(PistolTag))
{
snappinessDefault = DEFAULT_SNAPPINESS_PISTOL;
}
else if (actionTags.Test_AnySet(ShotgunTag))
{
snappinessDefault = DEFAULT_SNAPPINESS_SHOTGUN;
}
else
{
snappinessDefault = DEFAULT_SNAPPINESS_RIFLE;
}
float snappiness = EffectManager.GetValue(CustomEnums.RecoilSnappiness, player.inventory.holdingItemItemValue, snappinessDefault, player);
//targetRotationXY = Vector2.Lerp(targetRotationXY, Vector2.zero, returnSpeed * Time.deltaTime);
float scaledDeltaTime = (Time.time - lastKickTime) * snappiness * 3;
Vector3 result = Vector3.Lerp(Vector3.zero, new Vector3(targetRotationXY.x, targetRotationXY.y), Mathf.Sin(Mathf.PI * .5f * Mathf.Lerp(0, 1, scaledDeltaTime)));
targetRotationXY -= new Vector2(result.x, result.y);
targetReturnXY += new Vector2(result.x, result.y);
player.movementInput.rotation += result;
if (scaledDeltaTime >= 1)
{
targetRotationXY = Vector3.zero;
returnSpeedCur = Vector3.zero;
state = RecoilState.Return;
}
}
else if (state == RecoilState.Return)
{
//Log.Out($"target return {targetReturnXY}");
//if (targetReturnXY.sqrMagnitude <= 1e-6 && totalRotationXY.sqrMagnitude <= 1e-6)
//{
// targetReturnXY = Vector3.zero;
// totalRotationXY = Vector2.zero;
// returnSpeedCur = Vector3.zero;
// totalReturnCur = Vector3.zero;
// //returnScaledDelta = 1;
// state = RecoilState.None;
// return;
//}
FastTags<TagGroup.Global> actionTags = player.inventory.holdingItemItemValue.ItemClass.ItemTags;
MultiActionManager.ModifyItemTags(player.inventory.holdingItemItemValue, player.inventory.holdingItemData.actionData[MultiActionManager.GetActionIndexForEntity(player)], ref actionTags);
float returnSpeedDefault;
if (actionTags.Test_AnySet(PistolTag))
{
returnSpeedDefault = DEFAULT_RETURN_SPEED_PISTOL;
}
else if (actionTags.Test_AnySet(ShotgunTag))
{
returnSpeedDefault = DEFAULT_RETURN_SPEED_SHOTGUN;
}
else
{
returnSpeedDefault = DEFAULT_RETURN_SPEED_RIFLE;
}
float returnSpeed = EffectManager.GetValue(CustomEnums.RecoilReturnSpeed, player.inventory.holdingItemItemValue, returnSpeedDefault, player);
//returnScaledDelta += returnSpeed * Time.deltaTime;
Vector3 result = Vector3.SmoothDamp(Vector3.zero, new Vector3(targetReturnXY.x, targetReturnXY.y), ref returnSpeedCur, 1 / returnSpeed);
targetReturnXY -= new Vector2(result.x, result.y);
player.movementInput.rotation -= result;
if (enableCap)
{
result = Vector3.SmoothDamp(Vector3.zero, new Vector3(totalRotationXY.x, totalRotationXY.y), ref totalReturnCur, 4 / returnSpeed);
totalRotationXY -= new Vector2(result.x, result.y);
}
if (targetReturnXY.sqrMagnitude <= 1e-6 && totalRotationXY.sqrMagnitude <= 1e-6 && Time.time - lastKickTime >= 1 / returnSpeed)
{
targetReturnXY = Vector3.zero;
totalRotationXY = Vector2.zero;
returnSpeedCur = Vector3.zero;
totalReturnCur = Vector3.zero;
state = RecoilState.None;
}
}
else
{
ClearData();
}
}
private static float ApplyOppositeCompensation(float target, float mod, out float modRes)
{
//mouse movement come in with the same direction as recoil
if (mod * target >= 0)
{
modRes = mod;
return target;
}
float res = target + mod;
//is mouse movement enough to compensate the recoil?
if (res * target >= 0)
{
modRes = 0;
return res;
}
else
{
modRes = res;
return 0;
}
}
}
}