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,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace KFCommonUtilityLib.Scripts.Attributes
{
[System.AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class ActionDataTargetAttribute : Attribute
{
public ActionDataTargetAttribute(Type DataType)
{
this.DataType = DataType;
}
public Type DataType { get; }
}
}

View File

@@ -0,0 +1,45 @@
using System;
using HarmonyLib;
namespace KFCommonUtilityLib.Scripts.Attributes
{
public interface IMethodTarget
{
}
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public sealed class MethodTargetPrefixAttribute : Attribute, IMethodTarget
{
public MethodTargetPrefixAttribute()
{
}
}
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public sealed class MethodTargetPostfixAttribute : Attribute, IMethodTarget
{
public MethodTargetPostfixAttribute()
{
}
}
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public sealed class MethodTargetTranspilerAttribute : Attribute, IMethodTarget
{
public MethodTargetTranspilerAttribute()
{
}
}
public static class IMethodTargetExtension
{
public static string GetTargetMethodIdentifier(this HarmonyMethod self)
{
return (self.methodName ?? "this[]") + (self.argumentTypes == null ? string.Empty : string.Join(",", Array.ConvertAll(self.argumentTypes, type => type.FullDescription())));
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
namespace KFCommonUtilityLib.Scripts.Attributes
{
[System.AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class TypeTargetAttribute : Attribute
{
// This is a positional argument
public TypeTargetAttribute(Type baseType)
{
BaseType = baseType;
}
public Type BaseType { get; }
}
}

View File

@@ -0,0 +1,14 @@
using System;
namespace KFCommonUtilityLib.Scripts.Attributes
{
[AttributeUsage(System.AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class TypeTargetExtensionAttribute : Attribute
{
public Type ModuleType { get; }
public TypeTargetExtensionAttribute(Type moduleType)
{
ModuleType = moduleType;
}
}
}

View File

@@ -0,0 +1,175 @@
using System;
using System.Collections.Generic;
using UnityEngine;
public class ConsoleCmdCalibrateWeapon : ConsoleCmdAbstract
{
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
{
if (!_senderInfo.IsLocalGame || _params.Count < 2)
{
Log.Error("too few params: expecting 2 at least");
return;
}
bool flag = Enum.TryParse<CalibrateType>(_params[0], out var calType);
if (!flag)
{
Log.Error("Only following commands are valid: " + string.Join(",", Enum.GetNames(typeof(CalibrateType))));
return;
}
flag = Enum.TryParse<TweakType>(_params[1], out var tweakType);
if (!flag)
{
Log.Error("Only following tweak type are valid: " + String.Join(",", Enum.GetNames(typeof(TweakType))));
return;
}
Transform targetTrans = null;
var inv = GameManager.Instance.World.GetPrimaryPlayer().inventory;
if (calType != CalibrateType.offset)
{
var weaponTrans = inv.GetHoldingItemTransform();
if (weaponTrans == null)
{
Log.Error("player is not holding anything!");
return;
}
targetTrans = weaponTrans.Find(_params[2]);
if (targetTrans == null)
{
Log.Error("transform not found on weapon!");
return;
}
}
Vector3 param = Vector3.zero;
if (tweakType != TweakType.log)
{
int parseIndex;
if (calType != CalibrateType.offset)
{
parseIndex = 3;
if (_params.Count < 4)
{
Log.Error("relative or absolute value is required to calibrate!");
return;
}
}
else
{
parseIndex = 2;
if (_params.Count < 3)
{
Log.Error("offset value is required to calibrate!");
return;
}
}
if (_params.Count < parseIndex + 2)
param = StringParsers.ParseVector3(_params[parseIndex]);
else if (_params.Count == parseIndex + 2)
{
flag = float.TryParse(_params[parseIndex + 1], out float value);
if (!flag)
{
Log.Error("offset value is NAN!");
return;
}
switch (_params[parseIndex])
{
case "x":
param.x = value;
break;
case "y":
param.y = value;
break;
case "z":
param.z = value;
break;
default:
Log.Error("must specify x/y/z axis!");
return;
}
}
else
{
Log.Error("too many params!");
return;
}
}
switch (calType)
{
case CalibrateType.pos:
targetTrans.localPosition = DoCalibrate(tweakType, targetTrans.localPosition, param);
break;
case CalibrateType.rot:
targetTrans.localEulerAngles = DoCalibrate(tweakType, targetTrans.localEulerAngles, param);
break;
case CalibrateType.scale:
targetTrans.localScale = DoCalibrate(tweakType, targetTrans.localScale, param);
break;
case CalibrateType.offset:
//var zoomAction = Convert.ChangeType(inv.holdingItemData.actionData[1], typeof(ItemActionZoom).GetNestedType("ItemActionDataZoom", System.Reflection.BindingFlags.NonPublic));
if (!(inv.holdingItemData.actionData[1] is ItemActionZoom.ItemActionDataZoom zoomAction))
{
Log.Error("holding item can not aim!");
return;
}
zoomAction.ScopeCameraOffset = DoCalibrate(tweakType, zoomAction.ScopeCameraOffset, param);
break;
}
}
private Vector3 DoCalibrate(TweakType type, Vector3 origin, Vector3 param)
{
Vector3 res = origin;
switch (type)
{
case TweakType.abs:
res = param;
break;
case TweakType.rel:
res = origin + param;
break;
case TweakType.log:
Log.Out(res.ToString("F6"));
break;
}
return res;
}
public override string[] getCommands()
{
return new string[] { "calibrate", "calib" };
}
public override string getDescription()
{
return "adjust weapon transform rotation, position, scale, scope offset in game and print current value for xml editing purpose.";
}
public override bool IsExecuteOnClient => true;
public override int DefaultPermissionLevel => 1000;
private enum CalibrateType
{
pos,
rot,
scale,
offset
}
private enum TweakType
{
abs,
rel,
log
}
}

View File

@@ -0,0 +1,57 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class ConsoleCmdListParticleScripts : ConsoleCmdAbstract
{
public override bool IsExecuteOnClient => true;
public override bool AllowedInMainMenu => false;
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
{
if(_params.Count > 1)
{
Log.Error("Invalid param count: expecting 0 or 1!");
return;
}
if (_params.Count == 0)
{
HashSet<string> typeNames = new HashSet<string>();
foreach (var pe in ParticleEffect.loadedTs.Values)
{
foreach (var script in pe.GetComponentsInChildren<MonoBehaviour>())
{
typeNames.Add(script.GetType().AssemblyQualifiedName);
}
}
string print = string.Join("\n", typeNames.ToList().OrderBy(s => s));
Log.Out($"Listing all scripts...\n{print}\n");
}
else
{
int id = ParticleEffect.ToId(_params[0]);
var pe = ParticleEffect.GetDynamicTransform(id);
if (pe)
{
string print = "";
foreach (var script in pe.GetComponentsInChildren<MonoBehaviour>())
{
print += $"{(script.transform.parent != null ? pe.GetChildPath(script.transform) : script.transform.name)} - {script.GetType().AssemblyQualifiedName}\n";
}
Log.Out($"{_params[0]} has following scripts attached:\n{print}");
}
}
}
public override string[] getCommands()
{
return new[] { "listpes" };
}
public override string getDescription()
{
return "list monobehaviour on all the particle effects that are currently loaded, or the specified one only.";
}
}

View File

@@ -0,0 +1,59 @@
using HarmonyLib;
using System.Collections.Generic;
using System.Reflection;
namespace KFCommonUtilityLib.Scripts.ConsoleCmd
{
public class ConsoleCmdMultiActionItemValueDebug : ConsoleCmdAbstract
{
public override bool IsExecuteOnClient => true;
public override int DefaultPermissionLevel => base.DefaultPermissionLevel;
public override bool AllowedInMainMenu => false;
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
{
EntityPlayerLocal player = GameManager.Instance.World?.GetPrimaryPlayer();
if (player)
{
ItemValue itemValue = player.inventory.holdingItemItemValue;
LogMeta(itemValue);
}
}
public static void LogMeta(ItemValue itemValue)
{
if (itemValue != null && itemValue.ItemClass != null)
{
var metadata = itemValue.Metadata;
if (metadata != null)
{
Log.Out("Logging metadata...");
foreach (var pair in metadata)
{
if (pair.Value != null)
{
Log.Out($"key: {pair.Key}, type: {pair.Value.typeTag.ToString()}, value: {pair.Value.GetValue()}");
}
}
}
else
{
Log.Out("Metadata is null!");
}
Log.Out($"meta: {itemValue.Meta}, ammo index: {itemValue.SelectedAmmoTypeIndex}");
}
}
public override string[] getCommands()
{
return new string[] { "maivd" };
}
public override string getDescription()
{
return "Debug ItemValue metadata and stuff.";
}
}
}

View File

@@ -0,0 +1,59 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace KFCommonUtilityLib.Scripts.ConsoleCmd
{
public class ConsoleCmdPlayerDebugInfo : ConsoleCmdAbstract
{
public override bool IsExecuteOnClient => true;
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
{
EntityPlayerLocal player = GameManager.Instance.World.GetPrimaryPlayer();
RenderTexture playerCamRT = player.playerCamera.targetTexture;
if (playerCamRT != null)
SaveTextureToFileUtility.SaveTextureToFile(playerCamRT, Application.dataPath + playerCamRT.name, playerCamRT.width, playerCamRT.height, SaveTextureToFileUtility.SaveTextureFileFormat.PNG, 95, true, (bool res) => Log.Out(res ? $"player camera rendertexture saved to {Application.dataPath}" : "failed to save player camera render texture!"));
RenderTexture finalCamRT = player.finalCamera.targetTexture;
if (finalCamRT != null)
SaveTextureToFileUtility.SaveTextureToFile(finalCamRT, Application.dataPath + finalCamRT.name, finalCamRT.width, finalCamRT.height, SaveTextureToFileUtility.SaveTextureFileFormat.PNG, 95, true, (bool res) => Log.Out(res ? $"final camera rendertexture saved to {Application.dataPath}" : "failed to save final camera render texture!"));
Renderer[] renderers = player.gameObject.GetComponentsInChildren<Renderer>();
Log.Out($"renderers layers: \n{string.Join("\n", renderers.Select(r => r.gameObject.name + " layer:" + r.gameObject.layer))})");
Log.Out($"player transform values: {player.transform.name} {player.transform.position}/{player.transform.eulerAngles}");
Log.Out($"player camera transform values: {player.playerCamera.transform.parent.name}/{player.playerCamera.gameObject.name} {player.playerCamera.transform.localPosition}/{player.playerCamera.transform.localEulerAngles} viewport {player.playerCamera.rect} render layers {player.playerCamera.cullingMask} fov {player.playerCamera.fieldOfView}");
Log.Out($"final camera transform values: {player.finalCamera.transform.parent.name}/{player.finalCamera.gameObject.name} {player.finalCamera.transform.localPosition}/{player.finalCamera.transform.localEulerAngles} viewport {player.finalCamera.rect} render layers {player.finalCamera.cullingMask} fov {player.finalCamera.fieldOfView}");
Log.Out($"vp components list:\n{string.Join("\n", player.RootTransform.GetComponentsInChildren<vp_Component>().Select(c => c.GetType().Name + " on " + c.transform.name))}");
foreach (var animator in player.RootTransform.GetComponentsInChildren<Animator>())
{
Log.Out($"animator transform {animator.name} values: {animator.transform.localPosition}/{animator.transform.localEulerAngles}");
}
Log.Out("PRINTING PLAYER HIERARCHY:");
Log.Out(PrintTransform(player.RootTransform));
Transform fpsArm = ((AvatarLocalPlayerController)player.emodel.avatarController).FPSArms.animator.transform;
Log.Out($"FPS ARM:\nparent {fpsArm.parent.name}\n{PrintTransform(fpsArm)}");
}
private static string PrintTransform(Transform parent, string str = "", int indent = 0)
{
str += "".PadLeft(indent * 4) + $"{parent.name}/pos:{parent.transform.localPosition}/rot:{parent.localEulerAngles}" + "\n";
indent++;
foreach (Transform child in parent)
{
str = PrintTransform(child, str, indent);
}
return str;
}
public override string[] getCommands()
{
return new string[] { "printpinfo" };
}
public override string getDescription()
{
return "print player debug info.";
}
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
namespace KFCommonUtilityLib.Scripts.ConsoleCmd
{
public class ConsoleCmdPrintLocalCache : ConsoleCmdAbstract
{
public override bool IsExecuteOnClient => true;
public override bool AllowedInMainMenu => false;
public override int DefaultPermissionLevel => 1000;
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
{
if (_params.Count != 1)
return;
int index = int.Parse(_params[0]);
var player = GameManager.Instance.World.GetPrimaryPlayer();
if (player != null && player.inventory.holdingItemData.actionData[index] is IModuleContainerFor<ActionModuleLocalPassiveCache.LocalPassiveCacheData> module)
{
ActionModuleLocalPassiveCache.LocalPassiveCacheData instance = module.Instance;
foreach (int hash in instance)
{
Log.Out($"cache {instance.GetCachedName(hash)} value {instance.GetCachedValue(hash)}");
}
}
}
public override string[] getCommands()
{
return new[] { "plc" };
}
public override string getDescription()
{
return "Show local cache for current holding item.";
}
}
}

View File

@@ -0,0 +1,28 @@
using System.Collections.Generic;
public class ConsoleCmdReloadLog : ConsoleCmdAbstract
{
public static bool LogInfo { get; private set; } = false;
public override bool IsExecuteOnClient => true;
public override bool AllowedInMainMenu => false;
public override int DefaultPermissionLevel => 1000;
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
{
LogInfo = !LogInfo;
Log.Out($"Log Reload Info: {LogInfo}");
}
public override string[] getCommands()
{
return new string[] { "reloadlog", "rlog" };
}
public override string getDescription()
{
return "Print reload animation length and multiplier.";
}
}

753
Scripts/FbxExporter07.cs Normal file
View File

@@ -0,0 +1,753 @@
// ***********************************************************************
// Copyright (c) 2017 Unity Technologies. All rights reserved.
//
// Licensed under the ##LICENSENAME##.
// See LICENSE.md file in the project root for full license information.
// ***********************************************************************
using Autodesk.Fbx;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public class FbxExporter07 : System.IDisposable
{
const string Title =
"Example 07: exporting a skinned mesh with bones";
const string Subject =
@"Example FbxExporter07 illustrates how to:
1) create and initialize an exporter
2) create a scene
3) create a skeleton
4) exported mesh
5) bind mesh to skeleton
6) create a bind pose
7) export the skinned mesh to a FBX file (FBX201400 compatible)
";
const string Keywords =
"export skeleton mesh skin cluster pose";
const string Comments =
"";
const string MenuItemName = "File/Export FBX/7. Skinned mesh with bones";
const string FileBaseName = "example_skinned_mesh_with_bones";
/// <summary>
/// Create instance of example
/// </summary>
public static FbxExporter07 Create() { return new FbxExporter07(); }
/// <summary>
/// Export GameObject's as a skinned mesh with bones
/// </summary>
protected void ExportSkinnedMesh(Animator unityAnimator, FbxScene fbxScene, FbxNode fbxParentNode)
{
GameObject unityGo = unityAnimator.gameObject;
SkinnedMeshRenderer unitySkin = unityGo.GetComponentInChildren<SkinnedMeshRenderer>();
if (unitySkin == null)
{
Log.Error("could not find skinned mesh");
return;
}
var meshInfo = GetSkinnedMeshInfo(unitySkin.gameObject);
if (meshInfo.renderer == null)
{
Log.Error("mesh has no renderer");
return;
}
// create an FbxNode and add it as a child of fbxParentNode
FbxNode fbxNode = FbxNode.Create(fbxScene, meshInfo.unityObject.name);
SetNodeMatrix(fbxNode, meshInfo.unityObject.transform);
Dictionary<Transform, FbxNode> boneNodes = new Dictionary<Transform, FbxNode>();
// export skeleton
if (ExportSkeleton(meshInfo, fbxScene, fbxNode, boneNodes, out FbxNode meshNode))
{
// export skin
FbxNode fbxMeshNode = ExportMesh(meshInfo, fbxScene, fbxNode, meshNode);
FbxMesh fbxMesh = fbxMeshNode.GetMesh();
if (fbxMesh == null)
{
Log.Error("Could not find mesh");
return;
}
// bind mesh to skeleton
ExportSkin(meshInfo, fbxScene, fbxMesh, fbxParentNode, boneNodes);
// add bind pose
ExportBindPose(fbxNode, fbxMeshNode, fbxScene, boneNodes);
fbxParentNode.AddChild(fbxNode);
NumNodes++;
if (Verbose)
Log.Out(string.Format("exporting {0} {1}", "Skin", fbxNode.GetName()));
}
else
{
Log.Error("failed to export skeleton");
}
}
/// <summary>
/// Export bones of skinned mesh
/// </summary>
protected bool ExportSkeleton(MeshInfo meshInfo, FbxScene fbxScene, FbxNode fbxParentNode, Dictionary<Transform, FbxNode> boneNodes, out FbxNode meshNode)
{
SkinnedMeshRenderer unitySkinnedMeshRenderer = meshInfo.renderer as SkinnedMeshRenderer;
meshNode = null;
if (unitySkinnedMeshRenderer.bones.Length <= 0)
{
return false;
}
Dictionary<Transform, Matrix4x4> boneBindPose = new Dictionary<Transform, Matrix4x4>();
for (int boneIndex = 0; boneIndex < unitySkinnedMeshRenderer.bones.Length; boneIndex++)
{
Transform unityBoneTransform = unitySkinnedMeshRenderer.bones[boneIndex];
FbxNode fbxBoneNode = FbxNode.Create(fbxScene, unityBoneTransform.name);
// Create the node's attributes
FbxSkeleton fbxSkeleton = FbxSkeleton.Create(fbxScene, unityBoneTransform.name + "_Skel");
var fbxSkeletonType = FbxSkeleton.EType.eLimbNode;
if (unityBoneTransform == unityBoneTransform.root || fbxParentNode.GetName().Equals(unityBoneTransform.parent.name))
{
fbxSkeletonType = FbxSkeleton.EType.eRoot;
}
fbxSkeleton.SetSkeletonType(fbxSkeletonType);
fbxSkeleton.Size.Set(1.0f);
// Set the node's attribute
fbxBoneNode.SetNodeAttribute(fbxSkeleton);
boneBindPose.Add(unityBoneTransform, meshInfo.BindPoses[boneIndex]);
// save relatation between unity transform and fbx bone node for skinning
boneNodes[unityBoneTransform] = fbxBoneNode;
}
Transform root = meshInfo.unityObject.transform;
Dictionary<Transform, FbxNode> dict_empty_parents = new Dictionary<Transform, FbxNode>();
// set the hierarchy for the FbxNodes
foreach (KeyValuePair<Transform, FbxNode> t in boneNodes)
{
Matrix4x4 pose;
// if this is a root node then don't need to do anything
if (t.Key == t.Key.root)
{
fbxParentNode.AddChild(t.Value);
pose = boneBindPose[t.Key].inverse; // assuming parent is identity matrix
SetBoneMatrix(t.Value, pose);
}
else if (!boneNodes.ContainsKey(t.Key.parent))
{
Transform parent = t.Key.parent, cur = t.Key;
FbxNode parentNode = null, curNode = t.Value;
//pose = GetLocalMatrix(cur); // localToWorld
while (parent != root)
{
if (!boneNodes.TryGetValue(parent, out parentNode))
{
if (dict_empty_parents.TryGetValue(parent, out parentNode))
{
parentNode.AddChild(curNode);
pose = GetLocalMatrix(cur, true);
SetBoneMatrix(curNode, pose);
//if (boneNodes.ContainsKey(cur))
//{
// pose = GetLocalMatrix(cur, true);
// SetBoneMatrix(curNode, pose);
//}
//else
//{
// SetNodeMatrix(curNode, cur);
//}
break;
}
else
{
parentNode = FbxNode.Create(fbxScene, parent.name);
parentNode.AddChild(curNode);
pose = GetLocalMatrix(cur, true);
SetBoneMatrix(curNode, pose);
//if (boneNodes.ContainsKey(cur))
//{
// pose = GetLocalMatrix(cur, true);
// SetBoneMatrix(curNode, pose);
//}
//else
//{
// SetNodeMatrix(curNode, cur);
//}
if (parent.TryGetComponent<SkinnedMeshRenderer>(out _))
meshNode = parentNode;
dict_empty_parents.Add(parent, parentNode);
cur = parent;
parent = parent.parent;
curNode = parentNode;
parentNode = null;
}
}
else
{
parentNode.AddChild(curNode);
pose = GetLocalMatrix(cur, true);
SetBoneMatrix(curNode, pose);
//if (boneNodes.ContainsKey(cur))
//{
// pose = GetLocalMatrix(cur, true);
// SetBoneMatrix(curNode, pose);
//}
//else
//{
// SetNodeMatrix(curNode, cur);
//}
break;
}
}
if (parent == root)
{
if (parent.TryGetComponent<SkinnedMeshRenderer>(out _))
meshNode = fbxParentNode;
fbxParentNode.AddChild(curNode);
if (boneNodes.ContainsKey(cur))
{
pose = boneBindPose[cur].inverse;
//pose = boneBindPose[parent] * boneBindPose[cur].inverse;
SetBoneMatrix(curNode, pose);
}
else
{
SetNodeMatrix(curNode, cur);
}
}
}
else
{
boneNodes[t.Key.parent].AddChild(t.Value);
// inverse of my bind pose times parent bind pose
pose = boneBindPose[t.Key.parent] * boneBindPose[t.Key].inverse;
SetBoneMatrix(t.Value, pose);
}
}
return true;
}
private Matrix4x4 GetLocalMatrix(Transform t, bool inverse)
{
//var matrix = Matrix4x4.TRS(t.localPosition, t.localRotation, t.localScale);
var matrix = t.worldToLocalMatrix * t.parent.localToWorldMatrix;
return inverse ? matrix.inverse : matrix;
}
private void SetBoneMatrix(FbxNode node, Matrix4x4 pose)
{
// use FbxMatrix to get translation and rotation relative to parent
FbxMatrix matrix = new FbxMatrix();
matrix.SetColumn(0, new FbxVector4(pose.GetRow(0).x, pose.GetRow(0).y, pose.GetRow(0).z, pose.GetRow(0).w));
matrix.SetColumn(1, new FbxVector4(pose.GetRow(1).x, pose.GetRow(1).y, pose.GetRow(1).z, pose.GetRow(1).w));
matrix.SetColumn(2, new FbxVector4(pose.GetRow(2).x, pose.GetRow(2).y, pose.GetRow(2).z, pose.GetRow(2).w));
matrix.SetColumn(3, new FbxVector4(pose.GetRow(3).x, pose.GetRow(3).y, pose.GetRow(3).z, pose.GetRow(3).w));
FbxVector4 translation, rotation, shear, scale;
double sign;
matrix.GetElements(out translation, out rotation, out shear, out scale, out sign);
// Negating the x value of the translation, and the y and z values of the prerotation
// to convert from Unity to Maya coordinates (left to righthanded)
node.LclTranslation.Set(new FbxDouble3(-translation.X, translation.Y, translation.Z));
node.LclRotation.Set(new FbxDouble3(0, 0, 0));
node.LclScaling.Set(new FbxDouble3(scale.X, scale.Y, scale.Z));
node.SetRotationActive(true);
node.SetPivotState(FbxNode.EPivotSet.eSourcePivot, FbxNode.EPivotState.ePivotReference);
node.SetPreRotation(FbxNode.EPivotSet.eSourcePivot, new FbxVector4(rotation.X, -rotation.Y, -rotation.Z));
}
private void SetNodeMatrix(FbxNode node, Transform unityTransform)
{
// get local position of fbxNode (from Unity)
UnityEngine.Vector3 unityTranslate = unityTransform.localPosition;
UnityEngine.Vector3 unityRotate = unityTransform.localRotation.eulerAngles;
UnityEngine.Vector3 unityScale = unityTransform.localScale;
// transfer transform data from Unity to Fbx
// Negating the x value of the translation, and the y and z values of the rotation
// to convert from Unity to Maya coordinates (left to righthanded)
var fbxTranslate = new FbxDouble3(-unityTranslate.x, unityTranslate.y, unityTranslate.z);
var fbxRotate = new FbxDouble3(unityRotate.x, -unityRotate.y, -unityRotate.z);
var fbxScale = new FbxDouble3(unityScale.x, unityScale.y, unityScale.z);
// set the local position of fbxNode
node.LclTranslation.Set(fbxTranslate);
node.LclRotation.Set(fbxRotate);
node.LclScaling.Set(fbxScale);
}
/// <summary>
/// Export binding of mesh to skeleton
/// </summary>
protected void ExportSkin(MeshInfo meshInfo, FbxScene fbxScene, FbxMesh fbxMesh, FbxNode fbxRootNode, Dictionary<Transform, FbxNode> boneNodes)
{
SkinnedMeshRenderer unitySkinnedMeshRenderer
= meshInfo.renderer as SkinnedMeshRenderer;
FbxSkin fbxSkin = FbxSkin.Create(fbxScene, (meshInfo.unityObject.name + "_Skin"));
FbxAMatrix fbxMeshMatrix = fbxRootNode.EvaluateGlobalTransform();
// keep track of the bone index -> fbx cluster mapping, so that we can add the bone weights afterwards
Dictionary<int, FbxCluster> boneCluster = new Dictionary<int, FbxCluster>();
for (int i = 0; i < unitySkinnedMeshRenderer.bones.Length; i++)
{
FbxNode fbxBoneNode = boneNodes[unitySkinnedMeshRenderer.bones[i]];
// Create the deforming cluster
FbxCluster fbxCluster = FbxCluster.Create(fbxScene, "BoneWeightCluster");
fbxCluster.SetLink(fbxBoneNode);
fbxCluster.SetLinkMode(FbxCluster.ELinkMode.eTotalOne);
boneCluster.Add(i, fbxCluster);
// set the Transform and TransformLink matrix
fbxCluster.SetTransformMatrix(fbxMeshMatrix);
FbxAMatrix fbxLinkMatrix = fbxBoneNode.EvaluateGlobalTransform();
fbxCluster.SetTransformLinkMatrix(fbxLinkMatrix);
// add the cluster to the skin
fbxSkin.AddCluster(fbxCluster);
}
// set the vertex weights for each bone
SetVertexWeights(meshInfo, boneCluster);
// Add the skin to the mesh after the clusters have been added
fbxMesh.AddDeformer(fbxSkin);
}
/// <summary>
/// set weight vertices to cluster
/// </summary>
protected void SetVertexWeights(MeshInfo meshInfo, Dictionary<int, FbxCluster> boneCluster)
{
// set the vertex weights for each bone
for (int i = 0; i < meshInfo.BoneWeights.Length; i++)
{
var boneWeights = meshInfo.BoneWeights;
int[] indices = {
boneWeights [i].boneIndex0,
boneWeights [i].boneIndex1,
boneWeights [i].boneIndex2,
boneWeights [i].boneIndex3
};
float[] weights = {
boneWeights [i].weight0,
boneWeights [i].weight1,
boneWeights [i].weight2,
boneWeights [i].weight3
};
for (int j = 0; j < indices.Length; j++)
{
if (weights[j] <= 0)
{
continue;
}
if (!boneCluster.ContainsKey(indices[j]))
{
continue;
}
boneCluster[indices[j]].AddControlPointIndex(i, weights[j]);
}
}
}
/// <summary>
/// Export bind pose of mesh to skeleton
/// </summary>
protected void ExportBindPose(FbxNode fbxRootNode, FbxNode meshNode, FbxScene fbxScene, Dictionary<Transform, FbxNode> boneNodes)
{
FbxPose fbxPose = FbxPose.Create(fbxScene, fbxRootNode.GetName());
// set as bind pose
fbxPose.SetIsBindPose(true);
// assume each bone node has one weighted vertex cluster
foreach (FbxNode fbxNode in boneNodes.Values)
{
// EvaluateGlobalTransform returns an FbxAMatrix (affine matrix)
// which has to be converted to an FbxMatrix so that it can be passed to fbxPose.Add().
// The hierarchy for FbxMatrix and FbxAMatrix is as follows:
//
// FbxDouble4x4
// / \
// FbxMatrix FbxAMatrix
//
// Therefore we can't convert directly from FbxAMatrix to FbxMatrix,
// however FbxMatrix has a constructor that takes an FbxAMatrix.
FbxMatrix fbxBindMatrix = new FbxMatrix(fbxNode.EvaluateGlobalTransform());
fbxPose.Add(fbxNode, fbxBindMatrix);
}
FbxMatrix bindMatrix = new FbxMatrix(meshNode.EvaluateGlobalTransform());
fbxPose.Add(meshNode, bindMatrix);
// add the pose to the scene
fbxScene.AddPose(fbxPose);
}
/// <summary>
/// Unconditionally export this mesh object to the file.
/// We have decided; this mesh is definitely getting exported.
/// </summary>
public FbxNode ExportMesh(MeshInfo meshInfo, FbxScene fbxScene, FbxNode fbxNode, FbxNode meshNode)
{
if (!meshInfo.IsValid)
{
Log.Error("Invalid mesh info");
return null;
}
// create a node for the mesh
if (meshNode == null)
{
meshNode = FbxNode.Create(fbxScene, "geo");
fbxNode.AddChild(meshNode);
}
// create the mesh structure.
FbxMesh fbxMesh = FbxMesh.Create(fbxScene, "Mesh");
// Create control points.
int NumControlPoints = meshInfo.VertexCount;
fbxMesh.InitControlPoints(NumControlPoints);
// copy control point data from Unity to FBX
for (int v = 0; v < NumControlPoints; v++)
{
// convert from left to right-handed by negating x (Unity negates x again on import)
fbxMesh.SetControlPointAt(new FbxVector4(-meshInfo.Vertices[v].x, meshInfo.Vertices[v].y, meshInfo.Vertices[v].z), v);
}
/*
* Create polygons
* Triangles have to be added in reverse order,
* or else they will be inverted on import
* (due to the conversion from left to right handed coords)
*/
for (int f = 0; f < meshInfo.Triangles.Length / 3; f++)
{
fbxMesh.BeginPolygon();
fbxMesh.AddPolygon(meshInfo.Triangles[3 * f + 2]);
fbxMesh.AddPolygon(meshInfo.Triangles[3 * f + 1]);
fbxMesh.AddPolygon(meshInfo.Triangles[3 * f]);
fbxMesh.EndPolygon();
}
// set the fbxNode containing the mesh
meshNode.SetNodeAttribute(fbxMesh);
meshNode.SetShadingMode(FbxNode.EShadingMode.eWireFrame);
return meshNode;
}
protected void ExportComponents(GameObject unityGo, FbxScene fbxScene, FbxNode fbxParentNode)
{
Animator unityAnimator = unityGo.GetComponent<Animator>();
Log.Out($"Exporting Components: {unityGo.name}, animator: {(unityAnimator == null ? null : unityAnimator.ToString())}");
if (unityAnimator == null)
return;
ExportSkinnedMesh(unityAnimator, fbxScene, fbxParentNode);
return;
}
/// <summary>
/// Export all the objects in the set.
/// Return the number of objects in the set that we exported.
/// </summary>
public int ExportAll(IEnumerable<UnityEngine.Object> unityExportSet)
{
// Create the FBX manager
using (var fbxManager = FbxManager.Create())
{
// Configure IO settings.
fbxManager.SetIOSettings(FbxIOSettings.Create(fbxManager, Globals.IOSROOT));
// Create the exporter
var fbxExporter = FbxExporter.Create(fbxManager, "Exporter");
// Initialize the exporter.
int fileFormat = -1;
fileFormat = fbxManager.GetIOPluginRegistry().FindWriterIDByDescription("FBX ascii (*.fbx)");
bool status = fbxExporter.Initialize(LastFilePath, fileFormat, fbxManager.GetIOSettings());
// Check that initialization of the fbxExporter was successful
if (!status)
{
Log.Error("failed to initialize exporter");
return 0;
}
// By default, FBX exports in its most recent version. You might want to specify
// an older version for compatibility with other applications.
fbxExporter.SetFileExportVersion("FBX201400");
// Create a scene
var fbxScene = FbxScene.Create(fbxManager, "Scene");
// create scene info
FbxDocumentInfo fbxSceneInfo = FbxDocumentInfo.Create(fbxManager, "SceneInfo");
// set some scene info values
fbxSceneInfo.mTitle = Title;
fbxSceneInfo.mSubject = Subject;
fbxSceneInfo.mAuthor = "Unity Technologies";
fbxSceneInfo.mRevision = "1.0";
fbxSceneInfo.mKeywords = Keywords;
fbxSceneInfo.mComment = Comments;
fbxScene.SetSceneInfo(fbxSceneInfo);
var fbxSettings = fbxScene.GetGlobalSettings();
fbxSettings.SetSystemUnit(FbxSystemUnit.m); // Unity unit is meters
// The Unity axis system has Y up, Z forward, X to the right (left handed system with odd parity).
// The Maya axis system has Y up, Z forward, X to the left (right handed system with odd parity).
// We need to export right-handed for Maya because ConvertScene can't switch handedness:
// https://forums.autodesk.com/t5/fbx-forum/get-confused-with-fbxaxissystem-convertscene/td-p/4265472
fbxSettings.SetAxisSystem(FbxAxisSystem.MayaYUp);
FbxNode fbxRootNode = fbxScene.GetRootNode();
// export set of objects
foreach (var obj in unityExportSet)
{
var unityGo = GetGameObject(obj);
if (unityGo)
{
this.ExportComponents(unityGo, fbxScene, fbxRootNode);
}
}
// Export the scene to the file.
status = fbxExporter.Export(fbxScene);
// cleanup
fbxScene.Destroy();
fbxExporter.Destroy();
return status == true ? NumNodes : 0;
}
}
/// <summary>
/// Number of nodes exported including siblings and decendents
/// </summary>
public int NumNodes { private set; get; }
/// <summary>
/// Clean up this class on garbage collection
/// </summary>
public void Dispose() { }
static bool Verbose { get { return true; } }
const string NamePrefix = "";
/// <summary>
/// manage the selection of a filename
/// </summary>
static string LastFilePath { get; set; }
const string Extension = "fbx";
///<summary>
///Information about the mesh that is important for exporting.
///</summary>
public struct MeshInfo
{
/// <summary>
/// The transform of the mesh.
/// </summary>
public Matrix4x4 xform;
public Mesh mesh;
public Renderer renderer;
/// <summary>
/// The gameobject in the scene to which this mesh is attached.
/// This can be null: don't rely on it existing!
/// </summary>
public GameObject unityObject;
/// <summary>
/// Return true if there's a valid mesh information
/// </summary>
/// <value>The vertex count.</value>
public bool IsValid { get { return mesh != null; } }
/// <summary>
/// Gets the vertex count.
/// </summary>
/// <value>The vertex count.</value>
public int VertexCount { get { return mesh.vertexCount; } }
/// <summary>
/// Gets the triangles. Each triangle is represented as 3 indices from the vertices array.
/// Ex: if triangles = [3,4,2], then we have one triangle with vertices vertices[3], vertices[4], and vertices[2]
/// </summary>
/// <value>The triangles.</value>
public int[] Triangles { get { return mesh.triangles; } }
/// <summary>
/// Gets the vertices, represented in local coordinates.
/// </summary>
/// <value>The vertices.</value>
public Vector3[] Vertices { get { return mesh.vertices; } }
/// <summary>
/// Gets the normals for the vertices.
/// </summary>
/// <value>The normals.</value>
public Vector3[] Normals { get { return mesh.normals; } }
/// <summary>
/// Gets the uvs.
/// </summary>
/// <value>The uv.</value>
public Vector2[] UV { get { return mesh.uv; } }
public BoneWeight[] BoneWeights { get { return mesh.boneWeights; } }
public Matrix4x4[] BindPoses { get { return mesh.bindposes; } }
/// <summary>
/// Initializes a new instance of the <see cref="MeshInfo"/> struct.
/// </summary>
/// <param name="gameObject">The GameObject the mesh is attached to.</param>
/// <param name="mesh">A mesh we want to export</param>
public MeshInfo(GameObject gameObject, Mesh mesh, Renderer renderer)
{
this.renderer = renderer;
this.mesh = mesh;
this.xform = gameObject.transform.localToWorldMatrix;
this.unityObject = gameObject;
}
}
/// <summary>
/// Get a mesh renderer's mesh.
/// </summary>
private MeshInfo GetSkinnedMeshInfo(GameObject gameObject)
{
// Verify that we are rendering. Otherwise, don't export.
var renderer = gameObject.GetComponentInChildren<SkinnedMeshRenderer>();
if (!renderer)
{
Log.Error("could not find renderer");
return new MeshInfo();
}
var mesh = renderer.sharedMesh;
if (!mesh)
{
Log.Error("Could not find mesh");
return new MeshInfo();
}
return new MeshInfo(gameObject, mesh, renderer);
}
/// <summary>
/// Get the GameObject
/// </summary>
private static GameObject GetGameObject(Object obj)
{
if (obj is UnityEngine.Transform)
{
var xform = obj as UnityEngine.Transform;
return xform.gameObject;
}
else if (obj is UnityEngine.GameObject)
{
return obj as UnityEngine.GameObject;
}
else if (obj is Component)
{
var mono = obj as Component;
return mono.gameObject;
}
return null;
}
private static string MakeFileName(string basename = "test", string extension = "fbx")
{
return basename + "." + extension;
}
// use the SaveFile panel to allow user to enter a file name
public static void OnExport(IEnumerable<Object> objects, string filePath = null)
{
if (!File.Exists(filePath))
filePath = Path.Combine(Application.dataPath, MakeFileName(FileBaseName, Extension));
LastFilePath = filePath;
using (var fbxExporter = Create())
{
// ensure output directory exists
EnsureDirectory(filePath);
if (fbxExporter.ExportAll(objects) > 0)
{
string message = string.Format("Successfully exported: {0}", filePath);
Log.Out(message);
}
else
{
Log.Warning("Nothing exported!");
}
}
}
private static void EnsureDirectory(string path)
{
//check to make sure the path exists, and if it doesn't then
//create all the missing directories.
FileInfo fileInfo = new FileInfo(path);
if (!fileInfo.Exists)
{
Directory.CreateDirectory(fileInfo.Directory.FullName);
}
}
}

View File

@@ -0,0 +1,28 @@
public static class CustomEnums
{
#region Triggers
public static MinEventTypes onSelfMagzineDeplete;
public static MinEventTypes onReloadAboutToStart;
public static MinEventTypes onRechargeValueUpdate;
public static MinEventTypes onSelfItemSwitchMode;
public static MinEventTypes onSelfBurstModeChanged;
public static MinEventTypes onSelfFirstCVarSync;
public static MinEventTypes onSelfHoldingItemAssemble;
#endregion
#region Passives
public static PassiveEffects ReloadSpeedRatioFPV2TPV;
public static PassiveEffects RecoilSnappiness;
public static PassiveEffects RecoilReturnSpeed;
//public static PassiveEffects ProjectileImpactDamagePercentEntity;
//public static PassiveEffects ProjectileImpactDamagePercentBlock;
public static PassiveEffects PartialReloadCount;
public static PassiveEffects CustomTaggedEffect;
public static PassiveEffects KickDegreeHorizontalModifier;
public static PassiveEffects KickDegreeVerticalModifier;
public static PassiveEffects WeaponErgonomics;
public static PassiveEffects RecoilCameraShakeStrength;
public static PassiveEffects BurstShotInterval;
public static PassiveEffects MaxWeaponSpread;
#endregion
}

View File

@@ -0,0 +1,46 @@
using InControl;
public class PlayerActionKFLib : CustomPlayerActionVersionBase
{
public static PlayerActionKFLib Instance { get; private set; }
public override ControllerActionType ControllerActionDisplay => ControllerActionType.OnFoot;
public PlayerAction ToggleFireMode;
public PlayerAction ToggleActionMode;
public PlayerAction ToggleZoom;
public PlayerActionKFLib()
{
Name = "KFLibPlayerActions";
Version = 1;
Instance = this;
Enabled = true;
var localActions = Platform.PlatformManager.NativePlatform.Input.PrimaryPlayer;
var permaActions = localActions.PermanentActions;
UserData = new PlayerActionData.ActionSetUserData(new PlayerActionsBase[] { localActions, permaActions });
localActions.AddUniConflict(this);
permaActions.AddUniConflict(this);
}
public override void CreateActions()
{
ToggleFireMode = CreatePlayerAction("ToggleFireMode");
ToggleFireMode.UserData = new PlayerActionData.ActionUserData("inpActToggleFireModeName", "inpActToggleFireModeDesc", PlayerActionData.GroupPlayerControl);
ToggleActionMode = CreatePlayerAction("ToggleMode");
ToggleActionMode.UserData = new PlayerActionData.ActionUserData("inpActToggleWeaponModeName", "inpActToggleWeaponModeDesc", PlayerActionData.GroupPlayerControl);
ToggleZoom = CreatePlayerAction("ToggleZoomLevel");
ToggleZoom.UserData = new PlayerActionData.ActionUserData("inpActToggleZoomLevelName", "inpActToggleZoomLevelDesc", PlayerActionData.GroupPlayerControl);
}
public override void CreateDefaultJoystickBindings()
{
}
public override void CreateDefaultKeyboardBindings()
{
ToggleFireMode.AddDefaultBinding(new Key[] { Key.Z });
ToggleActionMode.AddDefaultBinding(new Key[] { Key.X });
ToggleZoom.AddDefaultBinding(Mouse.MiddleButton);
}
}

View File

@@ -0,0 +1,38 @@
using InControl;
public class PlayerActionToggleFireMode : CustomPlayerActionVersionBase
{
public static PlayerActionToggleFireMode Instance { get; private set; }
public override ControllerActionType ControllerActionDisplay => ControllerActionType.OnFoot;
public PlayerAction Toggle;
public PlayerActionToggleFireMode()
{
Name = "ToggleFireMode";
Version = 1;
Instance = this;
Enabled = true;
var localActions = Platform.PlatformManager.NativePlatform.Input.PrimaryPlayer;
var permaActions = localActions.PermanentActions;
UserData = new PlayerActionData.ActionSetUserData(new PlayerActionsBase[] { localActions, permaActions });
localActions.AddUniConflict(this);
permaActions.AddUniConflict(this);
}
public override void CreateActions()
{
Toggle = CreatePlayerAction("ToggleFireMode");
Toggle.UserData = new PlayerActionData.ActionUserData("inpActToggleFireModeName", "inpActToggleFireModeDesc", PlayerActionData.GroupPlayerControl);
}
public override void CreateDefaultJoystickBindings()
{
}
public override void CreateDefaultKeyboardBindings()
{
Toggle.AddDefaultBinding(new Key[] { Key.Z });
}
}

View File

@@ -0,0 +1,38 @@
using InControl;
public class PlayerActionToggleMode : CustomPlayerActionVersionBase
{
public static PlayerActionToggleMode Instance { get; private set; }
public override ControllerActionType ControllerActionDisplay => ControllerActionType.OnFoot;
public PlayerAction Toggle;
public PlayerActionToggleMode()
{
Name = "WeaponMode";
Version = 1;
Instance = this;
Enabled = true;
var localActions = Platform.PlatformManager.NativePlatform.Input.PrimaryPlayer;
var permaActions = localActions.PermanentActions;
UserData = new PlayerActionData.ActionSetUserData(new PlayerActionsBase[] { localActions, permaActions });
localActions.AddUniConflict(this);
permaActions.AddUniConflict(this);
}
public override void CreateActions()
{
Toggle = CreatePlayerAction("ToggleMode");
Toggle.UserData = new PlayerActionData.ActionUserData("inpActToggleWeaponModeName", "inpActToggleWeaponModeDesc", PlayerActionData.GroupPlayerControl);
}
public override void CreateDefaultJoystickBindings()
{
}
public override void CreateDefaultKeyboardBindings()
{
Toggle.AddDefaultBinding(new Key[] { Key.X });
}
}

View File

@@ -0,0 +1,208 @@
using System.Collections.Generic;
using System.Xml.Linq;
using UnityEngine;
public class ItemActionAltMode : ItemActionHoldOpen
{
protected string cvarStateSwitch = null;
protected bool[] altInfiniteAmmo = null;
protected bool originalInfiniteAmmo = false;
private string altModeAnimatorBool = "altMode";
protected List<IRequirement>[] altRequirements;
public int getCurAltIndex(EntityAlive holdingEntity)
{
return MathUtils.Max((int)holdingEntity.GetCVar(cvarStateSwitch), 0) - 1;
}
public virtual void setAltSound(ItemActionData _actionData)
{
ItemActionDataAltMode _data = _actionData as ItemActionDataAltMode;
_data.SetAltSound();
int altIndex = _data.modeIndex;
if (altIndex >= 0)
soundEmpty = _data.altSoundEmpty.Length > altIndex ? _data.altSoundEmpty[altIndex] : string.Empty;
else
soundEmpty = _data.originalSoundEmpty;
}
public override void OnModificationsChanged(ItemActionData _data)
{
base.OnModificationsChanged(_data);
var _dataAlt = _data as ItemActionDataAltMode;
string originalValue = "";
Properties.ParseString("Sound_start", ref originalValue);
_dataAlt.originalSoundStart = _dataAlt.invData.itemValue.GetPropertyOverride("Sound_start", originalValue);
if (_dataAlt.originalSoundStart.Contains("silenced"))
_dataAlt.suppressFlashOnOrigin = true;
originalValue = "";
Properties.ParseString("Sound_loop", ref originalValue);
_dataAlt.originalSoundLoop = _dataAlt.invData.itemValue.GetPropertyOverride("Sound_loop", originalValue);
originalValue = "";
Properties.ParseString("Sound_end", ref originalValue);
_dataAlt.originalSoundEnd = _dataAlt.invData.itemValue.GetPropertyOverride("Sound_end", originalValue);
originalValue = "";
Properties.ParseString("Sound_empty", ref originalValue);
_dataAlt.originalSoundEmpty = _dataAlt.invData.itemValue.GetPropertyOverride("Sound_empty", originalValue);
string _altString = string.Empty;
Properties.ParseString("Alt_Sound_Start", ref _altString);
_altString = _dataAlt.invData.itemValue.GetPropertyOverride("Alt_Sound_Start", _altString);
_dataAlt.altSoundStart = _altString.Split(',');
_dataAlt.suppressFlashOnAlt = new bool[_dataAlt.altSoundStart.Length];
for (int i = 0; i < _dataAlt.suppressFlashOnAlt.Length; ++i)
{
if (_dataAlt.altSoundStart[i].Contains("silenced"))
_dataAlt.suppressFlashOnAlt[i] = true;
}
_altString = string.Empty;
Properties.ParseString("Alt_Sound_Loop", ref _altString);
_altString = _dataAlt.invData.itemValue.GetPropertyOverride("Alt_Sound_Loop", _altString);
_dataAlt.altSoundLoop = _altString.Split(',');
_altString = string.Empty;
Properties.ParseString("Alt_Sound_End", ref _altString);
_altString = _dataAlt.invData.itemValue.GetPropertyOverride("Alt_Sound_End", _altString);
_dataAlt.altSoundEnd = _altString.Split(',');
_altString = string.Empty;
Properties.ParseString("Alt_Sound_Empty", ref _altString);
_altString = _dataAlt.invData.itemValue.GetPropertyOverride("Alt_Sound_Empty", _altString);
_dataAlt.altSoundEmpty = _altString.Split(',');
}
public override ItemActionData CreateModifierData(ItemInventoryData _invData, int _indexInEntityOfAction)
{
return new ItemActionDataAltMode(_invData, _indexInEntityOfAction, cvarStateSwitch);
}
public override void ReadFrom(DynamicProperties _props)
{
base.ReadFrom(_props);
string _altString = string.Empty;
_props.ParseString("Cvar_State_Switch", ref cvarStateSwitch);
_props.ParseString("Alt_InfiniteAmmo", ref _altString);
string[] _altInfiniteAmmo = _altString.Split(',');
altInfiniteAmmo = new bool[_altInfiniteAmmo.Length];
for (int i = 0; i < altInfiniteAmmo.Length; ++i)
altInfiniteAmmo[i] = bool.Parse(_altInfiniteAmmo[i]);
originalInfiniteAmmo = InfiniteAmmo;
altRequirements = new List<IRequirement>[_altInfiniteAmmo.Length + 1];
}
public void ParseAltRequirements(XElement _node, int _actionIdx)
{
foreach (XElement elem in _node.Elements("property"))
{
if (elem.HasAttribute("class") && elem.GetAttribute("class").Contains(_actionIdx.ToString()))
{
for (int i = 0; i < altRequirements.Length; ++i)
{
var requirements = new List<IRequirement>();
requirements.AddRange(ExecutionRequirements);
foreach (XElement childElem in elem.Elements())
{
if (childElem.Name.LocalName.Equals("requirements" + i))
{
requirements.AddRange(RequirementBase.ParseRequirements(childElem));
break;
}
}
altRequirements[i] = requirements;
}
break;
}
}
}
public void SetAltRequirement(ItemActionData _actionData)
{
if (_actionData is ItemActionDataAltMode _data)
ExecutionRequirements = altRequirements[_data.modeIndex + 1];
}
public override void ExecuteAction(ItemActionData _actionData, bool _bReleased)
{
ItemActionDataAltMode _data = _actionData as ItemActionDataAltMode;
int curAltIndex = _data.modeIndex;
if (!_bReleased && curAltIndex >= 0)
InfiniteAmmo = altInfiniteAmmo.Length > curAltIndex ? altInfiniteAmmo[curAltIndex] : false;
else
InfiniteAmmo = originalInfiniteAmmo;
base.ExecuteAction(_actionData, _bReleased);
}
public override void ItemActionEffects(GameManager _gameManager, ItemActionData _actionData, int _firingState, Vector3 _startPos, Vector3 _direction, int _userData = 0)
{
if (_firingState != 0)
setAltSound(_actionData);
base.ItemActionEffects(_gameManager, _actionData, _firingState, _startPos, _direction, _userData);
}
public override void OnHoldingUpdate(ItemActionData _actionData)
{
base.OnHoldingUpdate(_actionData);
if (GameManager.IsDedicatedServer || !(_actionData is ItemActionDataAltMode _data))
return;
EntityAlive holdingEntity = _data.invData.holdingEntity;
int altIndex = getCurAltIndex(holdingEntity);
if (altIndex != _data.modeIndex)
{
if (_data.modeIndex >= 0)
setAnimatorBool(holdingEntity, altModeAnimatorBool + (_data.modeIndex + 1).ToString(), false);
if (altIndex >= 0)
setAnimatorBool(holdingEntity, altModeAnimatorBool + (altIndex + 1).ToString(), true);
_data.modeIndex = altIndex;
}
}
public class ItemActionDataAltMode : ItemActionDataRanged
{
public ItemActionDataAltMode(ItemInventoryData _invData, int _indexInEntityOfAction, string cvar_switch) : base(_invData, _indexInEntityOfAction)
{
}
public void SetAltSound()
{
if (modeIndex >= 0)
{
SoundStart = altSoundStart.Length > modeIndex ? altSoundStart[modeIndex] : string.Empty;
SoundLoop = altSoundLoop.Length > modeIndex ? altSoundLoop[modeIndex] : string.Empty;
SoundEnd = altSoundEnd.Length > modeIndex ? altSoundEnd[modeIndex] : string.Empty;
IsFlashSuppressed = suppressFlashOnAlt.Length > modeIndex ? suppressFlashOnAlt[modeIndex] : false;
}
else
{
SoundStart = originalSoundStart;
SoundLoop = originalSoundLoop;
SoundEnd = originalSoundEnd;
IsFlashSuppressed = suppressFlashOnOrigin;
}
}
public int modeIndex = -1;
public string originalSoundStart = string.Empty;
public string originalSoundLoop = string.Empty;
public string originalSoundEnd = string.Empty;
public string originalSoundEmpty = string.Empty;
public string[] altSoundStart = null;
public string[] altSoundLoop = null;
public string[] altSoundEnd = null;
public string[] altSoundEmpty = null;
public bool suppressFlashOnOrigin = false;
public bool[] suppressFlashOnAlt;
}
}

View File

@@ -0,0 +1,172 @@
using System.Collections;
using UnityEngine;
public class ItemActionHoldOpen : ItemActionRanged
{
private const string emptyAnimatorBool = "empty";
//private EntityAlive lastHoldingEntity = null;
//private HashSet<EntityAlive> hashset_dirty = new HashSet<EntityAlive>();
//private bool reloadReset = false;
public Animator getAnimator(EntityAlive holdingEntity)
{
Animator animator = null;
//should not use ?. here because when you use something from bag ui entry, the holding item is destroyed but still referenced in the avatar controller
//and ?. will try to access that reference instead of return null and throw NRE, while != in unity is override to return null in such case
if (holdingEntity.emodel.avatarController is AvatarMultiBodyController multiBody && multiBody.HeldItemAnimator != null)
animator = multiBody.HeldItemAnimator;
else if (holdingEntity.emodel.avatarController is LegacyAvatarController legacy && legacy.HeldItemTransform != null)
animator = legacy.HeldItemTransform.GetComponent<Animator>();
return animator;
}
public void setAnimatorBool(EntityAlive holdingEntity, string parameter, bool flag)
{
holdingEntity.emodel.avatarController.UpdateBool(parameter, flag, false);
//Animator animator = getAnimator(holdingEntity);
//if (animator)
//{
// animator.SetBool(parameter, flag);
// //Log.Out("trying to set param: " + parameter + " flag: " + flag + " result: " + getAnimatorBool(holdingEntity, parameter) + " transform: " + animator.transform.name);
//}
}
public void setAnimatorFloat(EntityAlive holdingEntity, string parameter, float value)
{
holdingEntity.emodel.avatarController.UpdateFloat(parameter, value, false);
//Animator animator = getAnimator(holdingEntity);
//if (animator)
//{
// animator.SetFloat(parameter, value);
// //Log.Out("trying to set param: " + parameter + " flag: " + flag + " result: " + getAnimatorBool(holdingEntity, parameter) + " transform: " + animator.transform.name);
//}
}
public override int getUserData(ItemActionData _actionData)
{
return _actionData.invData.itemValue.Meta <= 0 ? 1 : 0;
}
public override void ItemActionEffects(GameManager _gameManager, ItemActionData _actionData, int _firingState, Vector3 _startPos, Vector3 _direction, int _userData = 0)
{
base.ItemActionEffects(_gameManager, _actionData, _firingState, _startPos, _direction, _userData);
if (_firingState != (int)ItemActionFiringState.Off && (_userData & 1) > 0)
setAnimatorBool(_actionData.invData.holdingEntity, emptyAnimatorBool, true);
}
public override void ReloadGun(ItemActionData _actionData)
{
base.ReloadGun(_actionData);
//delay 2 frames before reloading, since the animation is likely to be triggered the next frame this is called
ThreadManager.StartCoroutine(DelaySetEmpty(_actionData, false, 2));
//reloadReset = true;
}
public override void StartHolding(ItemActionData _data)
{
//reloadReset = false;
//if(_data.invData.itemValue.Meta <= 0)
// hashset_dirty.Add(_data.invData.holdingEntity);
//lastHoldingEntity = _data.invData.holdingEntity;
//lastHoldingEntity.inventory.OnToolbeltItemsChangedInternal += OnStartHolding;
//delay 1 frame before equipping weapon
if (_data.invData.itemValue.Meta <= 0)
ThreadManager.StartCoroutine(DelaySetEmpty(_data, true, 1));
base.StartHolding(_data);
}
//protected virtual void OnStartHolding()
//{
// if(lastHoldingEntity.inventory.holdingItemItemValue.Meta <= 0)
// setAnimatorBool(lastHoldingEntity, emptyAnimatorBool, true);
// //hashset_dirty.Add(lastHoldingEntity);
// //Log.Out("Entity " + lastHoldingEntity.entityId + " start holding " + lastHoldingEntity.inventory.holdingItemItemValue.ItemClass.Name + " meta: " + lastHoldingEntity.inventory.holdingItemItemValue.Meta);
// lastHoldingEntity.inventory.OnToolbeltItemsChangedInternal -= OnStartHolding;
// lastHoldingEntity = null;
//}
public override void ConsumeAmmo(ItemActionData _actionData)
{
base.ConsumeAmmo(_actionData);
if (_actionData.invData.itemValue.Meta == 0)
_actionData.invData.holdingEntity.FireEvent(CustomEnums.onSelfMagzineDeplete, true);
}
public override void SwapAmmoType(EntityAlive _entity, int _ammoItemId = -1)
{
setAnimatorBool(_entity, emptyAnimatorBool, true);
base.SwapAmmoType(_entity, _ammoItemId);
/*
ItemActionDataRanged _action = _entity.inventory.holdingItemData.actionData[0] as ItemActionDataRanged;
Log.Out("is reloading: " + _action.isReloading + " item: " + _action.invData.itemValue.ItemClass.Name + " meta: " + _action.invData.itemValue.Meta + " holding item: " + _entity.inventory.holdingItemItemValue.ItemClass.Name + " holding meta: " + _entity.inventory.holdingItemItemValue.Meta);
if (_action != null && !_action.isReloading && _action.invData.itemValue.Meta <= 0)
*/
}
private IEnumerator DelaySetEmpty(ItemActionData _actionData, bool empty, int delay)
{
for (int i = 0; i < delay; i++)
{
yield return null;
}
if (_actionData.invData.holdingEntity.inventory.holdingItemIdx == _actionData.invData.slotIdx)
{
setAnimatorBool(_actionData.invData.holdingEntity, emptyAnimatorBool, empty);
}
yield break;
}
//public override void OnHoldingUpdate(ItemActionData _actionData)
//{
// base.OnHoldingUpdate(_actionData);
// if (GameManager.IsDedicatedServer)
// return;
// if (reloadReset)
// {
// setAnimatorBool(GameManager.Instance.World.GetPrimaryPlayer(), emptyAnimatorBool, false);
// reloadReset = false;
// }
// if (hashset_dirty.Count <= 0)
// return;
// foreach (EntityAlive holdingEntity in hashset_dirty)
// setAnimatorBool(holdingEntity, emptyAnimatorBool, true);
// hashset_dirty.Clear();
// /*
// EntityAlive holdingEntity = _actionData.invData.holdingEntity;
// if (hash_dirty.Count <= 0 || !hash_dirty.ContainsKey(holdingEntity))
// return;
// if (hash_dirty[holdingEntity])
// hash_dirty[holdingEntity] = false;
// else
// {
// hash_dirty.Remove(holdingEntity);
// setAnimatorBool(holdingEntity, emptyAnimatorBool, true);
// }
// */
// /*
// EntityAlive holdingEntity = _actionData.invData.holdingEntity;
// bool isReloading = (_actionData as ItemActionDataRanged).isReloading;
// if (holdingEntity.isEntityRemote && !isReloading)
// return;
// int meta = _actionData.invData.itemValue.Meta;
// if (!isReloading && meta <= 0 && !getAnimatorBool(holdingEntity, emptyAnimatorBool))
// {
// Log.Out("trying to update param: " + emptyAnimatorBool + " flag: " + true);
// setAnimatorBool(holdingEntity, emptyAnimatorBool, true);
// }
// else if ((isReloading || meta > 0) && getAnimatorBool(holdingEntity, emptyAnimatorBool))
// {
// Log.Out("trying to update param: " + emptyAnimatorBool + " flag: " + false);
// setAnimatorBool(holdingEntity, emptyAnimatorBool, false);
// }
// */
//}
}

View File

@@ -0,0 +1,188 @@
using Audio;
using KFCommonUtilityLib.Scripts.Utilities;
using System;
using UnityEngine;
public class ItemActionRampUp : ItemActionHoldOpen
{
public override void ExecuteAction(ItemActionData _actionData, bool _bReleased)
{
var _rampData = _actionData as ItemActionDataRampUp;
if (!_bReleased && (InfiniteAmmo || _actionData.invData.itemValue.Meta > 0) && _actionData.invData.itemValue.PercentUsesLeft > 0)
{
_rampData.bReleased = false;
if (!_rampData.prepareStarted)
_rampData.invData.gameManager.ItemActionEffectsServer(_rampData.invData.holdingEntity.entityId, _rampData.invData.slotIdx, _rampData.indexInEntityOfAction, 0, Vector3.zero, Vector3.zero, 4);
if (Time.time - _rampData.prepareStartTime < _rampData.prepareTime)
return;
}
base.ExecuteAction(_actionData, _bReleased);
}
public override void ItemActionEffects(GameManager _gameManager, ItemActionData _actionData, int _firingState, Vector3 _startPos, Vector3 _direction, int _userData = 0)
{
base.ItemActionEffects(_gameManager, _actionData, _firingState, _startPos, _direction, _userData);
var _rampData = _actionData as ItemActionDataRampUp;
var entity = _rampData.invData.holdingEntity;
if (_firingState != 0)
{
if ((_userData & 2) > 0)
{
Manager.Stop(entity.entityId, _rampData.rampSound);
_rampData.rampStarted = true;
_rampData.rampStartTime = Time.time;
Manager.Play(entity, _rampData.rampSound);
}
}
else if ((_userData & 4) > 0)
{
//Log.Out("released, try aim charge!" + _userData);
ResetRamp(_rampData);
if (!_rampData.prepareStarted)
{
//Log.Out("released and aim charge!");
Manager.Stop(entity.entityId, _rampData.prepareSound);
_rampData.prepareStarted = true;
_rampData.prepareStartTime = Time.time;
Manager.Play(entity, _rampData.prepareSound);
setAnimatorBool(_rampData.invData.holdingEntity, "prepare", true);
setAnimatorFloat(_rampData.invData.holdingEntity, "prepareSpeed", _rampData.prepareSpeed);
}
}
else
{
//Log.Out("released, reset all!" + _userData + entity.AimingGun);
ResetAll(_rampData);
}
}
public override int getUserData(ItemActionData _actionData)
{
var _rampData = _actionData as ItemActionDataRampUp;
return base.getUserData(_actionData) | (Convert.ToInt32(_rampData.curBurstCount == _rampData.minRampShots) << 1) | (Convert.ToInt32(_rampData.zoomPrepare && _rampData.invData.holdingEntity.AimingGun) << 2);
}
public override void OnHoldingUpdate(ItemActionData _actionData)
{
base.OnHoldingUpdate(_actionData);
var _rampData = _actionData as ItemActionDataRampUp;
if (_rampData.invData.holdingEntity.isEntityRemote)
return;
bool aiming = _rampData.invData.holdingEntity.AimingGun;
if (!_rampData.prepareStarted && _rampData.zoomPrepare && aiming)
{
_rampData.invData.gameManager.ItemActionEffectsServer(_rampData.invData.holdingEntity.entityId, _rampData.invData.slotIdx, _rampData.indexInEntityOfAction, 0, Vector3.zero, Vector3.zero, 4);
//Log.Out("Aim charge!");
}
else if (_rampData.prepareStarted && _rampData.bReleased && (!_rampData.zoomPrepare || !aiming))
{
_rampData.invData.gameManager.ItemActionEffectsServer(_rampData.invData.holdingEntity.entityId, _rampData.invData.slotIdx, _rampData.indexInEntityOfAction, 0, Vector3.zero, Vector3.zero, 0);
//Log.Out("Stop charge!");
}
else if (_rampData.rampStarted)
{
float rampElapsed = Time.time - _rampData.rampStartTime;
if (rampElapsed > 0)
_rampData.Delay /= rampElapsed > _rampData.rampTime ? _rampData.maxMultiplier : rampElapsed * (_rampData.maxMultiplier - 1) / _rampData.rampTime + 1;
}
}
public override void StopHolding(ItemActionData _data)
{
base.StopHolding(_data);
var _rampData = _data as ItemActionDataRampUp;
ResetRamp(_rampData);
}
public override void ReloadGun(ItemActionData _actionData)
{
base.ReloadGun(_actionData);
var _rampData = _actionData as ItemActionDataRampUp;
ResetRamp(_rampData);
}
private void ResetAll(ItemActionDataRampUp _rampData)
{
ResetPrepare(_rampData);
ResetRamp(_rampData);
//Log.Out("Reset all!");
}
private void ResetPrepare(ItemActionDataRampUp _rampData)
{
_rampData.prepareStarted = false;
Manager.Stop(_rampData.invData.holdingEntity.entityId, _rampData.prepareSound);
setAnimatorBool(_rampData.invData.holdingEntity, "prepare", false);
//Log.Out("Reset Prepare!");
}
private void ResetRamp(ItemActionDataRampUp _rampData)
{
_rampData.rampStarted = false;
Manager.Stop(_rampData.invData.holdingEntity.entityId, _rampData.rampSound);
//Log.Out("Reset Ramp!");
}
public override ItemActionData CreateModifierData(ItemInventoryData _invData, int _indexInEntityOfAction)
{
return new ItemActionDataRampUp(_invData, _indexInEntityOfAction);
}
public override void OnModificationsChanged(ItemActionData _data)
{
base.OnModificationsChanged(_data);
var _rampData = _data as ItemActionDataRampUp;
string originalValue = 1.ToString();
Properties.ParseString("RampMultiplier", ref originalValue);
_rampData.maxMultiplier = Mathf.Max(float.Parse(_rampData.invData.itemValue.GetPropertyOverrideForAction("RampMultiplier", originalValue, _data.indexInEntityOfAction)), 0);
originalValue = 0.ToString();
Properties.ParseString("RampTime", ref originalValue);
_rampData.rampTime = float.Parse(_rampData.invData.itemValue.GetPropertyOverrideForAction("RampTime", originalValue, _data.indexInEntityOfAction));
originalValue = 1.ToString();
Properties.ParseString("MinRampShots", ref originalValue);
_rampData.minRampShots = Mathf.Max(int.Parse(_rampData.invData.itemValue.GetPropertyOverrideForAction("MinRampShots", originalValue, _data.indexInEntityOfAction)), 1);
originalValue = string.Empty;
Properties.ParseString("RampStartSound", ref originalValue);
_rampData.rampSound = _rampData.invData.itemValue.GetPropertyOverrideForAction("RampStartSound", originalValue, _data.indexInEntityOfAction);
originalValue = 0.ToString();
Properties.ParseString("PrepareTime", ref originalValue);
_rampData.prepareTime = float.Parse(_rampData.invData.itemValue.GetPropertyOverrideForAction("PrepareTime", originalValue, _data.indexInEntityOfAction));
_rampData.prepareSpeed = float.Parse(originalValue) / _rampData.prepareTime;
originalValue = string.Empty;
Properties.ParseString("PrepareSound", ref originalValue);
_rampData.prepareSound = _rampData.invData.itemValue.GetPropertyOverrideForAction("PrepareSound", originalValue, _data.indexInEntityOfAction);
originalValue = false.ToString();
Properties.ParseString("PrepareOnAim", ref originalValue);
_rampData.zoomPrepare = bool.Parse(_rampData.invData.itemValue.GetPropertyOverrideForAction("PrepareOnAim", originalValue, _data.indexInEntityOfAction));
}
public class ItemActionDataRampUp : ItemActionDataRanged
{
public ItemActionDataRampUp(ItemInventoryData _invData, int _indexInEntityOfAction) : base(_invData, _indexInEntityOfAction)
{
}
public float maxMultiplier = 1f;
public float rampTime = 0f;
public float prepareTime = 0f;
public float prepareSpeed = 1f;
public string rampSound = string.Empty;
public string prepareSound = string.Empty;
public int minRampShots = 1;
public float rampStartTime = 0f;
public bool rampStarted = false;
public float prepareStartTime = 0f;
public bool prepareStarted = false;
public bool zoomPrepare = false;
}
}

View File

@@ -0,0 +1,76 @@
public class ItemActionRechargeable : ItemActionAltMode
{
protected string[] cvarToConsume = null;
protected string[] cvarConsumption = null;
protected string[] cvarNoConsumptionTemp = null;
public override void ReadFrom(DynamicProperties _props)
{
base.ReadFrom(_props);
string _altString = string.Empty;
_props.ParseString("Cvar_To_Consume", ref _altString);
cvarToConsume = _altString.Split(',');
_altString = string.Empty;
_props.ParseString("Cvar_Consumption", ref _altString);
cvarConsumption = _altString.Split(',');
if (cvarToConsume.Length != cvarConsumption.Length)
Log.Error("cvar to consume count does not match cvar consumption count!");
_altString = string.Empty;
_props.ParseString("Cvar_No_Consumption_Burst_Count", ref _altString);
cvarNoConsumptionTemp = _altString.Split(',');
}
public override void ExecuteAction(ItemActionData _actionData, bool _bReleased)
{
ItemActionDataAltMode _data = _actionData as ItemActionDataAltMode;
EntityAlive holdingEntity = _data.invData.holdingEntity;
ItemValue itemValue = _data.invData.itemValue;
if (!_bReleased)
{
int curAltIndex = _data.modeIndex;
if (curAltIndex >= 0)
InfiniteAmmo = altInfiniteAmmo.Length > curAltIndex ? altInfiniteAmmo[curAltIndex] : false;
else
InfiniteAmmo = originalInfiniteAmmo;
int burstCount = GetBurstCount(_actionData);
if ((_data.curBurstCount >= burstCount && burstCount != -1) || (!InfiniteAmmo && itemValue.Meta <= 0))
{
base.ExecuteAction(_actionData, _bReleased);
return;
}
if (curAltIndex >= 0 && cvarConsumption.Length > curAltIndex && !string.IsNullOrEmpty(cvarConsumption[curAltIndex]))
{
float consumption = holdingEntity.GetCVar(cvarConsumption[curAltIndex]);
if (cvarNoConsumptionTemp.Length > curAltIndex && !string.IsNullOrEmpty(cvarNoConsumptionTemp[curAltIndex]))
{
float isConsumption0 = holdingEntity.GetCVar(cvarNoConsumptionTemp[curAltIndex]);
if (isConsumption0 > 0)
{
consumption = 0;
holdingEntity.SetCVar(cvarNoConsumptionTemp[curAltIndex], --isConsumption0);
}
}
float stock = holdingEntity.GetCVar(cvarToConsume[curAltIndex]);
if (stock < consumption)
{
holdingEntity.PlayOneShot(_data.altSoundEmpty.Length >= curAltIndex ? _data.altSoundEmpty[curAltIndex] : _data.originalSoundEmpty);
return;
}
if (holdingEntity.inventory.holdingItemItemValue.PercentUsesLeft > 0f)
{
float left = stock - consumption;
holdingEntity.SetCVar(cvarToConsume[curAltIndex], left);
}
base.ExecuteAction(_actionData, _bReleased);
return;
}
}
base.ExecuteAction(_actionData, _bReleased);
}
}

View File

@@ -0,0 +1,261 @@
using GUI_2;
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.StaticManagers;
using KFCommonUtilityLib.Scripts.Utilities;
using System.Collections;
using Unity.Mathematics;
[TypeTarget(typeof(ItemActionAttack)), ActionDataTarget(typeof(AlternativeData))]
public class ActionModuleAlternative
{
internal static ItemValue InventorySetItemTemp;
[HarmonyPatch(nameof(ItemAction.StartHolding)), MethodTargetPrefix]
private bool Prefix_StartHolding(ItemActionData _data, AlternativeData __customData)
{
//__customData.Init();
int prevMode = __customData.mapping.CurMode;
__customData.UpdateUnlockState(_data.invData.itemValue);
if (prevMode != __customData.mapping.CurMode && _data.invData.holdingEntity is EntityPlayerLocal player)
{
MultiActionManager.FireToggleModeEvent(player, __customData.mapping);
}
MultiActionManager.SetMappingForEntity(_data.invData.holdingEntity.entityId, __customData.mapping);
if (_data.invData.holdingEntity is EntityPlayerLocal)
{
MultiActionManager.inputCD = math.max(0.5f, MultiActionManager.inputCD);
//ThreadManager.StartCoroutine(DelaySetExecutionIndex(_data.invData.holdingEntity, __customData.mapping));
}
return true;
}
//[MethodTargetPostfix(nameof(ItemActionAttack.StartHolding))]
//private void Postfix_StartHolding(AlternativeData __customData)
//{
// __customData.UpdateMuzzleTransformOverride();
// __customData.OverrideMuzzleTransform(__customData.mapping.CurMode);
//}
private static IEnumerator DelaySetExecutionIndex(EntityAlive player, MultiActionMapping mapping)
{
yield return null;
yield return null;
if (GameManager.Instance.GetGameStateManager().IsGameStarted())
player?.emodel?.avatarController?.UpdateInt(MultiActionUtils.ExecutingActionIndexHash, mapping.CurActionIndex);
}
[HarmonyPatch(nameof(ItemActionRanged.CancelReload)), MethodTargetPrefix]
private bool Prefix_CancelReload(ItemActionData _actionData, AlternativeData __customData)
{
if (__customData.mapping == null)
return true;
int actionIndex = __customData.mapping.CurActionIndex;
if (ConsoleCmdReloadLog.LogInfo)
Log.Out($"cancel reload {actionIndex}");
if (actionIndex == 0)
return true;
_actionData.invData.holdingEntity.inventory.holdingItem.Actions[actionIndex].CancelReload(_actionData.invData.holdingEntity.inventory.holdingItemData.actionData[actionIndex]);
return false;
}
[HarmonyPatch(nameof(ItemAction.CancelAction)), MethodTargetPrefix]
private bool Prefix_CancelAction(ItemActionData _actionData, AlternativeData __customData)
{
if (__customData.mapping == null)
return true;
int actionIndex = __customData.mapping.CurActionIndex;
if (ConsoleCmdReloadLog.LogInfo)
Log.Out($"cancel action {actionIndex}");
if (actionIndex == 0)
return true;
_actionData.invData.holdingEntity.inventory.holdingItem.Actions[actionIndex].CancelAction(_actionData.invData.holdingEntity.inventory.holdingItemData.actionData[actionIndex]);
return false;
}
[HarmonyPatch(nameof(ItemAction.IsStatChanged)), MethodTargetPrefix]
private bool Prefix_IsStatChanged(ref bool __result)
{
var mapping = MultiActionManager.GetMappingForEntity(GameManager.Instance.World.GetPrimaryPlayerId());
__result |= mapping != null && mapping.CheckDisplayMode();
return false;
}
//[MethodTargetPostfix(nameof(ItemActionAttack.StopHolding))]
//private void Postfix_StopHolding(AlternativeData __customData)
//{
// //moved to harmony patch
// //MultiActionManager.SetMappingForEntity(_data.invData.holdingEntity.entityId, null);
// __customData.mapping.SaveMeta();
//}
//todo: change to action specific property
[HarmonyPatch(nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
private void Postfix_OnModificationChanged(ItemActionData _data, ItemActionAttack __instance, AlternativeData __customData)
{
__instance.Properties.ParseString("ToggleActionSound", ref __customData.toggleSound);
__customData.toggleSound = _data.invData.itemValue.GetPropertyOverrideForAction("ToggleActionSound", __customData.toggleSound, __instance.ActionIndex);
__customData.mapping.toggleSound = __customData.toggleSound;
}
[HarmonyPatch(nameof(ItemAction.SetupRadial)), MethodTargetPrefix]
private bool Prefix_SetupRadial(XUiC_Radial _xuiRadialWindow, EntityPlayerLocal _epl)
{
var mapping = MultiActionManager.GetMappingForEntity(_epl.entityId);
if (mapping != null)
{
var radialContextItem = new AlternativeRadialContextItem(mapping, _xuiRadialWindow, _epl);
_xuiRadialWindow.SetCommonData(UIUtils.GetButtonIconForAction(_epl.playerInput.Reload), handleRadialCommand, radialContextItem, radialContextItem.PreSelectedIndex, false, radialValidTest);
}
return false;
}
private bool radialValidTest(XUiC_Radial _sender, XUiC_Radial.RadialContextAbs _context)
{
AlternativeRadialContextItem radialContextItem = _context as AlternativeRadialContextItem;
if (radialContextItem == null)
{
return false;
}
EntityPlayerLocal entityPlayer = _sender.xui.playerUI.entityPlayer;
return radialContextItem.mapping == MultiActionManager.GetMappingForEntity(entityPlayer.entityId) && radialContextItem.mapping.CurActionIndex == radialContextItem.ActionIndex;
}
//redirect reload call to shared meta action, which then sets ItemActionIndex animator param to its action index
//for example if action 3 share meta with action 0, then ItemActionIndex is set to 0 on reload begin.
//since event param item action data is set to the shared meta action data, all reload related passive calculation and trigger events goes there.
private void handleRadialCommand(XUiC_Radial _sender, int _commandIndex, XUiC_Radial.RadialContextAbs _context)
{
AlternativeRadialContextItem radialContextItem = _context as AlternativeRadialContextItem;
if (radialContextItem == null)
{
return;
}
EntityPlayerLocal entityPlayer = _sender.xui.playerUI.entityPlayer;
if (radialContextItem.mapping == MultiActionManager.GetMappingForEntity(entityPlayer.entityId) && radialContextItem.mapping.CurActionIndex == radialContextItem.ActionIndex)
{
entityPlayer.MinEventContext.ItemActionData = entityPlayer.inventory.holdingItemData.actionData?[radialContextItem.ActionIndex];
(entityPlayer.inventory.holdingItem.Actions?[radialContextItem.ActionIndex] as ItemActionRanged)?.SwapSelectedAmmo(entityPlayer, _commandIndex);
}
}
public class AlternativeData
{
public MultiActionMapping mapping;
public string toggleSound;
public ItemInventoryData invData;
//private bool inited = false;
private readonly bool[] unlocked = new bool[MultiActionIndice.MAX_ACTION_COUNT];
//public Transform[] altMuzzleTrans = new Transform[MultiActionIndice.MAX_ACTION_COUNT];
//public Transform[] altMuzzleTransDBarrel = new Transform[MultiActionIndice.MAX_ACTION_COUNT];
public AlternativeData(ItemInventoryData invData, int actionIndex, ActionModuleAlternative module)
{
this.invData = invData;
Init();
}
//public void UpdateMuzzleTransformOverride()
//{
// for (int i = 0; i < MultiActionIndice.MAX_ACTION_COUNT; i++)
// {
// int curActionIndex = mapping.indices.GetActionIndexForMode(i);
// if (curActionIndex < 0)
// {
// break;
// }
// var rangedData = invData.actionData[curActionIndex] as ItemActionRanged.ItemActionDataRanged;
// if (rangedData != null)
// {
// if (rangedData.IsDoubleBarrel)
// {
// altMuzzleTrans[i] = AnimationRiggingManager.GetTransformOverrideByName($"Muzzle_L{curActionIndex}", rangedData.invData.model) ?? rangedData.muzzle;
// altMuzzleTransDBarrel[i] = AnimationRiggingManager.GetTransformOverrideByName($"Muzzle_R{curActionIndex}", rangedData.invData.model) ?? rangedData.muzzle2;
// }
// else
// {
// altMuzzleTrans[i] = AnimationRiggingManager.GetTransformOverrideByName($"Muzzle{curActionIndex}", rangedData.invData.model) ?? rangedData.muzzle;
// }
// }
// }
//}
public void Init()
{
//if (inited)
// return;
//inited = true;
MultiActionIndice indices = MultiActionManager.GetActionIndiceForItemID(invData.item.Id);
mapping = new MultiActionMapping(this, indices, invData.holdingEntity, InventorySetItemTemp, toggleSound, invData.slotIdx, unlocked);
UpdateUnlockState(InventorySetItemTemp);
}
public void UpdateUnlockState(ItemValue itemValue)
{
//if (!inited)
// return;
unlocked[0] = true;
for (int i = 1; i < mapping.ModeCount; i++)
{
bool flag = true;
int actionIndex = mapping.indices.GetActionIndexForMode(i);
ItemAction action = itemValue.ItemClass.Actions[actionIndex];
action.Properties.ParseBool("ActionUnlocked", ref flag);
if (bool.TryParse(itemValue.GetPropertyOverride($"ActionUnlocked_{actionIndex}", flag.ToString()), out bool overrideFlag))
flag = overrideFlag;
unlocked[i] = flag;
}
//by the time we check unlock state, ItemValue in inventory slot might not be ready yet
mapping.SaveMeta(itemValue);
mapping.CurMode = mapping.CurMode;
mapping.ReadMeta(itemValue);
}
public bool IsActionUnlocked(int actionIndex)
{
int mode = mapping.indices.GetModeForAction(actionIndex);
if (mode >= MultiActionIndice.MAX_ACTION_COUNT || mode < 0)
return false;
return unlocked[mode];
}
// public void OverrideMuzzleTransform(int mode)
// {
// var rangedData = invData.actionData[mapping.indices.GetActionIndexForMode(mode)] as ItemActionRanged.ItemActionDataRanged;
// if (rangedData != null)
// {
// if (rangedData.IsDoubleBarrel)
// {
// rangedData.muzzle = altMuzzleTrans[mode];
// rangedData.muzzle2 = altMuzzleTransDBarrel[mode];
// }
// else
// {
// rangedData.muzzle = altMuzzleTrans[mode];
// }
// }
//#if DEBUG
// Log.Out($"setting muzzle transform for action {rangedData.indexInEntityOfAction} to {rangedData.muzzle.name}\n{StackTraceUtility.ExtractStackTrace()}");
//#endif
// }
}
//todo: don't setup for every mode, and use reload animation from shared action
public class AlternativeRadialContextItem : XUiC_Radial.RadialContextAbs
{
public MultiActionMapping mapping;
public int ActionIndex { get; private set; }
public int PreSelectedIndex { get; private set; }
public AlternativeRadialContextItem(MultiActionMapping mapping, XUiC_Radial _xuiRadialWindow, EntityPlayerLocal _epl)
{
this.mapping = mapping;
ActionIndex = mapping.CurActionIndex;
PreSelectedIndex = mapping.SetupRadial(_xuiRadialWindow, _epl);
}
}
}

View File

@@ -0,0 +1,36 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
[TypeTarget(typeof(ItemAction)), ActionDataTarget(typeof(AnimationLockedData))]
public class ActionModuleAnimationLocked
{
[HarmonyPatch(nameof(ItemAction.StartHolding)), MethodTargetPostfix]
private void Postfix_StartHolding(AnimationLockedData __customData)
{
__customData.isLocked = false;
__customData.isReloadLocked = false;
}
[HarmonyPatch(nameof(ItemAction.IsActionRunning)), MethodTargetPostfix]
private void Postfix_IsActionRunning(AnimationLockedData __customData, ref bool __result)
{
__result |= __customData.isLocked;
}
[HarmonyPatch(typeof(ItemActionAttack), nameof(ItemActionAttack.CanReload)), MethodTargetPostfix]
private void Postfix_CanReload_ItemActionAttack(AnimationLockedData __customData, ref bool __result)
{
__result &= !__customData.isReloadLocked;
}
public class AnimationLockedData
{
public bool isLocked = false;
public bool isReloadLocked = false;
public AnimationLockedData(ItemInventoryData invData, int actionIndex, ActionModuleAnimationLocked module)
{
}
}
}

View File

@@ -0,0 +1,135 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using System.Collections.Generic;
using System.Reflection.Emit;
using UniLinq;
using static AnimationDelayData;
[TypeTarget(typeof(ItemAction))]
public class ActionModuleCustomAnimationDelay
{
[HarmonyPatch(typeof(ItemActionEat), nameof(ItemAction.OnHoldingUpdate))]
[HarmonyPatch(typeof(ItemActionGainSkill), nameof(ItemAction.OnHoldingUpdate))]
[HarmonyPatch(typeof(ItemActionLearnRecipe), nameof(ItemAction.OnHoldingUpdate))]
[HarmonyPatch(typeof(ItemActionQuest), nameof(ItemAction.OnHoldingUpdate))]
[HarmonyPatch(typeof(ItemActionEat), nameof(ItemAction.IsActionRunning))]
[HarmonyPatch(typeof(ItemActionGainSkill), nameof(ItemAction.IsActionRunning))]
[HarmonyPatch(typeof(ItemActionLearnRecipe), nameof(ItemAction.IsActionRunning))]
[HarmonyPatch(typeof(ItemActionQuest), nameof(ItemAction.IsActionRunning))]
[MethodTargetTranspiler]
private static IEnumerable<CodeInstruction> Transpiler_OnHoldingUpdate(IEnumerable<CodeInstruction> instructions)
{
var codes = instructions.ToList();
var fld_delayarr = AccessTools.Field(typeof(AnimationDelayData), nameof(AnimationDelayData.AnimationDelay));
var fld_raycast = AccessTools.Field(typeof(AnimationDelays), nameof(AnimationDelays.RayCast));
for (var i = 0; i < codes.Count; i++)
{
if (codes[i].LoadsField(fld_delayarr))
{
for (int j = i + 1; j < codes.Count; j++)
{
if (codes[j].LoadsField(fld_raycast))
{
bool flag = codes[i - 1].LoadsConstant(2f);
codes.RemoveRange(flag ? i - 1 : i, j - i + (flag ? 3 : 1));
codes.InsertRange(flag ? i - 1 : i, new[]
{
new CodeInstruction(OpCodes.Ldarg_0),
CodeInstruction.LoadField(typeof(ItemAction), nameof(ItemAction.Delay))
});
break;
}
}
break;
}
}
return codes;
}
//[HarmonyPatch(nameof(ItemAction.OnHoldingUpdate)), MethodTargetPrefix]
//private bool Prefix_OnHoldingUpdate(ItemAction __instance, ItemActionData _actionData, out AnimationDelays __state)
//{
// __state = AnimationDelayData.AnimationDelay[_actionData.invData.item.HoldType.Value];
// if (!__instance.UseAnimation)
// return true;
// var modifiedData = __state;
// modifiedData.RayCast = __instance.Delay;
// AnimationDelayData.AnimationDelay[_actionData.invData.item.HoldType.Value] = modifiedData;
// return true;
//}
//[HarmonyPatch(nameof(ItemAction.OnHoldingUpdate)), MethodTargetPostfix]
//private void Postfix_OnHoldingUpdate(ItemAction __instance, ItemActionData _actionData, AnimationDelays __state)
//{
// if (!__instance.UseAnimation)
// return;
// AnimationDelayData.AnimationDelay[_actionData.invData.item.HoldType.Value] = __state;
//}
//[HarmonyPatch(nameof(ItemAction.IsActionRunning)), MethodTargetPrefix]
//private bool Prefix_IsActionRunning(ItemAction __instance, ItemActionData _actionData, out AnimationDelays __state)
//{
// __state = AnimationDelayData.AnimationDelay[_actionData.invData.item.HoldType.Value];
// if (!__instance.UseAnimation)
// return true;
// var modifiedData = __state;
// modifiedData.RayCast = __instance.Delay * .5f;
// AnimationDelayData.AnimationDelay[_actionData.invData.item.HoldType.Value] = modifiedData;
// return true;
//}
//[HarmonyPatch(nameof(ItemAction.IsActionRunning)), MethodTargetPostfix]
//private void Postfix_IsActionRunning(ItemAction __instance, ItemActionData _actionData, AnimationDelays __state)
//{
// if (!__instance.UseAnimation)
// return;
// AnimationDelayData.AnimationDelay[_actionData.invData.item.HoldType.Value] = __state;
//}
//following are fix for item use time from menu entry
//when IsActionRunning is called from coroutine which is started by menu entry,
//as OnHoldingUpdate is not called every frame, the check might yield false before item actually gets consumed, thus returning the item
//so we call OnHoldingUpdate to properly consume the item
//vanilla method on the other hand, is forcing double delay in IsActionRunning
[HarmonyPatch(typeof(ItemActionEat), nameof(ItemAction.IsActionRunning)), MethodTargetPostfix]
private void Postfix_IsActionRunning_ItemActionEat(ItemActionEat __instance, ItemActionData _actionData/*, AnimationDelays __state*/, bool __result)
{
//Postfix_IsActionRunning(__instance, _actionData, __state);
if (!__result && ((ItemActionEat.MyInventoryData)_actionData).bEatingStarted)
{
__instance.OnHoldingUpdate(_actionData);
}
}
[HarmonyPatch(typeof(ItemActionGainSkill), nameof(ItemAction.IsActionRunning)), MethodTargetPostfix]
private void Postfix_IsActionRunning_ItemActionGainSkill(ItemActionGainSkill __instance, ItemActionData _actionData/*, AnimationDelays __state*/, bool __result)
{
//Postfix_IsActionRunning(__instance, _actionData, __state);
if (!__result && ((ItemActionGainSkill.MyInventoryData)_actionData).bReadingStarted)
{
__instance.OnHoldingUpdate(_actionData);
}
}
[HarmonyPatch(typeof(ItemActionLearnRecipe), nameof(ItemAction.IsActionRunning)), MethodTargetPostfix]
private void Postfix_IsActionRunning_ItemActionLearnRecipe(ItemActionLearnRecipe __instance, ItemActionData _actionData/*, AnimationDelays __state*/, bool __result)
{
//Postfix_IsActionRunning(__instance, _actionData, __state);
if (!__result && ((ItemActionLearnRecipe.MyInventoryData)_actionData).bReadingStarted)
{
__instance.OnHoldingUpdate(_actionData);
}
}
[HarmonyPatch(typeof(ItemActionQuest), nameof(ItemAction.IsActionRunning)), MethodTargetPostfix]
private void Postfix_IsActionRunning_ItemActionQuest(ItemActionQuest __instance, ItemActionData _actionData/*, AnimationDelays __state*/, bool __result)
{
//Postfix_IsActionRunning(__instance, _actionData, __state);
if (!__result && ((ItemActionQuest.MyInventoryData)_actionData).bQuestAccept)
{
__instance.OnHoldingUpdate(_actionData);
}
}
}

View File

@@ -0,0 +1,78 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
public class DisplayAsBuffEntityUINotification : BuffEntityUINotification
{
public ActionModuleDisplayAsBuff.DisplayValueType displayType = ActionModuleDisplayAsBuff.DisplayValueType.Meta;
public string displayData = string.Empty;
public override float CurrentValue
{
get
{
EntityPlayerLocal player = GameManager.Instance.World.GetPrimaryPlayer();
if (player == null)
return 0;
switch (displayType)
{
case ActionModuleDisplayAsBuff.DisplayValueType.Meta:
return player.inventory.holdingItemItemValue.Meta;
case ActionModuleDisplayAsBuff.DisplayValueType.MetaData:
return (float)player.inventory.holdingItemItemValue.GetMetadata(displayData);
default:
return 0;
}
}
}
public override bool Visible => true;
public override EnumEntityUINotificationDisplayMode DisplayMode => EnumEntityUINotificationDisplayMode.IconPlusCurrentValue;
}
[TypeTarget(typeof(ItemActionRanged))]
public class ActionModuleDisplayAsBuff
{
public enum DisplayValueType
{
Meta,
MetaData
}
private DisplayAsBuffEntityUINotification notification;
private BuffClass buffClass;
[HarmonyPatch(nameof(ItemAction.ReadFrom)), MethodTargetPostfix]
private void Postfix_ReadFrom(DynamicProperties _props)
{
notification = new DisplayAsBuffEntityUINotification();
_props.Values.TryGetValue("DisplayType", out string str);
EnumUtils.TryParse(str, out notification.displayType, true);
_props.Values.TryGetValue("DisplayData", out notification.displayData);
_props.Values.TryGetValue("DisplayBuff", out str);
BuffClass buffClass = BuffManager.GetBuff(str);
BuffValue buff = new BuffValue(buffClass.Name, Vector3i.zero, -1, buffClass);
notification.SetBuff(buff);
}
[HarmonyPatch(nameof(ItemAction.StartHolding)), MethodTargetPostfix]
private void Postfix_StartHolding(ItemActionData _data)
{
EntityPlayerLocal player = _data.invData.holdingEntity as EntityPlayerLocal;
if (player != null && notification != null)
{
notification.SetStats(player.Stats);
player.Stats.NotificationAdded(notification);
}
}
[HarmonyPatch(nameof(ItemAction.StopHolding)), MethodTargetPostfix]
private void Postfix_StopHolding(ItemActionData _data)
{
EntityPlayerLocal player = _data.invData.holdingEntity as EntityPlayerLocal;
if (player != null && notification != null)
{
player.Stats.NotificationRemoved(notification);
}
}
}

View File

@@ -0,0 +1,67 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.StaticManagers;
[TypeTarget(typeof(ItemActionDynamic))]
public class ActionModuleDynamicGraze
{
private string dynamicSoundStart = null;
[HarmonyPatch(nameof(ItemAction.ExecuteAction)), MethodTargetPrefix]
private bool Prefix_ExecuteAction(ItemActionDynamic __instance, ItemActionData _actionData, bool _bReleased, out (bool executed, string originalSound) __state)
{
if (!_bReleased && !string.IsNullOrEmpty(dynamicSoundStart) && _actionData.invData.holdingEntity is EntityPlayerLocal player)
{
var targets = AnimationRiggingManager.GetRigTargetsFromPlayer(player);
if (targets && !targets.Destroyed && targets.IsAnimationSet)
{
__state = (true, __instance.soundStart);
__instance.soundStart = dynamicSoundStart;
return true;
}
}
__state = (false, null);
return true;
}
[HarmonyPatch(nameof(ItemAction.ExecuteAction)), MethodTargetPostfix]
private void Postfix_ExecuteAction(ItemActionDynamic __instance, (bool executed, string originalSound) __state)
{
if (__state.executed)
{
__instance.soundStart = __state.originalSound;
}
}
[HarmonyPatch(nameof(ItemAction.OnHoldingUpdate)), MethodTargetPrefix]
private bool Prefix_OnHoldingUpdate(ItemActionDynamic __instance, ItemActionData _actionData, out (bool executed, bool useGrazeCast) __state)
{
if (_actionData.invData.holdingEntity is EntityPlayerLocal player)
{
var targets = AnimationRiggingManager.GetRigTargetsFromPlayer(player);
if (targets && !targets.Destroyed && targets.ItemCurrent)
{
__state = (true, __instance.UseGrazingHits);
__instance.UseGrazingHits = false;
return true;
}
}
__state = (false, false);
return true;
}
[HarmonyPatch(nameof(ItemAction.OnHoldingUpdate)), MethodTargetPostfix]
private void Postfix_OnHoldingUpdate(ItemActionDynamic __instance, (bool executed, bool useGrazeCast) __state)
{
if (__state.executed)
{
__instance.UseGrazingHits = __state.useGrazeCast;
}
}
[HarmonyPatch(nameof(ItemAction.ReadFrom)), MethodTargetPostfix]
private void Postfix_ReadFrom(DynamicProperties _props)
{
_props.ParseString("DynamicSoundStart", ref dynamicSoundStart);
}
}

View File

@@ -0,0 +1,84 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.Utilities;
[TypeTarget(typeof(ItemActionAttack)), ActionDataTarget(typeof(DynamicMuzzleFlashData))]
public class ActionModuleDynamicMuzzleFlash
{
private struct State
{
public bool executed;
public string particlesMuzzleFire;
public string particlesMuzzleSmoke;
public string particlesMuzzleFireFpv;
public string particlesMuzzleSmokeFpv;
}
[HarmonyPatch(nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
private void Postfix_OnModificationsChanged(ItemActionAttack __instance, ItemActionAttackData _data, DynamicMuzzleFlashData __customData)
{
__customData.particlesMuzzleFire = _data.invData.itemValue.GetPropertyOverrideForAction("Particles_muzzle_fire", __instance.particlesMuzzleFire, __instance.ActionIndex);
__customData.particlesMuzzleFireFpv = _data.invData.itemValue.GetPropertyOverrideForAction("Particles_muzzle_fire_fpv", __instance.particlesMuzzleFireFpv, __instance.ActionIndex);
__customData.particlesMuzzleSmoke = _data.invData.itemValue.GetPropertyOverrideForAction("Particles_muzzle_smoke", __instance.particlesMuzzleSmoke, __instance.ActionIndex);
__customData.particlesMuzzleSmokeFpv = _data.invData.itemValue.GetPropertyOverrideForAction("Particles_muzzle_smoke_fpv", __instance.particlesMuzzleSmokeFpv, __instance.ActionIndex);
if (!string.IsNullOrEmpty(__customData.particlesMuzzleFire) && !ParticleEffect.IsAvailable(__customData.particlesMuzzleFire))
{
ParticleEffect.LoadAsset(__customData.particlesMuzzleFire);
}
if (!string.IsNullOrEmpty(__customData.particlesMuzzleFireFpv) && !ParticleEffect.IsAvailable(__customData.particlesMuzzleFireFpv))
{
ParticleEffect.LoadAsset(__customData.particlesMuzzleFireFpv);
}
if (!string.IsNullOrEmpty(__customData.particlesMuzzleSmoke) && !ParticleEffect.IsAvailable(__customData.particlesMuzzleSmoke))
{
ParticleEffect.LoadAsset(__customData.particlesMuzzleSmoke);
}
if (!string.IsNullOrEmpty(__customData.particlesMuzzleSmokeFpv) && !ParticleEffect.IsAvailable(__customData.particlesMuzzleSmokeFpv))
{
ParticleEffect.LoadAsset(__customData.particlesMuzzleSmokeFpv);
}
}
[HarmonyPatch(nameof(ItemAction.ItemActionEffects)), MethodTargetPrefix]
private bool Prefix_ItemActionEffects(ItemActionAttack __instance, DynamicMuzzleFlashData __customData, out State __state)
{
__state = new State()
{
executed = true,
particlesMuzzleFire = __instance.particlesMuzzleFire,
particlesMuzzleFireFpv = __instance.particlesMuzzleFireFpv,
particlesMuzzleSmoke = __instance.particlesMuzzleSmoke,
particlesMuzzleSmokeFpv = __instance.particlesMuzzleSmokeFpv
};
__instance.particlesMuzzleFire = __customData.particlesMuzzleFire;
__instance.particlesMuzzleFireFpv = __customData .particlesMuzzleFireFpv;
__instance.particlesMuzzleSmoke = __customData.particlesMuzzleSmoke;
__instance.particlesMuzzleSmokeFpv = __customData.particlesMuzzleSmokeFpv;
return true;
}
[HarmonyPatch(nameof(ItemAction.ItemActionEffects)), MethodTargetPostfix]
private void Postfix_ItemActionEffects(ItemActionAttack __instance, State __state)
{
if (__state.executed)
{
__instance.particlesMuzzleFire = __state.particlesMuzzleFire;
__instance.particlesMuzzleFireFpv = __state.particlesMuzzleFireFpv;
__instance.particlesMuzzleSmoke = __state.particlesMuzzleSmoke;
__instance.particlesMuzzleSmokeFpv = __state.particlesMuzzleSmokeFpv;
}
}
public class DynamicMuzzleFlashData
{
public string particlesMuzzleFire;
public string particlesMuzzleFireFpv;
public string particlesMuzzleSmoke;
public string particlesMuzzleSmokeFpv;
public DynamicMuzzleFlashData(ItemInventoryData _invData, int _indexOfAction, ActionModuleDynamicMuzzleFlash _module)
{
}
}
}

View File

@@ -0,0 +1,89 @@
using HarmonyLib;
using KFCommonUtilityLib;
using KFCommonUtilityLib.Scripts.Attributes;
using UnityEngine;
[TypeTarget(typeof(ItemActionZoom)), ActionDataTarget(typeof(DynamicSensitivityData))]
public class ActionModuleDynamicSensitivity
{
[HarmonyPatch(nameof(ItemAction.AimingSet)), MethodTargetPostfix]
private void Postfix_AimingSet(ItemActionData _actionData, bool _isAiming, bool _wasAiming, DynamicSensitivityData __customData)
{
float originalSensitivity = GamePrefs.GetFloat(EnumGamePrefs.OptionsZoomSensitivity);
if (_isAiming)
{
PlayerMoveController.Instance.mouseZoomSensitivity = originalSensitivity / Mathf.Sqrt(__customData.ZoomRatio);
}
else
{
PlayerMoveController.Instance.mouseZoomSensitivity = originalSensitivity;
}
}
[HarmonyPatch(nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
private void Postfix_OnModificationsChanged(ItemActionZoom __instance, ItemActionData _data, DynamicSensitivityData __customData)
{
if (_data is IModuleContainerFor<ActionModuleVariableZoom.VariableZoomData> variableZoomData)
{
__customData.variableZoomData = variableZoomData.Instance;
}
else
{
string str = __instance.Properties.GetString("ZoomRatio");
if (string.IsNullOrEmpty(str))
{
str = "1";
}
__customData.ZoomRatio = StringParsers.ParseFloat(_data.invData.itemValue.GetPropertyOverride("ZoomRatio", str));
}
__customData.dsRangeOverride = StringParsers.ParseVector2(_data.invData.itemValue.GetPropertyOverride("DynamicSensitivityRange", "0,0"));
}
[HarmonyPatch(nameof(ItemAction.OnHoldingUpdate)), MethodTargetPostfix]
private void Postfix_OnHoldingUpdate(ItemActionData _actionData, DynamicSensitivityData __customData)
{
if (((ItemActionZoom.ItemActionDataZoom)_actionData).aimingValue)
{
float originalSensitivity = GamePrefs.GetFloat(EnumGamePrefs.OptionsZoomSensitivity);
if (__customData.activated)
{
PlayerMoveController.Instance.mouseZoomSensitivity = originalSensitivity / Mathf.Sqrt(__customData.ZoomRatio);
}
else
{
PlayerMoveController.Instance.mouseZoomSensitivity = originalSensitivity;
}
}
}
public class DynamicSensitivityData
{
public ActionModuleVariableZoom.VariableZoomData variableZoomData = null;
private float zoomRatio = 1.0f;
public Vector2 dsRangeOverride = Vector2.zero;
public bool activated = false;
public float ZoomRatio
{
get
{
if (variableZoomData != null)
{
if (dsRangeOverride.x > 0 && dsRangeOverride.y >= dsRangeOverride.x)
{
return Mathf.Lerp(dsRangeOverride.x, dsRangeOverride.y, Mathf.InverseLerp(variableZoomData.minScale, variableZoomData.maxScale, variableZoomData.curScale));
}
return variableZoomData.curScale;
}
return zoomRatio;
}
set => zoomRatio = value;
}
public DynamicSensitivityData(ItemInventoryData _invData, int _indexInEntityOfAction, ActionModuleDynamicSensitivity _module)
{
}
}
}

View File

@@ -0,0 +1,153 @@
using HarmonyLib;
using KFCommonUtilityLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.StaticManagers;
using System.Collections.Generic;
using System.Reflection.Emit;
using UniLinq;
using UnityEngine;
[TypeTarget(typeof(ItemActionZoom)), ActionDataTarget(typeof(ErgoData))]
public class ActionModuleErgoAffected
{
public static readonly int AimSpeedModifierHash = Animator.StringToHash("AimSpeedModifier");
public float zoomInTimeBase;
public float aimSpeedModifierBase;
[HarmonyPatch(nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
private void Postfix_OnModificationChanged(ItemActionData _data, ItemActionZoom __instance, ErgoData __customData)
{
zoomInTimeBase = 0.3f;
__instance.Properties.ParseFloat("ZoomInTimeBase", ref zoomInTimeBase);
aimSpeedModifierBase = 1f;
__instance.Properties.ParseFloat("AimSpeedModifierBase", ref aimSpeedModifierBase);
__customData.aimStartTime = float.MaxValue;
__customData.aimSet = false;
}
[HarmonyPatch(nameof(ItemAction.ExecuteAction)), MethodTargetPostfix]
private void Postfix_ExecuteAction(ItemActionData _actionData, ItemActionZoom __instance, bool _bReleased, ErgoData __customData)
{
EntityAlive holdingEntity = _actionData.invData.holdingEntity;
ItemActionData prevActionData = holdingEntity.MinEventContext.ItemActionData;
holdingEntity.MinEventContext.ItemActionData = _actionData.invData.actionData[MultiActionManager.GetActionIndexForEntity(holdingEntity)];
float ergoValue = EffectManager.GetValue(CustomEnums.WeaponErgonomics, _actionData.invData.itemValue, 0, holdingEntity);
float aimSpeedModifier = Mathf.Lerp(0.2f, 1, ergoValue);
Log.Out($"Ergo is {ergoValue}, base aim modifier is {aimSpeedModifierBase}, aim speed is {aimSpeedModifier * aimSpeedModifierBase}");
holdingEntity.emodel.avatarController.UpdateFloat(AimSpeedModifierHash, aimSpeedModifier * aimSpeedModifierBase, true);
holdingEntity.MinEventContext.ItemActionData = prevActionData;
if ((_actionData as ItemActionZoom.ItemActionDataZoom).aimingValue && !_bReleased)
{
__customData.aimStartTime = Time.time;
}
else if (!(_actionData as ItemActionZoom.ItemActionDataZoom).aimingValue)
{
__customData.aimStartTime = float.MaxValue;
}
__customData.aimSet = false;
}
//[MethodTargetPostfix(nameof(ItemAction.OnHoldingUpdate))]
//private void Postfix_OnHoldingUpdate(ItemActionData _actionData, ErgoData __customData)
//{
// if ((_actionData as ItemActionZoom.ItemActionDataZoom).aimingValue && Time.time - __customData.aimStartTime > zoomInTimeBase)
// {
// __customData.aimSet = true;
// }
// else
// {
// __customData.aimSet = false;
// }
//}
public class ErgoData
{
public float aimStartTime;
public bool aimSet;
public ActionModuleErgoAffected module;
public ErgoData(ItemInventoryData _invData, int _indexInEntityOfAction, ActionModuleErgoAffected _module)
{
aimStartTime = float.MaxValue;
aimSet = false;
module = _module;
}
}
}
[HarmonyPatch]
public static class ErgoPatches
{
[HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.updateAccuracy))]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> Transpiler_ItemActionRanged_updateAccuracy(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var codes = instructions.ToList();
var mtd_lerp = AccessTools.Method(typeof(Mathf), nameof(Mathf.Lerp), new[] { typeof(float), typeof(float), typeof(float) });
for (int i = 0; i < codes.Count; i++)
{
if (codes[i].Calls(mtd_lerp))
{
codes.InsertRange(i, new[]
{
new CodeInstruction(OpCodes.Ldarg_0),
new CodeInstruction(OpCodes.Ldarg_1),
new CodeInstruction(OpCodes.Ldarg_2),
CodeInstruction.Call(typeof(ErgoPatches), nameof(CalcErgoModifier)),
});
break;
}
}
return codes;
}
private static float CalcErgoModifier(float originalValue, ItemAction action, ItemActionData actionData, bool aiming)
{
ItemActionRanged.ItemActionDataRanged rangedData = actionData as ItemActionRanged.ItemActionDataRanged;
if (aiming && rangedData.invData.actionData[1] is IModuleContainerFor<ActionModuleErgoAffected.ErgoData> dataModule && !dataModule.Instance.aimSet && Time.time - dataModule.Instance.aimStartTime > 0)
{
ActionModuleErgoAffected.ErgoData ergoData = dataModule.Instance;
float baseAimTime = ergoData.module.zoomInTimeBase;
float baseAimMultiplier = ergoData.module.aimSpeedModifierBase;
baseAimTime /= baseAimMultiplier;
float modifiedErgo = EffectManager.GetValue(CustomEnums.WeaponErgonomics, rangedData.invData.itemValue, 1f, rangedData.invData.holdingEntity);
modifiedErgo = Mathf.Lerp(0.2f, 1, modifiedErgo);
float perc = (Time.time - ergoData.aimStartTime) * modifiedErgo / baseAimTime;
if (perc >= 1)
{
ergoData.aimSet = true;
perc = 1;
}
//Log.Out($"Time passed {Time.time - dataModule.Instance.aimStartTime} base time {baseAimTime} perc {perc}");
return perc;
}
return originalValue;
}
[HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.onHoldingEntityFired))]
[HarmonyPrefix]
private static bool Prefix_onHoldingEntityFired_ItemActionRanged(ItemActionData _actionData, out float __state)
{
__state = (_actionData as ItemActionRanged.ItemActionDataRanged).lastAccuracy;
return true;
}
[HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.onHoldingEntityFired))]
[HarmonyPostfix]
private static void Postfix_onHoldingEntityFired_ItemActionRanged(ItemActionData _actionData, float __state)
{
ItemActionRanged.ItemActionDataRanged rangedData = _actionData as ItemActionRanged.ItemActionDataRanged;
if (rangedData.invData.holdingEntity.AimingGun && rangedData.invData.actionData[1] is IModuleContainerFor<ActionModuleErgoAffected.ErgoData> dataModule)
{
float aimMultiplier = EffectManager.GetValue(PassiveEffects.SpreadMultiplierAiming, rangedData.invData.itemValue, .1f, rangedData.invData.holdingEntity);
rangedData.lastAccuracy = Mathf.Lerp(__state, rangedData.lastAccuracy, aimMultiplier);
ActionModuleErgoAffected.ErgoData ergoData = dataModule.Instance;
if (Time.time > ergoData.aimStartTime)
{
ergoData.aimSet = false;
ergoData.aimStartTime = Time.time;
}
}
}
}

View File

@@ -0,0 +1,335 @@
using HarmonyLib;
using KFCommonUtilityLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.StaticManagers;
using KFCommonUtilityLib.Scripts.Utilities;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[TypeTarget(typeof(ItemActionRanged)), ActionDataTarget(typeof(FireModeData))]
public class ActionModuleFireModeSelector
{
public struct FireMode
{
public byte burstCount;
public bool isFullAuto;
}
public string fireModeSwitchingSound = null;
private List<FireMode> modeCache = new List<FireMode>();
private List<string> nameCache = new List<string>();
public static string[] FireModeNames = new[]
{
"FireMode",
"FireMode1",
"FireMode2",
"FireMode3",
"FireMode4",
};
public static int[] FireModeParamHashes = new[]
{
Animator.StringToHash("FireMode"),
Animator.StringToHash("FireMode1"),
Animator.StringToHash("FireMode2"),
Animator.StringToHash("FireMode3"),
Animator.StringToHash("FireMode4"),
};
public static int[] FireModeSwitchParamHashes = new[]
{
Animator.StringToHash("FireModeChanged"),
Animator.StringToHash("FireModeChanged1"),
Animator.StringToHash("FireModeChanged2"),
Animator.StringToHash("FireModeChanged3"),
Animator.StringToHash("FireModeChanged4"),
};
[HarmonyPatch(nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
private void Postfix_OnModificationChanged(ItemActionData _data, FireModeData __customData, ItemActionRanged __instance)
{
__instance.Properties.ParseString("FireModeSwitchingSound", ref fireModeSwitchingSound);
int actionIndex = _data.indexInEntityOfAction;
for (int i = 0; i < 99; i++)
{
if (!__instance.Properties.Contains($"FireMode{i}.BurstCount"))
{
break;
}
string burstCount = 1.ToString();
__instance.Properties.ParseString($"FireMode{i}.BurstCount", ref burstCount);
string isFullAuto = false.ToString();
__instance.Properties.ParseString($"FireMode{i}.IsFullAuto", ref isFullAuto);
string modeName = null;
__instance.Properties.ParseString($"FireMode{i}.ModeName", ref modeName);
modeCache.Add(new FireMode
{
burstCount = byte.Parse(_data.invData.itemValue.GetPropertyOverrideForAction($"FireMode{i}.BurstCount", burstCount, actionIndex)),
isFullAuto = bool.Parse(_data.invData.itemValue.GetPropertyOverrideForAction($"FireMode{i}.IsFullAuto", isFullAuto, actionIndex))
});
nameCache.Add(_data.invData.itemValue.GetPropertyOverrideForAction($"FireMode{i}.ModeName", modeName, actionIndex));
}
for (int i = 0; i < 99; i++)
{
string burstCount = _data.invData.itemValue.GetPropertyOverrideForAction($"FireModePlus{i}.BurstCount", null, actionIndex);
if (burstCount == null)
{
break;
}
modeCache.Add(new FireMode
{
burstCount = byte.Parse(_data.invData.itemValue.GetPropertyOverrideForAction($"FireModePlus{i}.BurstCount", burstCount, actionIndex)),
isFullAuto = bool.Parse(_data.invData.itemValue.GetPropertyOverrideForAction($"FireModePlus{i}.IsFullAuto", "false", actionIndex))
});
nameCache.Add(_data.invData.itemValue.GetPropertyOverrideForAction($"FireModePlus{i}.ModeName", null, actionIndex));
}
__customData.fireModes = modeCache.ToArray();
modeCache.Clear();
__customData.modeNames = nameCache.ToArray();
nameCache.Clear();
if (_data.invData.itemValue.GetMetadata(FireModeNames[actionIndex]) is int mode)
{
__customData.currentFireMode = (byte)mode;
}
if (__customData.currentFireMode < 0 || __customData.currentFireMode >= __customData.fireModes.Length)
{
__customData.currentFireMode = 0;
}
if (__customData.delayFiringCo != null)
{
ThreadManager.StopCoroutine(__customData.delayFiringCo);
__customData.delayFiringCo = null;
}
__customData.isRequestedByCoroutine = false;
}
[HarmonyPatch(nameof(ItemAction.StartHolding)), MethodTargetPostfix]
private void Postfix_StartHolding(ItemActionData _data, FireModeData __customData)
{
__customData.SetFireMode(_data, __customData.currentFireMode);
}
[HarmonyPatch(nameof(ItemAction.OnHoldingUpdate)), MethodTargetPostfix]
private static void Postfix_OnHoldingUpdate(ItemActionData _actionData, FireModeData __customData)
{
__customData.UpdateDelay(_actionData);
__customData.inputReleased = true;
}
[HarmonyPatch(nameof(ItemAction.StopHolding)), MethodTargetPostfix]
private static void Postfix_StopHolding(FireModeData __customData)
{
if (__customData.delayFiringCo != null)
{
ThreadManager.StopCoroutine(__customData.delayFiringCo);
__customData.delayFiringCo = null;
}
__customData.isRequestedByCoroutine = false;
__customData.inputReleased = true;
}
[HarmonyPatch(nameof(ItemAction.ExecuteAction)), MethodTargetPrefix]
private bool Prefix_ExecuteAction(ItemActionData _actionData, ItemActionRanged __instance, FireModeData __customData, bool _bReleased)
{
if (__customData.isRequestedByCoroutine)
{
return true;
}
__customData.inputReleased = _bReleased;
if (__customData.delayFiringCo == null)
{
if (_bReleased || _actionData.invData.itemValue.Meta == 0 || _actionData.invData.itemValue.PercentUsesLeft <= 0)
{
return true;
}
FireMode curFireMode = __customData.fireModes[__customData.currentFireMode];
if (curFireMode.burstCount == 1)
{
return true;
}
var rangedData = _actionData as ItemActionRanged.ItemActionDataRanged;
if (__instance.GetBurstCount(_actionData) > rangedData.curBurstCount)
{
__customData.StartFiring(__instance, _actionData);
}
}
return false;
}
[HarmonyPatch(nameof(ItemActionRanged.GetBurstCount)), MethodTargetPostfix]
private void Postfix_GetBurstCount(FireModeData __customData, ref int __result)
{
FireMode fireMode = __customData.fireModes[__customData.currentFireMode];
__result = fireMode.isFullAuto ? 999 : fireMode.burstCount;
}
[HarmonyPatch(nameof(ItemAction.IsActionRunning)), MethodTargetPostfix]
private void Postfix_IsActionRunning(FireModeData __customData, ref bool __result)
{
__result |= __customData.delayFiringCo != null;
}
public class FireModeData
{
public string switchSound;
public FireMode[] fireModes;
public string[] modeNames;
public byte currentFireMode;
public Coroutine delayFiringCo;
public bool isRequestedByCoroutine;
public float shotDelay;
public float burstDelay;
public bool inputReleased;
public FireModeData(ItemInventoryData invData, int actionIndex, ActionModuleFireModeSelector module)
{
}
public void CycleFireMode(ItemActionData _data)
{
SetFireMode(_data, (byte)((currentFireMode + 1) % fireModes.Length));
}
public void SetFireMode(ItemActionData _data, byte _fireMode)
{
if (currentFireMode != _fireMode)
{
currentFireMode = _fireMode;
FireMode curFireMode = fireModes[currentFireMode];
if (!string.IsNullOrEmpty(switchSound))
{
_data.invData.holdingEntity.PlayOneShot(switchSound);
}
_data.invData.holdingEntity.emodel.avatarController.TriggerEvent(FireModeSwitchParamHashes[_data.indexInEntityOfAction]);
}
if (!string.IsNullOrEmpty(modeNames[_fireMode]))
{
GameManager.ShowTooltip(_data.invData.holdingEntity as EntityPlayerLocal, modeNames[_fireMode], true);
}
else
{
GameManager.ShowTooltip(_data.invData.holdingEntity as EntityPlayerLocal, "ttCurrentFiringMode", _fireMode.ToString(), null, null, true);
}
//GameManager.ShowTooltip(_data.invData.holdingEntity as EntityPlayerLocal, "ttCurrentFiringMode", string.IsNullOrEmpty(modeNames[_fireMode]) ? _fireMode.ToString() : Localization.Get(modeNames[_fireMode]), null, null, true);
_data.invData.holdingEntity.FireEvent(CustomEnums.onSelfBurstModeChanged);
UpdateDelay(_data);
ItemValue itemValue = _data.invData.itemValue;
if (itemValue != null)
{
if (itemValue.Metadata == null)
{
itemValue.Metadata = new Dictionary<string, TypedMetadataValue>();
}
if (!itemValue.Metadata.TryGetValue(ActionModuleFireModeSelector.FireModeNames[_data.indexInEntityOfAction], out var metadata) || !metadata.SetValue((int)_fireMode))
{
itemValue.Metadata[ActionModuleFireModeSelector.FireModeNames[_data.indexInEntityOfAction]] = new TypedMetadataValue((int)_fireMode, TypedMetadataValue.TypeTag.Integer);
}
}
}
public void UpdateDelay(ItemActionData _data)
{
FireMode curFireMode = fireModes[currentFireMode];
if (curFireMode.burstCount == 1)
{
return;
}
float burstInterval = EffectManager.GetValue(CustomEnums.BurstShotInterval, _data.invData.itemValue, -1, _data.invData.holdingEntity);
var rangedData = _data as ItemActionRanged.ItemActionDataRanged;
if (burstInterval > 0 && rangedData.Delay > burstInterval)
{
shotDelay = burstInterval;
burstDelay = (rangedData.Delay - burstInterval) * curFireMode.burstCount;
}
else
{
shotDelay = rangedData.Delay;
burstDelay = 0;
}
}
public void StartFiring(ItemActionRanged _instance, ItemActionData _data)
{
UpdateDelay(_data);
if (delayFiringCo != null)
{
ThreadManager.StopCoroutine(delayFiringCo);
}
((ItemActionRanged.ItemActionDataRanged)_data).bPressed = true;
((ItemActionRanged.ItemActionDataRanged)_data).bReleased = false;
delayFiringCo = ThreadManager.StartCoroutine(DelayFiring(_instance, _data));
}
private IEnumerator DelayFiring(ItemActionRanged _instance, ItemActionData _data)
{
FireMode curFireMode = fireModes[currentFireMode];
var rangedData = _data as ItemActionRanged.ItemActionDataRanged;
byte curBurstCount = rangedData.curBurstCount;
for (int i = 0; i < curFireMode.burstCount; i++)
{
isRequestedByCoroutine = true;
rangedData.bPressed = true;
rangedData.bReleased = false;
rangedData.m_LastShotTime = 0;
_instance.ExecuteAction(_data, false);
rangedData.curBurstCount = (byte)(curBurstCount + i + 1);
isRequestedByCoroutine = false;
if (rangedData.invData.itemValue.Meta <= 0 && !_instance.HasInfiniteAmmo(_data))
{
goto cleanup;
}
yield return new WaitForSeconds(shotDelay);
}
yield return new WaitForSeconds(burstDelay);
cleanup:
delayFiringCo = null;
if (inputReleased)
{
_instance.ExecuteAction(_data, true);
}
}
}
}
[HarmonyPatch]
public static class FireModePatches
{
[HarmonyPatch(typeof(PlayerMoveController), nameof(PlayerMoveController.Update))]
[HarmonyPrefix]
private static bool Prefix_Update_PlayerMoveController(PlayerMoveController __instance)
{
if (DroneManager.Debug_LocalControl || !__instance.gameManager.gameStateManager.IsGameStarted() || GameStats.GetInt(EnumGameStats.GameState) != 1)
return true;
bool isUIOpen = __instance.windowManager.IsCursorWindowOpen() || __instance.windowManager.IsInputActive() || __instance.windowManager.IsModalWindowOpen();
UpdateLocalInput(__instance.entityPlayerLocal, __instance.playerInput, isUIOpen, Time.deltaTime);
return true;
}
private static void UpdateLocalInput(EntityPlayerLocal _player, PlayerActionsLocal _input, bool _isUIOpen, float _deltaTime)
{
if (_isUIOpen || _player.emodel.IsRagdollActive || _player.IsDead() || _player.AttachedToEntity != null)
{
return;
}
if (PlayerActionToggleFireMode.Instance.Enabled && PlayerActionToggleFireMode.Instance.Toggle.WasPressed)
{
if (_player.inventory.IsHoldingItemActionRunning())
{
return;
}
var actionData = _player.inventory.holdingItemData.actionData[MultiActionManager.GetActionIndexForEntity(_player)];
if (actionData is IModuleContainerFor<ActionModuleFireModeSelector.FireModeData> fireModeData)
{
fireModeData.Instance.CycleFireMode(actionData);
}
}
}
}

View File

@@ -0,0 +1,85 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using System.Collections;
using UnityEngine;
[TypeTarget(typeof(ItemActionRanged))]
public class ActionModuleHoldOpen
{
private const string emptyAnimatorBool = "empty";
private int emptyAnimatorBoolHash;
[HarmonyPatch(nameof(ItemAction.ReadFrom)), MethodTargetPostfix]
private void Postfix_ReadFrom(DynamicProperties _props, ItemActionRanged __instance)
{
int metaIndex = __instance.ActionIndex;
if (_props.Values.TryGetValue("ShareMetaWith", out string str) && int.TryParse(str, out metaIndex))
{
}
if (metaIndex > 0)
{
emptyAnimatorBoolHash = Animator.StringToHash(emptyAnimatorBool + __instance.ActionIndex);
}
else
{
emptyAnimatorBoolHash = Animator.StringToHash(emptyAnimatorBool);
}
}
[HarmonyPatch(nameof(ItemActionRanged.getUserData)), MethodTargetPostfix]
public void Postfix_getUserData(ItemActionData _actionData, ref int __result)
{
__result |= (_actionData.invData.itemValue.Meta <= 0 ? 1 : 0);
}
[HarmonyPatch(nameof(ItemAction.ItemActionEffects)), MethodTargetPostfix]
public void Postfix_ItemActionEffects(ItemActionData _actionData, int _firingState, int _userData)
{
if (_firingState != (int)ItemActionFiringState.Off && (_userData & 1) > 0)
_actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(emptyAnimatorBoolHash, true, false);
}
[HarmonyPatch(nameof(ItemActionRanged.ReloadGun)), MethodTargetPostfix]
public void Postfix_ReloadGun(ItemActionData _actionData)
{
//delay 2 frames before reloading, since the animation is likely to be triggered the next frame this is called
ThreadManager.StartCoroutine(DelaySetEmpty(_actionData, false, 2));
}
[HarmonyPatch(nameof(ItemAction.StartHolding)), MethodTargetPrefix]
public bool Prefix_StartHolding(ItemActionData _data)
{
//delay 1 frame before equipping weapon
if (_data.invData.itemValue.Meta <= 0)
ThreadManager.StartCoroutine(DelaySetEmpty(_data, true, 2));
return true;
}
[HarmonyPatch(nameof(ItemActionRanged.ConsumeAmmo)), MethodTargetPostfix]
public void Postfix_ConsumeAmmo(ItemActionData _actionData)
{
if (_actionData.invData.itemValue.Meta == 0)
_actionData.invData.holdingEntity.FireEvent(CustomEnums.onSelfMagzineDeplete, true);
}
[HarmonyPatch(nameof(ItemAction.SwapAmmoType)), MethodTargetPrefix]
public bool Prefix_SwapAmmoType(EntityAlive _entity)
{
_entity.emodel.avatarController.UpdateBool(emptyAnimatorBoolHash, true, false);
return true;
}
private IEnumerator DelaySetEmpty(ItemActionData _actionData, bool empty, int delay)
{
for (int i = 0; i < delay; i++)
{
yield return null;
}
if (_actionData.invData.holdingEntity.inventory.holdingItemIdx == _actionData.invData.slotIdx)
{
_actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(emptyAnimatorBoolHash, empty, false);
}
yield break;
}
}

View File

@@ -0,0 +1,24 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
[TypeTarget(typeof(ItemAction))]
public class ActionModuleInspectable
{
public bool allowEmptyInspect;
[HarmonyPatch(nameof(ItemAction.ReadFrom)), MethodTargetPostfix]
private void Postfix_ReadFrom(DynamicProperties _props)
{
allowEmptyInspect = _props.GetBool("allowEmptyInspect");
}
[HarmonyPatch(typeof(ItemActionDynamic), nameof(ItemAction.CancelAction)), MethodTargetPostfix]
private void Postfix_CancelAction_ItemActionDynamic(ItemActionDynamic.ItemActionDynamicData _actionData)
{
var entity = _actionData.invData.holdingEntity;
if (!entity.MovementRunning && _actionData != null && !entity.inventory.holdingItem.IsActionRunning(entity.inventory.holdingItemData))
{
entity.emodel.avatarController._setTrigger("weaponInspect", false);
}
}
}

View File

@@ -0,0 +1,225 @@
using HarmonyLib;
using KFCommonUtilityLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.StaticManagers;
using UnityEngine;
[TypeTarget(typeof(ItemActionRanged)), ActionDataTarget(typeof(InterruptData))]
public class ActionModuleInterruptReload
{
public float holdBeforeCancel = 0.06f;
public string firingStateName = "";
public bool instantFiringCancel = false;
[HarmonyPatch(nameof(ItemAction.StartHolding)), MethodTargetPrefix]
private bool Prefix_StartHolding(InterruptData __customData)
{
__customData.Reset();
return true;
}
[HarmonyPatch(nameof(ItemAction.ReadFrom)), MethodTargetPostfix]
private void Postfix_ReadFrom(DynamicProperties _props)
{
firingStateName = _props.GetString("FiringStateFullName");
instantFiringCancel = _props.GetBool("InstantFiringCancel");
}
[HarmonyPatch(nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
private void Postfix_OnModificationsChanged(ItemActionData _data, InterruptData __customData)
{
var invData = _data.invData;
__customData.itemAnimator = AnimationGraphBuilder.DummyWrapper;
__customData.eventBridge = null;
if (invData.model && invData.model.TryGetComponent<AnimationTargetsAbs>(out var targets) && !targets.Destroyed && targets.IsAnimationSet)
{
__customData.itemAnimator = targets.GraphBuilder.WeaponWrapper;
if (__customData.itemAnimator.IsValid)
{
__customData.eventBridge = targets.ItemAnimator.GetComponent<AnimationReloadEvents>();
}
}
}
private struct State
{
public bool executed;
public bool isReloading;
public bool isWeaponReloading;
public float lastShotTime;
}
[HarmonyPatch(nameof(ItemAction.IsActionRunning)), MethodTargetPostfix]
private void Postfix_IsActionRunning(ref bool __result, InterruptData __customData)
{
__result &= !__customData.instantFiringRequested;
}
[HarmonyPatch(nameof(ItemAction.ExecuteAction)), MethodTargetPrefix]
private bool Prefix_ExecuteAction(ItemActionData _actionData, bool _bReleased, InterruptData __customData, out State __state)
{
__state = default;
if (!_bReleased && __customData.isInterruptRequested && __customData.instantFiringRequested)
{
if (_actionData.invData.itemValue.Meta > 0)
{
if (ConsoleCmdReloadLog.LogInfo)
Log.Out($"instant firing cancel prefix!");
ItemActionRanged.ItemActionDataRanged rangedData = _actionData as ItemActionRanged.ItemActionDataRanged;
__state.executed = true;
__state.isReloading = rangedData.isReloading;
__state.isWeaponReloading = rangedData.isWeaponReloading;
__state.lastShotTime = rangedData.m_LastShotTime;
rangedData.isReloading = false;
rangedData.isWeaponReloading = false;
}
else
{
if (ConsoleCmdReloadLog.LogInfo)
Log.Out($"not fired! meta is 0");
__customData.isInterruptRequested = false;
__customData.instantFiringRequested = false;
return false;
}
}
return true;
}
[HarmonyPatch(nameof(ItemAction.ExecuteAction)), MethodTargetPostfix]
private void Postfix_ExecuteAction(ItemActionData _actionData, InterruptData __customData, State __state)
{
if (__state.executed)
{
if (ConsoleCmdReloadLog.LogInfo)
Log.Out($"instant firing cancel postfix!");
ItemActionRanged.ItemActionDataRanged rangedData = _actionData as ItemActionRanged.ItemActionDataRanged;
rangedData.isReloading = __state.isReloading;
rangedData.isWeaponReloading = __state.isWeaponReloading;
if (__customData.itemAnimator.IsValid && __customData.eventBridge)
{
if (rangedData.m_LastShotTime > __state.lastShotTime && rangedData.m_LastShotTime < Time.time + 1f)
{
if (ConsoleCmdReloadLog.LogInfo)
Log.Out($"executed!");
__customData.eventBridge.OnReloadEnd();
__customData.itemAnimator.Play(firingStateName, -1, 0f);
}
else
{
if (ConsoleCmdReloadLog.LogInfo)
Log.Out($"not fired! last shot time {__state.lastShotTime} ranged data shot time {rangedData.m_LastShotTime} cur time {Time.time}");
__customData.isInterruptRequested = false;
__customData.instantFiringRequested = false;
}
}
}
}
[HarmonyPatch(nameof(ItemAction.ItemActionEffects)), MethodTargetPrefix]
private bool Prefix_ItemActionEffects(ItemActionData _actionData, int _firingState, InterruptData __customData)
{
var rangedData = _actionData as ItemActionRanged.ItemActionDataRanged;
if (_firingState != 0 && (rangedData.isReloading || rangedData.isWeaponReloading) && !(rangedData.invData.holdingEntity is EntityPlayerLocal) && __customData.eventBridge)
{
__customData.eventBridge.OnReloadEnd();
__customData.itemAnimator.Play(firingStateName, -1, 0f);
}
return true;
}
public bool IsRequestPossible(InterruptData interruptData)
{
return interruptData.eventBridge && interruptData.itemAnimator.IsValid;
}
public class InterruptData
{
public bool isInterruptRequested;
public float holdStartTime = -1f;
public bool instantFiringRequested = false;
public AnimationReloadEvents eventBridge;
public IAnimatorWrapper itemAnimator;
public InterruptData(ItemInventoryData invData, int actionIndex, ActionModuleInterruptReload module)
{
//if (invData.model && invData.model.TryGetComponent<AnimationTargetsAbs>(out var targets) && !targets.Destroyed)
//{
// itemAnimator = targets.ItemAnimator;
// if (itemAnimator)
// {
// eventBridge = itemAnimator.GetComponent<AnimationReloadEvents>();
// }
//}
}
public void Reset()
{
isInterruptRequested = false;
holdStartTime = -1f;
instantFiringRequested = false;
}
}
}
[HarmonyPatch]
internal static class ReloadInterruptionPatches
{
//interrupt reload with firing
[HarmonyPatch(typeof(ItemClass), nameof(ItemClass.ExecuteAction))]
[HarmonyPrefix]
private static bool Prefix_ExecuteAction_ItemClass(ItemClass __instance, int _actionIdx, ItemInventoryData _data, bool _bReleased, PlayerActionsLocal _playerActions)
{
ItemAction curAction = __instance.Actions[_actionIdx];
if (curAction is ItemActionRanged || curAction is ItemActionZoom)
{
int curActionIndex = MultiActionManager.GetActionIndexForEntity(_data.holdingEntity);
var rangedAction = __instance.Actions[curActionIndex] as ItemActionRanged;
var rangedData = _data.actionData[curActionIndex] as ItemActionRanged.ItemActionDataRanged;
if (rangedData != null && rangedData is IModuleContainerFor<ActionModuleInterruptReload.InterruptData> dataModule && rangedAction is IModuleContainerFor<ActionModuleInterruptReload> actionModule)
{
if (!_bReleased && _playerActions != null && actionModule.Instance.IsRequestPossible(dataModule.Instance) && ((_playerActions.Primary.IsPressed && _actionIdx == curActionIndex && _data.itemValue.Meta > 0) || (_playerActions.Secondary.IsPressed && curAction is ItemActionZoom)) && (rangedData.isReloading || rangedData.isWeaponReloading) && !dataModule.Instance.isInterruptRequested)
{
if (dataModule.Instance.holdStartTime < 0)
{
dataModule.Instance.holdStartTime = Time.time;
return false;
}
if (Time.time - dataModule.Instance.holdStartTime >= actionModule.Instance.holdBeforeCancel)
{
if (!rangedAction.reloadCancelled(rangedData))
{
rangedAction.CancelReload(rangedData);
}
if (ConsoleCmdReloadLog.LogInfo)
Log.Out($"interrupt requested!");
dataModule.Instance.isInterruptRequested = true;
if (actionModule.Instance.instantFiringCancel && curAction is ItemActionRanged)
{
if (ConsoleCmdReloadLog.LogInfo)
Log.Out($"instant firing cancel!");
dataModule.Instance.instantFiringRequested = true;
return true;
}
}
return false;
}
if (_bReleased)
{
dataModule.Instance.Reset();
}
}
}
return true;
}
[HarmonyPatch(typeof(ItemAction), nameof(ItemAction.CancelReload))]
[HarmonyPrefix]
private static bool Prefix_CancelReload_ItemAction(ItemActionData _actionData)
{
if (_actionData?.invData?.holdingEntity is EntityPlayerLocal && AnimationRiggingManager.IsHoldingRiggedWeapon(_actionData.invData.holdingEntity as EntityPlayerLocal))
{
return false;
}
return true;
}
}

View File

@@ -0,0 +1,60 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.StaticManagers;
using System.Collections.Generic;
using System.Reflection.Emit;
using UniLinq;
[TypeTarget(typeof(ItemActionRanged))]
public class ActionModuleInvariableRPM
{
//added as a transpiler so that it's applied before all post processing
[HarmonyPatch(typeof(ItemActionRanged), nameof(ItemAction.OnHoldingUpdate)), MethodTargetTranspiler]
private static IEnumerable<CodeInstruction> Transpiler_OnHoldingUpdate_ItemActionRanged(IEnumerable<CodeInstruction> instructions)
{
var codes = instructions.ToList();
var mtd_getvalue = AccessTools.Method(typeof(EffectManager), nameof(EffectManager.GetValue));
for (int i = 0; i < codes.Count; i++)
{
if (codes[i].Calls(mtd_getvalue))
{
int start = -1;
for (int j = i; j >= 0; j--)
{
if (codes[j].opcode == OpCodes.Stloc_0)
{
start = j + 2;
break;
}
}
if (start >= 0)
{
codes.InsertRange(i + 2, new[]
{
new CodeInstruction(OpCodes.Ldarg_0),
new CodeInstruction(OpCodes.Ldloc_0),
CodeInstruction.Call(typeof(ActionModuleInvariableRPM), nameof(CalcFixedRPM))
});
codes.RemoveRange(start, i - start + 2);
//Log.Out("Invariable RPM Patch applied!");
}
break;
}
}
return codes;
}
private static float CalcFixedRPM(ItemActionRanged rangedAction, ItemActionRanged.ItemActionDataRanged rangedData)
{
float rpm = 60f / rangedData.OriginalDelay;
float perc = 1f;
var tags = rangedData.invData.item.ItemTags;
MultiActionManager.ModifyItemTags(rangedData.invData.itemValue, rangedData, ref tags);
rangedData.invData.item.Effects.ModifyValue(rangedData.invData.holdingEntity, PassiveEffects.RoundsPerMinute, ref rpm, ref perc, rangedData.invData.itemValue.Quality, tags);
//Log.Out($"fixed RPM {res}");
return 60f / (rpm * perc);
}
}

View File

@@ -0,0 +1,105 @@
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.StaticManagers;
using System;
using System.Collections;
using System.Collections.Generic;
[TypeTarget(typeof(ItemAction)), ActionDataTarget(typeof(LocalPassiveCacheData))]
public class ActionModuleLocalPassiveCache
{
//public int[] nameHashes;
//[MethodTargetPostfix(nameof(ItemAction.ReadFrom))]
//private void Postfix_ReadFrom(DynamicProperties _props)
//{
// string str = _props.Values["CachePassives"];
// if (!string.IsNullOrEmpty(str))
// {
// nameHashes = Array.ConvertAll(str.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries), s => s.GetHashCode());
// }
//}
//[MethodTargetPrefix(nameof(ItemAction.StartHolding))]
//private bool Prefix_StartHolding(ItemActionData _data, LocalPassiveCacheData __customData)
//{
// if (nameHashes != null)
// {
// for (int i = 0; i < nameHashes.Length; i++)
// {
// __customData.passives[i] = EffectManager.GetValue(nameHashes[i], _data.invData.itemValue, 0, _data.invData.holdingEntity);
// __customData.markedForCache[i] = false;
// }
// }
// return true;
//}
//[MethodTargetPrefix(nameof(ItemAction.OnHoldingUpdate))]
//private bool Prefix_OnHoldingUpdate(ItemActionData _actionData, LocalPassiveCacheData __customData)
//{
// if (!_actionData.invData.holdingEntity.isEntityRemote && nameHashes != null)
// {
// for (int i = 0; i < nameHashes.Length; i++)
// {
// if (__customData.markedForCache[i])
// {
// __customData.cache[i] = EffectManager.GetValue(nameHashes[i], _actionData.invData.itemValue, 0, _actionData.invData.holdingEntity);
// __customData.markedForCache[i] = false;
// }
// }
// }
// return true;
//}
public class LocalPassiveCacheData : IEnumerable<int>
{
//public float[] cache;
//public bool[] markedForCache;
//public ActionModuleLocalPassiveCache _cacheModule;
public ItemInventoryData invData;
private Dictionary<int, float> dict_hash_value = new Dictionary<int, float>();
private Dictionary<int, string> dict_hash_name = new Dictionary<int, string>();
public LocalPassiveCacheData(ItemInventoryData _invData, int _indexOfAction, ActionModuleLocalPassiveCache _cacheModule)
{
//this._cacheModule = _cacheModule;
this.invData = _invData;
//if (_cacheModule.nameHashes != null)
//{
// cache = new float[_cacheModule.nameHashes.Length];
// //markedForCache = new bool[_cacheModule.nameHashes.Length];
//}
}
public void CachePassive(PassiveEffects target, int targetHash, string targetStr, FastTags<TagGroup.Global> tags)
{
if (invData.holdingEntity.isEntityRemote)
return;
if (!dict_hash_name.ContainsKey(targetHash))
dict_hash_name[targetHash] = targetStr;
dict_hash_value[targetHash] = EffectManager.GetValue(target, invData.itemValue, 0, invData.holdingEntity, null, tags);
//markedForCache[index] = true;
}
public float GetCachedValue(int targetHash)
{
return dict_hash_value.TryGetValue(targetHash, out float res) ? res : 0;
}
public string GetCachedName(int targetHash)
{
return dict_hash_name.TryGetValue(targetHash, out string res) ? res : string.Empty;
}
public IEnumerator<int> GetEnumerator()
{
return dict_hash_value.Keys.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@@ -0,0 +1,178 @@
using HarmonyLib;
using KFCommonUtilityLib;
using KFCommonUtilityLib.Scripts.Attributes;
using System;
using System.Collections.Generic;
using System.Reflection.Emit;
using UniLinq;
[TypeTarget(typeof(ItemActionRanged))]
public class ActionModuleMetaConsumer
{
public string[] consumeDatas;
public FastTags<TagGroup.Global>[] consumeTags;
private float[] consumeStocks;
private float[] consumeValues;
private static FastTags<TagGroup.Global> TagsConsumption = FastTags<TagGroup.Global>.Parse("ConsumptionValue");
[HarmonyPatch(nameof(ItemAction.ReadFrom)), MethodTargetPostfix]
private void Postfix_ReadFrom(DynamicProperties _props, ItemAction __instance)
{
string consumeData = string.Empty;
_props.Values.TryGetValue("ConsumeData", out consumeData);
_props.Values.TryGetValue("ConsumeTags", out string tags);
FastTags<TagGroup.Global> commonTags = string.IsNullOrEmpty(tags) ? FastTags<TagGroup.Global>.none : FastTags<TagGroup.Global>.Parse(tags);
if (string.IsNullOrEmpty(consumeData))
{
Log.Error($"No consume data found on item {__instance.item.Name} action {__instance.ActionIndex}");
return;
}
consumeDatas = consumeData.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray();
consumeTags = consumeDatas.Select(s => FastTags<TagGroup.Global>.Parse(s) | commonTags | TagsConsumption).ToArray();
consumeStocks = new float[consumeDatas.Length];
consumeValues = new float[consumeDatas.Length];
}
[HarmonyPatch(typeof(ItemActionRanged), nameof(ItemAction.ExecuteAction)), MethodTargetTranspiler]
private static IEnumerable<CodeInstruction> Transpiler_ItemActionRanged_ExecuteAction(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var codes = instructions.ToList();
var fld_started = AccessTools.Field(typeof(ItemActionRanged.ItemActionDataRanged), nameof(ItemActionRanged.ItemActionDataRanged.burstShotStarted));
var fld_infinite = AccessTools.Field(typeof(ItemActionAttack), nameof(ItemActionAttack.InfiniteAmmo));
var mtd_consume = AccessTools.Method(typeof(ItemActionRanged), nameof(ItemActionRanged.ConsumeAmmo));
var lbd_module = generator.DeclareLocal(typeof(ActionModuleMetaConsumer));
var prop_instance = AccessTools.PropertyGetter(typeof(IModuleContainerFor<ActionModuleMetaConsumer>), nameof(IModuleContainerFor<ActionModuleMetaConsumer>.Instance));
var prop_itemvalue = AccessTools.PropertyGetter(typeof(ItemInventoryData), nameof(ItemInventoryData.itemValue));
for (int i = 0; i < codes.Count; i++)
{
if (codes[i].opcode == OpCodes.Stloc_S && ((LocalBuilder)codes[i].operand).LocalIndex == 6)
{
codes.InsertRange(i - 4, new[]
{
new CodeInstruction(OpCodes.Ldarg_0).WithLabels(codes[i - 4].ExtractLabels()),
new CodeInstruction(OpCodes.Castclass, typeof(IModuleContainerFor<ActionModuleMetaConsumer>)),
new CodeInstruction(OpCodes.Callvirt, prop_instance),
new CodeInstruction(OpCodes.Stloc_S, lbd_module),
});
i += 4;
}
else if (codes[i].StoresField(fld_started) && codes[i - 1].LoadsConstant(1))
{
var lbl = generator.DefineLabel();
var original = codes[i - 2];
codes.InsertRange(i - 2, new[]
{
new CodeInstruction(OpCodes.Ldloc_S, lbd_module).WithLabels(original.ExtractLabels()),
new CodeInstruction(OpCodes.Ldloc_0),
CodeInstruction.LoadField(typeof(ItemActionData), nameof(ItemActionData.invData)),
new CodeInstruction(OpCodes.Callvirt, prop_itemvalue),
new CodeInstruction(OpCodes.Ldloc_S, 6),
new CodeInstruction(OpCodes.Ldarg_0),
CodeInstruction.LoadField(typeof(ItemActionAttack), nameof(ItemActionAttack.soundEmpty)),
CodeInstruction.Call(typeof(ActionModuleMetaConsumer), nameof(CheckAndCacheMetaData)),
new CodeInstruction(OpCodes.Brtrue_S, lbl),
new CodeInstruction(OpCodes.Ret)
});
original.WithLabels(lbl);
i += 10;
}
else if (codes[i].Calls(mtd_consume))
{
var lbl = generator.DefineLabel();
for (int j = i - 1; j >= 0; j--)
{
if (codes[j].LoadsField(fld_infinite) && codes[j + 1].Branches(out _))
{
codes[j + 1].operand = lbl;
break;
}
}
codes.InsertRange(i + 1, new[]
{
new CodeInstruction(OpCodes.Ldloc_S, lbd_module).WithLabels(lbl),
new CodeInstruction(OpCodes.Ldloc_0),
CodeInstruction.LoadField(typeof(ItemActionData), nameof(ItemActionData.invData)),
new CodeInstruction(OpCodes.Callvirt, prop_itemvalue),
new CodeInstruction(OpCodes.Ldloc_S, 6),
CodeInstruction.Call(typeof(ActionModuleMetaConsumer), nameof(ConsumeMetaData)),
});
break;
}
}
return codes;
}
public bool CheckAndCacheMetaData(ItemValue itemValue, EntityAlive holdingEntity, string soundEmpty)
{
for (int i = 0; i < consumeDatas.Length; i++)
{
string consumeData = consumeDatas[i];
float stock = (float)itemValue.GetMetadata(consumeData);
float consumption = EffectManager.GetValue(CustomEnums.CustomTaggedEffect, itemValue, float.MaxValue, holdingEntity, null, consumeTags[i]);
if (stock < consumption)
{
holdingEntity.PlayOneShot(soundEmpty);
return false;
}
consumeStocks[i] = stock;
consumeValues[i] = consumption;
}
return true;
}
public void ConsumeMetaData(ItemValue itemValue, EntityAlive holdingEntity)
{
for (int i = 0; i < consumeDatas.Length; i++)
{
itemValue.SetMetadata(consumeDatas[i], consumeStocks[i] - consumeValues[i], TypedMetadataValue.TypeTag.Float);
holdingEntity.MinEventContext.Tags = consumeTags[i];
holdingEntity.FireEvent(CustomEnums.onRechargeValueUpdate, true);
}
}
//[HarmonyPatch(nameof(ItemAction.ExecuteAction)), MethodTargetPrefix]
//private bool Prefix_ExecuteAction(ItemActionData _actionData, bool _bReleased, ItemActionRanged __instance)
//{
// ItemActionRanged.ItemActionDataRanged _data = _actionData as ItemActionRanged.ItemActionDataRanged;
// EntityAlive holdingEntity = _actionData.invData.holdingEntity;
// ItemValue itemValue = _actionData.invData.itemValue;
// if (!_bReleased)
// {
// int burstCount = __instance.GetBurstCount(_actionData);
// if (holdingEntity.inventory.holdingItemItemValue.PercentUsesLeft <= 0f || (_data.curBurstCount >= burstCount && burstCount != -1) || (!__instance.InfiniteAmmo && itemValue.Meta <= 0))
// {
// return true;
// }
// for (int i = 0; i < consumeDatas.Length; i++)
// {
// string consumeData = consumeDatas[i];
// float stock = (float)itemValue.GetMetadata(consumeData);
// float consumption = EffectManager.GetValue(CustomEnums.CustomTaggedEffect, itemValue, float.MaxValue, _actionData.invData.holdingEntity, null, consumeTags[i]);
// if (stock < consumption)
// {
// if (!_data.bPressed)
// {
// holdingEntity.PlayOneShot(__instance.soundEmpty);
// _data.bPressed = true;
// }
// return false;
// }
// consumeStocks[i] = stock;
// consumeValues[i] = consumption;
// }
// for (int i = 0; i < consumeDatas.Length; i++)
// {
// itemValue.SetMetadata(consumeDatas[i], consumeStocks[i] - consumeValues[i], TypedMetadataValue.TypeTag.Float);
// holdingEntity.MinEventContext.Tags = consumeTags[i];
// holdingEntity.FireEvent(CustomEnums.onRechargeValueUpdate, true);
// }
// }
// return true;
//}
}

View File

@@ -0,0 +1,166 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.StaticManagers;
using System;
using UniLinq;
using UnityEngine;
[TypeTarget(typeof(ItemActionRanged)), ActionDataTarget(typeof(MetaRechargerData))]
public class ActionModuleMetaRecharger
{
public struct RechargeTags
{
public FastTags<TagGroup.Global> tagsOriginal;
public FastTags<TagGroup.Global> tagsInterval;
public FastTags<TagGroup.Global> tagsMaximum;
public FastTags<TagGroup.Global> tagsValue;
public FastTags<TagGroup.Global> tagsDecrease;
public FastTags<TagGroup.Global> tagsDecreaseInterval;
}
public string[] rechargeDatas;
public RechargeTags[] rechargeTags;
private static readonly FastTags<TagGroup.Global> TagsInterval = FastTags<TagGroup.Global>.Parse("RechargeDataInterval");
private static readonly FastTags<TagGroup.Global> TagsMaximum = FastTags<TagGroup.Global>.Parse("RechargeDataMaximum");
private static readonly FastTags<TagGroup.Global> TagsValue = FastTags<TagGroup.Global>.Parse("RechargeDataValue");
private static readonly FastTags<TagGroup.Global> TagsDecrease = FastTags<TagGroup.Global>.Parse("RechargeDataDecrease");
private static readonly FastTags<TagGroup.Global> TagsDecreaseInterval = FastTags<TagGroup.Global>.Parse("RechargeDecreaseInterval");
[HarmonyPatch(nameof(ItemAction.ReadFrom)), MethodTargetPostfix]
private void Postfix_ReadFrom(DynamicProperties _props, ItemAction __instance)
{
rechargeDatas = null;
rechargeTags = null;
string rechargeData = string.Empty;
_props.Values.TryGetValue("RechargeData", out rechargeData);
_props.Values.TryGetValue("RechargeTags", out string tags);
FastTags<TagGroup.Global> commonTags = string.IsNullOrEmpty(tags) ? FastTags<TagGroup.Global>.none : FastTags<TagGroup.Global>.Parse(tags);
if (string.IsNullOrEmpty(rechargeData))
{
Log.Error($"No recharge data found on item {__instance.item.Name} action {__instance.ActionIndex}");
return;
}
rechargeDatas = rechargeData.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray();
rechargeTags = rechargeDatas.Select(s =>
{
var _tags = FastTags<TagGroup.Global>.Parse(s) | commonTags;
return new RechargeTags
{
tagsOriginal = _tags,
tagsInterval = _tags | TagsInterval,
tagsMaximum = _tags | TagsMaximum,
tagsValue = _tags | TagsValue,
tagsDecrease = _tags | TagsDecrease,
tagsDecreaseInterval = _tags | TagsDecreaseInterval,
};
}).ToArray();
}
[HarmonyPatch(nameof(ItemAction.StartHolding)), MethodTargetPrefix]
private bool Prefix_StartHolding(ItemActionData _data, MetaRechargerData __customData)
{
EntityAlive holdingEntity = _data.invData.holdingEntity;
if (holdingEntity.isEntityRemote)
return true;
for (int i = 0; i < rechargeDatas.Length; i++)
{
holdingEntity.MinEventContext.Tags = rechargeTags[i].tagsOriginal;
holdingEntity.FireEvent(CustomEnums.onRechargeValueUpdate, true);
}
return true;
}
public class MetaRechargerData : IBackgroundInventoryUpdater
{
private ActionModuleMetaRecharger module;
private float lastUpdateTime, lastDecreaseTime;
private int indexOfAction;
public int Index => indexOfAction;
public MetaRechargerData(ItemInventoryData _invData, int _indexOfAction, ActionModuleMetaRecharger _rechargeModule)
{
module = _rechargeModule;
indexOfAction = _indexOfAction;
lastUpdateTime = lastDecreaseTime = Time.time;
if (_rechargeModule.rechargeDatas == null)
return;
BackgroundInventoryUpdateManager.RegisterUpdater(_invData.holdingEntity, _invData.slotIdx, this);
}
public bool OnUpdate(ItemInventoryData invData)
{
ItemValue itemValue = invData.itemValue;
EntityAlive holdingEntity = invData.holdingEntity;
holdingEntity.MinEventContext.ItemInventoryData = invData;
holdingEntity.MinEventContext.ItemValue = itemValue;
holdingEntity.MinEventContext.ItemActionData = invData.actionData[indexOfAction];
float curTime = Time.time;
bool res = false;
for (int i = 0; i < module.rechargeDatas.Length; i++)
{
string rechargeData = module.rechargeDatas[i];
RechargeTags rechargeTag = module.rechargeTags[i];
float updateInterval = EffectManager.GetValue(CustomEnums.CustomTaggedEffect, itemValue, float.MaxValue, holdingEntity, null, rechargeTag.tagsInterval);
float decreaseInterval = EffectManager.GetValue(CustomEnums.CustomTaggedEffect, itemValue, float.MaxValue, holdingEntity, null, rechargeTag.tagsDecreaseInterval);
float deltaTime = curTime - lastUpdateTime;
float deltaDecreaseTime = curTime - lastDecreaseTime;
if (deltaTime > updateInterval || deltaDecreaseTime > decreaseInterval)
{
//Log.Out($"last update time {lastUpdateTime} cur time {curTime} update interval {updateInterval}");
float cur;
if (!itemValue.HasMetadata(rechargeData))
{
itemValue.SetMetadata(rechargeData, 0, TypedMetadataValue.TypeTag.Float);
cur = 0;
}
else
{
cur = (float)itemValue.GetMetadata(rechargeData);
}
float max = EffectManager.GetValue(CustomEnums.CustomTaggedEffect, itemValue, 0, holdingEntity, null, rechargeTag.tagsMaximum);
bool modified = false;
if (cur > max)
{
if (deltaDecreaseTime > decreaseInterval)
{
//the result updated here won't exceed max so it's set somewhere else, decrease slowly
float dec = EffectManager.GetValue(CustomEnums.CustomTaggedEffect, itemValue, float.MaxValue, holdingEntity, null, rechargeTag.tagsDecrease);
cur = Mathf.Max(cur - dec, max);
lastDecreaseTime = curTime;
modified = true;
}
lastUpdateTime = curTime;
}
else
{
if (cur < max && deltaTime > updateInterval)
{
//add up and clamp to max
float add = EffectManager.GetValue(CustomEnums.CustomTaggedEffect, itemValue, 0, holdingEntity, null, rechargeTag.tagsValue);
cur = Mathf.Min(cur + add, max);
lastUpdateTime = curTime;
modified = true;
}
//always set lastDecreaseTime if not overcharged, since we don't want overcharged data to decrease right after it's charged
lastDecreaseTime = curTime;
}
if (modified)
{
itemValue.SetMetadata(rechargeData, cur, TypedMetadataValue.TypeTag.Float);
}
if (invData.slotIdx == holdingEntity.inventory.holdingItemIdx && invData.slotIdx >= 0)
{
holdingEntity.MinEventContext.Tags = rechargeTag.tagsOriginal;
itemValue.FireEvent(CustomEnums.onRechargeValueUpdate, holdingEntity.MinEventContext);
//Log.Out($"action index is {holdingEntity.MinEventContext.ItemActionData.indexInEntityOfAction} after firing event");
}
res |= modified;
}
}
return res;
}
}
}

View File

@@ -0,0 +1,222 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.StaticManagers;
using KFCommonUtilityLib.Scripts.Utilities;
[TypeTarget(typeof(ItemActionAttack)), ActionDataTarget(typeof(MultiActionData))]
public class ActionModuleMultiActionFix
{
private int actionIndex;
public string GetDisplayType(ItemValue itemValue)
{
string displayType = itemValue.GetPropertyOverrideForAction("DisplayType", null, actionIndex);
if (string.IsNullOrEmpty(displayType))
{
displayType = itemValue.ItemClass.DisplayType;
}
return displayType;
}
[HarmonyPatch(nameof(ItemAction.ReadFrom)), MethodTargetPostfix]
public void Postfix_ReadFrom(ItemActionAttack __instance)
{
actionIndex = __instance.ActionIndex;
}
[HarmonyPatch(nameof(ItemAction.StartHolding)), MethodTargetPrefix]
public bool Prefix_StartHolding(ItemActionData _data, out ItemActionData __state)
{
SetAndSaveItemActionData(_data, out __state);
return true;
}
[HarmonyPatch(nameof(ItemAction.StartHolding)), MethodTargetPostfix]
public void Postfix_StartHolding(ItemActionData _data, ItemActionData __state)
{
RestoreItemActionData(_data, __state);
}
[HarmonyPatch(typeof(ItemActionRanged), nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
public void Postfix_OnModificationChanged_ItemActionRanged(ItemActionData _data, ItemActionAttack __instance)
{
var rangedData = _data as ItemActionRanged.ItemActionDataRanged;
if (rangedData != null)
{
string muzzleName;
string indexExtension = (_data.indexInEntityOfAction > 0 ? _data.indexInEntityOfAction.ToString() : "");
if (rangedData.IsDoubleBarrel)
{
muzzleName = _data.invData.itemValue.GetPropertyOverrideForAction($"Muzzle_L_Name", $"Muzzle_L{indexExtension}", _data.indexInEntityOfAction);
rangedData.muzzle = AnimationRiggingManager.GetTransformOverrideByName(rangedData.invData.model, muzzleName) ?? rangedData.muzzle;
muzzleName = _data.invData.itemValue.GetPropertyOverrideForAction($"Muzzle_R_Name", $"Muzzle_R{indexExtension}", _data.indexInEntityOfAction);
rangedData.muzzle2 = AnimationRiggingManager.GetTransformOverrideByName(rangedData.invData.model, muzzleName) ?? rangedData.muzzle2;
}
else
{
muzzleName = _data.invData.itemValue.GetPropertyOverrideForAction($"Muzzle_Name", $"Muzzle{indexExtension}", _data.indexInEntityOfAction);
rangedData.muzzle = AnimationRiggingManager.GetTransformOverrideByName(rangedData.invData.model, muzzleName) ?? rangedData.muzzle;
}
}
}
[HarmonyPatch(typeof(ItemActionLauncher), nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
public void Postfix_OnModificationChanged_ItemActionLauncher(ItemActionData _data, ItemActionAttack __instance)
{
Postfix_OnModificationChanged_ItemActionRanged(_data, __instance);
if (_data is ItemActionLauncher.ItemActionDataLauncher launcherData)
{
string indexExtension = (_data.indexInEntityOfAction > 0 ? _data.indexInEntityOfAction.ToString() : "");
string jointName = _data.invData.itemValue.GetPropertyOverrideForAction($"ProjectileJoint_Name", $"ProjectileJoint{indexExtension}", _data.indexInEntityOfAction);
launcherData.projectileJoint = AnimationRiggingManager.GetTransformOverrideByName(launcherData.invData.model, jointName) ?? launcherData.projectileJoint;
}
}
[HarmonyPatch(nameof(ItemAction.StopHolding)), MethodTargetPrefix]
public bool Prefix_StopHolding(ItemActionData _data, out ItemActionData __state)
{
SetAndSaveItemActionData(_data, out __state);
return true;
}
[HarmonyPatch(nameof(ItemAction.StopHolding)), MethodTargetPostfix]
public void Postfix_StopHolding(ItemActionData _data, ItemActionData __state)
{
RestoreItemActionData(_data, __state);
}
[HarmonyPatch(typeof(ItemActionLauncher), nameof(ItemAction.ItemActionEffects)), MethodTargetPrefix]
public bool Prefix_ItemActionEffects(ItemActionData _actionData, out ItemActionData __state)
{
SetAndSaveItemActionData(_actionData, out __state);
return true;
}
[HarmonyPatch(typeof(ItemActionLauncher), nameof(ItemAction.ItemActionEffects)), MethodTargetPostfix]
public void Postfix_ItemActionEffects(ItemActionData _actionData, ItemActionData __state)
{
RestoreItemActionData(_actionData, __state);
}
[HarmonyPatch(nameof(ItemAction.CancelAction)), MethodTargetPrefix]
public bool Prefix_CancelAction(ItemActionData _actionData, out ItemActionData __state)
{
SetAndSaveItemActionData(_actionData, out __state);
return true;
}
[HarmonyPatch(nameof(ItemAction.CancelAction)), MethodTargetPostfix]
public void Postfix_CancelAction(ItemActionData _actionData, ItemActionData __state)
{
RestoreItemActionData(_actionData, __state);
}
[HarmonyPatch(nameof(ItemActionAttack.CancelReload)), MethodTargetPrefix]
public bool Prefix_CancelReload(ItemActionData _actionData, out ItemActionData __state)
{
SetAndSaveItemActionData(_actionData, out __state);
return true;
}
[HarmonyPatch(nameof(ItemActionAttack.CancelReload)), MethodTargetPostfix]
public void Postfix_CancelReload(ItemActionData _actionData, ItemActionData __state)
{
RestoreItemActionData(_actionData, __state);
}
[HarmonyPatch(nameof(ItemActionAttack.ReloadGun)), MethodTargetPrefix]
public bool Prefix_ReloadGun(ItemActionData _actionData)
{
//int reloadAnimationIndex = MultiActionManager.GetMetaIndexForActionIndex(_actionData.invData.holdingEntity.entityId, _actionData.indexInEntityOfAction);
_actionData.invData.holdingEntity.emodel?.avatarController?.UpdateInt(MultiActionUtils.ExecutingActionIndexHash, _actionData.indexInEntityOfAction, false);
_actionData.invData.holdingEntity.MinEventContext.ItemActionData = _actionData;
//MultiActionManager.GetMappingForEntity(_actionData.invData.holdingEntity.entityId)?.SaveMeta();
return true;
}
[HarmonyPatch(nameof(ItemAction.OnHUD)), MethodTargetPrefix]
public bool Prefix_OnHUD(ItemActionData _actionData)
{
if (_actionData.invData?.holdingEntity?.MinEventContext?.ItemActionData == null || _actionData.indexInEntityOfAction != _actionData.invData.holdingEntity.MinEventContext.ItemActionData.indexInEntityOfAction)
return false;
return true;
}
//[MethodTargetPrefix(nameof(ItemActionAttack.ExecuteAction), typeof(ItemActionRanged))]
//public bool Prefix_ExecuteAction(ItemActionData _actionData, MultiActionData __customData)
//{
// //when executing action, set last action index so that correct accuracy is used for drawing crosshair
// if (_actionData.invData.holdingEntity is EntityPlayerLocal player)
// {
// ((ItemActionRanged.ItemActionDataRanged)_actionData).lastAccuracy = __customData.lastAccuracy;
// }
// return true;
//}
//[MethodTargetPrefix("updateAccuracy", typeof(ItemActionRanged))]
//public bool Prefix_updateAccuracy(ItemActionData _actionData, MultiActionData __customData)
//{
// if (_actionData.invData.holdingEntity is EntityPlayerLocal player && MultiActionManager.GetActionIndexForEntityID(player.entityId) == _actionData.indexInEntityOfAction)
// return true;
// //always update custom accuracy
// ItemActionRanged.ItemActionDataRanged rangedData = _actionData as ItemActionRanged.ItemActionDataRanged;
// (rangedData.lastAccuracy, __customData.lastAccuracy) = (__customData.lastAccuracy, rangedData.lastAccuracy);
// return true;
//}
//[MethodTargetPostfix("updateAccuracy", typeof(ItemActionRanged))]
//public void Postfix_updateAccuracy(ItemActionData _actionData, MultiActionData __customData)
//{
// //retain rangedData accuracy if it's the last executed action
// ItemActionRanged.ItemActionDataRanged rangedData = _actionData as ItemActionRanged.ItemActionDataRanged;
// if (_actionData.invData.holdingEntity is EntityPlayerLocal player && MultiActionManager.GetActionIndexForEntityID(player.entityId) == _actionData.indexInEntityOfAction)
// {
// __customData.lastAccuracy = rangedData.lastAccuracy;
// }
// else
// {
// (rangedData.lastAccuracy, __customData.lastAccuracy) = (__customData.lastAccuracy, rangedData.lastAccuracy);
// }
//}
[HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.onHoldingEntityFired)), MethodTargetPrefix]
public bool Prefix_onHoldingEntityFired(ItemActionData _actionData)
{
if (!_actionData.invData.holdingEntity.isEntityRemote)
{
_actionData.invData.holdingEntity?.emodel?.avatarController.UpdateInt(MultiActionUtils.ExecutingActionIndexHash, _actionData.indexInEntityOfAction);
//_actionData.invData.holdingEntity?.emodel?.avatarController.CancelEvent("WeaponFire");
}
return true;
}
//[MethodTargetPostfix("onHoldingEntityFired", typeof(ItemActionRanged))]
//public void Postfix_onHoldingEntityFired(ItemActionData _actionData, MultiActionData __customData)
//{
// //after firing, if it's the last executed action then update custom accuracy
// if (_actionData.invData.holdingEntity is EntityPlayerLocal player && MultiActionManager.GetActionIndexForEntityID(player.entityId) == _actionData.indexInEntityOfAction)
// {
// __customData.lastAccuracy = ((ItemActionRanged.ItemActionDataRanged)_actionData).lastAccuracy;
// }
//}
public static void SetAndSaveItemActionData(ItemActionData _actionData, out ItemActionData lastActionData)
{
lastActionData = _actionData.invData.holdingEntity.MinEventContext.ItemActionData;
_actionData.invData.holdingEntity.MinEventContext.ItemActionData = _actionData;
}
public static void RestoreItemActionData(ItemActionData _actionData, ItemActionData lastActionData)
{
if (lastActionData != null)
_actionData.invData.holdingEntity.MinEventContext.ItemActionData = lastActionData;
}
public class MultiActionData
{
public float lastAccuracy;
public MultiActionData(ItemInventoryData _invData, int _indexInEntityOfAction, ActionModuleMultiActionFix _module)
{
}
}
}

View File

@@ -0,0 +1,302 @@
using HarmonyLib;
using KFCommonUtilityLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.StaticManagers;
using KFCommonUtilityLib.Scripts.Utilities;
using System.Collections.Generic;
using System.Reflection.Emit;
using UniLinq;
using UnityEngine;
[TypeTarget(typeof(ItemActionRanged)), ActionDataTarget(typeof(MultiBarrelData))]
public class ActionModuleMultiBarrel
{
[HarmonyPatch(nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
public void Postfix_OnModificationChanged(ItemActionData _data, MultiBarrelData __customData, ItemActionRanged __instance)
{
int actionIndex = _data.indexInEntityOfAction;
string originalValue = false.ToString();
__instance.Properties.ParseString("MuzzleIsPerRound", ref originalValue);
__customData.muzzleIsPerRound = bool.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("MuzzleIsPerRound", originalValue, actionIndex));
originalValue = false.ToString();
__instance.Properties.ParseString("OneRoundMultiShot", ref originalValue);
__customData.oneRoundMultishot = bool.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("OneRoundMultiShot", originalValue, actionIndex));
originalValue = 1.ToString();
__instance.Properties.ParseString("RoundsPerShot", ref originalValue);
__customData.roundsPerShot = int.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("RoundsPerShot", originalValue, actionIndex));
originalValue = 1.ToString();
__instance.Properties.ParseString("BarrelCount", ref originalValue);
__customData.barrelCount = int.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("BarrelCount", originalValue, actionIndex));
//Log.Out($"MuzzleIsPerRound: {__customData.muzzleIsPerRound} OneRoundMultiShot: {__customData.oneRoundMultishot} RoundsPerShot: {__customData.roundsPerShot} BarrelCount: {__customData.barrelCount}");
__customData.muzzles = new Transform[__customData.barrelCount];
__customData.projectileJoints = new Transform[__customData.barrelCount];
for (int i = 0; i < __customData.barrelCount; i++)
{
string muzzleName = _data.invData.itemValue.GetPropertyOverrideForAction($"MBMuzzle{i}_Name", $"MBMuzzle{i}", actionIndex);
__customData.muzzles[i] = AnimationRiggingManager.GetTransformOverrideByName(_data.invData.model, muzzleName);
string jointName = _data.invData.itemValue.GetPropertyOverrideForAction($"MBProjectileJoint{i}_Name", $"MBProjectileJoint{i}", actionIndex);
__customData.projectileJoints[i] = AnimationRiggingManager.GetTransformOverrideByName(_data.invData.model, jointName);
}
int meta = MultiActionUtils.GetMetaByActionIndex(_data.invData.itemValue, actionIndex);
__customData.SetCurrentBarrel(meta);
((ItemActionRanged.ItemActionDataRanged)_data).IsDoubleBarrel = false;
}
[HarmonyPatch(typeof(ItemActionLauncher), nameof(ItemAction.StartHolding)), MethodTargetPrefix]
public void Prefix_StartHolding_ItemActionLauncher(ItemActionData _data, ItemActionLauncher __instance, MultiBarrelData __customData)
{
ItemActionLauncher.ItemActionDataLauncher launcherData = _data as ItemActionLauncher.ItemActionDataLauncher;
launcherData.projectileJoint = __customData.projectileJoints[0];
}
[HarmonyPatch(typeof(ItemActionLauncher), nameof(ItemAction.StartHolding)), MethodTargetPostfix]
public void Postfix_StartHolding_ItemActionLauncher(ItemActionData _data, ItemActionLauncher __instance, MultiBarrelData __customData)
{
ItemActionLauncher.ItemActionDataLauncher launcherData = _data as ItemActionLauncher.ItemActionDataLauncher;
if (launcherData?.projectileInstance != null && __customData.oneRoundMultishot && __customData.roundsPerShot > 1)
{
int count = launcherData.projectileInstance.Count;
int times = __customData.roundsPerShot - 1;
for (int i = 0; i < times; i++)
{
launcherData.projectileJoint = __customData.projectileJoints[i + 1];
for (int j = 0; j < count; j++)
{
launcherData.projectileInstance.Add(__instance.instantiateProjectile(_data));
}
}
}
launcherData.projectileJoint = __customData.projectileJoints[__customData.curBarrelIndex];
}
[HarmonyPatch(nameof(ItemActionRanged.getUserData)), MethodTargetPostfix]
public void Postfix_getUserData(MultiBarrelData __customData, ref int __result)
{
__result |= ((byte)__customData.curBarrelIndex) << 8;
}
[HarmonyPatch(typeof(ItemActionRanged), nameof(ItemAction.ItemActionEffects)), MethodTargetPrefix]
public bool Prefix_ItemActionEffects_ItemActionRanged(ItemActionData _actionData, int _userData, int _firingState, MultiBarrelData __customData)
{
ItemActionRanged.ItemActionDataRanged rangedData = _actionData as ItemActionRanged.ItemActionDataRanged;
if (rangedData != null && _firingState != 0)
{
byte index = (byte)(_userData >> 8);
rangedData.muzzle = __customData.muzzles[index];
__customData.SetAnimatorParam(index);
}
return true;
}
[HarmonyPatch(typeof(ItemActionLauncher), nameof(ItemAction.ItemActionEffects)), MethodTargetPrefix]
public bool Prefix_ItemActionEffects_ItemActionLauncher(ItemActionData _actionData, int _userData, int _firingState, MultiBarrelData __customData)
{
ItemActionLauncher.ItemActionDataLauncher launcherData = _actionData as ItemActionLauncher.ItemActionDataLauncher;
if (launcherData != null)
{
launcherData.projectileJoint = __customData.projectileJoints[(byte)(_userData >> 8)];
}
return Prefix_ItemActionEffects_ItemActionRanged(_actionData, _userData, _firingState, __customData);
}
[HarmonyPatch(typeof(ItemActionRanged), nameof(ItemAction.ExecuteAction)), MethodTargetTranspiler]
private static IEnumerable<CodeInstruction> Transpiler_ExecuteAction_ItemActionRanged(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var codes = instructions.ToList();
var mtd_getmax = AccessTools.Method(typeof(ItemActionRanged), nameof(ItemActionRanged.GetMaxAmmoCount));
var mtd_consume = AccessTools.Method(typeof(ItemActionRanged), nameof(ItemActionRanged.ConsumeAmmo));
var prop_instance = AccessTools.PropertyGetter(typeof(IModuleContainerFor<MultiBarrelData>), nameof(IModuleContainerFor<MultiBarrelData>.Instance));
Label loopStart = generator.DefineLabel();
Label loopCondi = generator.DefineLabel();
LocalBuilder lbd_data_module = generator.DeclareLocal(typeof(ActionModuleMultiBarrel.MultiBarrelData));
LocalBuilder lbd_i = generator.DeclareLocal(typeof(int));
LocalBuilder lbd_rounds = generator.DeclareLocal(typeof(int));
for (int i = 0; i < codes.Count; i++)
{
//prepare loop and store local variables
if (codes[i].opcode == OpCodes.Stloc_S && ((LocalBuilder)codes[i].operand).LocalIndex == 6)
{
codes[i + 1].WithLabels(loopStart);
codes.InsertRange(i + 1, new[]
{
new CodeInstruction(OpCodes.Ldarg_1),
new CodeInstruction(OpCodes.Castclass, typeof(IModuleContainerFor<MultiBarrelData>)),
new CodeInstruction(OpCodes.Callvirt, prop_instance),
new CodeInstruction(OpCodes.Stloc_S, lbd_data_module),
new CodeInstruction(OpCodes.Ldc_I4_0),
new CodeInstruction(OpCodes.Stloc_S, lbd_i),
new CodeInstruction(OpCodes.Ldloc_S, lbd_data_module),
CodeInstruction.LoadField(typeof(ActionModuleMultiBarrel.MultiBarrelData), nameof(ActionModuleMultiBarrel.MultiBarrelData.roundsPerShot)),
new CodeInstruction(OpCodes.Stloc_S, lbd_rounds),
new CodeInstruction(OpCodes.Br_S, loopCondi),
});
i += 11;
}
//one round multi shot check
else if (codes[i].Calls(mtd_consume))
{
Label lbl = generator.DefineLabel();
codes[i - 5].WithLabels(lbl);
codes.InsertRange(i - 5, new[]
{
new CodeInstruction(OpCodes.Ldloc_S, lbd_data_module),
CodeInstruction.LoadField(typeof(ActionModuleMultiBarrel.MultiBarrelData), nameof(ActionModuleMultiBarrel.MultiBarrelData.oneRoundMultishot)),
new CodeInstruction(OpCodes.Brfalse_S, lbl),
new CodeInstruction(OpCodes.Ldloc_S, lbd_i),
new CodeInstruction(OpCodes.Ldc_I4_0),
new CodeInstruction(OpCodes.Bgt_S, codes[i - 3].operand)
});
i += 6;
}
//loop conditions and cycle barrels
else if (codes[i].Calls(mtd_getmax))
{
Label lbl_pre = generator.DefineLabel();
Label lbl_post = generator.DefineLabel();
CodeInstruction origin = codes[i - 2];
codes.InsertRange(i - 2, new[]
{
new CodeInstruction(OpCodes.Ldloc_S, lbd_data_module).WithLabels(origin.ExtractLabels()),
CodeInstruction.LoadField(typeof(ActionModuleMultiBarrel.MultiBarrelData), nameof(ActionModuleMultiBarrel.MultiBarrelData.muzzleIsPerRound)),
new CodeInstruction(OpCodes.Brfalse_S, lbl_pre),
new CodeInstruction(OpCodes.Ldloc_S, lbd_data_module),
CodeInstruction.Call(typeof(ActionModuleMultiBarrel.MultiBarrelData), nameof(ActionModuleMultiBarrel.MultiBarrelData.CycleBarrels)),
new CodeInstruction(OpCodes.Ldloc_S, 6).WithLabels(lbl_pre),
CodeInstruction.LoadField(typeof(EntityAlive), nameof(EntityAlive.inventory)),
CodeInstruction.Call(typeof(Inventory), nameof(Inventory.CallOnToolbeltChangedInternal)),
new CodeInstruction(OpCodes.Ldloc_S, lbd_i),
new CodeInstruction(OpCodes.Ldc_I4_1),
new CodeInstruction(OpCodes.Add),
new CodeInstruction(OpCodes.Stloc_S, lbd_i),
new CodeInstruction(OpCodes.Ldloc_S, lbd_i).WithLabels(loopCondi),
new CodeInstruction(OpCodes.Ldloc_S, lbd_rounds),
new CodeInstruction(OpCodes.Blt_S, loopStart),
new CodeInstruction(OpCodes.Ldloc_S, lbd_data_module),
CodeInstruction.LoadField(typeof(ActionModuleMultiBarrel.MultiBarrelData), nameof(ActionModuleMultiBarrel.MultiBarrelData.muzzleIsPerRound)),
new CodeInstruction(OpCodes.Brtrue_S, lbl_post),
new CodeInstruction(OpCodes.Ldloc_S, lbd_data_module),
CodeInstruction.Call(typeof(ActionModuleMultiBarrel.MultiBarrelData), nameof(ActionModuleMultiBarrel.MultiBarrelData.CycleBarrels))
});
origin.WithLabels(lbl_post);
break;
}
}
return codes;
}
private static void LogInfo(int cur, int max) => Log.Out($"max rounds {max}, cur {cur}");
public class MultiBarrelData
{
public ItemInventoryData invData;
public int actionIndex;
public ActionModuleMultiBarrel module;
public bool muzzleIsPerRound;
public bool oneRoundMultishot;
public int roundsPerShot;
public int barrelCount;
public int curBarrelIndex;
public Transform[] muzzles;
public Transform[] projectileJoints;
public MultiBarrelData(ItemInventoryData _invData, int _indexInEntityOfAction, ActionModuleMultiBarrel _module)
{
invData = _invData;
actionIndex = _indexInEntityOfAction;
module = _module;
}
public void CycleBarrels()
{
curBarrelIndex = ++curBarrelIndex >= barrelCount ? 0 : curBarrelIndex;
//Log.Out($"cycle barrel index {curBarrelIndex}");
}
public void SetCurrentBarrel(int roundLeft)
{
if (muzzleIsPerRound)
{
int totalSwitches;
if (oneRoundMultishot)
{
totalSwitches = roundLeft * roundsPerShot;
}
else
{
totalSwitches = roundLeft;
}
int lastCycleSwitches = totalSwitches % barrelCount;
int barrelGroup = barrelCount / roundsPerShot;
curBarrelIndex = (barrelCount - lastCycleSwitches) / barrelGroup * barrelGroup;
}
else
{
if (oneRoundMultishot)
{
curBarrelIndex = barrelCount - (roundLeft % barrelCount);
}
else
{
curBarrelIndex = barrelCount - ((roundLeft + 1) / roundsPerShot) % barrelCount;
}
}
if (curBarrelIndex >= barrelCount)
{
curBarrelIndex = 0;
}
SetAnimatorParam(curBarrelIndex);
//Log.Out($"set barrel index {curBarrelIndex}");
}
public void SetAnimatorParam(int barrelIndex)
{
invData.holdingEntity.emodel.avatarController.UpdateInt("barrelIndex", barrelIndex, true);
//Log.Out($"set param index {barrelIndex}");
}
}
}
[HarmonyPatch]
public class MultiBarrelPatches
{
[HarmonyPatch(typeof(AnimatorRangedReloadState), nameof(AnimatorRangedReloadState.OnStateEnter))]
[HarmonyPostfix]
private static void Postfix_OnStateEnter_AnimatorRangedReloadState(AnimatorRangedReloadState __instance)
{
ItemActionLauncher.ItemActionDataLauncher launcherData = __instance.actionData as ItemActionLauncher.ItemActionDataLauncher;
if (launcherData != null && launcherData is IModuleContainerFor<ActionModuleMultiBarrel.MultiBarrelData> dataModule && dataModule.Instance.oneRoundMultishot && dataModule.Instance.roundsPerShot > 1)
{
int count = launcherData.projectileInstance.Count;
int times = dataModule.Instance.roundsPerShot - 1;
for (int i = 0; i < count; i++)
{
for (int j = 0; j < times; j++)
{
launcherData.projectileJoint = dataModule.Instance.projectileJoints[j + 1];
launcherData.projectileInstance.Insert(i * (times + 1) + j + 1, ((ItemActionLauncher)__instance.actionRanged).instantiateProjectile(launcherData));
}
}
}
}
[HarmonyPatch(typeof(AnimatorRangedReloadState), nameof(AnimatorRangedReloadState.OnStateExit))]
[HarmonyPostfix]
private static void Postfix_OnStateExit_AnimatorRangedReloadState(AnimatorRangedReloadState __instance)
{
if (__instance.actionData is IModuleContainerFor<ActionModuleMultiBarrel.MultiBarrelData> dataModule)
{
dataModule.Instance.SetCurrentBarrel(__instance.actionData.invData.itemValue.Meta);
}
}
}

View File

@@ -0,0 +1,276 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.Utilities;
using UnityEngine;
using static ItemActionRanged;
[TypeTarget(typeof(ItemActionRanged)), ActionDataTarget(typeof(RampUpData))]
public class ActionModuleRampUp
{
public enum State
{
RampUp,
Stable,
RampDown
}
private readonly static int prepareHash = Animator.StringToHash("prepare");
private readonly static int prepareSpeedHash = Animator.StringToHash("prepareSpeed");
private readonly static int rampHash = Animator.StringToHash("ramp");
private readonly static int prepareRatioHash = Animator.StringToHash("prepareRatio");
private readonly static int rampRatioHash = Animator.StringToHash("rampRatio");
private readonly static int totalRatioHash = Animator.StringToHash("totalRatio");
[HarmonyPatch(nameof(ItemAction.OnHoldingUpdate)), MethodTargetPostfix]
public void Postfix_OnHoldingUpdate(ItemActionData _actionData, RampUpData __customData, ItemActionRanged __instance)
{
var rangedData = _actionData as ItemActionDataRanged;
__customData.originalDelay = rangedData.Delay;
if (rangedData.invData.holdingEntity.isEntityRemote)
return;
bool aiming = rangedData.invData.holdingEntity.AimingGun;
bool isRampUp = ((rangedData.bPressed && !rangedData.bReleased && __instance.notReloading(rangedData) && rangedData.curBurstCount < __instance.GetBurstCount(rangedData)) || (__customData.zoomPrepare && aiming)) && (__instance.InfiniteAmmo || _actionData.invData.itemValue.Meta > 0) && _actionData.invData.itemValue.PercentUsesLeft > 0;
UpdateTick(__customData, _actionData, isRampUp);
if (__customData.rampRatio > 0)
{
rangedData.Delay /= __customData.rampRatio >= 1f ? __customData.maxMultiplier : __customData.rampRatio * (__customData.maxMultiplier - 1f) + 1f;
}
}
[HarmonyPatch(nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
public void Postfix_OnModificationsChanged(ItemActionData _data, RampUpData __customData, ItemActionRanged __instance)
{
int actionIndex = __instance.ActionIndex;
string originalValue = 1.ToString();
__instance.Properties.ParseString("RampMultiplier", ref originalValue);
__customData.maxMultiplier = Mathf.Max(float.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("RampMultiplier", originalValue, actionIndex)), 1);
originalValue = 0.ToString();
__instance.Properties.ParseString("RampUpTime", ref originalValue);
__customData.rampUpTime = float.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("RampTime", originalValue, actionIndex));
originalValue = string.Empty;
__instance.Properties.ParseString("RampUpSound", ref originalValue);
__customData.rampUpSound = _data.invData.itemValue.GetPropertyOverrideForAction("RampStartSound", originalValue, actionIndex);
originalValue = 0.ToString();
__instance.Properties.ParseString("RampDownTime", ref originalValue);
__customData.rampDownTime = Mathf.Max(float.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("RampTime", originalValue, actionIndex)), 0);
originalValue = string.Empty;
__instance.Properties.ParseString("RampDownSound", ref originalValue);
__customData.rampDownSound = _data.invData.itemValue.GetPropertyOverrideForAction("RampStartSound", originalValue, actionIndex);
originalValue = 0.ToString();
__instance.Properties.ParseString("PrepareTime", ref originalValue);
__customData.prepareTime = float.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("PrepareTime", originalValue, actionIndex));
__customData.prepareSpeed = float.Parse(originalValue) / __customData.prepareTime;
originalValue = string.Empty;
__instance.Properties.ParseString("PrepareSound", ref originalValue);
__customData.prepareSound = _data.invData.itemValue.GetPropertyOverrideForAction("PrepareSound", originalValue, actionIndex);
originalValue = false.ToString();
__instance.Properties.ParseString("PrepareOnAim", ref originalValue);
__customData.zoomPrepare = bool.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("PrepareOnAim", originalValue, actionIndex));
originalValue = string.Empty;
__instance.Properties.ParseString("RampStableSound", ref originalValue);
__customData.rampStableSound = _data.invData.itemValue.GetPropertyOverrideForAction("RampStableSound", originalValue, actionIndex);
__customData.totalChargeTime = __customData.prepareTime + __customData.rampUpTime;
__customData.rampDownTimeScale = __customData.rampDownTime > 0 ? (__customData.totalChargeTime) / __customData.rampDownTime : float.MaxValue;
ResetAll(__customData, _data);
}
[HarmonyPatch(nameof(ItemAction.StopHolding)), MethodTargetPostfix]
public void Postfix_StopHolding(RampUpData __customData, ItemActionData _data)
{
ResetAll(__customData, _data);
}
[HarmonyPatch(nameof(ItemAction.ExecuteAction)), MethodTargetPrefix]
public bool Prefix_ExecuteAction(RampUpData __customData, ItemActionRanged __instance, ItemActionData _actionData, bool _bReleased)
{
ItemActionDataRanged rangedData = _actionData as ItemActionDataRanged;
if (!_bReleased && (__instance.InfiniteAmmo || _actionData.invData.itemValue.Meta > 0) && _actionData.invData.itemValue.PercentUsesLeft > 0)
{
rangedData.bReleased = false;
rangedData.bPressed = true;
if (__customData.curTime < __customData.prepareTime)
return false;
}
return true;
}
private void UpdateTick(RampUpData data, ItemActionData actionData, bool isRampUp)
{
float previousTime = data.curTime;
float deltaTime = Time.time - data.lastTickTime;
data.lastTickTime = Time.time;
ref float curTime = ref data.curTime;
ref State curState = ref data.curState;
float totalChargeTime = data.totalChargeTime;
switch (curState)
{
case State.RampUp:
{
curTime = Mathf.Max(curTime, 0);
if (isRampUp)
{
actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(prepareHash, true, true);
if (curTime < totalChargeTime)
{
curTime += deltaTime;
}
if (curTime >= data.prepareTime)
{
actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(rampHash, true, true);
}
if (curTime >= totalChargeTime)
{
//Log.Out($"change state from {curState} to stable");
actionData.invData.holdingEntity.PlayOneShot(data.rampStableSound);
curState = State.Stable;
}
}
else
{
//Log.Out($"change state from {curState} to ramp down");
actionData.invData.holdingEntity.StopOneShot(data.rampUpSound);
actionData.invData.holdingEntity.PlayOneShot(data.rampDownSound);
curState = State.RampDown;
}
break;
}
case State.RampDown:
{
curTime = Mathf.Min(curTime, totalChargeTime);
if (!isRampUp)
{
actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(rampHash, false, true);
if (curTime > 0)
{
curTime -= deltaTime * data.rampDownTimeScale;
}
if (curTime < data.prepareTime)
{
actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(prepareHash, false, true);
}
if (curTime <= 0)
{
//Log.Out($"change state from {curState} to stable");
//actionData.invData.holdingEntity.PlayOneShot(data.rampStableSound);
curState = State.Stable;
}
}
else
{
//Log.Out($"change state from {curState} to ramp up");
actionData.invData.holdingEntity.StopOneShot(data.rampDownSound);
actionData.invData.holdingEntity.PlayOneShot(data.rampUpSound);
curState = State.RampUp;
}
break;
}
case State.Stable:
{
if (isRampUp)
{
if (curTime < totalChargeTime)
{
//Log.Out($"change state from {curState} to ramp up");
actionData.invData.holdingEntity.StopOneShot(data.rampStableSound);
actionData.invData.holdingEntity.StopOneShot(data.rampDownSound);
actionData.invData.holdingEntity.PlayOneShot(data.rampUpSound);
curState = State.RampUp;
}
else
{
actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(prepareHash, true, true);
actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(rampHash, true, true);
}
}
else
{
if (curTime > 0)
{
//Log.Out($"change state from {curState} to ramp down");
actionData.invData.holdingEntity.StopOneShot(data.rampStableSound);
actionData.invData.holdingEntity.StopOneShot(data.rampUpSound);
actionData.invData.holdingEntity.PlayOneShot(data.rampDownSound);
curState = State.RampDown;
}
else
{
actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(prepareHash, false, true);
actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(rampHash, false, true);
}
}
break;
}
}
//Log.Out($"turret burst fire rate {turret.burstFireRate} max {turret.burstFireRateMax} cur time {curTime} cur state {curState} is ramp up {isRampUp} turret: ison {turret.IsOn} has target {turret.hasTarget} state {turret.state}");
actionData.invData.holdingEntity.emodel.avatarController.UpdateFloat(prepareSpeedHash, data.prepareSpeed);
if (curTime != previousTime)
{
actionData.invData.holdingEntity.emodel.avatarController.UpdateFloat(prepareRatioHash, data.prepareRatio = (data.prepareTime == 0 ? 1f : Mathf.Clamp01(curTime / data.prepareTime)));
actionData.invData.holdingEntity.emodel.avatarController.UpdateFloat(rampRatioHash, data.rampRatio = (data.rampUpTime == 0 ? 1f : Mathf.Clamp01((curTime - data.prepareTime) / data.rampUpTime)));
actionData.invData.holdingEntity.emodel.avatarController.UpdateFloat(totalRatioHash, data.totalRatio = (totalChargeTime == 0 ? 1f : Mathf.Clamp01(curTime / totalChargeTime)));
}
}
private void ResetAll(RampUpData _rampData, ItemActionData _actionData)
{
_rampData.curTime = 0f;
_rampData.lastTickTime = Time.time;
_rampData.curState = State.Stable;
_rampData.prepareRatio = 0f;
_rampData.rampRatio = 0f;
_rampData.totalRatio = 0f;
((ItemActionDataRanged)_actionData).Delay = _rampData.originalDelay;
_actionData.invData.holdingEntity.StopOneShot(_rampData.prepareSound);
_actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(prepareHash, false, true);
_actionData.invData.holdingEntity.StopOneShot(_rampData.rampUpSound);
_actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(rampHash, false, true);
//Log.Out("Reset all!");
}
public class RampUpData
{
public float maxMultiplier = 1f;
public string prepareSound = string.Empty;
public float prepareSpeed = 1f;
public float prepareTime = 0f;
public string rampUpSound = string.Empty;
public float rampUpTime = 0f;
public float totalChargeTime = 0f;
public string rampDownSound = string.Empty;
public float rampDownTime = 0f;
public float rampDownTimeScale = float.MaxValue;
public string rampStableSound = string.Empty;
public float originalDelay = 0f;
public float curTime = 0f;
public State curState = State.Stable;
public float prepareRatio = 0f;
public float rampRatio = 0f;
public float totalRatio = 0f;
public float lastTickTime = 0f;
public bool zoomPrepare = false;
public ActionModuleRampUp rampUpModule;
public RampUpData(ItemInventoryData _invData, int _indexInEntityOfAction, ActionModuleRampUp _module)
{
rampUpModule = _module;
}
}
}

View File

@@ -0,0 +1,30 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.Utilities;
using UniLinq;
[TypeTarget(typeof(ItemAction)), ActionDataTarget(typeof(TaggedData))]
public class ActionModuleTagged
{
[HarmonyPatch(nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
private void Postfix_OnModificationChanged(ItemAction __instance, ItemActionData _data, TaggedData __customData)
{
var tags = __instance.Properties.GetString("ActionTags").Split(',', System.StringSplitOptions.RemoveEmptyEntries);
var tags_to_add = _data.invData.itemValue.GetAllPropertyOverridesForAction("ActionTagsAppend", __instance.ActionIndex).SelectMany(s => s.Split(',', System.StringSplitOptions.RemoveEmptyEntries));
var tags_to_remove = _data.invData.itemValue.GetAllPropertyOverridesForAction("ActionTagsRemove", __instance.ActionIndex).SelectMany(s => s.Split(',', System.StringSplitOptions.RemoveEmptyEntries));
var tags_result = tags.Union(tags_to_add);
tags_result = tags_result.Except(tags_to_remove);
__customData.tags = tags_result.Any() ? FastTags<TagGroup.Global>.Parse(string.Join(",", tags_result)) : FastTags<TagGroup.Global>.none;
//Log.Out($"tags: {string.Join(",", tags_result)}");
}
public class TaggedData
{
public FastTags<TagGroup.Global> tags;
public TaggedData(ItemInventoryData _invData, int _indexInEntityOfAction, ActionModuleTagged _module)
{
}
}
}

View File

@@ -0,0 +1,48 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using System.Collections.Generic;
using System.Reflection.Emit;
using UnityEngine;
[TypeTarget(typeof(ItemAction))]
public class ActionModuleTranspilerTest
{
[HarmonyPatch(typeof(ItemActionAttack), nameof(ItemAction.ExecuteAction)), MethodTargetTranspiler]
private static IEnumerable<CodeInstruction> Transpiler_InvalidTest(IEnumerable<CodeInstruction> instructions)
{
yield return new CodeInstruction(OpCodes.Ldstr, "Ranged!");
yield return CodeInstruction.Call(typeof(ActionModuleTranspilerTest), nameof(ActionModuleTranspilerTest.CallSomething));
foreach (var ins in instructions)
{
yield return ins;
}
}
[HarmonyPatch(typeof(ItemActionRanged), nameof(ItemAction.ExecuteAction)), MethodTargetTranspiler]
private static IEnumerable<CodeInstruction> Transpiler_RangedTest(IEnumerable<CodeInstruction> instructions)
{
yield return new CodeInstruction(OpCodes.Ldstr, "Ranged!");
yield return CodeInstruction.Call(typeof(ActionModuleTranspilerTest), nameof(ActionModuleTranspilerTest.CallSomething));
foreach (var ins in instructions)
{
yield return ins;
}
}
[HarmonyPatch(typeof(ItemActionCatapult), nameof(ItemAction.ExecuteAction)), MethodTargetTranspiler]
private static IEnumerable<CodeInstruction> Transpiler_CatapultTest(IEnumerable<CodeInstruction> instructions)
{
yield return new CodeInstruction(OpCodes.Ldstr, "Catapult!");
yield return CodeInstruction.Call(typeof(ActionModuleTranspilerTest), nameof(ActionModuleTranspilerTest.CallSomething));
foreach (var ins in instructions)
{
yield return ins;
}
}
private static void CallSomething(string str)
{
Log.Out($"Call something: {str}\n{StackTraceUtility.ExtractStackTrace()}");
}
}

View File

@@ -0,0 +1,73 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using UnityEngine;
[TypeTarget(typeof(ItemActionZoom)), ActionDataTarget(typeof(VariableZoomData))]
public class ActionModuleVariableZoom
{
public static float zoomScale = 7.5f;
[HarmonyPatch(nameof(ItemAction.ConsumeScrollWheel)), MethodTargetPostfix]
private void Postfix_ConsumeScrollWheel(ItemActionData _actionData, float _scrollWheelInput, PlayerActionsLocal _playerInput, VariableZoomData __customData)
{
if (!_actionData.invData.holdingEntity.AimingGun || _scrollWheelInput == 0f)
{
return;
}
ItemActionZoom.ItemActionDataZoom itemActionDataZoom = (ItemActionZoom.ItemActionDataZoom)_actionData;
if (!itemActionDataZoom.bZoomInProgress)
{
//__customData.curScale = Utils.FastClamp(__customData.curScale + _scrollWheelInput * zoomScale, __customData.minScale, __customData.maxScale);
__customData.curSteps = Utils.FastClamp01(__customData.curSteps + _scrollWheelInput);
__customData.curFov = Utils.FastLerp(__customData.maxFov, __customData.minFov, GetNext(__customData.curSteps));
__customData.curScale = Mathf.Pow(Mathf.Rad2Deg * 2 * Mathf.Atan(Mathf.Tan(Mathf.Deg2Rad * 7.5f) / __customData.curFov), 2);
__customData.shouldUpdate = true;
}
}
private float GetNext(float cur)
{
return Mathf.Sin(Mathf.PI * cur / 2);
}
[HarmonyPatch(nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
private void Postfix_OnModificationChanged(ItemActionZoom __instance, ItemActionData _data, VariableZoomData __customData)
{
string str = __instance.Properties.GetString("ZoomRatio");
if (string.IsNullOrEmpty(str))
{
str = "1";
}
__customData.maxScale = StringParsers.ParseFloat(_data.invData.itemValue.GetPropertyOverride("ZoomRatio", str));
str = __instance.Properties.GetString("ZoomRatioMin");
if (string.IsNullOrEmpty(str))
{
str = __customData.maxScale.ToString();
}
__customData.minScale = StringParsers.ParseFloat(_data.invData.itemValue.GetPropertyOverride("ZoomRatioMin", str));
//__customData.curScale = Utils.FastClamp(__customData.curScale, __customData.minScale, __customData.maxScale);
__customData.maxFov = Mathf.Rad2Deg * 2 * Mathf.Atan(Mathf.Tan(Mathf.Deg2Rad * 7.5f) / Mathf.Sqrt(__customData.minScale));
__customData.minFov = Mathf.Rad2Deg * 2 * Mathf.Atan(Mathf.Tan(Mathf.Deg2Rad * 7.5f) / Mathf.Sqrt(__customData.maxScale));
__customData.curFov = Utils.FastClamp(__customData.curFov, __customData.minFov, __customData.maxFov);
__customData.curScale = Mathf.Pow(Mathf.Rad2Deg * 2 * Mathf.Atan(Mathf.Tan(Mathf.Deg2Rad * 7.5f) / __customData.curFov), 2);
__customData.curSteps = Mathf.InverseLerp(__customData.maxFov, __customData.minFov, __customData.curFov);
__customData.shouldUpdate = true;
}
public class VariableZoomData
{
public float maxScale = 1f;
public float minScale = 1f;
public float curScale = 0f;
public float maxFov = 15f;
public float minFov = 15f;
public float curFov = 90f;
public float curSteps = 0;
public bool shouldUpdate = true;
public VariableZoomData(ItemInventoryData _invData, int _indexInEntityOfAction, ActionModuleVariableZoom _module)
{
}
}
}

View File

@@ -0,0 +1,261 @@
using GUI_2;
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.StaticManagers;
using KFCommonUtilityLib.Scripts.Utilities;
using System.Collections;
using Unity.Mathematics;
[TypeTarget(typeof(ItemActionAttack)), ActionDataTarget(typeof(AlternativeData))]
public class ActionModuleAlternative
{
internal static ItemValue InventorySetItemTemp;
[HarmonyPatch(nameof(ItemAction.StartHolding)), MethodTargetPrefix]
private bool Prefix_StartHolding(ItemActionData _data, AlternativeData __customData)
{
//__customData.Init();
int prevMode = __customData.mapping.CurMode;
__customData.UpdateUnlockState(_data.invData.itemValue);
if (prevMode != __customData.mapping.CurMode && _data.invData.holdingEntity is EntityPlayerLocal player)
{
MultiActionManager.FireToggleModeEvent(player, __customData.mapping);
}
MultiActionManager.SetMappingForEntity(_data.invData.holdingEntity.entityId, __customData.mapping);
if (_data.invData.holdingEntity is EntityPlayerLocal)
{
MultiActionManager.inputCD = math.max(0.5f, MultiActionManager.inputCD);
//ThreadManager.StartCoroutine(DelaySetExecutionIndex(_data.invData.holdingEntity, __customData.mapping));
}
return true;
}
//[MethodTargetPostfix(nameof(ItemActionAttack.StartHolding))]
//private void Postfix_StartHolding(AlternativeData __customData)
//{
// __customData.UpdateMuzzleTransformOverride();
// __customData.OverrideMuzzleTransform(__customData.mapping.CurMode);
//}
private static IEnumerator DelaySetExecutionIndex(EntityAlive player, MultiActionMapping mapping)
{
yield return null;
yield return null;
if (GameManager.Instance.GetGameStateManager().IsGameStarted())
player?.emodel?.avatarController?.UpdateInt(MultiActionUtils.ExecutingActionIndexHash, mapping.CurActionIndex);
}
[HarmonyPatch(nameof(ItemActionRanged.CancelReload)), MethodTargetPrefix]
private bool Prefix_CancelReload(ItemActionData _actionData, AlternativeData __customData)
{
if (__customData.mapping == null)
return true;
int actionIndex = __customData.mapping.CurActionIndex;
if (ConsoleCmdReloadLog.LogInfo)
Log.Out($"cancel reload {actionIndex}");
if (actionIndex == 0)
return true;
_actionData.invData.holdingEntity.inventory.holdingItem.Actions[actionIndex].CancelReload(_actionData.invData.holdingEntity.inventory.holdingItemData.actionData[actionIndex]);
return false;
}
[HarmonyPatch(nameof(ItemAction.CancelAction)), MethodTargetPrefix]
private bool Prefix_CancelAction(ItemActionData _actionData, AlternativeData __customData)
{
if (__customData.mapping == null)
return true;
int actionIndex = __customData.mapping.CurActionIndex;
if (ConsoleCmdReloadLog.LogInfo)
Log.Out($"cancel action {actionIndex}");
if (actionIndex == 0)
return true;
_actionData.invData.holdingEntity.inventory.holdingItem.Actions[actionIndex].CancelAction(_actionData.invData.holdingEntity.inventory.holdingItemData.actionData[actionIndex]);
return false;
}
[HarmonyPatch(nameof(ItemAction.IsStatChanged)), MethodTargetPrefix]
private bool Prefix_IsStatChanged(ref bool __result)
{
var mapping = MultiActionManager.GetMappingForEntity(GameManager.Instance.World.GetPrimaryPlayerId());
__result |= mapping != null && mapping.CheckDisplayMode();
return false;
}
//[MethodTargetPostfix(nameof(ItemActionAttack.StopHolding))]
//private void Postfix_StopHolding(AlternativeData __customData)
//{
// //moved to harmony patch
// //MultiActionManager.SetMappingForEntity(_data.invData.holdingEntity.entityId, null);
// __customData.mapping.SaveMeta();
//}
//todo: change to action specific property
[HarmonyPatch(nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
private void Postfix_OnModificationChanged(ItemActionData _data, ItemActionAttack __instance, AlternativeData __customData)
{
__instance.Properties.ParseString("ToggleActionSound", ref __customData.toggleSound);
__customData.toggleSound = _data.invData.itemValue.GetPropertyOverrideForAction("ToggleActionSound", __customData.toggleSound, __instance.ActionIndex);
__customData.mapping.toggleSound = __customData.toggleSound;
}
[HarmonyPatch(nameof(ItemAction.SetupRadial)), MethodTargetPrefix]
private bool Prefix_SetupRadial(XUiC_Radial _xuiRadialWindow, EntityPlayerLocal _epl)
{
var mapping = MultiActionManager.GetMappingForEntity(_epl.entityId);
if (mapping != null)
{
var radialContextItem = new AlternativeRadialContextItem(mapping, _xuiRadialWindow, _epl);
_xuiRadialWindow.SetCommonData(UIUtils.GetButtonIconForAction(_epl.playerInput.Reload), handleRadialCommand, radialContextItem, radialContextItem.PreSelectedIndex, false, radialValidTest);
}
return false;
}
private bool radialValidTest(XUiC_Radial _sender, XUiC_Radial.RadialContextAbs _context)
{
AlternativeRadialContextItem radialContextItem = _context as AlternativeRadialContextItem;
if (radialContextItem == null)
{
return false;
}
EntityPlayerLocal entityPlayer = _sender.xui.playerUI.entityPlayer;
return radialContextItem.mapping == MultiActionManager.GetMappingForEntity(entityPlayer.entityId) && radialContextItem.mapping.CurActionIndex == radialContextItem.ActionIndex;
}
//redirect reload call to shared meta action, which then sets ItemActionIndex animator param to its action index
//for example if action 3 share meta with action 0, then ItemActionIndex is set to 0 on reload begin.
//since event param item action data is set to the shared meta action data, all reload related passive calculation and trigger events goes there.
private void handleRadialCommand(XUiC_Radial _sender, int _commandIndex, XUiC_Radial.RadialContextAbs _context)
{
AlternativeRadialContextItem radialContextItem = _context as AlternativeRadialContextItem;
if (radialContextItem == null)
{
return;
}
EntityPlayerLocal entityPlayer = _sender.xui.playerUI.entityPlayer;
if (radialContextItem.mapping == MultiActionManager.GetMappingForEntity(entityPlayer.entityId) && radialContextItem.mapping.CurActionIndex == radialContextItem.ActionIndex)
{
entityPlayer.MinEventContext.ItemActionData = entityPlayer.inventory.holdingItemData.actionData?[radialContextItem.ActionIndex];
(entityPlayer.inventory.holdingItem.Actions?[radialContextItem.ActionIndex] as ItemActionRanged)?.SwapSelectedAmmo(entityPlayer, _commandIndex);
}
}
public class AlternativeData
{
public MultiActionMapping mapping;
public string toggleSound;
public ItemInventoryData invData;
//private bool inited = false;
private readonly bool[] unlocked = new bool[MultiActionIndice.MAX_ACTION_COUNT];
//public Transform[] altMuzzleTrans = new Transform[MultiActionIndice.MAX_ACTION_COUNT];
//public Transform[] altMuzzleTransDBarrel = new Transform[MultiActionIndice.MAX_ACTION_COUNT];
public AlternativeData(ItemInventoryData invData, int actionIndex, ActionModuleAlternative module)
{
this.invData = invData;
Init();
}
//public void UpdateMuzzleTransformOverride()
//{
// for (int i = 0; i < MultiActionIndice.MAX_ACTION_COUNT; i++)
// {
// int curActionIndex = mapping.indices.GetActionIndexForMode(i);
// if (curActionIndex < 0)
// {
// break;
// }
// var rangedData = invData.actionData[curActionIndex] as ItemActionRanged.ItemActionDataRanged;
// if (rangedData != null)
// {
// if (rangedData.IsDoubleBarrel)
// {
// altMuzzleTrans[i] = AnimationRiggingManager.GetTransformOverrideByName($"Muzzle_L{curActionIndex}", rangedData.invData.model) ?? rangedData.muzzle;
// altMuzzleTransDBarrel[i] = AnimationRiggingManager.GetTransformOverrideByName($"Muzzle_R{curActionIndex}", rangedData.invData.model) ?? rangedData.muzzle2;
// }
// else
// {
// altMuzzleTrans[i] = AnimationRiggingManager.GetTransformOverrideByName($"Muzzle{curActionIndex}", rangedData.invData.model) ?? rangedData.muzzle;
// }
// }
// }
//}
public void Init()
{
//if (inited)
// return;
//inited = true;
MultiActionIndice indices = MultiActionManager.GetActionIndiceForItemID(invData.item.Id);
mapping = new MultiActionMapping(this, indices, invData.holdingEntity, InventorySetItemTemp, toggleSound, invData.slotIdx, unlocked);
UpdateUnlockState(InventorySetItemTemp);
}
public void UpdateUnlockState(ItemValue itemValue)
{
//if (!inited)
// return;
unlocked[0] = true;
for (int i = 1; i < mapping.ModeCount; i++)
{
bool flag = true;
int actionIndex = mapping.indices.GetActionIndexForMode(i);
ItemAction action = itemValue.ItemClass.Actions[actionIndex];
action.Properties.ParseBool("ActionUnlocked", ref flag);
if (bool.TryParse(itemValue.GetPropertyOverride($"ActionUnlocked_{actionIndex}", flag.ToString()), out bool overrideFlag))
flag = overrideFlag;
unlocked[i] = flag;
}
//by the time we check unlock state, ItemValue in inventory slot might not be ready yet
mapping.SaveMeta(itemValue);
mapping.CurMode = mapping.CurMode;
mapping.ReadMeta(itemValue);
}
public bool IsActionUnlocked(int actionIndex)
{
int mode = mapping.indices.GetModeForAction(actionIndex);
if (mode >= MultiActionIndice.MAX_ACTION_COUNT || mode < 0)
return false;
return unlocked[mode];
}
// public void OverrideMuzzleTransform(int mode)
// {
// var rangedData = invData.actionData[mapping.indices.GetActionIndexForMode(mode)] as ItemActionRanged.ItemActionDataRanged;
// if (rangedData != null)
// {
// if (rangedData.IsDoubleBarrel)
// {
// rangedData.muzzle = altMuzzleTrans[mode];
// rangedData.muzzle2 = altMuzzleTransDBarrel[mode];
// }
// else
// {
// rangedData.muzzle = altMuzzleTrans[mode];
// }
// }
//#if DEBUG
// Log.Out($"setting muzzle transform for action {rangedData.indexInEntityOfAction} to {rangedData.muzzle.name}\n{StackTraceUtility.ExtractStackTrace()}");
//#endif
// }
}
//todo: don't setup for every mode, and use reload animation from shared action
public class AlternativeRadialContextItem : XUiC_Radial.RadialContextAbs
{
public MultiActionMapping mapping;
public int ActionIndex { get; private set; }
public int PreSelectedIndex { get; private set; }
public AlternativeRadialContextItem(MultiActionMapping mapping, XUiC_Radial _xuiRadialWindow, EntityPlayerLocal _epl)
{
this.mapping = mapping;
ActionIndex = mapping.CurActionIndex;
PreSelectedIndex = mapping.SetupRadial(_xuiRadialWindow, _epl);
}
}
}

View File

@@ -0,0 +1,36 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
[TypeTarget(typeof(ItemAction)), ActionDataTarget(typeof(AnimationLockedData))]
public class ActionModuleAnimationLocked
{
[HarmonyPatch(nameof(ItemAction.StartHolding)), MethodTargetPostfix]
private void Postfix_StartHolding(AnimationLockedData __customData)
{
__customData.isLocked = false;
__customData.isReloadLocked = false;
}
[HarmonyPatch(nameof(ItemAction.IsActionRunning)), MethodTargetPostfix]
private void Postfix_IsActionRunning(AnimationLockedData __customData, ref bool __result)
{
__result |= __customData.isLocked;
}
[HarmonyPatch(typeof(ItemActionAttack), nameof(ItemActionAttack.CanReload)), MethodTargetPostfix]
private void Postfix_CanReload_ItemActionAttack(AnimationLockedData __customData, ref bool __result)
{
__result &= !__customData.isReloadLocked;
}
public class AnimationLockedData
{
public bool isLocked = false;
public bool isReloadLocked = false;
public AnimationLockedData(ItemInventoryData invData, int actionIndex, ActionModuleAnimationLocked module)
{
}
}
}

View File

@@ -0,0 +1,135 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using System.Collections.Generic;
using System.Reflection.Emit;
using UniLinq;
using static AnimationDelayData;
[TypeTarget(typeof(ItemAction))]
public class ActionModuleCustomAnimationDelay
{
[HarmonyPatch(typeof(ItemActionEat), nameof(ItemAction.OnHoldingUpdate))]
[HarmonyPatch(typeof(ItemActionGainSkill), nameof(ItemAction.OnHoldingUpdate))]
[HarmonyPatch(typeof(ItemActionLearnRecipe), nameof(ItemAction.OnHoldingUpdate))]
[HarmonyPatch(typeof(ItemActionQuest), nameof(ItemAction.OnHoldingUpdate))]
[HarmonyPatch(typeof(ItemActionEat), nameof(ItemAction.IsActionRunning))]
[HarmonyPatch(typeof(ItemActionGainSkill), nameof(ItemAction.IsActionRunning))]
[HarmonyPatch(typeof(ItemActionLearnRecipe), nameof(ItemAction.IsActionRunning))]
[HarmonyPatch(typeof(ItemActionQuest), nameof(ItemAction.IsActionRunning))]
[MethodTargetTranspiler]
private static IEnumerable<CodeInstruction> Transpiler_OnHoldingUpdate(IEnumerable<CodeInstruction> instructions)
{
var codes = instructions.ToList();
var fld_delayarr = AccessTools.Field(typeof(AnimationDelayData), nameof(AnimationDelayData.AnimationDelay));
var fld_raycast = AccessTools.Field(typeof(AnimationDelays), nameof(AnimationDelays.RayCast));
for (var i = 0; i < codes.Count; i++)
{
if (codes[i].LoadsField(fld_delayarr))
{
for (int j = i + 1; j < codes.Count; j++)
{
if (codes[j].LoadsField(fld_raycast))
{
bool flag = codes[i - 1].LoadsConstant(2f);
codes.RemoveRange(flag ? i - 1 : i, j - i + (flag ? 3 : 1));
codes.InsertRange(flag ? i - 1 : i, new[]
{
new CodeInstruction(OpCodes.Ldarg_0),
CodeInstruction.LoadField(typeof(ItemAction), nameof(ItemAction.Delay))
});
break;
}
}
break;
}
}
return codes;
}
//[HarmonyPatch(nameof(ItemAction.OnHoldingUpdate)), MethodTargetPrefix]
//private bool Prefix_OnHoldingUpdate(ItemAction __instance, ItemActionData _actionData, out AnimationDelays __state)
//{
// __state = AnimationDelayData.AnimationDelay[_actionData.invData.item.HoldType.Value];
// if (!__instance.UseAnimation)
// return true;
// var modifiedData = __state;
// modifiedData.RayCast = __instance.Delay;
// AnimationDelayData.AnimationDelay[_actionData.invData.item.HoldType.Value] = modifiedData;
// return true;
//}
//[HarmonyPatch(nameof(ItemAction.OnHoldingUpdate)), MethodTargetPostfix]
//private void Postfix_OnHoldingUpdate(ItemAction __instance, ItemActionData _actionData, AnimationDelays __state)
//{
// if (!__instance.UseAnimation)
// return;
// AnimationDelayData.AnimationDelay[_actionData.invData.item.HoldType.Value] = __state;
//}
//[HarmonyPatch(nameof(ItemAction.IsActionRunning)), MethodTargetPrefix]
//private bool Prefix_IsActionRunning(ItemAction __instance, ItemActionData _actionData, out AnimationDelays __state)
//{
// __state = AnimationDelayData.AnimationDelay[_actionData.invData.item.HoldType.Value];
// if (!__instance.UseAnimation)
// return true;
// var modifiedData = __state;
// modifiedData.RayCast = __instance.Delay * .5f;
// AnimationDelayData.AnimationDelay[_actionData.invData.item.HoldType.Value] = modifiedData;
// return true;
//}
//[HarmonyPatch(nameof(ItemAction.IsActionRunning)), MethodTargetPostfix]
//private void Postfix_IsActionRunning(ItemAction __instance, ItemActionData _actionData, AnimationDelays __state)
//{
// if (!__instance.UseAnimation)
// return;
// AnimationDelayData.AnimationDelay[_actionData.invData.item.HoldType.Value] = __state;
//}
//following are fix for item use time from menu entry
//when IsActionRunning is called from coroutine which is started by menu entry,
//as OnHoldingUpdate is not called every frame, the check might yield false before item actually gets consumed, thus returning the item
//so we call OnHoldingUpdate to properly consume the item
//vanilla method on the other hand, is forcing double delay in IsActionRunning
[HarmonyPatch(typeof(ItemActionEat), nameof(ItemAction.IsActionRunning)), MethodTargetPostfix]
private void Postfix_IsActionRunning_ItemActionEat(ItemActionEat __instance, ItemActionData _actionData/*, AnimationDelays __state*/, bool __result)
{
//Postfix_IsActionRunning(__instance, _actionData, __state);
if (!__result && ((ItemActionEat.MyInventoryData)_actionData).bEatingStarted)
{
__instance.OnHoldingUpdate(_actionData);
}
}
[HarmonyPatch(typeof(ItemActionGainSkill), nameof(ItemAction.IsActionRunning)), MethodTargetPostfix]
private void Postfix_IsActionRunning_ItemActionGainSkill(ItemActionGainSkill __instance, ItemActionData _actionData/*, AnimationDelays __state*/, bool __result)
{
//Postfix_IsActionRunning(__instance, _actionData, __state);
if (!__result && ((ItemActionGainSkill.MyInventoryData)_actionData).bReadingStarted)
{
__instance.OnHoldingUpdate(_actionData);
}
}
[HarmonyPatch(typeof(ItemActionLearnRecipe), nameof(ItemAction.IsActionRunning)), MethodTargetPostfix]
private void Postfix_IsActionRunning_ItemActionLearnRecipe(ItemActionLearnRecipe __instance, ItemActionData _actionData/*, AnimationDelays __state*/, bool __result)
{
//Postfix_IsActionRunning(__instance, _actionData, __state);
if (!__result && ((ItemActionLearnRecipe.MyInventoryData)_actionData).bReadingStarted)
{
__instance.OnHoldingUpdate(_actionData);
}
}
[HarmonyPatch(typeof(ItemActionQuest), nameof(ItemAction.IsActionRunning)), MethodTargetPostfix]
private void Postfix_IsActionRunning_ItemActionQuest(ItemActionQuest __instance, ItemActionData _actionData/*, AnimationDelays __state*/, bool __result)
{
//Postfix_IsActionRunning(__instance, _actionData, __state);
if (!__result && ((ItemActionQuest.MyInventoryData)_actionData).bQuestAccept)
{
__instance.OnHoldingUpdate(_actionData);
}
}
}

View File

@@ -0,0 +1,78 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
public class DisplayAsBuffEntityUINotification : BuffEntityUINotification
{
public ActionModuleDisplayAsBuff.DisplayValueType displayType = ActionModuleDisplayAsBuff.DisplayValueType.Meta;
public string displayData = string.Empty;
public override float CurrentValue
{
get
{
EntityPlayerLocal player = GameManager.Instance.World.GetPrimaryPlayer();
if (player == null)
return 0;
switch (displayType)
{
case ActionModuleDisplayAsBuff.DisplayValueType.Meta:
return player.inventory.holdingItemItemValue.Meta;
case ActionModuleDisplayAsBuff.DisplayValueType.MetaData:
return (float)player.inventory.holdingItemItemValue.GetMetadata(displayData);
default:
return 0;
}
}
}
public override bool Visible => true;
public override EnumEntityUINotificationDisplayMode DisplayMode => EnumEntityUINotificationDisplayMode.IconPlusCurrentValue;
}
[TypeTarget(typeof(ItemActionRanged))]
public class ActionModuleDisplayAsBuff
{
public enum DisplayValueType
{
Meta,
MetaData
}
private DisplayAsBuffEntityUINotification notification;
private BuffClass buffClass;
[HarmonyPatch(nameof(ItemAction.ReadFrom)), MethodTargetPostfix]
private void Postfix_ReadFrom(DynamicProperties _props)
{
notification = new DisplayAsBuffEntityUINotification();
_props.Values.TryGetValue("DisplayType", out string str);
EnumUtils.TryParse(str, out notification.displayType, true);
_props.Values.TryGetValue("DisplayData", out notification.displayData);
_props.Values.TryGetValue("DisplayBuff", out str);
BuffClass buffClass = BuffManager.GetBuff(str);
BuffValue buff = new BuffValue(buffClass.Name, Vector3i.zero, -1, buffClass);
notification.SetBuff(buff);
}
[HarmonyPatch(nameof(ItemAction.StartHolding)), MethodTargetPostfix]
private void Postfix_StartHolding(ItemActionData _data)
{
EntityPlayerLocal player = _data.invData.holdingEntity as EntityPlayerLocal;
if (player != null && notification != null)
{
notification.SetStats(player.Stats);
player.Stats.NotificationAdded(notification);
}
}
[HarmonyPatch(nameof(ItemAction.StopHolding)), MethodTargetPostfix]
private void Postfix_StopHolding(ItemActionData _data)
{
EntityPlayerLocal player = _data.invData.holdingEntity as EntityPlayerLocal;
if (player != null && notification != null)
{
player.Stats.NotificationRemoved(notification);
}
}
}

View File

@@ -0,0 +1,67 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.StaticManagers;
[TypeTarget(typeof(ItemActionDynamic))]
public class ActionModuleDynamicGraze
{
private string dynamicSoundStart = null;
[HarmonyPatch(nameof(ItemAction.ExecuteAction)), MethodTargetPrefix]
private bool Prefix_ExecuteAction(ItemActionDynamic __instance, ItemActionData _actionData, bool _bReleased, out (bool executed, string originalSound) __state)
{
if (!_bReleased && !string.IsNullOrEmpty(dynamicSoundStart) && _actionData.invData.holdingEntity is EntityPlayerLocal player)
{
var targets = AnimationRiggingManager.GetRigTargetsFromPlayer(player);
if (targets && !targets.Destroyed && targets.IsAnimationSet)
{
__state = (true, __instance.soundStart);
__instance.soundStart = dynamicSoundStart;
return true;
}
}
__state = (false, null);
return true;
}
[HarmonyPatch(nameof(ItemAction.ExecuteAction)), MethodTargetPostfix]
private void Postfix_ExecuteAction(ItemActionDynamic __instance, (bool executed, string originalSound) __state)
{
if (__state.executed)
{
__instance.soundStart = __state.originalSound;
}
}
[HarmonyPatch(nameof(ItemAction.OnHoldingUpdate)), MethodTargetPrefix]
private bool Prefix_OnHoldingUpdate(ItemActionDynamic __instance, ItemActionData _actionData, out (bool executed, bool useGrazeCast) __state)
{
if (_actionData.invData.holdingEntity is EntityPlayerLocal player)
{
var targets = AnimationRiggingManager.GetRigTargetsFromPlayer(player);
if (targets && !targets.Destroyed && targets.ItemCurrent)
{
__state = (true, __instance.UseGrazingHits);
__instance.UseGrazingHits = false;
return true;
}
}
__state = (false, false);
return true;
}
[HarmonyPatch(nameof(ItemAction.OnHoldingUpdate)), MethodTargetPostfix]
private void Postfix_OnHoldingUpdate(ItemActionDynamic __instance, (bool executed, bool useGrazeCast) __state)
{
if (__state.executed)
{
__instance.UseGrazingHits = __state.useGrazeCast;
}
}
[HarmonyPatch(nameof(ItemAction.ReadFrom)), MethodTargetPostfix]
private void Postfix_ReadFrom(DynamicProperties _props)
{
_props.ParseString("DynamicSoundStart", ref dynamicSoundStart);
}
}

View File

@@ -0,0 +1,84 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.Utilities;
[TypeTarget(typeof(ItemActionAttack)), ActionDataTarget(typeof(DynamicMuzzleFlashData))]
public class ActionModuleDynamicMuzzleFlash
{
private struct State
{
public bool executed;
public string particlesMuzzleFire;
public string particlesMuzzleSmoke;
public string particlesMuzzleFireFpv;
public string particlesMuzzleSmokeFpv;
}
[HarmonyPatch(nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
private void Postfix_OnModificationsChanged(ItemActionAttack __instance, ItemActionAttackData _data, DynamicMuzzleFlashData __customData)
{
__customData.particlesMuzzleFire = _data.invData.itemValue.GetPropertyOverrideForAction("Particles_muzzle_fire", __instance.particlesMuzzleFire, __instance.ActionIndex);
__customData.particlesMuzzleFireFpv = _data.invData.itemValue.GetPropertyOverrideForAction("Particles_muzzle_fire_fpv", __instance.particlesMuzzleFireFpv, __instance.ActionIndex);
__customData.particlesMuzzleSmoke = _data.invData.itemValue.GetPropertyOverrideForAction("Particles_muzzle_smoke", __instance.particlesMuzzleSmoke, __instance.ActionIndex);
__customData.particlesMuzzleSmokeFpv = _data.invData.itemValue.GetPropertyOverrideForAction("Particles_muzzle_smoke_fpv", __instance.particlesMuzzleSmokeFpv, __instance.ActionIndex);
if (!string.IsNullOrEmpty(__customData.particlesMuzzleFire) && !ParticleEffect.IsAvailable(__customData.particlesMuzzleFire))
{
ParticleEffect.LoadAsset(__customData.particlesMuzzleFire);
}
if (!string.IsNullOrEmpty(__customData.particlesMuzzleFireFpv) && !ParticleEffect.IsAvailable(__customData.particlesMuzzleFireFpv))
{
ParticleEffect.LoadAsset(__customData.particlesMuzzleFireFpv);
}
if (!string.IsNullOrEmpty(__customData.particlesMuzzleSmoke) && !ParticleEffect.IsAvailable(__customData.particlesMuzzleSmoke))
{
ParticleEffect.LoadAsset(__customData.particlesMuzzleSmoke);
}
if (!string.IsNullOrEmpty(__customData.particlesMuzzleSmokeFpv) && !ParticleEffect.IsAvailable(__customData.particlesMuzzleSmokeFpv))
{
ParticleEffect.LoadAsset(__customData.particlesMuzzleSmokeFpv);
}
}
[HarmonyPatch(nameof(ItemAction.ItemActionEffects)), MethodTargetPrefix]
private bool Prefix_ItemActionEffects(ItemActionAttack __instance, DynamicMuzzleFlashData __customData, out State __state)
{
__state = new State()
{
executed = true,
particlesMuzzleFire = __instance.particlesMuzzleFire,
particlesMuzzleFireFpv = __instance.particlesMuzzleFireFpv,
particlesMuzzleSmoke = __instance.particlesMuzzleSmoke,
particlesMuzzleSmokeFpv = __instance.particlesMuzzleSmokeFpv
};
__instance.particlesMuzzleFire = __customData.particlesMuzzleFire;
__instance.particlesMuzzleFireFpv = __customData .particlesMuzzleFireFpv;
__instance.particlesMuzzleSmoke = __customData.particlesMuzzleSmoke;
__instance.particlesMuzzleSmokeFpv = __customData.particlesMuzzleSmokeFpv;
return true;
}
[HarmonyPatch(nameof(ItemAction.ItemActionEffects)), MethodTargetPostfix]
private void Postfix_ItemActionEffects(ItemActionAttack __instance, State __state)
{
if (__state.executed)
{
__instance.particlesMuzzleFire = __state.particlesMuzzleFire;
__instance.particlesMuzzleFireFpv = __state.particlesMuzzleFireFpv;
__instance.particlesMuzzleSmoke = __state.particlesMuzzleSmoke;
__instance.particlesMuzzleSmokeFpv = __state.particlesMuzzleSmokeFpv;
}
}
public class DynamicMuzzleFlashData
{
public string particlesMuzzleFire;
public string particlesMuzzleFireFpv;
public string particlesMuzzleSmoke;
public string particlesMuzzleSmokeFpv;
public DynamicMuzzleFlashData(ItemInventoryData _invData, int _indexOfAction, ActionModuleDynamicMuzzleFlash _module)
{
}
}
}

View File

@@ -0,0 +1,93 @@
using HarmonyLib;
using KFCommonUtilityLib;
using KFCommonUtilityLib.Scripts.Attributes;
using UnityEngine;
[TypeTarget(typeof(ItemActionZoom)), ActionDataTarget(typeof(DynamicSensitivityData))]
public class ActionModuleDynamicSensitivity
{
[HarmonyPatch(nameof(ItemAction.AimingSet)), MethodTargetPostfix]
private void Postfix_AimingSet(ItemActionData _actionData, bool _isAiming, bool _wasAiming, DynamicSensitivityData __customData)
{
float originalSensitivity = GamePrefs.GetFloat(EnumGamePrefs.OptionsZoomSensitivity);
if (_isAiming)
{
PlayerMoveController.Instance.mouseZoomSensitivity = originalSensitivity / Mathf.Sqrt(__customData.ZoomRatio);
}
else
{
PlayerMoveController.Instance.mouseZoomSensitivity = originalSensitivity;
}
}
[HarmonyPatch(nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
private void Postfix_OnModificationsChanged(ItemActionZoom __instance, ItemActionData _data, DynamicSensitivityData __customData)
{
if (_data is IModuleContainerFor<ActionModuleVariableZoom.VariableZoomData> variableZoomData)
{
__customData.variableZoomData = variableZoomData.Instance;
}
else
{
string str = __instance.Properties.GetString("ZoomRatio");
if (string.IsNullOrEmpty(str))
{
str = "1";
}
__customData.ZoomRatio = StringParsers.ParseFloat(_data.invData.itemValue.GetPropertyOverride("ZoomRatio", str));
}
__customData.dsRangeOverride = StringParsers.ParseVector2(_data.invData.itemValue.GetPropertyOverride("DynamicSensitivityRange", "0,0"));
}
[HarmonyPatch(nameof(ItemAction.OnHoldingUpdate)), MethodTargetPostfix]
private void Postfix_OnHoldingUpdate(ItemActionData _actionData, DynamicSensitivityData __customData)
{
if (((ItemActionZoom.ItemActionDataZoom)_actionData).aimingValue)
{
float originalSensitivity = GamePrefs.GetFloat(EnumGamePrefs.OptionsZoomSensitivity);
if (__customData.activated)
{
PlayerMoveController.Instance.mouseZoomSensitivity = originalSensitivity / Mathf.Sqrt(__customData.ZoomRatio);
}
else
{
PlayerMoveController.Instance.mouseZoomSensitivity = originalSensitivity;
}
}
}
public class DynamicSensitivityData
{
public ActionModuleVariableZoom.VariableZoomData variableZoomData = null;
private float zoomRatio = 1.0f;
public Vector2 dsRangeOverride = Vector2.zero;
public bool activated = false;
public float ZoomRatio
{
get
{
if (variableZoomData != null)
{
if (dsRangeOverride.x > 0 && dsRangeOverride.y >= dsRangeOverride.x)
{
return Mathf.Lerp(dsRangeOverride.x, dsRangeOverride.y, Mathf.InverseLerp(variableZoomData.minScale, variableZoomData.maxScale, variableZoomData.curScale));
}
if (!variableZoomData.forceFov)
{
return variableZoomData.curScale;
}
return 1f;
}
return zoomRatio;
}
set => zoomRatio = value;
}
public DynamicSensitivityData(ItemInventoryData _invData, int _indexInEntityOfAction, ActionModuleDynamicSensitivity _module)
{
}
}
}

View File

@@ -0,0 +1,157 @@
using HarmonyLib;
using KFCommonUtilityLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.StaticManagers;
using System.Collections.Generic;
using System.Reflection.Emit;
using UniLinq;
using UnityEngine;
using static ActionModuleErgoAffected;
[TypeTarget(typeof(ItemActionZoom)), ActionDataTarget(typeof(ErgoData))]
public class ActionModuleErgoAffected
{
public static readonly int AimSpeedModifierHash = Animator.StringToHash("AimSpeedModifier");
public float zoomInTimeBase;
public float aimSpeedModifierBase;
[HarmonyPatch(nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
private void Postfix_OnModificationChanged(ItemActionData _data, ItemActionZoom __instance, ErgoData __customData)
{
zoomInTimeBase = 0.3f;
__instance.Properties.ParseFloat("ZoomInTimeBase", ref zoomInTimeBase);
aimSpeedModifierBase = 1f;
__instance.Properties.ParseFloat("AimSpeedModifierBase", ref aimSpeedModifierBase);
__customData.aimStartTime = float.MaxValue;
__customData.aimSet = false;
}
[HarmonyPatch(nameof(ItemAction.ExecuteAction)), MethodTargetPostfix]
private void Postfix_ExecuteAction(ItemActionData _actionData, ItemActionZoom __instance, bool _bReleased, ErgoData __customData)
{
EntityAlive holdingEntity = _actionData.invData.holdingEntity;
ItemActionData prevActionData = holdingEntity.MinEventContext.ItemActionData;
holdingEntity.MinEventContext.ItemActionData = _actionData.invData.actionData[MultiActionManager.GetActionIndexForEntity(holdingEntity)];
__customData.curErgo = EffectManager.GetValue(CustomEnums.WeaponErgonomics, _actionData.invData.itemValue, 0, holdingEntity);
float aimSpeedModifier = __customData.ModifiedErgo;
Log.Out($"Ergo is {__customData.curErgo}, base aim modifier is {aimSpeedModifierBase}, aim speed is {aimSpeedModifier * aimSpeedModifierBase}");
holdingEntity.emodel.avatarController.UpdateFloat(AimSpeedModifierHash, aimSpeedModifier * aimSpeedModifierBase, true);
holdingEntity.MinEventContext.ItemActionData = prevActionData;
if ((_actionData as ItemActionZoom.ItemActionDataZoom).aimingValue && !_bReleased)
{
__customData.aimStartTime = Time.time;
}
else if (!(_actionData as ItemActionZoom.ItemActionDataZoom).aimingValue)
{
__customData.aimStartTime = float.MaxValue;
}
__customData.aimSet = false;
}
//[MethodTargetPostfix(nameof(ItemAction.OnHoldingUpdate))]
//private void Postfix_OnHoldingUpdate(ItemActionData _actionData, ErgoData __customData)
//{
// if ((_actionData as ItemActionZoom.ItemActionDataZoom).aimingValue && Time.time - __customData.aimStartTime > zoomInTimeBase)
// {
// __customData.aimSet = true;
// }
// else
// {
// __customData.aimSet = false;
// }
//}
public class ErgoData
{
public float aimStartTime;
public bool aimSet;
public ActionModuleErgoAffected module;
public float curErgo;
public float ModifiedErgo => Mathf.Lerp(0.2f, 1, curErgo);
public ErgoData(ItemInventoryData _invData, int _indexInEntityOfAction, ActionModuleErgoAffected _module)
{
aimStartTime = float.MaxValue;
aimSet = false;
module = _module;
}
}
}
[HarmonyPatch]
public static class ErgoPatches
{
[HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.updateAccuracy))]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> Transpiler_ItemActionRanged_updateAccuracy(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var codes = instructions.ToList();
var mtd_lerp = AccessTools.Method(typeof(Mathf), nameof(Mathf.Lerp), new[] { typeof(float), typeof(float), typeof(float) });
for (int i = 0; i < codes.Count; i++)
{
if (codes[i].Calls(mtd_lerp))
{
codes.InsertRange(i, new[]
{
new CodeInstruction(OpCodes.Ldarg_0),
new CodeInstruction(OpCodes.Ldarg_1),
new CodeInstruction(OpCodes.Ldarg_2),
CodeInstruction.Call(typeof(ErgoPatches), nameof(CalcErgoModifier)),
});
break;
}
}
return codes;
}
private static float CalcErgoModifier(float originalValue, ItemAction action, ItemActionData actionData, bool aiming)
{
ItemActionRanged.ItemActionDataRanged rangedData = actionData as ItemActionRanged.ItemActionDataRanged;
if (aiming && rangedData.invData.actionData[1] is IModuleContainerFor<ActionModuleErgoAffected.ErgoData> dataModule && !dataModule.Instance.aimSet && Time.time - dataModule.Instance.aimStartTime > 0)
{
ActionModuleErgoAffected.ErgoData ergoData = dataModule.Instance;
float baseAimTime = ergoData.module.zoomInTimeBase;
float baseAimMultiplier = ergoData.module.aimSpeedModifierBase;
baseAimTime /= baseAimMultiplier;
//float modifiedErgo = EffectManager.GetValue(CustomEnums.WeaponErgonomics, rangedData.invData.itemValue, 1f, rangedData.invData.holdingEntity);
float modifiedErgo = ergoData.ModifiedErgo;
float perc = (Time.time - ergoData.aimStartTime) * modifiedErgo / baseAimTime;
if (perc >= 1)
{
ergoData.aimSet = true;
perc = 1;
}
//Log.Out($"Time passed {Time.time - dataModule.Instance.aimStartTime} base time {baseAimTime} perc {perc}");
return perc;
}
return originalValue;
}
[HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.onHoldingEntityFired))]
[HarmonyPrefix]
private static bool Prefix_onHoldingEntityFired_ItemActionRanged(ItemActionData _actionData, out float __state)
{
__state = (_actionData as ItemActionRanged.ItemActionDataRanged).lastAccuracy;
return true;
}
[HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.onHoldingEntityFired))]
[HarmonyPostfix]
private static void Postfix_onHoldingEntityFired_ItemActionRanged(ItemActionData _actionData, float __state)
{
ItemActionRanged.ItemActionDataRanged rangedData = _actionData as ItemActionRanged.ItemActionDataRanged;
if (rangedData.invData.holdingEntity.AimingGun && rangedData.invData.actionData[1] is IModuleContainerFor<ActionModuleErgoAffected.ErgoData> dataModule)
{
float aimMultiplier = EffectManager.GetValue(PassiveEffects.SpreadMultiplierAiming, rangedData.invData.itemValue, .1f, rangedData.invData.holdingEntity);
rangedData.lastAccuracy = Mathf.Lerp(__state, rangedData.lastAccuracy, aimMultiplier);
ActionModuleErgoAffected.ErgoData ergoData = dataModule.Instance;
if (Time.time > ergoData.aimStartTime)
{
ergoData.aimSet = false;
ergoData.aimStartTime = Time.time;
}
}
}
}

View File

@@ -0,0 +1,336 @@
using HarmonyLib;
using KFCommonUtilityLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.StaticManagers;
using KFCommonUtilityLib.Scripts.Utilities;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[TypeTarget(typeof(ItemActionRanged)), ActionDataTarget(typeof(FireModeData))]
public class ActionModuleFireModeSelector
{
public struct FireMode
{
public byte burstCount;
public bool isFullAuto;
}
public string fireModeSwitchingSound = null;
private List<FireMode> modeCache = new List<FireMode>();
private List<string> nameCache = new List<string>();
public static string[] FireModeNames = new[]
{
"FireMode",
"FireMode1",
"FireMode2",
"FireMode3",
"FireMode4",
};
public static int[] FireModeParamHashes = new[]
{
Animator.StringToHash("FireMode"),
Animator.StringToHash("FireMode1"),
Animator.StringToHash("FireMode2"),
Animator.StringToHash("FireMode3"),
Animator.StringToHash("FireMode4"),
};
public static int[] FireModeSwitchParamHashes = new[]
{
Animator.StringToHash("FireModeChanged"),
Animator.StringToHash("FireModeChanged1"),
Animator.StringToHash("FireModeChanged2"),
Animator.StringToHash("FireModeChanged3"),
Animator.StringToHash("FireModeChanged4"),
};
[HarmonyPatch(nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
private void Postfix_OnModificationChanged(ItemActionData _data, FireModeData __customData, ItemActionRanged __instance)
{
__instance.Properties.ParseString("FireModeSwitchingSound", ref fireModeSwitchingSound);
int actionIndex = _data.indexInEntityOfAction;
for (int i = 0; i < 99; i++)
{
if (!__instance.Properties.Contains($"FireMode{i}.BurstCount"))
{
break;
}
string burstCount = 1.ToString();
__instance.Properties.ParseString($"FireMode{i}.BurstCount", ref burstCount);
string isFullAuto = false.ToString();
__instance.Properties.ParseString($"FireMode{i}.IsFullAuto", ref isFullAuto);
string modeName = null;
__instance.Properties.ParseString($"FireMode{i}.ModeName", ref modeName);
modeCache.Add(new FireMode
{
burstCount = byte.Parse(_data.invData.itemValue.GetPropertyOverrideForAction($"FireMode{i}.BurstCount", burstCount, actionIndex)),
isFullAuto = bool.Parse(_data.invData.itemValue.GetPropertyOverrideForAction($"FireMode{i}.IsFullAuto", isFullAuto, actionIndex))
});
nameCache.Add(_data.invData.itemValue.GetPropertyOverrideForAction($"FireMode{i}.ModeName", modeName, actionIndex));
}
for (int i = 0; i < 99; i++)
{
string burstCount = _data.invData.itemValue.GetPropertyOverrideForAction($"FireModePlus{i}.BurstCount", null, actionIndex);
if (burstCount == null)
{
break;
}
modeCache.Add(new FireMode
{
burstCount = byte.Parse(_data.invData.itemValue.GetPropertyOverrideForAction($"FireModePlus{i}.BurstCount", burstCount, actionIndex)),
isFullAuto = bool.Parse(_data.invData.itemValue.GetPropertyOverrideForAction($"FireModePlus{i}.IsFullAuto", "false", actionIndex))
});
nameCache.Add(_data.invData.itemValue.GetPropertyOverrideForAction($"FireModePlus{i}.ModeName", null, actionIndex));
}
__customData.fireModes = modeCache.ToArray();
modeCache.Clear();
__customData.modeNames = nameCache.ToArray();
nameCache.Clear();
if (_data.invData.itemValue.GetMetadata(FireModeNames[actionIndex]) is int mode)
{
__customData.currentFireMode = (byte)mode;
}
if (__customData.currentFireMode < 0 || __customData.currentFireMode >= __customData.fireModes.Length)
{
__customData.currentFireMode = 0;
}
if (__customData.delayFiringCo != null)
{
ThreadManager.StopCoroutine(__customData.delayFiringCo);
__customData.delayFiringCo = null;
}
__customData.isRequestedByCoroutine = false;
}
[HarmonyPatch(nameof(ItemAction.StartHolding)), MethodTargetPostfix]
private void Postfix_StartHolding(ItemActionData _data, FireModeData __customData)
{
__customData.SetFireMode(_data, __customData.currentFireMode);
}
[HarmonyPatch(nameof(ItemAction.OnHoldingUpdate)), MethodTargetPostfix]
private static void Postfix_OnHoldingUpdate(ItemActionData _actionData, FireModeData __customData)
{
__customData.UpdateDelay(_actionData);
__customData.inputReleased = true;
}
[HarmonyPatch(nameof(ItemAction.StopHolding)), MethodTargetPostfix]
private static void Postfix_StopHolding(FireModeData __customData)
{
if (__customData.delayFiringCo != null)
{
ThreadManager.StopCoroutine(__customData.delayFiringCo);
__customData.delayFiringCo = null;
}
__customData.isRequestedByCoroutine = false;
__customData.inputReleased = true;
}
[HarmonyPatch(nameof(ItemAction.ExecuteAction)), MethodTargetPrefix]
private bool Prefix_ExecuteAction(ItemActionData _actionData, ItemActionRanged __instance, FireModeData __customData, bool _bReleased)
{
if (__customData.isRequestedByCoroutine)
{
return true;
}
__customData.inputReleased = _bReleased;
if (__customData.delayFiringCo == null)
{
if (_bReleased || _actionData.invData.itemValue.Meta == 0 || _actionData.invData.itemValue.PercentUsesLeft <= 0)
{
return true;
}
FireMode curFireMode = __customData.fireModes[__customData.currentFireMode];
if (curFireMode.burstCount == 1)
{
return true;
}
var rangedData = _actionData as ItemActionRanged.ItemActionDataRanged;
if (__instance.GetBurstCount(_actionData) > rangedData.curBurstCount)
{
__customData.StartFiring(__instance, _actionData);
}
}
return false;
}
[HarmonyPatch(nameof(ItemActionRanged.GetBurstCount)), MethodTargetPostfix]
private void Postfix_GetBurstCount(FireModeData __customData, ref int __result)
{
FireMode fireMode = __customData.fireModes[__customData.currentFireMode];
__result = fireMode.isFullAuto ? 999 : fireMode.burstCount;
}
[HarmonyPatch(nameof(ItemAction.IsActionRunning)), MethodTargetPostfix]
private void Postfix_IsActionRunning(FireModeData __customData, ref bool __result)
{
__result |= __customData.delayFiringCo != null;
}
public class FireModeData
{
public string switchSound;
public FireMode[] fireModes;
public string[] modeNames;
public byte currentFireMode;
public Coroutine delayFiringCo;
public bool isRequestedByCoroutine;
public float shotDelay;
public float burstDelay;
public bool inputReleased;
public FireModeData(ItemInventoryData invData, int actionIndex, ActionModuleFireModeSelector module)
{
}
public void CycleFireMode(ItemActionData _data)
{
SetFireMode(_data, (byte)((currentFireMode + 1) % fireModes.Length));
}
public void SetFireMode(ItemActionData _data, byte _fireMode)
{
if (currentFireMode != _fireMode)
{
currentFireMode = _fireMode;
FireMode curFireMode = fireModes[currentFireMode];
if (!string.IsNullOrEmpty(switchSound))
{
_data.invData.holdingEntity.PlayOneShot(switchSound);
}
_data.invData.holdingEntity.emodel.avatarController.TriggerEvent(FireModeSwitchParamHashes[_data.indexInEntityOfAction]);
}
if (!string.IsNullOrEmpty(modeNames[_fireMode]))
{
GameManager.ShowTooltip(_data.invData.holdingEntity as EntityPlayerLocal, modeNames[_fireMode], true);
}
else
{
GameManager.ShowTooltip(_data.invData.holdingEntity as EntityPlayerLocal, "ttCurrentFiringMode", _fireMode.ToString(), null, null, true);
}
//GameManager.ShowTooltip(_data.invData.holdingEntity as EntityPlayerLocal, "ttCurrentFiringMode", string.IsNullOrEmpty(modeNames[_fireMode]) ? _fireMode.ToString() : Localization.Get(modeNames[_fireMode]), null, null, true);
_data.invData.holdingEntity.FireEvent(CustomEnums.onSelfBurstModeChanged);
UpdateDelay(_data);
ItemValue itemValue = _data.invData.itemValue;
if (itemValue != null)
{
if (itemValue.Metadata == null)
{
itemValue.Metadata = new Dictionary<string, TypedMetadataValue>();
}
if (!itemValue.Metadata.TryGetValue(ActionModuleFireModeSelector.FireModeNames[_data.indexInEntityOfAction], out var metadata) || !metadata.SetValue((int)_fireMode))
{
itemValue.Metadata[ActionModuleFireModeSelector.FireModeNames[_data.indexInEntityOfAction]] = new TypedMetadataValue((int)_fireMode, TypedMetadataValue.TypeTag.Integer);
}
_data.invData.holdingEntity.inventory.CallOnToolbeltChangedInternal();
}
}
public void UpdateDelay(ItemActionData _data)
{
FireMode curFireMode = fireModes[currentFireMode];
if (curFireMode.burstCount == 1)
{
return;
}
float burstInterval = EffectManager.GetValue(CustomEnums.BurstShotInterval, _data.invData.itemValue, -1, _data.invData.holdingEntity);
var rangedData = _data as ItemActionRanged.ItemActionDataRanged;
if (burstInterval > 0 && rangedData.Delay > burstInterval)
{
shotDelay = burstInterval;
burstDelay = (rangedData.Delay - burstInterval) * curFireMode.burstCount;
}
else
{
shotDelay = rangedData.Delay;
burstDelay = 0;
}
}
public void StartFiring(ItemActionRanged _instance, ItemActionData _data)
{
UpdateDelay(_data);
if (delayFiringCo != null)
{
ThreadManager.StopCoroutine(delayFiringCo);
}
((ItemActionRanged.ItemActionDataRanged)_data).bPressed = true;
((ItemActionRanged.ItemActionDataRanged)_data).bReleased = false;
delayFiringCo = ThreadManager.StartCoroutine(DelayFiring(_instance, _data));
}
private IEnumerator DelayFiring(ItemActionRanged _instance, ItemActionData _data)
{
FireMode curFireMode = fireModes[currentFireMode];
var rangedData = _data as ItemActionRanged.ItemActionDataRanged;
byte curBurstCount = rangedData.curBurstCount;
for (int i = 0; i < curFireMode.burstCount; i++)
{
isRequestedByCoroutine = true;
rangedData.bPressed = true;
rangedData.bReleased = false;
rangedData.m_LastShotTime = 0;
_instance.ExecuteAction(_data, false);
rangedData.curBurstCount = (byte)(curBurstCount + i + 1);
isRequestedByCoroutine = false;
if (rangedData.invData.itemValue.Meta <= 0 && !_instance.HasInfiniteAmmo(_data))
{
goto cleanup;
}
yield return new WaitForSeconds(shotDelay);
}
yield return new WaitForSeconds(burstDelay);
cleanup:
delayFiringCo = null;
if (inputReleased)
{
_instance.ExecuteAction(_data, true);
}
}
}
}
[HarmonyPatch]
public static class FireModePatches
{
[HarmonyPatch(typeof(PlayerMoveController), nameof(PlayerMoveController.Update))]
[HarmonyPrefix]
private static bool Prefix_Update_PlayerMoveController(PlayerMoveController __instance)
{
if (DroneManager.Debug_LocalControl || !__instance.gameManager.gameStateManager.IsGameStarted() || GameStats.GetInt(EnumGameStats.GameState) != 1)
return true;
bool isUIOpen = __instance.windowManager.IsCursorWindowOpen() || __instance.windowManager.IsInputActive() || __instance.windowManager.IsModalWindowOpen();
UpdateLocalInput(__instance.entityPlayerLocal, isUIOpen);
return true;
}
private static void UpdateLocalInput(EntityPlayerLocal _player, bool _isUIOpen)
{
if (_isUIOpen || _player.emodel.IsRagdollActive || _player.IsDead() || _player.AttachedToEntity != null)
{
return;
}
if (PlayerActionKFLib.Instance.Enabled && PlayerActionKFLib.Instance.ToggleFireMode.WasPressed)
{
if (_player.inventory.IsHoldingItemActionRunning())
{
return;
}
var actionData = _player.inventory.holdingItemData.actionData[MultiActionManager.GetActionIndexForEntity(_player)];
if (actionData is IModuleContainerFor<ActionModuleFireModeSelector.FireModeData> fireModeData)
{
fireModeData.Instance.CycleFireMode(actionData);
}
}
}
}

View File

@@ -0,0 +1,85 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using System.Collections;
using UnityEngine;
[TypeTarget(typeof(ItemActionRanged))]
public class ActionModuleHoldOpen
{
private const string emptyAnimatorBool = "empty";
private int emptyAnimatorBoolHash;
[HarmonyPatch(nameof(ItemAction.ReadFrom)), MethodTargetPostfix]
private void Postfix_ReadFrom(DynamicProperties _props, ItemActionRanged __instance)
{
int metaIndex = __instance.ActionIndex;
if (_props.Values.TryGetValue("ShareMetaWith", out string str) && int.TryParse(str, out metaIndex))
{
}
if (metaIndex > 0)
{
emptyAnimatorBoolHash = Animator.StringToHash(emptyAnimatorBool + __instance.ActionIndex);
}
else
{
emptyAnimatorBoolHash = Animator.StringToHash(emptyAnimatorBool);
}
}
[HarmonyPatch(nameof(ItemActionRanged.getUserData)), MethodTargetPostfix]
public void Postfix_getUserData(ItemActionData _actionData, ref int __result)
{
__result |= (_actionData.invData.itemValue.Meta <= 0 ? 1 : 0);
}
[HarmonyPatch(nameof(ItemAction.ItemActionEffects)), MethodTargetPostfix]
public void Postfix_ItemActionEffects(ItemActionData _actionData, int _firingState, int _userData)
{
if (_firingState != (int)ItemActionFiringState.Off && (_userData & 1) > 0)
_actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(emptyAnimatorBoolHash, true, false);
}
[HarmonyPatch(nameof(ItemActionRanged.ReloadGun)), MethodTargetPostfix]
public void Postfix_ReloadGun(ItemActionData _actionData)
{
//delay 2 frames before reloading, since the animation is likely to be triggered the next frame this is called
ThreadManager.StartCoroutine(DelaySetEmpty(_actionData, false, 2));
}
[HarmonyPatch(nameof(ItemAction.StartHolding)), MethodTargetPrefix]
public bool Prefix_StartHolding(ItemActionData _data)
{
//delay 1 frame before equipping weapon
if (_data.invData.itemValue.Meta <= 0)
ThreadManager.StartCoroutine(DelaySetEmpty(_data, true, 2));
return true;
}
[HarmonyPatch(nameof(ItemActionRanged.ConsumeAmmo)), MethodTargetPostfix]
public void Postfix_ConsumeAmmo(ItemActionData _actionData)
{
if (_actionData.invData.itemValue.Meta == 0)
_actionData.invData.holdingEntity.FireEvent(CustomEnums.onSelfMagzineDeplete, true);
}
[HarmonyPatch(nameof(ItemAction.SwapAmmoType)), MethodTargetPrefix]
public bool Prefix_SwapAmmoType(EntityAlive _entity)
{
_entity.emodel.avatarController.UpdateBool(emptyAnimatorBoolHash, true, false);
return true;
}
private IEnumerator DelaySetEmpty(ItemActionData _actionData, bool empty, int delay)
{
for (int i = 0; i < delay; i++)
{
yield return null;
}
if (_actionData.invData.holdingEntity.inventory.holdingItemIdx == _actionData.invData.slotIdx)
{
_actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(emptyAnimatorBoolHash, empty, false);
}
yield break;
}
}

View File

@@ -0,0 +1,24 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
[TypeTarget(typeof(ItemAction))]
public class ActionModuleInspectable
{
public bool allowEmptyInspect;
[HarmonyPatch(nameof(ItemAction.ReadFrom)), MethodTargetPostfix]
private void Postfix_ReadFrom(DynamicProperties _props)
{
allowEmptyInspect = _props.GetBool("allowEmptyInspect");
}
[HarmonyPatch(typeof(ItemActionDynamic), nameof(ItemAction.CancelAction)), MethodTargetPostfix]
private void Postfix_CancelAction_ItemActionDynamic(ItemActionDynamic.ItemActionDynamicData _actionData)
{
var entity = _actionData.invData.holdingEntity;
if (!entity.MovementRunning && _actionData != null && !entity.inventory.holdingItem.IsActionRunning(entity.inventory.holdingItemData))
{
entity.emodel.avatarController._setTrigger("weaponInspect", false);
}
}
}

View File

@@ -0,0 +1,225 @@
using HarmonyLib;
using KFCommonUtilityLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.StaticManagers;
using UnityEngine;
[TypeTarget(typeof(ItemActionRanged)), ActionDataTarget(typeof(InterruptData))]
public class ActionModuleInterruptReload
{
public float holdBeforeCancel = 0.06f;
public string firingStateName = "";
public bool instantFiringCancel = false;
[HarmonyPatch(nameof(ItemAction.StartHolding)), MethodTargetPrefix]
private bool Prefix_StartHolding(InterruptData __customData)
{
__customData.Reset();
return true;
}
[HarmonyPatch(nameof(ItemAction.ReadFrom)), MethodTargetPostfix]
private void Postfix_ReadFrom(DynamicProperties _props)
{
firingStateName = _props.GetString("FiringStateFullName");
instantFiringCancel = _props.GetBool("InstantFiringCancel");
}
[HarmonyPatch(nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
private void Postfix_OnModificationsChanged(ItemActionData _data, InterruptData __customData)
{
var invData = _data.invData;
__customData.itemAnimator = AnimationGraphBuilder.DummyWrapper;
__customData.eventBridge = null;
if (invData.model && invData.model.TryGetComponent<AnimationTargetsAbs>(out var targets) && !targets.Destroyed && targets.IsAnimationSet)
{
__customData.itemAnimator = targets.GraphBuilder.WeaponWrapper;
if (__customData.itemAnimator.IsValid)
{
__customData.eventBridge = targets.ItemAnimator.GetComponent<AnimationReloadEvents>();
}
}
}
private struct State
{
public bool executed;
public bool isReloading;
public bool isWeaponReloading;
public float lastShotTime;
}
[HarmonyPatch(nameof(ItemAction.IsActionRunning)), MethodTargetPostfix]
private void Postfix_IsActionRunning(ref bool __result, InterruptData __customData)
{
__result &= !__customData.instantFiringRequested;
}
[HarmonyPatch(nameof(ItemAction.ExecuteAction)), MethodTargetPrefix]
private bool Prefix_ExecuteAction(ItemActionData _actionData, bool _bReleased, InterruptData __customData, out State __state)
{
__state = default;
if (!_bReleased && __customData.isInterruptRequested && __customData.instantFiringRequested)
{
if (_actionData.invData.itemValue.Meta > 0)
{
if (ConsoleCmdReloadLog.LogInfo)
Log.Out($"instant firing cancel prefix!");
ItemActionRanged.ItemActionDataRanged rangedData = _actionData as ItemActionRanged.ItemActionDataRanged;
__state.executed = true;
__state.isReloading = rangedData.isReloading;
__state.isWeaponReloading = rangedData.isWeaponReloading;
__state.lastShotTime = rangedData.m_LastShotTime;
rangedData.isReloading = false;
rangedData.isWeaponReloading = false;
}
else
{
if (ConsoleCmdReloadLog.LogInfo)
Log.Out($"not fired! meta is 0");
__customData.isInterruptRequested = false;
__customData.instantFiringRequested = false;
return false;
}
}
return true;
}
[HarmonyPatch(nameof(ItemAction.ExecuteAction)), MethodTargetPostfix]
private void Postfix_ExecuteAction(ItemActionData _actionData, InterruptData __customData, State __state)
{
if (__state.executed)
{
if (ConsoleCmdReloadLog.LogInfo)
Log.Out($"instant firing cancel postfix!");
ItemActionRanged.ItemActionDataRanged rangedData = _actionData as ItemActionRanged.ItemActionDataRanged;
rangedData.isReloading = __state.isReloading;
rangedData.isWeaponReloading = __state.isWeaponReloading;
if (__customData.itemAnimator.IsValid && __customData.eventBridge)
{
if (rangedData.m_LastShotTime > __state.lastShotTime && rangedData.m_LastShotTime < Time.time + 1f)
{
if (ConsoleCmdReloadLog.LogInfo)
Log.Out($"executed!");
__customData.eventBridge.OnReloadEnd();
__customData.itemAnimator.Play(firingStateName, -1, 0f);
}
else
{
if (ConsoleCmdReloadLog.LogInfo)
Log.Out($"not fired! last shot time {__state.lastShotTime} ranged data shot time {rangedData.m_LastShotTime} cur time {Time.time}");
__customData.isInterruptRequested = false;
__customData.instantFiringRequested = false;
}
}
}
}
[HarmonyPatch(nameof(ItemAction.ItemActionEffects)), MethodTargetPrefix]
private bool Prefix_ItemActionEffects(ItemActionData _actionData, int _firingState, InterruptData __customData)
{
var rangedData = _actionData as ItemActionRanged.ItemActionDataRanged;
if (_firingState != 0 && (rangedData.isReloading || rangedData.isWeaponReloading) && !(rangedData.invData.holdingEntity is EntityPlayerLocal) && __customData.eventBridge)
{
__customData.eventBridge.OnReloadEnd();
__customData.itemAnimator.Play(firingStateName, -1, 0f);
}
return true;
}
public bool IsRequestPossible(InterruptData interruptData)
{
return interruptData.eventBridge && interruptData.itemAnimator.IsValid;
}
public class InterruptData
{
public bool isInterruptRequested;
public float holdStartTime = -1f;
public bool instantFiringRequested = false;
public AnimationReloadEvents eventBridge;
public IAnimatorWrapper itemAnimator;
public InterruptData(ItemInventoryData invData, int actionIndex, ActionModuleInterruptReload module)
{
//if (invData.model && invData.model.TryGetComponent<AnimationTargetsAbs>(out var targets) && !targets.Destroyed)
//{
// itemAnimator = targets.ItemAnimator;
// if (itemAnimator)
// {
// eventBridge = itemAnimator.GetComponent<AnimationReloadEvents>();
// }
//}
}
public void Reset()
{
isInterruptRequested = false;
holdStartTime = -1f;
instantFiringRequested = false;
}
}
}
[HarmonyPatch]
internal static class ReloadInterruptionPatches
{
//interrupt reload with firing
[HarmonyPatch(typeof(ItemClass), nameof(ItemClass.ExecuteAction))]
[HarmonyPrefix]
private static bool Prefix_ExecuteAction_ItemClass(ItemClass __instance, int _actionIdx, ItemInventoryData _data, bool _bReleased, PlayerActionsLocal _playerActions)
{
ItemAction curAction = __instance.Actions[_actionIdx];
if (curAction is ItemActionRanged || curAction is ItemActionZoom)
{
int curActionIndex = MultiActionManager.GetActionIndexForEntity(_data.holdingEntity);
var rangedAction = __instance.Actions[curActionIndex] as ItemActionRanged;
var rangedData = _data.actionData[curActionIndex] as ItemActionRanged.ItemActionDataRanged;
if (rangedData != null && rangedData is IModuleContainerFor<ActionModuleInterruptReload.InterruptData> dataModule && rangedAction is IModuleContainerFor<ActionModuleInterruptReload> actionModule)
{
if (!_bReleased && _playerActions != null && actionModule.Instance.IsRequestPossible(dataModule.Instance) && ((_playerActions.Primary.IsPressed && _actionIdx == curActionIndex && _data.itemValue.Meta > 0) || (_playerActions.Secondary.IsPressed && curAction is ItemActionZoom)) && (rangedData.isReloading || rangedData.isWeaponReloading) && !dataModule.Instance.isInterruptRequested)
{
if (dataModule.Instance.holdStartTime < 0)
{
dataModule.Instance.holdStartTime = Time.time;
return false;
}
if (Time.time - dataModule.Instance.holdStartTime >= actionModule.Instance.holdBeforeCancel)
{
if (!rangedAction.reloadCancelled(rangedData))
{
rangedAction.CancelReload(rangedData);
}
if (ConsoleCmdReloadLog.LogInfo)
Log.Out($"interrupt requested!");
dataModule.Instance.isInterruptRequested = true;
if (actionModule.Instance.instantFiringCancel && curAction is ItemActionRanged)
{
if (ConsoleCmdReloadLog.LogInfo)
Log.Out($"instant firing cancel!");
dataModule.Instance.instantFiringRequested = true;
return true;
}
}
return false;
}
if (_bReleased)
{
dataModule.Instance.Reset();
}
}
}
return true;
}
[HarmonyPatch(typeof(ItemAction), nameof(ItemAction.CancelReload))]
[HarmonyPrefix]
private static bool Prefix_CancelReload_ItemAction(ItemActionData _actionData)
{
if (_actionData?.invData?.holdingEntity is EntityPlayerLocal && AnimationRiggingManager.IsHoldingRiggedWeapon(_actionData.invData.holdingEntity as EntityPlayerLocal))
{
return false;
}
return true;
}
}

View File

@@ -0,0 +1,60 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.StaticManagers;
using System.Collections.Generic;
using System.Reflection.Emit;
using UniLinq;
[TypeTarget(typeof(ItemActionRanged))]
public class ActionModuleInvariableRPM
{
//added as a transpiler so that it's applied before all post processing
[HarmonyPatch(typeof(ItemActionRanged), nameof(ItemAction.OnHoldingUpdate)), MethodTargetTranspiler]
private static IEnumerable<CodeInstruction> Transpiler_OnHoldingUpdate_ItemActionRanged(IEnumerable<CodeInstruction> instructions)
{
var codes = instructions.ToList();
var mtd_getvalue = AccessTools.Method(typeof(EffectManager), nameof(EffectManager.GetValue));
for (int i = 0; i < codes.Count; i++)
{
if (codes[i].Calls(mtd_getvalue))
{
int start = -1;
for (int j = i; j >= 0; j--)
{
if (codes[j].opcode == OpCodes.Stloc_0)
{
start = j + 2;
break;
}
}
if (start >= 0)
{
codes.InsertRange(i + 2, new[]
{
new CodeInstruction(OpCodes.Ldarg_0),
new CodeInstruction(OpCodes.Ldloc_0),
CodeInstruction.Call(typeof(ActionModuleInvariableRPM), nameof(CalcFixedRPM))
});
codes.RemoveRange(start, i - start + 2);
//Log.Out("Invariable RPM Patch applied!");
}
break;
}
}
return codes;
}
private static float CalcFixedRPM(ItemActionRanged rangedAction, ItemActionRanged.ItemActionDataRanged rangedData)
{
float rpm = 60f / rangedData.OriginalDelay;
float perc = 1f;
var tags = rangedData.invData.item.ItemTags;
MultiActionManager.ModifyItemTags(rangedData.invData.itemValue, rangedData, ref tags);
rangedData.invData.item.Effects.ModifyValue(rangedData.invData.holdingEntity, PassiveEffects.RoundsPerMinute, ref rpm, ref perc, rangedData.invData.itemValue.Quality, tags);
//Log.Out($"fixed RPM {res}");
return 60f / (rpm * perc);
}
}

View File

@@ -0,0 +1,105 @@
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.StaticManagers;
using System;
using System.Collections;
using System.Collections.Generic;
[TypeTarget(typeof(ItemAction)), ActionDataTarget(typeof(LocalPassiveCacheData))]
public class ActionModuleLocalPassiveCache
{
//public int[] nameHashes;
//[MethodTargetPostfix(nameof(ItemAction.ReadFrom))]
//private void Postfix_ReadFrom(DynamicProperties _props)
//{
// string str = _props.Values["CachePassives"];
// if (!string.IsNullOrEmpty(str))
// {
// nameHashes = Array.ConvertAll(str.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries), s => s.GetHashCode());
// }
//}
//[MethodTargetPrefix(nameof(ItemAction.StartHolding))]
//private bool Prefix_StartHolding(ItemActionData _data, LocalPassiveCacheData __customData)
//{
// if (nameHashes != null)
// {
// for (int i = 0; i < nameHashes.Length; i++)
// {
// __customData.passives[i] = EffectManager.GetValue(nameHashes[i], _data.invData.itemValue, 0, _data.invData.holdingEntity);
// __customData.markedForCache[i] = false;
// }
// }
// return true;
//}
//[MethodTargetPrefix(nameof(ItemAction.OnHoldingUpdate))]
//private bool Prefix_OnHoldingUpdate(ItemActionData _actionData, LocalPassiveCacheData __customData)
//{
// if (!_actionData.invData.holdingEntity.isEntityRemote && nameHashes != null)
// {
// for (int i = 0; i < nameHashes.Length; i++)
// {
// if (__customData.markedForCache[i])
// {
// __customData.cache[i] = EffectManager.GetValue(nameHashes[i], _actionData.invData.itemValue, 0, _actionData.invData.holdingEntity);
// __customData.markedForCache[i] = false;
// }
// }
// }
// return true;
//}
public class LocalPassiveCacheData : IEnumerable<int>
{
//public float[] cache;
//public bool[] markedForCache;
//public ActionModuleLocalPassiveCache _cacheModule;
public ItemInventoryData invData;
private Dictionary<int, float> dict_hash_value = new Dictionary<int, float>();
private Dictionary<int, string> dict_hash_name = new Dictionary<int, string>();
public LocalPassiveCacheData(ItemInventoryData _invData, int _indexOfAction, ActionModuleLocalPassiveCache _cacheModule)
{
//this._cacheModule = _cacheModule;
this.invData = _invData;
//if (_cacheModule.nameHashes != null)
//{
// cache = new float[_cacheModule.nameHashes.Length];
// //markedForCache = new bool[_cacheModule.nameHashes.Length];
//}
}
public void CachePassive(PassiveEffects target, int targetHash, string targetStr, FastTags<TagGroup.Global> tags)
{
if (invData.holdingEntity.isEntityRemote)
return;
if (!dict_hash_name.ContainsKey(targetHash))
dict_hash_name[targetHash] = targetStr;
dict_hash_value[targetHash] = EffectManager.GetValue(target, invData.itemValue, 0, invData.holdingEntity, null, tags);
//markedForCache[index] = true;
}
public float GetCachedValue(int targetHash)
{
return dict_hash_value.TryGetValue(targetHash, out float res) ? res : 0;
}
public string GetCachedName(int targetHash)
{
return dict_hash_name.TryGetValue(targetHash, out string res) ? res : string.Empty;
}
public IEnumerator<int> GetEnumerator()
{
return dict_hash_value.Keys.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@@ -0,0 +1,178 @@
using HarmonyLib;
using KFCommonUtilityLib;
using KFCommonUtilityLib.Scripts.Attributes;
using System;
using System.Collections.Generic;
using System.Reflection.Emit;
using UniLinq;
[TypeTarget(typeof(ItemActionRanged))]
public class ActionModuleMetaConsumer
{
public string[] consumeDatas;
public FastTags<TagGroup.Global>[] consumeTags;
private float[] consumeStocks;
private float[] consumeValues;
private static FastTags<TagGroup.Global> TagsConsumption = FastTags<TagGroup.Global>.Parse("ConsumptionValue");
[HarmonyPatch(nameof(ItemAction.ReadFrom)), MethodTargetPostfix]
private void Postfix_ReadFrom(DynamicProperties _props, ItemAction __instance)
{
string consumeData = string.Empty;
_props.Values.TryGetValue("ConsumeData", out consumeData);
_props.Values.TryGetValue("ConsumeTags", out string tags);
FastTags<TagGroup.Global> commonTags = string.IsNullOrEmpty(tags) ? FastTags<TagGroup.Global>.none : FastTags<TagGroup.Global>.Parse(tags);
if (string.IsNullOrEmpty(consumeData))
{
Log.Error($"No consume data found on item {__instance.item.Name} action {__instance.ActionIndex}");
return;
}
consumeDatas = consumeData.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray();
consumeTags = consumeDatas.Select(s => FastTags<TagGroup.Global>.Parse(s) | commonTags | TagsConsumption).ToArray();
consumeStocks = new float[consumeDatas.Length];
consumeValues = new float[consumeDatas.Length];
}
[HarmonyPatch(typeof(ItemActionRanged), nameof(ItemAction.ExecuteAction)), MethodTargetTranspiler]
private static IEnumerable<CodeInstruction> Transpiler_ItemActionRanged_ExecuteAction(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var codes = instructions.ToList();
var fld_started = AccessTools.Field(typeof(ItemActionRanged.ItemActionDataRanged), nameof(ItemActionRanged.ItemActionDataRanged.burstShotStarted));
var fld_infinite = AccessTools.Field(typeof(ItemActionAttack), nameof(ItemActionAttack.InfiniteAmmo));
var mtd_consume = AccessTools.Method(typeof(ItemActionRanged), nameof(ItemActionRanged.ConsumeAmmo));
var lbd_module = generator.DeclareLocal(typeof(ActionModuleMetaConsumer));
var prop_instance = AccessTools.PropertyGetter(typeof(IModuleContainerFor<ActionModuleMetaConsumer>), nameof(IModuleContainerFor<ActionModuleMetaConsumer>.Instance));
var prop_itemvalue = AccessTools.PropertyGetter(typeof(ItemInventoryData), nameof(ItemInventoryData.itemValue));
for (int i = 0; i < codes.Count; i++)
{
if (codes[i].opcode == OpCodes.Stloc_S && ((LocalBuilder)codes[i].operand).LocalIndex == 6)
{
codes.InsertRange(i - 4, new[]
{
new CodeInstruction(OpCodes.Ldarg_0).WithLabels(codes[i - 4].ExtractLabels()),
new CodeInstruction(OpCodes.Castclass, typeof(IModuleContainerFor<ActionModuleMetaConsumer>)),
new CodeInstruction(OpCodes.Callvirt, prop_instance),
new CodeInstruction(OpCodes.Stloc_S, lbd_module),
});
i += 4;
}
else if (codes[i].StoresField(fld_started) && codes[i - 1].LoadsConstant(1))
{
var lbl = generator.DefineLabel();
var original = codes[i - 2];
codes.InsertRange(i - 2, new[]
{
new CodeInstruction(OpCodes.Ldloc_S, lbd_module).WithLabels(original.ExtractLabels()),
new CodeInstruction(OpCodes.Ldloc_0),
CodeInstruction.LoadField(typeof(ItemActionData), nameof(ItemActionData.invData)),
new CodeInstruction(OpCodes.Callvirt, prop_itemvalue),
new CodeInstruction(OpCodes.Ldloc_S, 6),
new CodeInstruction(OpCodes.Ldarg_0),
CodeInstruction.LoadField(typeof(ItemActionAttack), nameof(ItemActionAttack.soundEmpty)),
CodeInstruction.Call(typeof(ActionModuleMetaConsumer), nameof(CheckAndCacheMetaData)),
new CodeInstruction(OpCodes.Brtrue_S, lbl),
new CodeInstruction(OpCodes.Ret)
});
original.WithLabels(lbl);
i += 10;
}
else if (codes[i].Calls(mtd_consume))
{
var lbl = generator.DefineLabel();
for (int j = i - 1; j >= 0; j--)
{
if (codes[j].LoadsField(fld_infinite) && codes[j + 1].Branches(out _))
{
codes[j + 1].operand = lbl;
break;
}
}
codes.InsertRange(i + 1, new[]
{
new CodeInstruction(OpCodes.Ldloc_S, lbd_module).WithLabels(lbl),
new CodeInstruction(OpCodes.Ldloc_0),
CodeInstruction.LoadField(typeof(ItemActionData), nameof(ItemActionData.invData)),
new CodeInstruction(OpCodes.Callvirt, prop_itemvalue),
new CodeInstruction(OpCodes.Ldloc_S, 6),
CodeInstruction.Call(typeof(ActionModuleMetaConsumer), nameof(ConsumeMetaData)),
});
break;
}
}
return codes;
}
public bool CheckAndCacheMetaData(ItemValue itemValue, EntityAlive holdingEntity, string soundEmpty)
{
for (int i = 0; i < consumeDatas.Length; i++)
{
string consumeData = consumeDatas[i];
float stock = (float)itemValue.GetMetadata(consumeData);
float consumption = EffectManager.GetValue(CustomEnums.CustomTaggedEffect, itemValue, float.MaxValue, holdingEntity, null, consumeTags[i]);
if (stock < consumption)
{
holdingEntity.PlayOneShot(soundEmpty);
return false;
}
consumeStocks[i] = stock;
consumeValues[i] = consumption;
}
return true;
}
public void ConsumeMetaData(ItemValue itemValue, EntityAlive holdingEntity)
{
for (int i = 0; i < consumeDatas.Length; i++)
{
itemValue.SetMetadata(consumeDatas[i], consumeStocks[i] - consumeValues[i], TypedMetadataValue.TypeTag.Float);
holdingEntity.MinEventContext.Tags = consumeTags[i];
holdingEntity.FireEvent(CustomEnums.onRechargeValueUpdate, true);
}
}
//[HarmonyPatch(nameof(ItemAction.ExecuteAction)), MethodTargetPrefix]
//private bool Prefix_ExecuteAction(ItemActionData _actionData, bool _bReleased, ItemActionRanged __instance)
//{
// ItemActionRanged.ItemActionDataRanged _data = _actionData as ItemActionRanged.ItemActionDataRanged;
// EntityAlive holdingEntity = _actionData.invData.holdingEntity;
// ItemValue itemValue = _actionData.invData.itemValue;
// if (!_bReleased)
// {
// int burstCount = __instance.GetBurstCount(_actionData);
// if (holdingEntity.inventory.holdingItemItemValue.PercentUsesLeft <= 0f || (_data.curBurstCount >= burstCount && burstCount != -1) || (!__instance.InfiniteAmmo && itemValue.Meta <= 0))
// {
// return true;
// }
// for (int i = 0; i < consumeDatas.Length; i++)
// {
// string consumeData = consumeDatas[i];
// float stock = (float)itemValue.GetMetadata(consumeData);
// float consumption = EffectManager.GetValue(CustomEnums.CustomTaggedEffect, itemValue, float.MaxValue, _actionData.invData.holdingEntity, null, consumeTags[i]);
// if (stock < consumption)
// {
// if (!_data.bPressed)
// {
// holdingEntity.PlayOneShot(__instance.soundEmpty);
// _data.bPressed = true;
// }
// return false;
// }
// consumeStocks[i] = stock;
// consumeValues[i] = consumption;
// }
// for (int i = 0; i < consumeDatas.Length; i++)
// {
// itemValue.SetMetadata(consumeDatas[i], consumeStocks[i] - consumeValues[i], TypedMetadataValue.TypeTag.Float);
// holdingEntity.MinEventContext.Tags = consumeTags[i];
// holdingEntity.FireEvent(CustomEnums.onRechargeValueUpdate, true);
// }
// }
// return true;
//}
}

View File

@@ -0,0 +1,166 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.StaticManagers;
using System;
using UniLinq;
using UnityEngine;
[TypeTarget(typeof(ItemActionRanged)), ActionDataTarget(typeof(MetaRechargerData))]
public class ActionModuleMetaRecharger
{
public struct RechargeTags
{
public FastTags<TagGroup.Global> tagsOriginal;
public FastTags<TagGroup.Global> tagsInterval;
public FastTags<TagGroup.Global> tagsMaximum;
public FastTags<TagGroup.Global> tagsValue;
public FastTags<TagGroup.Global> tagsDecrease;
public FastTags<TagGroup.Global> tagsDecreaseInterval;
}
public string[] rechargeDatas;
public RechargeTags[] rechargeTags;
private static readonly FastTags<TagGroup.Global> TagsInterval = FastTags<TagGroup.Global>.Parse("RechargeDataInterval");
private static readonly FastTags<TagGroup.Global> TagsMaximum = FastTags<TagGroup.Global>.Parse("RechargeDataMaximum");
private static readonly FastTags<TagGroup.Global> TagsValue = FastTags<TagGroup.Global>.Parse("RechargeDataValue");
private static readonly FastTags<TagGroup.Global> TagsDecrease = FastTags<TagGroup.Global>.Parse("RechargeDataDecrease");
private static readonly FastTags<TagGroup.Global> TagsDecreaseInterval = FastTags<TagGroup.Global>.Parse("RechargeDecreaseInterval");
[HarmonyPatch(nameof(ItemAction.ReadFrom)), MethodTargetPostfix]
private void Postfix_ReadFrom(DynamicProperties _props, ItemAction __instance)
{
rechargeDatas = null;
rechargeTags = null;
string rechargeData = string.Empty;
_props.Values.TryGetValue("RechargeData", out rechargeData);
_props.Values.TryGetValue("RechargeTags", out string tags);
FastTags<TagGroup.Global> commonTags = string.IsNullOrEmpty(tags) ? FastTags<TagGroup.Global>.none : FastTags<TagGroup.Global>.Parse(tags);
if (string.IsNullOrEmpty(rechargeData))
{
Log.Error($"No recharge data found on item {__instance.item.Name} action {__instance.ActionIndex}");
return;
}
rechargeDatas = rechargeData.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray();
rechargeTags = rechargeDatas.Select(s =>
{
var _tags = FastTags<TagGroup.Global>.Parse(s) | commonTags;
return new RechargeTags
{
tagsOriginal = _tags,
tagsInterval = _tags | TagsInterval,
tagsMaximum = _tags | TagsMaximum,
tagsValue = _tags | TagsValue,
tagsDecrease = _tags | TagsDecrease,
tagsDecreaseInterval = _tags | TagsDecreaseInterval,
};
}).ToArray();
}
[HarmonyPatch(nameof(ItemAction.StartHolding)), MethodTargetPrefix]
private bool Prefix_StartHolding(ItemActionData _data, MetaRechargerData __customData)
{
EntityAlive holdingEntity = _data.invData.holdingEntity;
if (holdingEntity.isEntityRemote)
return true;
for (int i = 0; i < rechargeDatas.Length; i++)
{
holdingEntity.MinEventContext.Tags = rechargeTags[i].tagsOriginal;
holdingEntity.FireEvent(CustomEnums.onRechargeValueUpdate, true);
}
return true;
}
public class MetaRechargerData : IBackgroundInventoryUpdater
{
private ActionModuleMetaRecharger module;
private float lastUpdateTime, lastDecreaseTime;
private int indexOfAction;
public int Index => indexOfAction;
public MetaRechargerData(ItemInventoryData _invData, int _indexOfAction, ActionModuleMetaRecharger _rechargeModule)
{
module = _rechargeModule;
indexOfAction = _indexOfAction;
lastUpdateTime = lastDecreaseTime = Time.time;
if (_rechargeModule.rechargeDatas == null)
return;
BackgroundInventoryUpdateManager.RegisterUpdater(_invData.holdingEntity, _invData.slotIdx, this);
}
public bool OnUpdate(ItemInventoryData invData)
{
ItemValue itemValue = invData.itemValue;
EntityAlive holdingEntity = invData.holdingEntity;
holdingEntity.MinEventContext.ItemInventoryData = invData;
holdingEntity.MinEventContext.ItemValue = itemValue;
holdingEntity.MinEventContext.ItemActionData = invData.actionData[indexOfAction];
float curTime = Time.time;
bool res = false;
for (int i = 0; i < module.rechargeDatas.Length; i++)
{
string rechargeData = module.rechargeDatas[i];
RechargeTags rechargeTag = module.rechargeTags[i];
float updateInterval = EffectManager.GetValue(CustomEnums.CustomTaggedEffect, itemValue, float.MaxValue, holdingEntity, null, rechargeTag.tagsInterval);
float decreaseInterval = EffectManager.GetValue(CustomEnums.CustomTaggedEffect, itemValue, float.MaxValue, holdingEntity, null, rechargeTag.tagsDecreaseInterval);
float deltaTime = curTime - lastUpdateTime;
float deltaDecreaseTime = curTime - lastDecreaseTime;
if (deltaTime > updateInterval || deltaDecreaseTime > decreaseInterval)
{
//Log.Out($"last update time {lastUpdateTime} cur time {curTime} update interval {updateInterval}");
float cur;
if (!itemValue.HasMetadata(rechargeData))
{
itemValue.SetMetadata(rechargeData, 0, TypedMetadataValue.TypeTag.Float);
cur = 0;
}
else
{
cur = (float)itemValue.GetMetadata(rechargeData);
}
float max = EffectManager.GetValue(CustomEnums.CustomTaggedEffect, itemValue, 0, holdingEntity, null, rechargeTag.tagsMaximum);
bool modified = false;
if (cur > max)
{
if (deltaDecreaseTime > decreaseInterval)
{
//the result updated here won't exceed max so it's set somewhere else, decrease slowly
float dec = EffectManager.GetValue(CustomEnums.CustomTaggedEffect, itemValue, float.MaxValue, holdingEntity, null, rechargeTag.tagsDecrease);
cur = Mathf.Max(cur - dec, max);
lastDecreaseTime = curTime;
modified = true;
}
lastUpdateTime = curTime;
}
else
{
if (cur < max && deltaTime > updateInterval)
{
//add up and clamp to max
float add = EffectManager.GetValue(CustomEnums.CustomTaggedEffect, itemValue, 0, holdingEntity, null, rechargeTag.tagsValue);
cur = Mathf.Min(cur + add, max);
lastUpdateTime = curTime;
modified = true;
}
//always set lastDecreaseTime if not overcharged, since we don't want overcharged data to decrease right after it's charged
lastDecreaseTime = curTime;
}
if (modified)
{
itemValue.SetMetadata(rechargeData, cur, TypedMetadataValue.TypeTag.Float);
}
if (invData.slotIdx == holdingEntity.inventory.holdingItemIdx && invData.slotIdx >= 0)
{
holdingEntity.MinEventContext.Tags = rechargeTag.tagsOriginal;
itemValue.FireEvent(CustomEnums.onRechargeValueUpdate, holdingEntity.MinEventContext);
//Log.Out($"action index is {holdingEntity.MinEventContext.ItemActionData.indexInEntityOfAction} after firing event");
}
res |= modified;
}
}
return res;
}
}
}

View File

@@ -0,0 +1,222 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.StaticManagers;
using KFCommonUtilityLib.Scripts.Utilities;
[TypeTarget(typeof(ItemActionAttack)), ActionDataTarget(typeof(MultiActionData))]
public class ActionModuleMultiActionFix
{
private int actionIndex;
public string GetDisplayType(ItemValue itemValue)
{
string displayType = itemValue.GetPropertyOverrideForAction("DisplayType", null, actionIndex);
if (string.IsNullOrEmpty(displayType))
{
displayType = itemValue.ItemClass.DisplayType;
}
return displayType;
}
[HarmonyPatch(nameof(ItemAction.ReadFrom)), MethodTargetPostfix]
public void Postfix_ReadFrom(ItemActionAttack __instance)
{
actionIndex = __instance.ActionIndex;
}
[HarmonyPatch(nameof(ItemAction.StartHolding)), MethodTargetPrefix]
public bool Prefix_StartHolding(ItemActionData _data, out ItemActionData __state)
{
SetAndSaveItemActionData(_data, out __state);
return true;
}
[HarmonyPatch(nameof(ItemAction.StartHolding)), MethodTargetPostfix]
public void Postfix_StartHolding(ItemActionData _data, ItemActionData __state)
{
RestoreItemActionData(_data, __state);
}
[HarmonyPatch(typeof(ItemActionRanged), nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
public void Postfix_OnModificationChanged_ItemActionRanged(ItemActionData _data, ItemActionAttack __instance)
{
var rangedData = _data as ItemActionRanged.ItemActionDataRanged;
if (rangedData != null)
{
string muzzleName;
string indexExtension = (_data.indexInEntityOfAction > 0 ? _data.indexInEntityOfAction.ToString() : "");
if (rangedData.IsDoubleBarrel)
{
muzzleName = _data.invData.itemValue.GetPropertyOverrideForAction($"Muzzle_L_Name", $"Muzzle_L{indexExtension}", _data.indexInEntityOfAction);
rangedData.muzzle = AnimationRiggingManager.GetTransformOverrideByName(rangedData.invData.model, muzzleName) ?? rangedData.muzzle;
muzzleName = _data.invData.itemValue.GetPropertyOverrideForAction($"Muzzle_R_Name", $"Muzzle_R{indexExtension}", _data.indexInEntityOfAction);
rangedData.muzzle2 = AnimationRiggingManager.GetTransformOverrideByName(rangedData.invData.model, muzzleName) ?? rangedData.muzzle2;
}
else
{
muzzleName = _data.invData.itemValue.GetPropertyOverrideForAction($"Muzzle_Name", $"Muzzle{indexExtension}", _data.indexInEntityOfAction);
rangedData.muzzle = AnimationRiggingManager.GetTransformOverrideByName(rangedData.invData.model, muzzleName) ?? rangedData.muzzle;
}
}
}
[HarmonyPatch(typeof(ItemActionLauncher), nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
public void Postfix_OnModificationChanged_ItemActionLauncher(ItemActionData _data, ItemActionAttack __instance)
{
Postfix_OnModificationChanged_ItemActionRanged(_data, __instance);
if (_data is ItemActionLauncher.ItemActionDataLauncher launcherData)
{
string indexExtension = (_data.indexInEntityOfAction > 0 ? _data.indexInEntityOfAction.ToString() : "");
string jointName = _data.invData.itemValue.GetPropertyOverrideForAction($"ProjectileJoint_Name", $"ProjectileJoint{indexExtension}", _data.indexInEntityOfAction);
launcherData.projectileJoint = AnimationRiggingManager.GetTransformOverrideByName(launcherData.invData.model, jointName) ?? launcherData.projectileJoint;
}
}
[HarmonyPatch(nameof(ItemAction.StopHolding)), MethodTargetPrefix]
public bool Prefix_StopHolding(ItemActionData _data, out ItemActionData __state)
{
SetAndSaveItemActionData(_data, out __state);
return true;
}
[HarmonyPatch(nameof(ItemAction.StopHolding)), MethodTargetPostfix]
public void Postfix_StopHolding(ItemActionData _data, ItemActionData __state)
{
RestoreItemActionData(_data, __state);
}
[HarmonyPatch(typeof(ItemActionLauncher), nameof(ItemAction.ItemActionEffects)), MethodTargetPrefix]
public bool Prefix_ItemActionEffects(ItemActionData _actionData, out ItemActionData __state)
{
SetAndSaveItemActionData(_actionData, out __state);
return true;
}
[HarmonyPatch(typeof(ItemActionLauncher), nameof(ItemAction.ItemActionEffects)), MethodTargetPostfix]
public void Postfix_ItemActionEffects(ItemActionData _actionData, ItemActionData __state)
{
RestoreItemActionData(_actionData, __state);
}
[HarmonyPatch(nameof(ItemAction.CancelAction)), MethodTargetPrefix]
public bool Prefix_CancelAction(ItemActionData _actionData, out ItemActionData __state)
{
SetAndSaveItemActionData(_actionData, out __state);
return true;
}
[HarmonyPatch(nameof(ItemAction.CancelAction)), MethodTargetPostfix]
public void Postfix_CancelAction(ItemActionData _actionData, ItemActionData __state)
{
RestoreItemActionData(_actionData, __state);
}
[HarmonyPatch(nameof(ItemActionAttack.CancelReload)), MethodTargetPrefix]
public bool Prefix_CancelReload(ItemActionData _actionData, out ItemActionData __state)
{
SetAndSaveItemActionData(_actionData, out __state);
return true;
}
[HarmonyPatch(nameof(ItemActionAttack.CancelReload)), MethodTargetPostfix]
public void Postfix_CancelReload(ItemActionData _actionData, ItemActionData __state)
{
RestoreItemActionData(_actionData, __state);
}
[HarmonyPatch(nameof(ItemActionAttack.ReloadGun)), MethodTargetPrefix]
public bool Prefix_ReloadGun(ItemActionData _actionData)
{
//int reloadAnimationIndex = MultiActionManager.GetMetaIndexForActionIndex(_actionData.invData.holdingEntity.entityId, _actionData.indexInEntityOfAction);
_actionData.invData.holdingEntity.emodel?.avatarController?.UpdateInt(MultiActionUtils.ExecutingActionIndexHash, _actionData.indexInEntityOfAction, false);
_actionData.invData.holdingEntity.MinEventContext.ItemActionData = _actionData;
//MultiActionManager.GetMappingForEntity(_actionData.invData.holdingEntity.entityId)?.SaveMeta();
return true;
}
[HarmonyPatch(nameof(ItemAction.OnHUD)), MethodTargetPrefix]
public bool Prefix_OnHUD(ItemActionData _actionData)
{
if (_actionData.invData?.holdingEntity?.MinEventContext?.ItemActionData == null || _actionData.indexInEntityOfAction != _actionData.invData.holdingEntity.MinEventContext.ItemActionData.indexInEntityOfAction)
return false;
return true;
}
//[MethodTargetPrefix(nameof(ItemActionAttack.ExecuteAction), typeof(ItemActionRanged))]
//public bool Prefix_ExecuteAction(ItemActionData _actionData, MultiActionData __customData)
//{
// //when executing action, set last action index so that correct accuracy is used for drawing crosshair
// if (_actionData.invData.holdingEntity is EntityPlayerLocal player)
// {
// ((ItemActionRanged.ItemActionDataRanged)_actionData).lastAccuracy = __customData.lastAccuracy;
// }
// return true;
//}
//[MethodTargetPrefix("updateAccuracy", typeof(ItemActionRanged))]
//public bool Prefix_updateAccuracy(ItemActionData _actionData, MultiActionData __customData)
//{
// if (_actionData.invData.holdingEntity is EntityPlayerLocal player && MultiActionManager.GetActionIndexForEntityID(player.entityId) == _actionData.indexInEntityOfAction)
// return true;
// //always update custom accuracy
// ItemActionRanged.ItemActionDataRanged rangedData = _actionData as ItemActionRanged.ItemActionDataRanged;
// (rangedData.lastAccuracy, __customData.lastAccuracy) = (__customData.lastAccuracy, rangedData.lastAccuracy);
// return true;
//}
//[MethodTargetPostfix("updateAccuracy", typeof(ItemActionRanged))]
//public void Postfix_updateAccuracy(ItemActionData _actionData, MultiActionData __customData)
//{
// //retain rangedData accuracy if it's the last executed action
// ItemActionRanged.ItemActionDataRanged rangedData = _actionData as ItemActionRanged.ItemActionDataRanged;
// if (_actionData.invData.holdingEntity is EntityPlayerLocal player && MultiActionManager.GetActionIndexForEntityID(player.entityId) == _actionData.indexInEntityOfAction)
// {
// __customData.lastAccuracy = rangedData.lastAccuracy;
// }
// else
// {
// (rangedData.lastAccuracy, __customData.lastAccuracy) = (__customData.lastAccuracy, rangedData.lastAccuracy);
// }
//}
[HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.onHoldingEntityFired)), MethodTargetPrefix]
public bool Prefix_onHoldingEntityFired(ItemActionData _actionData)
{
if (!_actionData.invData.holdingEntity.isEntityRemote)
{
_actionData.invData.holdingEntity?.emodel?.avatarController.UpdateInt(MultiActionUtils.ExecutingActionIndexHash, _actionData.indexInEntityOfAction);
//_actionData.invData.holdingEntity?.emodel?.avatarController.CancelEvent("WeaponFire");
}
return true;
}
//[MethodTargetPostfix("onHoldingEntityFired", typeof(ItemActionRanged))]
//public void Postfix_onHoldingEntityFired(ItemActionData _actionData, MultiActionData __customData)
//{
// //after firing, if it's the last executed action then update custom accuracy
// if (_actionData.invData.holdingEntity is EntityPlayerLocal player && MultiActionManager.GetActionIndexForEntityID(player.entityId) == _actionData.indexInEntityOfAction)
// {
// __customData.lastAccuracy = ((ItemActionRanged.ItemActionDataRanged)_actionData).lastAccuracy;
// }
//}
public static void SetAndSaveItemActionData(ItemActionData _actionData, out ItemActionData lastActionData)
{
lastActionData = _actionData.invData.holdingEntity.MinEventContext.ItemActionData;
_actionData.invData.holdingEntity.MinEventContext.ItemActionData = _actionData;
}
public static void RestoreItemActionData(ItemActionData _actionData, ItemActionData lastActionData)
{
if (lastActionData != null)
_actionData.invData.holdingEntity.MinEventContext.ItemActionData = lastActionData;
}
public class MultiActionData
{
public float lastAccuracy;
public MultiActionData(ItemInventoryData _invData, int _indexInEntityOfAction, ActionModuleMultiActionFix _module)
{
}
}
}

View File

@@ -0,0 +1,302 @@
using HarmonyLib;
using KFCommonUtilityLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.StaticManagers;
using KFCommonUtilityLib.Scripts.Utilities;
using System.Collections.Generic;
using System.Reflection.Emit;
using UniLinq;
using UnityEngine;
[TypeTarget(typeof(ItemActionRanged)), ActionDataTarget(typeof(MultiBarrelData))]
public class ActionModuleMultiBarrel
{
[HarmonyPatch(nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
public void Postfix_OnModificationChanged(ItemActionData _data, MultiBarrelData __customData, ItemActionRanged __instance)
{
int actionIndex = _data.indexInEntityOfAction;
string originalValue = false.ToString();
__instance.Properties.ParseString("MuzzleIsPerRound", ref originalValue);
__customData.muzzleIsPerRound = bool.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("MuzzleIsPerRound", originalValue, actionIndex));
originalValue = false.ToString();
__instance.Properties.ParseString("OneRoundMultiShot", ref originalValue);
__customData.oneRoundMultishot = bool.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("OneRoundMultiShot", originalValue, actionIndex));
originalValue = 1.ToString();
__instance.Properties.ParseString("RoundsPerShot", ref originalValue);
__customData.roundsPerShot = int.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("RoundsPerShot", originalValue, actionIndex));
originalValue = 1.ToString();
__instance.Properties.ParseString("BarrelCount", ref originalValue);
__customData.barrelCount = int.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("BarrelCount", originalValue, actionIndex));
//Log.Out($"MuzzleIsPerRound: {__customData.muzzleIsPerRound} OneRoundMultiShot: {__customData.oneRoundMultishot} RoundsPerShot: {__customData.roundsPerShot} BarrelCount: {__customData.barrelCount}");
__customData.muzzles = new Transform[__customData.barrelCount];
__customData.projectileJoints = new Transform[__customData.barrelCount];
for (int i = 0; i < __customData.barrelCount; i++)
{
string muzzleName = _data.invData.itemValue.GetPropertyOverrideForAction($"MBMuzzle{i}_Name", $"MBMuzzle{i}", actionIndex);
__customData.muzzles[i] = AnimationRiggingManager.GetTransformOverrideByName(_data.invData.model, muzzleName);
string jointName = _data.invData.itemValue.GetPropertyOverrideForAction($"MBProjectileJoint{i}_Name", $"MBProjectileJoint{i}", actionIndex);
__customData.projectileJoints[i] = AnimationRiggingManager.GetTransformOverrideByName(_data.invData.model, jointName);
}
int meta = MultiActionUtils.GetMetaByActionIndex(_data.invData.itemValue, actionIndex);
__customData.SetCurrentBarrel(meta);
((ItemActionRanged.ItemActionDataRanged)_data).IsDoubleBarrel = false;
}
[HarmonyPatch(typeof(ItemActionLauncher), nameof(ItemAction.StartHolding)), MethodTargetPrefix]
public void Prefix_StartHolding_ItemActionLauncher(ItemActionData _data, ItemActionLauncher __instance, MultiBarrelData __customData)
{
ItemActionLauncher.ItemActionDataLauncher launcherData = _data as ItemActionLauncher.ItemActionDataLauncher;
launcherData.projectileJoint = __customData.projectileJoints[0];
}
[HarmonyPatch(typeof(ItemActionLauncher), nameof(ItemAction.StartHolding)), MethodTargetPostfix]
public void Postfix_StartHolding_ItemActionLauncher(ItemActionData _data, ItemActionLauncher __instance, MultiBarrelData __customData)
{
ItemActionLauncher.ItemActionDataLauncher launcherData = _data as ItemActionLauncher.ItemActionDataLauncher;
if (launcherData?.projectileInstance != null && __customData.oneRoundMultishot && __customData.roundsPerShot > 1)
{
int count = launcherData.projectileInstance.Count;
int times = __customData.roundsPerShot - 1;
for (int i = 0; i < times; i++)
{
launcherData.projectileJoint = __customData.projectileJoints[i + 1];
for (int j = 0; j < count; j++)
{
launcherData.projectileInstance.Add(__instance.instantiateProjectile(_data));
}
}
}
launcherData.projectileJoint = __customData.projectileJoints[__customData.curBarrelIndex];
}
[HarmonyPatch(nameof(ItemActionRanged.getUserData)), MethodTargetPostfix]
public void Postfix_getUserData(MultiBarrelData __customData, ref int __result)
{
__result |= ((byte)__customData.curBarrelIndex) << 8;
}
[HarmonyPatch(typeof(ItemActionRanged), nameof(ItemAction.ItemActionEffects)), MethodTargetPrefix]
public bool Prefix_ItemActionEffects_ItemActionRanged(ItemActionData _actionData, int _userData, int _firingState, MultiBarrelData __customData)
{
ItemActionRanged.ItemActionDataRanged rangedData = _actionData as ItemActionRanged.ItemActionDataRanged;
if (rangedData != null && _firingState != 0)
{
byte index = (byte)(_userData >> 8);
rangedData.muzzle = __customData.muzzles[index];
__customData.SetAnimatorParam(index);
}
return true;
}
[HarmonyPatch(typeof(ItemActionLauncher), nameof(ItemAction.ItemActionEffects)), MethodTargetPrefix]
public bool Prefix_ItemActionEffects_ItemActionLauncher(ItemActionData _actionData, int _userData, int _firingState, MultiBarrelData __customData)
{
ItemActionLauncher.ItemActionDataLauncher launcherData = _actionData as ItemActionLauncher.ItemActionDataLauncher;
if (launcherData != null)
{
launcherData.projectileJoint = __customData.projectileJoints[(byte)(_userData >> 8)];
}
return Prefix_ItemActionEffects_ItemActionRanged(_actionData, _userData, _firingState, __customData);
}
[HarmonyPatch(typeof(ItemActionRanged), nameof(ItemAction.ExecuteAction)), MethodTargetTranspiler]
private static IEnumerable<CodeInstruction> Transpiler_ExecuteAction_ItemActionRanged(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var codes = instructions.ToList();
var mtd_getmax = AccessTools.Method(typeof(ItemActionRanged), nameof(ItemActionRanged.GetMaxAmmoCount));
var mtd_consume = AccessTools.Method(typeof(ItemActionRanged), nameof(ItemActionRanged.ConsumeAmmo));
var prop_instance = AccessTools.PropertyGetter(typeof(IModuleContainerFor<MultiBarrelData>), nameof(IModuleContainerFor<MultiBarrelData>.Instance));
Label loopStart = generator.DefineLabel();
Label loopCondi = generator.DefineLabel();
LocalBuilder lbd_data_module = generator.DeclareLocal(typeof(ActionModuleMultiBarrel.MultiBarrelData));
LocalBuilder lbd_i = generator.DeclareLocal(typeof(int));
LocalBuilder lbd_rounds = generator.DeclareLocal(typeof(int));
for (int i = 0; i < codes.Count; i++)
{
//prepare loop and store local variables
if (codes[i].opcode == OpCodes.Stloc_S && ((LocalBuilder)codes[i].operand).LocalIndex == 6)
{
codes[i + 1].WithLabels(loopStart);
codes.InsertRange(i + 1, new[]
{
new CodeInstruction(OpCodes.Ldarg_1),
new CodeInstruction(OpCodes.Castclass, typeof(IModuleContainerFor<MultiBarrelData>)),
new CodeInstruction(OpCodes.Callvirt, prop_instance),
new CodeInstruction(OpCodes.Stloc_S, lbd_data_module),
new CodeInstruction(OpCodes.Ldc_I4_0),
new CodeInstruction(OpCodes.Stloc_S, lbd_i),
new CodeInstruction(OpCodes.Ldloc_S, lbd_data_module),
CodeInstruction.LoadField(typeof(ActionModuleMultiBarrel.MultiBarrelData), nameof(ActionModuleMultiBarrel.MultiBarrelData.roundsPerShot)),
new CodeInstruction(OpCodes.Stloc_S, lbd_rounds),
new CodeInstruction(OpCodes.Br_S, loopCondi),
});
i += 11;
}
//one round multi shot check
else if (codes[i].Calls(mtd_consume))
{
Label lbl = generator.DefineLabel();
codes[i - 5].WithLabels(lbl);
codes.InsertRange(i - 5, new[]
{
new CodeInstruction(OpCodes.Ldloc_S, lbd_data_module),
CodeInstruction.LoadField(typeof(ActionModuleMultiBarrel.MultiBarrelData), nameof(ActionModuleMultiBarrel.MultiBarrelData.oneRoundMultishot)),
new CodeInstruction(OpCodes.Brfalse_S, lbl),
new CodeInstruction(OpCodes.Ldloc_S, lbd_i),
new CodeInstruction(OpCodes.Ldc_I4_0),
new CodeInstruction(OpCodes.Bgt_S, codes[i - 3].operand)
});
i += 6;
}
//loop conditions and cycle barrels
else if (codes[i].Calls(mtd_getmax))
{
Label lbl_pre = generator.DefineLabel();
Label lbl_post = generator.DefineLabel();
CodeInstruction origin = codes[i - 2];
codes.InsertRange(i - 2, new[]
{
new CodeInstruction(OpCodes.Ldloc_S, lbd_data_module).WithLabels(origin.ExtractLabels()),
CodeInstruction.LoadField(typeof(ActionModuleMultiBarrel.MultiBarrelData), nameof(ActionModuleMultiBarrel.MultiBarrelData.muzzleIsPerRound)),
new CodeInstruction(OpCodes.Brfalse_S, lbl_pre),
new CodeInstruction(OpCodes.Ldloc_S, lbd_data_module),
CodeInstruction.Call(typeof(ActionModuleMultiBarrel.MultiBarrelData), nameof(ActionModuleMultiBarrel.MultiBarrelData.CycleBarrels)),
new CodeInstruction(OpCodes.Ldloc_S, 6).WithLabels(lbl_pre),
CodeInstruction.LoadField(typeof(EntityAlive), nameof(EntityAlive.inventory)),
CodeInstruction.Call(typeof(Inventory), nameof(Inventory.CallOnToolbeltChangedInternal)),
new CodeInstruction(OpCodes.Ldloc_S, lbd_i),
new CodeInstruction(OpCodes.Ldc_I4_1),
new CodeInstruction(OpCodes.Add),
new CodeInstruction(OpCodes.Stloc_S, lbd_i),
new CodeInstruction(OpCodes.Ldloc_S, lbd_i).WithLabels(loopCondi),
new CodeInstruction(OpCodes.Ldloc_S, lbd_rounds),
new CodeInstruction(OpCodes.Blt_S, loopStart),
new CodeInstruction(OpCodes.Ldloc_S, lbd_data_module),
CodeInstruction.LoadField(typeof(ActionModuleMultiBarrel.MultiBarrelData), nameof(ActionModuleMultiBarrel.MultiBarrelData.muzzleIsPerRound)),
new CodeInstruction(OpCodes.Brtrue_S, lbl_post),
new CodeInstruction(OpCodes.Ldloc_S, lbd_data_module),
CodeInstruction.Call(typeof(ActionModuleMultiBarrel.MultiBarrelData), nameof(ActionModuleMultiBarrel.MultiBarrelData.CycleBarrels))
});
origin.WithLabels(lbl_post);
break;
}
}
return codes;
}
private static void LogInfo(int cur, int max) => Log.Out($"max rounds {max}, cur {cur}");
public class MultiBarrelData
{
public ItemInventoryData invData;
public int actionIndex;
public ActionModuleMultiBarrel module;
public bool muzzleIsPerRound;
public bool oneRoundMultishot;
public int roundsPerShot;
public int barrelCount;
public int curBarrelIndex;
public Transform[] muzzles;
public Transform[] projectileJoints;
public MultiBarrelData(ItemInventoryData _invData, int _indexInEntityOfAction, ActionModuleMultiBarrel _module)
{
invData = _invData;
actionIndex = _indexInEntityOfAction;
module = _module;
}
public void CycleBarrels()
{
curBarrelIndex = ++curBarrelIndex >= barrelCount ? 0 : curBarrelIndex;
//Log.Out($"cycle barrel index {curBarrelIndex}");
}
public void SetCurrentBarrel(int roundLeft)
{
if (muzzleIsPerRound)
{
int totalSwitches;
if (oneRoundMultishot)
{
totalSwitches = roundLeft * roundsPerShot;
}
else
{
totalSwitches = roundLeft;
}
int lastCycleSwitches = totalSwitches % barrelCount;
int barrelGroup = barrelCount / roundsPerShot;
curBarrelIndex = (barrelCount - lastCycleSwitches) / barrelGroup * barrelGroup;
}
else
{
if (oneRoundMultishot)
{
curBarrelIndex = barrelCount - (roundLeft % barrelCount);
}
else
{
curBarrelIndex = barrelCount - ((roundLeft + 1) / roundsPerShot) % barrelCount;
}
}
if (curBarrelIndex >= barrelCount)
{
curBarrelIndex = 0;
}
SetAnimatorParam(curBarrelIndex);
//Log.Out($"set barrel index {curBarrelIndex}");
}
public void SetAnimatorParam(int barrelIndex)
{
invData.holdingEntity.emodel.avatarController.UpdateInt("barrelIndex", barrelIndex, true);
//Log.Out($"set param index {barrelIndex}");
}
}
}
[HarmonyPatch]
public class MultiBarrelPatches
{
[HarmonyPatch(typeof(AnimatorRangedReloadState), nameof(AnimatorRangedReloadState.OnStateEnter))]
[HarmonyPostfix]
private static void Postfix_OnStateEnter_AnimatorRangedReloadState(AnimatorRangedReloadState __instance)
{
ItemActionLauncher.ItemActionDataLauncher launcherData = __instance.actionData as ItemActionLauncher.ItemActionDataLauncher;
if (launcherData != null && launcherData is IModuleContainerFor<ActionModuleMultiBarrel.MultiBarrelData> dataModule && dataModule.Instance.oneRoundMultishot && dataModule.Instance.roundsPerShot > 1)
{
int count = launcherData.projectileInstance.Count;
int times = dataModule.Instance.roundsPerShot - 1;
for (int i = 0; i < count; i++)
{
for (int j = 0; j < times; j++)
{
launcherData.projectileJoint = dataModule.Instance.projectileJoints[j + 1];
launcherData.projectileInstance.Insert(i * (times + 1) + j + 1, ((ItemActionLauncher)__instance.actionRanged).instantiateProjectile(launcherData));
}
}
}
}
[HarmonyPatch(typeof(AnimatorRangedReloadState), nameof(AnimatorRangedReloadState.OnStateExit))]
[HarmonyPostfix]
private static void Postfix_OnStateExit_AnimatorRangedReloadState(AnimatorRangedReloadState __instance)
{
if (__instance.actionData is IModuleContainerFor<ActionModuleMultiBarrel.MultiBarrelData> dataModule)
{
dataModule.Instance.SetCurrentBarrel(__instance.actionData.invData.itemValue.Meta);
}
}
}

View File

@@ -0,0 +1,271 @@
using HarmonyLib;
using KFCommonUtilityLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.StaticManagers;
using System.Collections.Generic;
using UnityEngine;
[TypeTarget(typeof(ItemActionZoom)), ActionDataTarget(typeof(ProceduralAimingData))]
public class ActionModuleProceduralAiming
{
[HarmonyPatch(nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
public void Postfix_OnModificationsChanged(ItemActionZoom __instance, ItemActionData _data, ProceduralAimingData __customData)
{
if (_data is IModuleContainerFor<ActionModuleErgoAffected.ErgoData> dataModule)
{
__customData.zoomInTime = dataModule.Instance.module.zoomInTimeBase / dataModule.Instance.module.aimSpeedModifierBase;
__customData.ergoData = dataModule.Instance;
}
else
{
float zoomInTimeBase = 0.3f;
__instance.Properties.ParseFloat("ZoomInTimeBase", ref zoomInTimeBase);
float aimSpeedModifierBase = 1f;
__instance.Properties.ParseFloat("AimSpeedModifierBase", ref aimSpeedModifierBase);
__customData.zoomInTime = zoomInTimeBase / aimSpeedModifierBase;
__customData.ergoData = null;
}
__customData.playerOriginTransform = null;
__customData.playerCameraPosRef = _data.invData.holdingEntity is EntityPlayerLocal player && player.bFirstPersonView ? player.cameraTransform : null;
var targets = AnimationRiggingManager.GetRigTargetsFromPlayer(_data.invData.holdingEntity);
if (__customData.playerCameraPosRef)
{
if (targets.ItemFpv)
{
if (targets is RigTargets)
{
__customData.isRigWeapon = true;
__customData.playerOriginTransform = targets.ItemAnimator.transform;
__customData.rigWeaponLocalPosition = __customData.playerOriginTransform.localPosition;
__customData.rigWeaponLocalRotation = __customData.playerOriginTransform.localRotation;
}
else
{
__customData.isRigWeapon = false;
__customData.playerOriginTransform = __customData.playerCameraPosRef.FindInAllChildren("Hips");
}
__customData.playerCameraPosRef = targets.ItemFpv.Find("PlayerCameraPositionReference");
}
else
{
__customData.playerCameraPosRef = null;
}
}
if (__customData.playerCameraPosRef)
{
__customData.aimRefTransform = targets.ItemFpv.Find("ScopeBasePositionReference");
if (__customData.aimRefTransform)
{
var scopeRefTrans = __customData.aimRefTransform.Find("ScopePositionReference");
if (!scopeRefTrans)
{
scopeRefTrans = new GameObject("ScopePositionReference").transform;
scopeRefTrans.SetParent(__customData.aimRefTransform, false);
}
scopeRefTrans.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
scopeRefTrans.localScale = Vector3.one;
__customData.aimRefTransform = scopeRefTrans;
}
}
else
{
__customData.aimRefTransform = null;
}
__customData.ResetAiming();
__customData.UpdateCurrentReference(true);
}
[HarmonyPatch(nameof(ItemAction.StopHolding)), MethodTargetPostfix]
public void Postfix_StopHolding(ProceduralAimingData __customData)
{
__customData.ResetAiming();
}
//[HarmonyPatch(nameof(ItemAction.ExecuteAction)), MethodTargetPostfix]
//public void Postfix_ExecuteAction(ProceduralAimingData __customData, ItemActionData _actionData)
//{
// if (__customData.isAiming != ((ItemActionZoom.ItemActionDataZoom)_actionData).aimingValue)
// {
// __customData.UpdateCurrentReference();
// __customData.isAiming = ((ItemActionZoom.ItemActionDataZoom)_actionData).aimingValue;
// }
//}
public class ProceduralAimingData
{
public ActionModuleErgoAffected.ErgoData ergoData;
public float zoomInTime;
public Transform aimRefTransform;
public Transform playerCameraPosRef;
public Transform playerOriginTransform;
public bool isRigWeapon;
public Vector3 rigWeaponLocalPosition;
public Quaternion rigWeaponLocalRotation;
public bool isAiming;
public int curAimRefIndex = -1;
//move curAimRefOffset towards aimRefOffset first, then move curAimOffset towards curAimRefOffset
public Vector3 aimRefPosOffset;
public Quaternion aimRefRotOffset;
public Vector3 curAimPosOffset;
public Quaternion curAimRotOffset;
private Vector3 curAimPosVelocity;
private Quaternion curAimRotVelocity;
private Vector3 targetSwitchPosVelocity;
private Quaternion targetSwitchRotVelocity;
public List<AimReference> registeredReferences = new List<AimReference>();
private EntityPlayerLocal holdingEntity;
private int CurAimRefIndex
{
get
{
for (int i = registeredReferences.Count - 1; i >= 0; i--)
{
if (registeredReferences[i].gameObject.activeInHierarchy)
{
return i;
}
}
return -1;
}
}
private AimReference CurAimRef => curAimRefIndex >= 0 && curAimRefIndex < registeredReferences.Count ? registeredReferences[curAimRefIndex] : null;
public ProceduralAimingData(ItemInventoryData _invData, int _indexInEntityOfAction, ActionModuleProceduralAiming _module)
{
holdingEntity = _invData.holdingEntity as EntityPlayerLocal;
}
public void ResetAiming()
{
isAiming = false;
curAimRefIndex = -1;
aimRefPosOffset = Vector3.zero;
aimRefRotOffset = Quaternion.identity;
if (aimRefTransform)
{
aimRefTransform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
}
curAimPosOffset = Vector3.zero;
curAimRotOffset = Quaternion.identity;
curAimPosVelocity = Vector3.zero;
curAimRotVelocity = Quaternion.identity;
targetSwitchPosVelocity = Vector3.zero;
targetSwitchRotVelocity = Quaternion.identity;
if (isRigWeapon && playerOriginTransform)
{
playerOriginTransform.localPosition = rigWeaponLocalPosition;
playerOriginTransform.localRotation = rigWeaponLocalRotation;
}
}
public bool RegisterGroup(AimReference[] group, string name)
{
if (holdingEntity && holdingEntity.bFirstPersonView)
{
foreach (var reference in group)
{
if (reference.index == -1)
{
reference.index = registeredReferences.Count;
registeredReferences.Add(reference);
}
}
UpdateCurrentReference();
//Log.Out($"Register group {name}\n{StackTraceUtility.ExtractStackTrace()}");
return true;
}
return false;
}
public void UpdateCurrentReference(bool snapTo = false)
{
curAimRefIndex = CurAimRefIndex;
AimReference curAimRef = CurAimRef;
if (aimRefTransform && curAimRef)
{
aimRefPosOffset = curAimRef.positionOffset;
aimRefRotOffset = curAimRef.rotationOffset;
if (curAimRef.asReference)
{
aimRefPosOffset -= Vector3.Project(aimRefPosOffset - aimRefTransform.parent.InverseTransformPoint(playerCameraPosRef.position), aimRefRotOffset * Vector3.forward);
}
if (snapTo)
{
aimRefTransform.localPosition = aimRefPosOffset;
aimRefTransform.localRotation = aimRefRotOffset;
}
}
for (int i = 0; i < registeredReferences.Count; i++)
{
registeredReferences[i].UpdateEnableState(isAiming && curAimRefIndex == i);
}
}
public void LateUpdateAiming()
{
if (aimRefTransform && playerCameraPosRef && playerOriginTransform && CurAimRef)
{
if (isRigWeapon)
{
playerOriginTransform.SetLocalPositionAndRotation(rigWeaponLocalPosition, rigWeaponLocalRotation);
}
float zoomInTimeMod = ergoData == null ? zoomInTime : zoomInTime / ergoData.ModifiedErgo;
zoomInTimeMod *= 0.25f;
//move aimRef towards target
aimRefTransform.localPosition = Vector3.SmoothDamp(aimRefTransform.localPosition, aimRefPosOffset, ref targetSwitchPosVelocity, 0.075f);
aimRefTransform.localRotation = QuaternionUtil.SmoothDamp(aimRefTransform.localRotation, aimRefRotOffset, ref targetSwitchRotVelocity, 0.075f);
//calculate current target aim offset
Vector3 aimTargetPosOffset = playerCameraPosRef.InverseTransformDirection(playerCameraPosRef.position - aimRefTransform.position);
Quaternion aimTargetRotOffset = playerCameraPosRef.localRotation * Quaternion.Inverse(aimRefTransform.parent.localRotation * aimRefTransform.localRotation);
//move current aim offset towards target aim offset
if (isAiming)
{
curAimPosOffset = Vector3.SmoothDamp(curAimPosOffset, aimTargetPosOffset, ref curAimPosVelocity, zoomInTimeMod);
curAimRotOffset = QuaternionUtil.SmoothDamp(curAimRotOffset, aimTargetRotOffset, ref curAimRotVelocity, zoomInTimeMod);
}
else
{
curAimPosOffset = Vector3.SmoothDamp(curAimPosOffset, Vector3.zero, ref curAimPosVelocity, zoomInTimeMod);
curAimRotOffset = QuaternionUtil.SmoothDamp(curAimRotOffset, Quaternion.identity, ref curAimRotVelocity, zoomInTimeMod);
}
//apply offset to player
if (isRigWeapon)
{
(playerCameraPosRef.parent.rotation * curAimRotOffset * Quaternion.Inverse(playerCameraPosRef.parent.rotation)).ToAngleAxis(out var angle, out var axis);
playerOriginTransform.RotateAround(aimRefTransform.position, axis, angle);
playerOriginTransform.position += playerCameraPosRef.TransformDirection(curAimPosOffset);
}
else
{
playerOriginTransform.position += playerCameraPosRef.TransformDirection(curAimPosOffset);
(playerCameraPosRef.parent.rotation * curAimRotOffset * Quaternion.Inverse(playerCameraPosRef.parent.rotation)).ToAngleAxis(out var angle, out var axis);
playerOriginTransform.RotateAround(aimRefTransform.position, axis, angle);
}
}
}
}
}
[HarmonyPatch]
public static class ProceduralAimingPatches
{
[HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.LateUpdate))]
[HarmonyPostfix]
private static void Postfix_LateUpdate_EntityPlayerLocal(EntityPlayerLocal __instance)
{
if (__instance.inventory?.holdingItemData?.actionData?[1] is IModuleContainerFor<ActionModuleProceduralAiming.ProceduralAimingData> module)
{
if (__instance.AimingGun != module.Instance.isAiming)
{
module.Instance.isAiming = __instance.AimingGun;
module.Instance.UpdateCurrentReference(true);
}
module.Instance.LateUpdateAiming();
}
}
}

View File

@@ -0,0 +1,276 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.Utilities;
using UnityEngine;
using static ItemActionRanged;
[TypeTarget(typeof(ItemActionRanged)), ActionDataTarget(typeof(RampUpData))]
public class ActionModuleRampUp
{
public enum State
{
RampUp,
Stable,
RampDown
}
private readonly static int prepareHash = Animator.StringToHash("prepare");
private readonly static int prepareSpeedHash = Animator.StringToHash("prepareSpeed");
private readonly static int rampHash = Animator.StringToHash("ramp");
private readonly static int prepareRatioHash = Animator.StringToHash("prepareRatio");
private readonly static int rampRatioHash = Animator.StringToHash("rampRatio");
private readonly static int totalRatioHash = Animator.StringToHash("totalRatio");
[HarmonyPatch(nameof(ItemAction.OnHoldingUpdate)), MethodTargetPostfix]
public void Postfix_OnHoldingUpdate(ItemActionData _actionData, RampUpData __customData, ItemActionRanged __instance)
{
var rangedData = _actionData as ItemActionDataRanged;
__customData.originalDelay = rangedData.Delay;
if (rangedData.invData.holdingEntity.isEntityRemote)
return;
bool aiming = rangedData.invData.holdingEntity.AimingGun;
bool isRampUp = ((rangedData.bPressed && !rangedData.bReleased && __instance.notReloading(rangedData) && rangedData.curBurstCount < __instance.GetBurstCount(rangedData)) || (__customData.zoomPrepare && aiming)) && (__instance.InfiniteAmmo || _actionData.invData.itemValue.Meta > 0) && _actionData.invData.itemValue.PercentUsesLeft > 0;
UpdateTick(__customData, _actionData, isRampUp);
if (__customData.rampRatio > 0)
{
rangedData.Delay /= __customData.rampRatio >= 1f ? __customData.maxMultiplier : __customData.rampRatio * (__customData.maxMultiplier - 1f) + 1f;
}
}
[HarmonyPatch(nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
public void Postfix_OnModificationsChanged(ItemActionData _data, RampUpData __customData, ItemActionRanged __instance)
{
int actionIndex = __instance.ActionIndex;
string originalValue = 1.ToString();
__instance.Properties.ParseString("RampMultiplier", ref originalValue);
__customData.maxMultiplier = Mathf.Max(float.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("RampMultiplier", originalValue, actionIndex)), 1);
originalValue = 0.ToString();
__instance.Properties.ParseString("RampUpTime", ref originalValue);
__customData.rampUpTime = float.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("RampTime", originalValue, actionIndex));
originalValue = string.Empty;
__instance.Properties.ParseString("RampUpSound", ref originalValue);
__customData.rampUpSound = _data.invData.itemValue.GetPropertyOverrideForAction("RampStartSound", originalValue, actionIndex);
originalValue = 0.ToString();
__instance.Properties.ParseString("RampDownTime", ref originalValue);
__customData.rampDownTime = Mathf.Max(float.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("RampTime", originalValue, actionIndex)), 0);
originalValue = string.Empty;
__instance.Properties.ParseString("RampDownSound", ref originalValue);
__customData.rampDownSound = _data.invData.itemValue.GetPropertyOverrideForAction("RampStartSound", originalValue, actionIndex);
originalValue = 0.ToString();
__instance.Properties.ParseString("PrepareTime", ref originalValue);
__customData.prepareTime = float.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("PrepareTime", originalValue, actionIndex));
__customData.prepareSpeed = float.Parse(originalValue) / __customData.prepareTime;
originalValue = string.Empty;
__instance.Properties.ParseString("PrepareSound", ref originalValue);
__customData.prepareSound = _data.invData.itemValue.GetPropertyOverrideForAction("PrepareSound", originalValue, actionIndex);
originalValue = false.ToString();
__instance.Properties.ParseString("PrepareOnAim", ref originalValue);
__customData.zoomPrepare = bool.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("PrepareOnAim", originalValue, actionIndex));
originalValue = string.Empty;
__instance.Properties.ParseString("RampStableSound", ref originalValue);
__customData.rampStableSound = _data.invData.itemValue.GetPropertyOverrideForAction("RampStableSound", originalValue, actionIndex);
__customData.totalChargeTime = __customData.prepareTime + __customData.rampUpTime;
__customData.rampDownTimeScale = __customData.rampDownTime > 0 ? (__customData.totalChargeTime) / __customData.rampDownTime : float.MaxValue;
ResetAll(__customData, _data);
}
[HarmonyPatch(nameof(ItemAction.StopHolding)), MethodTargetPostfix]
public void Postfix_StopHolding(RampUpData __customData, ItemActionData _data)
{
ResetAll(__customData, _data);
}
[HarmonyPatch(nameof(ItemAction.ExecuteAction)), MethodTargetPrefix]
public bool Prefix_ExecuteAction(RampUpData __customData, ItemActionRanged __instance, ItemActionData _actionData, bool _bReleased)
{
ItemActionDataRanged rangedData = _actionData as ItemActionDataRanged;
if (!_bReleased && (__instance.InfiniteAmmo || _actionData.invData.itemValue.Meta > 0) && _actionData.invData.itemValue.PercentUsesLeft > 0)
{
rangedData.bReleased = false;
rangedData.bPressed = true;
if (__customData.curTime < __customData.prepareTime)
return false;
}
return true;
}
private void UpdateTick(RampUpData data, ItemActionData actionData, bool isRampUp)
{
float previousTime = data.curTime;
float deltaTime = Time.time - data.lastTickTime;
data.lastTickTime = Time.time;
ref float curTime = ref data.curTime;
ref State curState = ref data.curState;
float totalChargeTime = data.totalChargeTime;
switch (curState)
{
case State.RampUp:
{
curTime = Mathf.Max(curTime, 0);
if (isRampUp)
{
actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(prepareHash, true, true);
if (curTime < totalChargeTime)
{
curTime += deltaTime;
}
if (curTime >= data.prepareTime)
{
actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(rampHash, true, true);
}
if (curTime >= totalChargeTime)
{
//Log.Out($"change state from {curState} to stable");
actionData.invData.holdingEntity.PlayOneShot(data.rampStableSound);
curState = State.Stable;
}
}
else
{
//Log.Out($"change state from {curState} to ramp down");
actionData.invData.holdingEntity.StopOneShot(data.rampUpSound);
actionData.invData.holdingEntity.PlayOneShot(data.rampDownSound);
curState = State.RampDown;
}
break;
}
case State.RampDown:
{
curTime = Mathf.Min(curTime, totalChargeTime);
if (!isRampUp)
{
actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(rampHash, false, true);
if (curTime > 0)
{
curTime -= deltaTime * data.rampDownTimeScale;
}
if (curTime < data.prepareTime)
{
actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(prepareHash, false, true);
}
if (curTime <= 0)
{
//Log.Out($"change state from {curState} to stable");
//actionData.invData.holdingEntity.PlayOneShot(data.rampStableSound);
curState = State.Stable;
}
}
else
{
//Log.Out($"change state from {curState} to ramp up");
actionData.invData.holdingEntity.StopOneShot(data.rampDownSound);
actionData.invData.holdingEntity.PlayOneShot(data.rampUpSound);
curState = State.RampUp;
}
break;
}
case State.Stable:
{
if (isRampUp)
{
if (curTime < totalChargeTime)
{
//Log.Out($"change state from {curState} to ramp up");
actionData.invData.holdingEntity.StopOneShot(data.rampStableSound);
actionData.invData.holdingEntity.StopOneShot(data.rampDownSound);
actionData.invData.holdingEntity.PlayOneShot(data.rampUpSound);
curState = State.RampUp;
}
else
{
actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(prepareHash, true, true);
actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(rampHash, true, true);
}
}
else
{
if (curTime > 0)
{
//Log.Out($"change state from {curState} to ramp down");
actionData.invData.holdingEntity.StopOneShot(data.rampStableSound);
actionData.invData.holdingEntity.StopOneShot(data.rampUpSound);
actionData.invData.holdingEntity.PlayOneShot(data.rampDownSound);
curState = State.RampDown;
}
else
{
actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(prepareHash, false, true);
actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(rampHash, false, true);
}
}
break;
}
}
//Log.Out($"turret burst fire rate {turret.burstFireRate} max {turret.burstFireRateMax} cur time {curTime} cur state {curState} is ramp up {isRampUp} turret: ison {turret.IsOn} has target {turret.hasTarget} state {turret.state}");
actionData.invData.holdingEntity.emodel.avatarController.UpdateFloat(prepareSpeedHash, data.prepareSpeed);
if (curTime != previousTime)
{
actionData.invData.holdingEntity.emodel.avatarController.UpdateFloat(prepareRatioHash, data.prepareRatio = (data.prepareTime == 0 ? 1f : Mathf.Clamp01(curTime / data.prepareTime)));
actionData.invData.holdingEntity.emodel.avatarController.UpdateFloat(rampRatioHash, data.rampRatio = (data.rampUpTime == 0 ? 1f : Mathf.Clamp01((curTime - data.prepareTime) / data.rampUpTime)));
actionData.invData.holdingEntity.emodel.avatarController.UpdateFloat(totalRatioHash, data.totalRatio = (totalChargeTime == 0 ? 1f : Mathf.Clamp01(curTime / totalChargeTime)));
}
}
private void ResetAll(RampUpData _rampData, ItemActionData _actionData)
{
_rampData.curTime = 0f;
_rampData.lastTickTime = Time.time;
_rampData.curState = State.Stable;
_rampData.prepareRatio = 0f;
_rampData.rampRatio = 0f;
_rampData.totalRatio = 0f;
((ItemActionDataRanged)_actionData).Delay = _rampData.originalDelay;
_actionData.invData.holdingEntity.StopOneShot(_rampData.prepareSound);
_actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(prepareHash, false, true);
_actionData.invData.holdingEntity.StopOneShot(_rampData.rampUpSound);
_actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(rampHash, false, true);
//Log.Out("Reset all!");
}
public class RampUpData
{
public float maxMultiplier = 1f;
public string prepareSound = string.Empty;
public float prepareSpeed = 1f;
public float prepareTime = 0f;
public string rampUpSound = string.Empty;
public float rampUpTime = 0f;
public float totalChargeTime = 0f;
public string rampDownSound = string.Empty;
public float rampDownTime = 0f;
public float rampDownTimeScale = float.MaxValue;
public string rampStableSound = string.Empty;
public float originalDelay = 0f;
public float curTime = 0f;
public State curState = State.Stable;
public float prepareRatio = 0f;
public float rampRatio = 0f;
public float totalRatio = 0f;
public float lastTickTime = 0f;
public bool zoomPrepare = false;
public ActionModuleRampUp rampUpModule;
public RampUpData(ItemInventoryData _invData, int _indexInEntityOfAction, ActionModuleRampUp _module)
{
rampUpModule = _module;
}
}
}

View File

@@ -0,0 +1,30 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using KFCommonUtilityLib.Scripts.Utilities;
using UniLinq;
[TypeTarget(typeof(ItemAction)), ActionDataTarget(typeof(TaggedData))]
public class ActionModuleTagged
{
[HarmonyPatch(nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
private void Postfix_OnModificationChanged(ItemAction __instance, ItemActionData _data, TaggedData __customData)
{
var tags = __instance.Properties.GetString("ActionTags").Split(',', System.StringSplitOptions.RemoveEmptyEntries);
var tags_to_add = _data.invData.itemValue.GetAllPropertyOverridesForAction("ActionTagsAppend", __instance.ActionIndex).SelectMany(s => s.Split(',', System.StringSplitOptions.RemoveEmptyEntries));
var tags_to_remove = _data.invData.itemValue.GetAllPropertyOverridesForAction("ActionTagsRemove", __instance.ActionIndex).SelectMany(s => s.Split(',', System.StringSplitOptions.RemoveEmptyEntries));
var tags_result = tags.Union(tags_to_add);
tags_result = tags_result.Except(tags_to_remove);
__customData.tags = tags_result.Any() ? FastTags<TagGroup.Global>.Parse(string.Join(",", tags_result)) : FastTags<TagGroup.Global>.none;
//Log.Out($"tags: {string.Join(",", tags_result)}");
}
public class TaggedData
{
public FastTags<TagGroup.Global> tags;
public TaggedData(ItemInventoryData _invData, int _indexInEntityOfAction, ActionModuleTagged _module)
{
}
}
}

View File

@@ -0,0 +1,48 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using System.Collections.Generic;
using System.Reflection.Emit;
using UnityEngine;
[TypeTarget(typeof(ItemAction))]
public class ActionModuleTranspilerTest
{
[HarmonyPatch(typeof(ItemActionAttack), nameof(ItemAction.ExecuteAction)), MethodTargetTranspiler]
private static IEnumerable<CodeInstruction> Transpiler_InvalidTest(IEnumerable<CodeInstruction> instructions)
{
yield return new CodeInstruction(OpCodes.Ldstr, "Ranged!");
yield return CodeInstruction.Call(typeof(ActionModuleTranspilerTest), nameof(ActionModuleTranspilerTest.CallSomething));
foreach (var ins in instructions)
{
yield return ins;
}
}
[HarmonyPatch(typeof(ItemActionRanged), nameof(ItemAction.ExecuteAction)), MethodTargetTranspiler]
private static IEnumerable<CodeInstruction> Transpiler_RangedTest(IEnumerable<CodeInstruction> instructions)
{
yield return new CodeInstruction(OpCodes.Ldstr, "Ranged!");
yield return CodeInstruction.Call(typeof(ActionModuleTranspilerTest), nameof(ActionModuleTranspilerTest.CallSomething));
foreach (var ins in instructions)
{
yield return ins;
}
}
[HarmonyPatch(typeof(ItemActionCatapult), nameof(ItemAction.ExecuteAction)), MethodTargetTranspiler]
private static IEnumerable<CodeInstruction> Transpiler_CatapultTest(IEnumerable<CodeInstruction> instructions)
{
yield return new CodeInstruction(OpCodes.Ldstr, "Catapult!");
yield return CodeInstruction.Call(typeof(ActionModuleTranspilerTest), nameof(ActionModuleTranspilerTest.CallSomething));
foreach (var ins in instructions)
{
yield return ins;
}
}
private static void CallSomething(string str)
{
Log.Out($"Call something: {str}\n{StackTraceUtility.ExtractStackTrace()}");
}
}

View File

@@ -0,0 +1,260 @@
using HarmonyLib;
using KFCommonUtilityLib;
using KFCommonUtilityLib.Scripts.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using UnityEngine;
[TypeTarget(typeof(ItemActionZoom)), ActionDataTarget(typeof(VariableZoomData))]
public class ActionModuleVariableZoom
{
private const string METASAVENAME = "CurZoomStep";
public static float zoomScale = 7.5f;
[HarmonyPatch(nameof(ItemAction.ConsumeScrollWheel)), MethodTargetPostfix]
private void Postfix_ConsumeScrollWheel(ItemActionData _actionData, float _scrollWheelInput, PlayerActionsLocal _playerInput, VariableZoomData __customData)
{
if (!_actionData.invData.holdingEntity.AimingGun || _scrollWheelInput == 0f)
{
return;
}
ItemActionZoom.ItemActionDataZoom itemActionDataZoom = (ItemActionZoom.ItemActionDataZoom)_actionData;
if (!itemActionDataZoom.bZoomInProgress && !__customData.isToggleOnly)
{
__customData.curStep = Utils.FastClamp01(__customData.curStep + _scrollWheelInput);
__customData.stepSign = Mathf.Sign(_scrollWheelInput);
__customData.UpdateByStep();
ItemValue scopeValue = __customData.ScopeValue;
if (scopeValue != null)
{
scopeValue.SetMetadata(METASAVENAME, __customData.SignedStep, TypedMetadataValue.TypeTag.Float);
_actionData.invData.holdingEntity.inventory.CallOnToolbeltChangedInternal();
}
}
}
[HarmonyPatch(nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix]
private void Postfix_OnModificationChanged(ItemActionZoom __instance, ItemActionData _data, VariableZoomData __customData)
{
string str = __instance.Properties.GetString("ZoomRatio");
if (string.IsNullOrEmpty(str))
{
str = "1";
}
__customData.maxScale = StringParsers.ParseFloat(_data.invData.itemValue.GetPropertyOverride("ZoomRatio", str));
str = __instance.Properties.GetString("ZoomRatioMin");
if (string.IsNullOrEmpty(str))
{
str = __customData.maxScale.ToString();
}
__customData.minScale = StringParsers.ParseFloat(_data.invData.itemValue.GetPropertyOverride("ZoomRatioMin", str));
str = _data.invData.itemValue.GetPropertyOverride("ToggleOnly", null);
if (!string.IsNullOrEmpty(str) && bool.TryParse(str, out __customData.isToggleOnly)) ;
str = _data.invData.itemValue.GetPropertyOverride("ForceFovRange", null);
if (!string.IsNullOrEmpty(str) && StringParsers.TryParseRange(str, out __customData.fovRange) && __customData.fovRange.min > 0 && __customData.fovRange.max > 0)
{
__customData.fovRange = new FloatRange(Mathf.Min(__customData.fovRange.max, __customData.fovRange.min), Mathf.Max(__customData.fovRange.max, __customData.fovRange.min));
__customData.forceFov = true;
}
//__customData.maxFov = ScaleToFov(__customData.minScale);
//__customData.minFov = ScaleToFov(__customData.maxScale);
__customData.scopeValueIndex = _data.invData.itemValue.Modifications == null ? -1 : Array.FindIndex(_data.invData.itemValue.Modifications, static v => v?.ItemClass is IModuleContainerFor<ItemModuleVariableZoom>);
if (__customData.scopeValueIndex == -1 && _data.invData.itemValue.ItemClass is not IModuleContainerFor<ItemModuleVariableZoom>)
{
__customData.scopeValueIndex = int.MinValue;
}
ItemValue scopeValue = __customData.ScopeValue;
if (scopeValue != null)
{
if (scopeValue.GetMetadata(METASAVENAME) is float curStep)
{
__customData.curStep = Mathf.Abs(curStep);
__customData.stepSign = Mathf.Sign(curStep);
}
__customData.curStep = Utils.FastClamp01(__customData.curStep);
scopeValue.SetMetadata(METASAVENAME, __customData.SignedStep, TypedMetadataValue.TypeTag.Float);
_data.invData.holdingEntity.inventory.CallOnToolbeltChangedInternal();
}
else
{
__customData.curStep = Utils.FastClamp01(__customData.curStep);
}
__customData.UpdateByStep();
}
//public static float FovToScale(float fov)
//{
// return Mathf.Rad2Deg * 2 * Mathf.Atan(Mathf.Tan(Mathf.Deg2Rad * 27.5f) / fov);
//}
//public static float ScaleToFov(float scale)
//{
// return Mathf.Rad2Deg * 2 * Mathf.Atan(Mathf.Tan(Mathf.Deg2Rad * 27.5f) / scale);
//}
//public static float GetNext(float cur)
//{
// return Mathf.Sin(Mathf.PI * cur / 2);
//}
public class VariableZoomData
{
public float maxScale = 1f;
public float minScale = 1f;
public float curScale = 0f;
//public float maxFov = 15f;
//public float minFov = 15f;
//public float curFov = 90f;
public bool forceFov = false;
public FloatRange fovRange = new FloatRange(15f, 15f);
public float curStep = 0;
public float stepSign = 1f;
public bool isToggleOnly = false;
public bool shouldUpdate = true;
public int scopeValueIndex = int.MinValue;
public float SignedStep => curStep * stepSign;
public ItemValue ScopeValue
{
get
{
if (invData == null)
{
return null;
}
if (scopeValueIndex == -1)
{
return invData.itemValue;
}
else if (scopeValueIndex >= 0 && scopeValueIndex < invData.itemValue.Modifications.Length)
{
return invData.itemValue.Modifications[scopeValueIndex];
}
return null;
}
}
public ItemInventoryData invData = null;
public VariableZoomData(ItemInventoryData _invData, int _indexInEntityOfAction, ActionModuleVariableZoom _module)
{
invData = _invData;
}
public void ToggleZoom()
{
//if (scopeValue != null && scopeValue.GetMetadata(METASAVENAME) is float curStep)
//{
// this.curStep = Mathf.Abs(curStep);
// stepSign = MathF.Sign(curStep);
//}
if (stepSign > 0)
{
if (this.curStep >= 1)
{
this.curStep = 0;
}
else
{
this.curStep = 1;
}
}
else
{
if (this.curStep <= 0)
{
this.curStep = 1;
}
else
{
this.curStep = 0;
}
}
UpdateByStep();
ItemValue scopeValue = ScopeValue;
if (scopeValue != null)
{
scopeValue.SetMetadata(METASAVENAME, SignedStep, TypedMetadataValue.TypeTag.Float);
invData.holdingEntity.inventory.CallOnToolbeltChangedInternal();
}
}
public void UpdateByStep()
{
//curFov = Utils.FastLerp(maxFov, minFov, GetNext(curStep));
//curScale = FovToScale(curFov);
curScale = Utils.FastLerp(minScale, maxScale, curStep);
shouldUpdate = true;
}
}
}
[HarmonyPatch]
public static class VariableZoomPatches
{
[HarmonyPatch(typeof(PlayerMoveController), nameof(PlayerMoveController.Update))]
[HarmonyPrefix]
private static bool Prefix_Update_PlayerMoveController(PlayerMoveController __instance)
{
if (DroneManager.Debug_LocalControl || !__instance.gameManager.gameStateManager.IsGameStarted() || GameStats.GetInt(EnumGameStats.GameState) != 1)
return true;
bool isUIOpen = __instance.windowManager.IsCursorWindowOpen() || __instance.windowManager.IsInputActive() || __instance.windowManager.IsModalWindowOpen();
UpdateLocalInput(__instance.entityPlayerLocal, isUIOpen);
return true;
}
private static void UpdateLocalInput(EntityPlayerLocal _player, bool _isUIOpen)
{
if (_isUIOpen || _player.emodel.IsRagdollActive || _player.IsDead() || _player.AttachedToEntity != null)
{
return;
}
if (PlayerActionKFLib.Instance.Enabled && PlayerActionKFLib.Instance.ToggleZoom.WasPressed)
{
var actionData = _player.inventory.holdingItemData.actionData[1];
if (actionData is IModuleContainerFor<ActionModuleVariableZoom.VariableZoomData> variableZoomData)
{
variableZoomData.Instance.ToggleZoom();
}
}
}
//[HarmonyPatch(typeof(Inventory), nameof(Inventory.SetItem), new Type[] { typeof(int), typeof(ItemValue), typeof(int), typeof(bool) })]
//[HarmonyTranspiler]
//private static IEnumerable<CodeInstruction> Transpiler_Test(IEnumerable<CodeInstruction> instructions)
//{
// var codes = instructions.ToList();
// var fld = AccessTools.Field(typeof(ItemStack), nameof(ItemStack.count));
// for (int i = 0; i < codes.Count; i++)
// {
// if (codes[i].StoresField(fld))
// {
// codes.InsertRange(i + 1, new[]
// {
// new CodeInstruction(OpCodes.Ldloc_0),
// new CodeInstruction(OpCodes.Ldarg_0),
// new CodeInstruction(OpCodes.Ldarg_1),
// CodeInstruction.Call(typeof(VariableZoomPatches), nameof(LogMsg))
// });
// break;
// }
// }
// return codes;
//}
//private static void LogMsg(bool flag, Inventory inv, int idx)
//{
// if (inv.holdingItemIdx == idx)
// Log.Out($"changed: {flag}\n{StackTraceUtility.ExtractStackTrace()}");
//}
}

View File

@@ -0,0 +1,87 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using System;
using System.Collections.Generic;
[TypeTarget(typeof(ItemClassModifier))]
public class ItemModuleSortable
{
//public int priority = int.MaxValue;
//[HarmonyPatch(nameof(ItemClassModifier.Init)), MethodTargetPostfix]
//public void Postfix_Init(ItemClassModifier __instance)
//{
// __instance.Properties.ParseInt("ModSortPriority", ref priority);
//}
}
public struct ItemModuleSortableComparer : IComparer<ItemValue>
{
private string itemName;
public ItemModuleSortableComparer(ItemValue item)
{
itemName = item.ItemClass.Name;
}
public int Compare(ItemValue x, ItemValue y)
{
return GetPriority(x) - GetPriority(y);
}
private int GetPriority(ItemValue itemValue)
{
if (itemValue.ItemClass is ItemClassModifier modifierClass)
{
string str = null;
if (modifierClass.GetPropertyOverride("ModSortPriority", itemName, ref str) && int.TryParse(str, out int priority))
{
return priority;
}
}
return int.MaxValue;
}
}
[HarmonyPatch]
public static class ModSortingPatches
{
//[HarmonyPatch(typeof(XUiC_ItemPartStackGrid), nameof(XUiC_ItemPartStackGrid.HandleSlotChangedEvent))]
//[HarmonyTranspiler]
//public static IEnumerable<CodeInstruction> Transpiler_HandleSlotChangedEvent_XUiC_ItemPartStackGrid(IEnumerable<CodeInstruction> instructions)
//{
// var codes = instructions.ToList();
// var prop_setstack = AccessTools.PropertySetter(typeof(XUiC_AssembleWindow), nameof(XUiC_AssembleWindow.ItemStack));
// var idx = codes.FindIndex(x => x.Calls(prop_setstack));
// if (idx > 0)
// {
// codes.InsertRange(idx, new[]
// {
// new CodeInstruction(OpCodes.Dup),
// new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(ModSortingPatches), nameof(SortMods)))
// });
// }
// return codes;
//}
//[HarmonyPatch(typeof(XUiC_ItemPartStackGrid), nameof(XUiC_ItemPartStackGrid.HandleSlotChangedEvent))]
//[HarmonyPostfix]
//private static void Postfix_HandleSlotChangedEvent_XUiC_ItemPartStackGrid(XUiC_ItemPartStackGrid __instance)
//{
// __instance.SetParts(__instance.CurrentItem.itemValue?.Modifications);
//}
[HarmonyPatch(typeof(XUiC_AssembleWindowGroup), nameof(XUiC_AssembleWindowGroup.ItemStack), MethodType.Setter)]
[HarmonyPrefix]
private static void Postfix_set_ItemStack_XUiC_AssembleWindowGroup(ItemStack value)
{
SortMods(value);
}
private static void SortMods(ItemStack itemStack)
{
if (itemStack?.itemValue?.Modifications != null)
{
Array.Sort(itemStack.itemValue.Modifications, new ItemModuleSortableComparer(itemStack.itemValue));
}
}
}

View File

@@ -0,0 +1,6 @@
using KFCommonUtilityLib.Scripts.Attributes;
[TypeTarget(typeof(ItemClass))]
public class ItemModuleVariableZoom
{
}

View File

@@ -0,0 +1,12 @@
public class MinEventActionAddBuffToTargetAndSelf : MinEventActionAddBuff
{
public override bool CanExecute(MinEventTypes _eventType, MinEventParams _params)
{
bool flag = base.CanExecute(_eventType, _params);
if (targetType == TargetTypes.selfAOE)
flag = true;
if (flag && targetType != TargetTypes.self)
targets.Add(_params.Self);
return flag;
}
}

View File

@@ -0,0 +1,21 @@
public class MinEventActionAddItemToInventory : MinEventActionItemAccessBase
{
private ItemStack itemStackCache = new ItemStack();
public override bool CanExecute(MinEventTypes _eventType, MinEventParams _params)
{
if (itemValueCache == null)
{
itemValueCache = ItemClass.GetItem(itemName);
itemStackCache.itemValue = itemValueCache;
}
return !_params.Self.isEntityRemote && base.CanExecute(_eventType, _params);
}
public override void Execute(MinEventParams _params)
{
itemStackCache.count = GetCount(_params);
_params.Self.TryStackItem(itemStackCache);
}
}

View File

@@ -0,0 +1,16 @@
public class MinEventActionAddRoundsToInventory : MinEventActionAmmoAccessBase
{
private ItemStack itemStackCache = new ItemStack();
public override void Execute(MinEventParams _params)
{
var _ranged = _params.ItemValue.ItemClass.Actions[_params.ItemActionData.indexInEntityOfAction] as ItemActionRanged;
string ammoName = _ranged.MagazineItemNames[_params.ItemValue.SelectedAmmoTypeIndex];
if (!RoundsInInventory.TryGetValue(ammoName, out var ammoValue))
return;
itemStackCache.itemValue = ammoValue;
itemStackCache.count = GetCount(_params);
_params.Self.TryStackItem(itemStackCache);
}
}

View File

@@ -0,0 +1,28 @@
using System.Xml.Linq;
public class MinEventActionAddRoundsToMagazine : MinEventActionAmmoAccessBase
{
private float maxPerc = -1;
public override void Execute(MinEventParams _params)
{
_params.ItemValue.Meta += GetCount(_params);
if (maxPerc > 0)
_params.ItemValue.Meta = Utils.FastMin((int)((_params.ItemValue.ItemClass.Actions[0] as ItemActionRanged).GetMaxAmmoCount(_params.ItemActionData) * maxPerc), _params.ItemValue.Meta);
_params.Self?.inventory?.CallOnToolbeltChangedInternal();
}
public override bool ParseXmlAttribute(XAttribute _attribute)
{
if (base.ParseXmlAttribute(_attribute))
return true;
if (_attribute.Name.LocalName == "max")
{
maxPerc = float.Parse(_attribute.Value);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,56 @@
using System.Xml.Linq;
public class MinEventActionAmmoAccessBase : MinEventActionItemCountRandomBase
{
private bool useMag = false;
private bool useRounds = false;
private bool revert = false;
private float perc = 1;
protected override int GetCount(MinEventParams _params)
{
if (!useMag || !(_params.ItemValue.ItemClass.Actions[_params.ItemActionData.indexInEntityOfAction] is ItemActionRanged _ranged))
return base.GetCount(_params);
if (!useRounds)
return (int)(_ranged.GetMaxAmmoCount(_params.ItemActionData) * perc);
if (!revert)
return (int)((_params.ItemValue.Meta) * perc);
return (int)((_ranged.GetMaxAmmoCount(_params.ItemActionData) - _params.ItemValue.Meta) * perc);
}
public override bool CanExecute(MinEventTypes _eventType, MinEventParams _params)
{
return !_params.Self.isEntityRemote && base.CanExecute(_eventType, _params) && _params.ItemActionData is ItemActionRanged.ItemActionDataRanged && _params.ItemValue.ItemClass.Actions[_params.ItemActionData.indexInEntityOfAction] is ItemActionRanged;
}
public override bool ParseXmlAttribute(XAttribute _attribute)
{
if (base.ParseXmlAttribute(_attribute))
return true;
if (_attribute.Name.LocalName == "count" && _attribute.Value.Contains("MagazineSize"))
{
useMag = true;
string str = _attribute.Value;
if (str.StartsWith("%"))
{
useRounds = true;
str = str.Substring(1);
}
if (str.StartsWith("!"))
{
revert = true;
str = str.Substring(1);
}
string[] arr = str.Split(new char[] { '*' }, 2, System.StringSplitOptions.RemoveEmptyEntries);
if (arr.Length == 2)
return float.TryParse(arr[1], out perc);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,81 @@
using KFCommonUtilityLib.Scripts.NetPackages;
using System.Collections.Generic;
using System.Xml.Linq;
using UnityEngine;
public class MinEventActionAttachPrefabToEntitySync : MinEventActionAttachPrefabToEntity
{
private static Dictionary<string, GameObject> dict_loaded = new Dictionary<string, GameObject>();
//public override bool CanExecute(MinEventTypes _eventType, MinEventParams _params)
//{
// return base.CanExecute(_eventType, _params) && (_params.IsLocal || (_params.Self && !_params.Self.isEntityRemote));
//}
public override void Execute(MinEventParams _params)
{
base.Execute(_params);
if (ConnectionManager.Instance.IsServer)
{
ConnectionManager.Instance.SendPackage(NetPackageManager.GetPackage<NetPackageRemoteAttachPrefab>().Setup(_params.Self.entityId, prefab, parent_transform_path, local_offset, local_rotation, local_scale), false, -1, -1, _params.Self.entityId);
}
else if (_params.IsLocal || (_params.Self && !_params.Self.isEntityRemote))
{
ConnectionManager.Instance.SendToServer(NetPackageManager.GetPackage<NetPackageRemoteAttachPrefab>().Setup(_params.Self.entityId, prefab, parent_transform_path, local_offset, local_rotation, local_scale));
}
}
public override bool ParseXmlAttribute(XAttribute _attribute)
{
bool flag = false;
if (_attribute.Name.LocalName == "prefab")
{
prefab = _attribute.Value;
if (dict_loaded.TryGetValue(_attribute.Value, out GameObject go) && go)
{
goToInstantiate = go;
flag = true;
}
else
{
flag = base.ParseXmlAttribute(_attribute);
dict_loaded[_attribute.Value] = goToInstantiate;
}
}
else
{
flag = base.ParseXmlAttribute(_attribute);
}
return flag;
}
public static void RemoteAttachPrefab(EntityAlive entity, string prefab, string path, Vector3 local_offset, Vector3 local_rotation, Vector3 local_scale)
{
Transform transform = entity.RootTransform;
if (!string.IsNullOrEmpty(path))
{
transform = GameUtils.FindDeepChildActive(transform, path);
}
if (transform == null)
{
return;
}
GameObject goToInstantiate = dict_loaded[prefab];
string text = "tempPrefab_" + goToInstantiate.name;
Transform transform2 = GameUtils.FindDeepChild(transform, text);
if (transform2 == null)
{
GameObject gameObject = UnityEngine.Object.Instantiate<GameObject>(goToInstantiate);
if (gameObject == null)
{
return;
}
transform2 = gameObject.transform;
gameObject.name = text;
Utils.SetLayerRecursively(gameObject, transform.gameObject.layer, null);
transform2.parent = transform;
transform2.localPosition = local_offset;
transform2.localRotation = Quaternion.Euler(local_rotation.x, local_rotation.y, local_rotation.z);
transform2.localScale = local_scale;
}
}
}

View File

@@ -0,0 +1,8 @@
public class MinEventActionBroadcastPlaySoundLocal : MinEventActionPlaySound
{
public override bool CanExecute(MinEventTypes _eventType, MinEventParams _params)
{
return targetType == TargetTypes.self && !_params.Self.isEntityRemote && base.CanExecute(_eventType, _params);
}
}

View File

@@ -0,0 +1,248 @@
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using CodeWriter.ExpressionParser;
using UnityEngine;
public class MinEventActionCVarExpression : MinEventActionTargetedBase
{
private enum VariableType
{
None,
CVar,
RandomInt,
RandomFloat,
TierList
}
private class VariableInfo
{
public VariableType varType;
public string cvarName;
public float[] valueList;
public float randomMin;
public float randomMax;
}
public string cvarName;
public MinEventActionModifyCVar.OperationTypes operation;
private bool isValid = false;
private ExpressionContext<float> context;
private Expression<float> compiledExpr;
private VariableInfo[] variableInfos;
private MinEventParams minEventContext;
private EntityAlive target;
public override bool CanExecute(MinEventTypes _eventType, MinEventParams _params)
{
if (cvarName != null && cvarName.StartsWith("_"))
{
Log.Out("CVar '{0}' is readonly", new object[] { cvarName });
return false;
}
if (!isValid)
{
Log.Out("Invalid expression!");
return false;
}
return base.CanExecute(_eventType, _params);
}
public override void Execute(MinEventParams _params)
{
if (_params.Self.isEntityRemote && !_params.IsLocal)
{
return;
}
minEventContext = _params;
if (compiledExpr == null)
{
return;
}
for (int i = 0; i < targets.Count; i++)
{
target = targets[i];
float cvar = target.Buffs.GetCustomVar(cvarName);
float value = compiledExpr.Invoke();
switch (operation)
{
case MinEventActionModifyCVar.OperationTypes.set:
case MinEventActionModifyCVar.OperationTypes.setvalue:
cvar = value;
break;
case MinEventActionModifyCVar.OperationTypes.add:
cvar += value;
break;
case MinEventActionModifyCVar.OperationTypes.subtract:
cvar -= value;
break;
case MinEventActionModifyCVar.OperationTypes.multiply:
cvar *= value;
break;
case MinEventActionModifyCVar.OperationTypes.divide:
cvar /= ((value == 0f) ? 0.0001f : value);
break;
case MinEventActionModifyCVar.OperationTypes.percentadd:
cvar += cvar * value;
break;
case MinEventActionModifyCVar.OperationTypes.percentsubtract:
cvar -= cvar * value;
break;
}
target.Buffs.SetCustomVar(cvarName, cvar);
}
minEventContext = null;
target = null;
}
public override bool ParseXmlAttribute(XAttribute _attribute)
{
bool flag = base.ParseXmlAttribute(_attribute);
if (!flag)
{
switch(_attribute.Name.LocalName)
{
case "cvar":
cvarName = _attribute.Value;
flag = true;
break;
case "expression":
isValid = true;
string expr = _attribute.Value;
context = new ExpressionContext<float>();
List<VariableInfo> variableInfos = new List<VariableInfo>();
Dictionary<string, int> varStrs = new Dictionary<string, int>();
while (true)
{
int nextVarStart = expr.IndexOf('[');
if (nextVarStart < 0)
{
break;
}
int nextVarEnd = expr.IndexOf(']', nextVarStart);
if (nextVarEnd < 0)
{
isValid = false;
break;
}
string varStr = expr.Substring(nextVarStart + 1, nextVarEnd - nextVarStart - 1);
VariableInfo variableInfo = null;
if (varStr.StartsWith("@"))
{
if (!varStrs.ContainsKey(varStr))
{
variableInfo = new VariableInfo();
variableInfo.varType = VariableType.CVar;
variableInfo.cvarName = varStr.Substring(1);
varStrs.Add(varStr, variableInfos.Count);
}
}
else if (varStr.StartsWith("randomInt", StringComparison.OrdinalIgnoreCase))
{
variableInfo = new VariableInfo();
variableInfo.varType = VariableType.RandomInt;
Vector2 vector = StringParsers.ParseVector2(varStr.Substring(varStr.IndexOf('(') + 1, varStr.IndexOf(')') - (varStr.IndexOf('(') + 1)));
variableInfo.randomMin = (int)vector.x;
variableInfo.randomMax = (int)vector.y;
}
else if (varStr.StartsWith("randomFloat", StringComparison.OrdinalIgnoreCase))
{
variableInfo = new VariableInfo();
variableInfo.varType = VariableType.RandomFloat;
Vector2 vector = StringParsers.ParseVector2(varStr.Substring(varStr.IndexOf('(') + 1, varStr.IndexOf(')') - (varStr.IndexOf('(') + 1)));
variableInfo.randomMin = vector.x;
variableInfo.randomMax = vector.y;
}
else if (varStr.Contains(','))
{
if (!varStrs.ContainsKey(varStr))
{
variableInfo = new VariableInfo();
variableInfo.varType = VariableType.TierList;
string[] array = varStr.Split(',', StringSplitOptions.None);
variableInfo.valueList = new float[array.Length];
for (int i = 0; i < array.Length; i++)
{
variableInfo.valueList[i] = float.Parse(array[i]);
}
varStrs.Add(varStr, variableInfos.Count);
}
}
else if (float.TryParse(varStr, out _))
{
expr = expr.Remove(nextVarEnd).Remove(nextVarStart);
}
else
{
isValid = false;
break;
}
int curIndex = varStrs.TryGetValue(varStr, out var index) ? index : variableInfos.Count;
string varName = "x" + curIndex;
expr = expr.Remove(nextVarStart, nextVarEnd - nextVarStart + 1).Insert(nextVarStart, varName);
//Log.Out($"cur index {curIndex} var name {varStr} is new var {curIndex == variableInfos.Count}");
if (curIndex == variableInfos.Count)
{
context.RegisterVariable(varName, () => { return EvaluateVar(curIndex); });
}
if (variableInfo != null)
{
variableInfos.Add(variableInfo);
}
}
if (!isValid)
{
Log.Out("Invalid expression: {0}", new object[] { expr });
return false;
}
Log.Out($"Compiling expr {expr}...");
compiledExpr = FloatExpressionParser.Instance.Compile(expr, context, true);
this.variableInfos = variableInfos.ToArray();
flag = true;
break;
case "operation":
this.operation = EnumUtils.Parse<MinEventActionModifyCVar.OperationTypes>(_attribute.Value, true);
flag = true;
break;
}
}
return flag;
}
private float EvaluateVar(int index)
{
var variableInfo = variableInfos[index];
switch (variableInfo.varType)
{
case VariableType.CVar:
return target.Buffs.GetCustomVar(variableInfo.cvarName);
case VariableType.RandomInt:
return Mathf.Clamp(minEventContext.Self.rand.RandomRange((int)variableInfo.randomMin, (int)variableInfo.randomMax + 1), variableInfo.randomMin, variableInfo.randomMax);
case VariableType.RandomFloat:
return Mathf.Clamp(minEventContext.Self.rand.RandomRange(variableInfo.randomMin, variableInfo.randomMax + 1), variableInfo.randomMin, variableInfo.randomMax);
case VariableType.TierList:
if (minEventContext.ParentType == MinEffectController.SourceParentType.ItemClass || minEventContext.ParentType == MinEffectController.SourceParentType.ItemModifierClass)
{
if (!minEventContext.ItemValue.IsEmpty())
{
int tier = (int)(minEventContext.ItemValue.Quality - 1);
if (tier >= 0)
{
return variableInfo.valueList[tier];
}
}
}
else if (minEventContext.ParentType == MinEffectController.SourceParentType.ProgressionClass && minEventContext.ProgressionValue != null)
{
int level = minEventContext.ProgressionValue.CalculatedLevel(minEventContext.Self);
if (level >= 0)
{
return variableInfo.valueList[level];
}
}
return 0f;
default:
return 0f;
}
}
}

View File

@@ -0,0 +1,25 @@
public class MinEventActionDecreaseProgressionLevelAndRefundSP : MinEventActionSetProgressionLevel
{
public override void Execute(MinEventParams _params)
{
EntityPlayerLocal entityPlayerLocal = this.targets[0] as EntityPlayerLocal;
if (this.targets != null)
{
ProgressionValue progressionValue = entityPlayerLocal.Progression.GetProgressionValue(this.progressionName);
if (progressionValue != null)
{
if (this.level >= 0 && this.level < progressionValue.Level)
{
ProgressionClass progressionClass = progressionValue.ProgressionClass;
int spcount = 0;
for (int i = this.level + 1; i <= progressionValue.Level; i++)
spcount += progressionClass.CalculatedCostForLevel(i);
progressionValue.Level = this.level;
entityPlayerLocal.Progression.SkillPoints += spcount;
entityPlayerLocal.Progression.bProgressionStatsChanged = true;
entityPlayerLocal.bPlayerStatsChanged = true;
}
}
}
}
}

View File

@@ -0,0 +1,23 @@
using System.Xml.Linq;
public class MinEventActionItemAccessBase : MinEventActionItemCountRandomBase
{
protected string itemName;
protected ItemValue itemValueCache = null;
public override bool ParseXmlAttribute(XAttribute _attribute)
{
if (base.ParseXmlAttribute(_attribute))
return true;
switch (_attribute.Name.LocalName)
{
case "item":
itemName = _attribute.Value;
return true;
default:
return false;
}
}
}

View File

@@ -0,0 +1,60 @@
using System.Xml.Linq;
using UnityEngine;
public class MinEventActionItemCountRandomBase : MinEventActionBase
{
private bool useRange = false;
private bool useRandom = false;
private bool useCvar = false;
private int[] random;
private Vector2i range;
private string cvarRef;
private int constant;
protected virtual int GetCount(MinEventParams _params)
{
if (useRandom)
return random[Random.Range(0, random.Length)];
else if (useRange)
return Random.Range(range.x, range.y + 1);
else if (useCvar)
return (int)_params.Self.GetCVar(cvarRef);
else
return constant;
}
public override bool ParseXmlAttribute(XAttribute _attribute)
{
if (base.ParseXmlAttribute(_attribute))
return true;
switch (_attribute.Name.LocalName)
{
case "count":
string str = _attribute.Value;
if (str.StartsWith("random"))
{
useRandom = true;
string[] values = str.Substring(str.IndexOf('(') + 1, str.IndexOf(')') - str.IndexOf('(') - 1).Split(',');
random = new int[values.Length];
for (int i = 0; i < values.Length; i++)
random[i] = int.Parse(values[i]);
}
else if (str.StartsWith("range"))
{
useRange = true;
range = StringParsers.ParseVector2i(str.Substring(str.IndexOf('(') + 1, str.IndexOf(')') - str.IndexOf('(') - 1));
}
else if (str.StartsWith("@"))
{
useCvar = true;
cvarRef = str.Substring(1);
}
else
return int.TryParse(str, out constant);
return true;
default:
return false;
}
}
}

View File

@@ -0,0 +1,72 @@
using KFCommonUtilityLib;
using System.Xml.Linq;
public class MinEventActionModifyCVarWithLocalCache : MinEventActionModifyCVar
{
int targetHash;
private int actionIndex = -1;
public override bool CanExecute(MinEventTypes _eventType, MinEventParams _params)
{
bool flag = !_params.Self.isEntityRemote && (actionIndex < 0 ? _params.ItemActionData : _params.ItemActionData.invData.actionData[actionIndex]) is IModuleContainerFor<ActionModuleLocalPassiveCache.LocalPassiveCacheData> && base.CanExecute(_eventType, _params);
//Log.Out($"can execute {flag} is remote {_params.Self.isEntityRemote} action index {actionIndex} cache {targetHash.ToString()} action {(actionIndex < 0 ? _params.ItemActionData : _params.ItemActionData.invData.actionData[actionIndex]).GetType().Name}");
return flag;
}
public override void Execute(MinEventParams _params)
{
if (_params.Self.isEntityRemote && !_params.IsLocal)
{
return;
}
ActionModuleLocalPassiveCache.LocalPassiveCacheData _data = ((IModuleContainerFor<ActionModuleLocalPassiveCache.LocalPassiveCacheData>)(actionIndex < 0 ? _params.ItemActionData : _params.ItemActionData.invData.actionData[actionIndex])).Instance;
float value = _data.GetCachedValue(targetHash);
//Log.Out($"cache {targetHash.ToString()} value {value}");
for (int i = 0; i < targets.Count; i++)
{
float num = targets[i].Buffs.GetCustomVar(cvarName);
switch (operation)
{
case OperationTypes.set:
case OperationTypes.setvalue:
num = value;
break;
case OperationTypes.add:
num += value;
break;
case OperationTypes.subtract:
num -= value;
break;
case OperationTypes.multiply:
num *= value;
break;
case OperationTypes.divide:
num /= ((value == 0f) ? 0.0001f : value);
break;
}
targets[i].Buffs.SetCustomVar(cvarName, num, (targets[i].isEntityRemote && !_params.Self.isEntityRemote) || _params.IsLocal);
}
}
public override bool ParseXmlAttribute(XAttribute _attribute)
{
bool flag = false;
string name = _attribute.Name.LocalName;
if (name != null)
{
if (name == "cache")
{
targetHash = _attribute.Value.GetHashCode();
flag = true;
}
else if (name == "action_index")
{
actionIndex = int.Parse(_attribute.Value);
flag = true;
}
}
if (!flag)
flag = base.ParseXmlAttribute(_attribute);
return flag;
}
}

View File

@@ -0,0 +1,48 @@
using System.Xml.Linq;
class MinEventActionModifyCVarWithSelfRef : MinEventActionModifyCVar
{
public override bool CanExecute(MinEventTypes _eventType, MinEventParams _params)
{
return base.CanExecute(_eventType, _params);
}
public override void Execute(MinEventParams _params)
{
if (cvarRef)
{
if (_params.Self.isEntityRemote && !_params.IsLocal)
{
return;
}
value = _params.Self.Buffs.GetCustomVar(refCvarName);
for (int i = 0; i < targets.Count; i++)
{
float num = targets[i].Buffs.GetCustomVar(cvarName);
switch (operation)
{
case OperationTypes.set:
case OperationTypes.setvalue:
num = value;
break;
case OperationTypes.add:
num += value;
break;
case OperationTypes.subtract:
num -= value;
break;
case OperationTypes.multiply:
num *= value;
break;
case OperationTypes.divide:
num /= ((value == 0f) ? 0.0001f : value);
break;
}
targets[i].Buffs.SetCustomVar(cvarName, num, (targets[i].isEntityRemote && !_params.Self.isEntityRemote) || _params.IsLocal);
}
}
else
base.Execute(_params);
}
}

View File

@@ -0,0 +1,101 @@
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using UnityEngine;
public class MinEventActionOverrideZoomFOV : MinEventActionBase
{
private int fov = 0;
//private static Type zoomDataType = typeof(ItemActionZoom).GetNestedType("ItemActionDataZoom", System.Reflection.BindingFlags.NonPublic);
//private static FieldInfo fldZoomInProgress = AccessTools.Field(zoomDataType, "bZoomInProgress");
//private static FieldInfo fldTimeZoomStarted = AccessTools.Field(zoomDataType, "timeZoomStarted");
//private static FieldInfo fldCurrentZoom = AccessTools.Field(zoomDataType, "CurrentZoom");
//private static FieldInfo fldMaxZoomOut = AccessTools.Field(zoomDataType, "MaxZoomOut");
//private static FieldInfo fldMaxZoomIn = AccessTools.Field(zoomDataType, "MaxZoomIn");
//private static FieldInfo fldEndLerpFov = AccessTools.Field(typeof(EntityPlayerLocal), "lerpCameraEndFOV");
//private static MethodInfo mtdUpdateCameraPosition = AccessTools.Method(typeof(EntityAlive), "updateCameraPosition");
public override bool CanExecute(MinEventTypes _eventType, MinEventParams _params)
{
if (!base.CanExecute(_eventType, _params))
return false;
return _params.Self is EntityPlayerLocal player && player?.inventory != null && player.inventory.holdingItemData?.actionData[1] is ItemActionZoom.ItemActionDataZoom;
}
public override void Execute(MinEventParams _params)
{
base.Execute(_params);
var target = (EntityPlayerLocal)_params.Self;
var zoomActionData = (ItemActionZoom.ItemActionDataZoom)target.inventory.holdingItemData.actionData[1];
int targetFov = fov;
int targetMax = fov;
int targetMin = fov;
//restore min max fov
if (fov <= 0)
{
float fovSetting = (float)GamePrefs.GetInt(EnumGamePrefs.OptionsGfxFOV);
ItemAction action = target.inventory.holdingItem.Actions[1];
if (action.Properties != null && action.Properties.Values.ContainsKey("Zoom_max_out"))
{
targetMax = StringParsers.ParseSInt32(target.inventory.holdingItemData.itemValue.GetPropertyOverride("Zoom_max_out", action.Properties.Values["Zoom_max_out"]), 0, -1, NumberStyles.Integer);
}
else
{
targetMax = StringParsers.ParseSInt32(target.inventory.holdingItemData.itemValue.GetPropertyOverride("Zoom_max_out", fovSetting.ToString()), 0, -1, NumberStyles.Integer);
}
if (action.Properties != null && action.Properties.Values.ContainsKey("Zoom_max_in"))
{
targetMin = StringParsers.ParseSInt32(target.inventory.holdingItemData.itemValue.GetPropertyOverride("Zoom_max_in", action.Properties.Values["Zoom_max_in"]), 0, -1, NumberStyles.Integer);
}
else
{
targetMin = StringParsers.ParseSInt32(target.inventory.holdingItemData.itemValue.GetPropertyOverride("Zoom_max_in", fovSetting.ToString()), 0, -1, NumberStyles.Integer);
}
targetFov = targetMax;
}
zoomActionData.MaxZoomIn = targetMin;
zoomActionData.MaxZoomOut = targetMax;
zoomActionData.CurrentZoom = targetFov;
//Log.Out($"setting zoom override max {targetMax} min {targetMin} cur {targetFov}");
//if is aiming, lerp towards the final result
if (target.AimingGun)
{
//if lerp not in progress, start lerp
if (!target.bLerpCameraFlag)
{
zoomActionData.bZoomInProgress = true;
zoomActionData.timeZoomStarted = Time.time;
target.updateCameraPosition(true);
}
//if already in progress, set end value
else
{
target.lerpCameraEndFOV = targetFov;
}
//Log.Out($"begin lerp camera");
}
}
public override bool ParseXmlAttribute(XAttribute _attribute)
{
if(base.ParseXmlAttribute(_attribute))
return true;
switch(_attribute.Name.LocalName)
{
case "value":
fov = StringParsers.ParseSInt32(_attribute.Value);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,31 @@
//workaround for inventory sync
//full toolbelt data is sent when holding item value changed or whatever, after a certain delay
//causing remote players to update current holding item constantly
//thus we need to handle some holding event for remote players on local player side
using System.Xml.Linq;
public class MinEventActionRemoteHoldingBase : MinEventActionBase
{
protected bool isRemoteHolding = false;
protected bool localOnly = true;
public override bool ParseXmlAttribute(XAttribute _attribute)
{
if (base.ParseXmlAttribute(_attribute))
return true;
if (_attribute.Name == "local_only")
{
localOnly = bool.Parse(_attribute.Value);
return true;
}
return false;
}
public override bool CanExecute(MinEventTypes _eventType, MinEventParams _params)
{
isRemoteHolding = (_eventType == MinEventTypes.onSelfEquipStart && _params.Self.isEntityRemote);
return (!localOnly || !_params.Self.isEntityRemote) && (!_params.Self.isEntityRemote || isRemoteHolding) && base.CanExecute(_eventType, _params);
}
}

View File

@@ -0,0 +1,15 @@
public class MinEventActionRemoveItemFromInventory : MinEventActionItemAccessBase
{
public override bool CanExecute(MinEventTypes _eventType, MinEventParams _params)
{
if (itemValueCache == null)
itemValueCache = ItemClass.GetItem(itemName);
return !_params.Self.isEntityRemote && base.CanExecute(_eventType, _params);
}
public override void Execute(MinEventParams _params)
{
_params.Self.TryRemoveItem(GetCount(_params), itemValueCache);
}
}

View File

@@ -0,0 +1,46 @@
using KFCommonUtilityLib.Scripts.StaticManagers;
using UnityEngine;
public class MinEventActionRemovePrefabFromHeldItem : MinEventActionRemovePrefabFromEntity
{
public override bool CanExecute(MinEventTypes _eventType, MinEventParams _params)
{
return base.CanExecute(_eventType, _params) && _params.Transform;
}
public override void Execute(MinEventParams _params)
{
if (!_params.Self)
{
return;
}
Transform parent = AnimationRiggingManager.GetAddPartTransformOverride(_params.Transform, parent_transform_path, false);
if (parent)
{
Transform child = null;
string prefabName = "tempPrefab_" + base.prefabName;
if (_params.Transform.TryGetComponent<AnimationTargetsAbs>(out var targets))
{
GameObject prefab = targets.GetPrefab(prefabName);
if (prefab)
{
child = prefab.transform;
}
}
if (!child)
{
child = parent.Find(prefabName);
}
if (child)
{
if (child.TryGetComponent<AttachmentReferenceAppended>(out var reference))
{
reference.Remove();
}
child.parent = null;
GameObject.Destroy(child.gameObject);
}
}
}
}

View File

@@ -0,0 +1,19 @@
public class MinEventActionRemoveRoundsFromInventory : MinEventActionAmmoAccessBase
{
private ItemValue itemValueCache;
public override bool CanExecute(MinEventTypes _eventType, MinEventParams _params)
{
if (!base.CanExecute(_eventType, _params))
return false;
var _ranged = _params.ItemValue.ItemClass.Actions[_params.ItemActionData.indexInEntityOfAction] as ItemActionRanged;
string ammoName = _ranged.MagazineItemNames[_params.ItemValue.SelectedAmmoTypeIndex];
return RoundsInInventory.TryGetValue(ammoName, out itemValueCache);
}
public override void Execute(MinEventParams _params)
{
_params.Self.TryRemoveItem(GetCount(_params), itemValueCache);
}
}

View File

@@ -0,0 +1,64 @@
using System.Xml.Linq;
public class MinEventActionSetAmmoOnWeaponLabel : MinEventActionRemoteHoldingBase
{
private int slot = 0;
private bool maxAmmo = false;
private bool useHoldingItemValue = false;
private string[] wrap;
private bool usePattern = false;
public override bool ParseXmlAttribute(XAttribute _attribute)
{
bool flag = base.ParseXmlAttribute(_attribute);
if (!flag)
{
flag = true;
string name = _attribute.Name.LocalName;
switch (name)
{
case "slot":
slot = int.Parse(_attribute.Value);
break;
case "pattern":
string str = _attribute.Value;
wrap = str.Split(new string[] { "[ammo]" }, System.StringSplitOptions.None);
usePattern = true;
break;
case "max_ammo":
maxAmmo = bool.Parse(_attribute.Value);
break;
default:
flag = false;
break;
}
}
return flag;
}
public override bool CanExecute(MinEventTypes _eventType, MinEventParams _params)
{
//somehow when onSelfEquipStart is fired, holding item value is not successfully updated in MinEventParams
useHoldingItemValue = _eventType == MinEventTypes.onSelfEquipStart;
//consume_ammo = _eventType == MinEventTypes.onSelfRangedBurstShot;
return base.CanExecute(_eventType, _params);
}
public override void Execute(MinEventParams _params)
{
int meta;
var inv = _params.Self.inventory;
var value = useHoldingItemValue ? inv.holdingItemItemValue : _params.ItemValue;
if (!maxAmmo)
meta = value.Meta;
else
meta = (int)EffectManager.GetValue(PassiveEffects.MagazineSize, value, inv.GetHoldingGun().BulletsPerMagazine, _params.Self);
string str = usePattern ? string.Join(meta.ToString(), wrap) : meta.ToString();
//int num = consume_ammo ? meta - 1 : meta;
if (isRemoteHolding || localOnly)
NetPackageSyncWeaponLabelText.SetWeaponLabelText(_params.Self, slot, str);
else if (!_params.Self.isEntityRemote)
NetPackageSyncWeaponLabelText.NetSyncSetWeaponLabelText(_params.Self, slot, str);
}
}

View File

@@ -0,0 +1,64 @@
using System.Xml.Linq;
public class MinEventActionSetMetadataOnWeaponLabel : MinEventActionRemoteHoldingBase
{
private int slot = 0;
private bool useHoldingItemValue = false;
private string[] wrap;
private bool usePattern = false;
private string metadata;
public override bool ParseXmlAttribute(XAttribute _attribute)
{
bool flag = base.ParseXmlAttribute(_attribute);
if (!flag)
{
flag = true;
string name = _attribute.Name.LocalName;
switch (name)
{
case "slot":
slot = int.Parse(_attribute.Value);
break;
case "pattern":
string str = _attribute.Value;
wrap = str.Split(new string[] { "[metadata]" }, System.StringSplitOptions.None);
usePattern = true;
break;
case "metadata":
metadata = _attribute.Value;
break;
default:
flag = false;
break;
}
}
return flag;
}
public override bool CanExecute(MinEventTypes _eventType, MinEventParams _params)
{
//somehow when onSelfEquipStart is fired, holding item value is not successfully updated in MinEventParams
useHoldingItemValue = _eventType == MinEventTypes.onSelfEquipStart;
//consume_ammo = _eventType == MinEventTypes.onSelfRangedBurstShot;
return !string.IsNullOrEmpty(metadata) && base.CanExecute(_eventType, _params);
}
public override void Execute(MinEventParams _params)
{
var inv = _params.Self.inventory;
var value = useHoldingItemValue ? inv.holdingItemItemValue : _params.ItemValue;
object obj = value.GetMetadata(metadata);
if (obj is false || obj is null)
{
return;
}
string meta = obj.ToString();
string str = usePattern ? string.Join(meta.ToString(), wrap) : meta.ToString();
if (isRemoteHolding || localOnly)
NetPackageSyncWeaponLabelText.SetWeaponLabelText(_params.Self, slot, str);
else if (!_params.Self.isEntityRemote)
NetPackageSyncWeaponLabelText.NetSyncSetWeaponLabelText(_params.Self, slot, str);
}
}

View File

@@ -0,0 +1,64 @@
using KFCommonUtilityLib.Scripts.StaticManagers;
using System.Xml.Linq;
public class MinEventActionSetPassiveOnWeaponLabel : MinEventActionRemoteHoldingBase
{
private int slot = 0;
private bool useHoldingItemValue = false;
private string[] wrap;
private bool usePattern = false;
private PassiveEffects passive;
private FastTags<TagGroup.Global> tags;
public override bool ParseXmlAttribute(XAttribute _attribute)
{
bool flag = base.ParseXmlAttribute(_attribute);
if (!flag)
{
flag = true;
string name = _attribute.Name.LocalName;
switch (name)
{
case "slot":
slot = int.Parse(_attribute.Value);
break;
case "pattern":
string str = _attribute.Value;
wrap = str.Split(new string[] { "[passive]" }, System.StringSplitOptions.None);
usePattern = true;
break;
case "passive":
passive = CustomEffectEnumManager.RegisterOrGetEnum<PassiveEffects>(_attribute.Value, true);
break;
case "tags":
tags = FastTags<TagGroup.Global>.Parse(_attribute.Value);
break;
default:
flag = false;
break;
}
}
return flag;
}
public override bool CanExecute(MinEventTypes _eventType, MinEventParams _params)
{
//somehow when onSelfEquipStart is fired, holding item value is not successfully updated in MinEventParams
useHoldingItemValue = _eventType == MinEventTypes.onSelfEquipStart;
//consume_ammo = _eventType == MinEventTypes.onSelfRangedBurstShot;
return base.CanExecute(_eventType, _params);
}
public override void Execute(MinEventParams _params)
{
var inv = _params.Self.inventory;
var value = useHoldingItemValue ? inv.holdingItemItemValue : _params.ItemValue;
float res = EffectManager.GetValue(passive, value, 0, _params.Self, null, tags);
string str = usePattern ? string.Join(res.ToString(), wrap) : res.ToString();
if (isRemoteHolding || localOnly)
NetPackageSyncWeaponLabelText.SetWeaponLabelText(_params.Self, slot, str);
else if (!_params.Self.isEntityRemote)
NetPackageSyncWeaponLabelText.NetSyncSetWeaponLabelText(_params.Self, slot, str);
}
}

View File

@@ -0,0 +1,59 @@
using System.Xml.Linq;
public class MinEventActionSetStringOnWeaponLabel : MinEventActionRemoteHoldingBase
{
private int slot = 0;
private string text;
private bool isCvar = false;
private bool isMetadata = false;
public override bool ParseXmlAttribute(XAttribute _attribute)
{
bool flag = base.ParseXmlAttribute(_attribute);
if (!flag)
{
flag = true;
string name = _attribute.Name.LocalName;
switch (name)
{
case "slot":
slot = int.Parse(_attribute.Value);
break;
case "text":
text = _attribute.Value;
break;
case "cvar":
text = _attribute.Value;
isCvar = true;
isMetadata = false;
break;
case "metadata":
text = _attribute.Value;
isMetadata = true;
isCvar = false;
break;
default:
flag = false;
break;
}
}
return flag;
}
public override bool CanExecute(MinEventTypes _eventType, MinEventParams _params)
{
if (isMetadata && (_params.ItemValue == null || !_params.ItemValue.HasMetadata(text)))
return false;
return base.CanExecute(_eventType, _params);
}
public override void Execute(MinEventParams _params)
{
if (isRemoteHolding || localOnly)
NetPackageSyncWeaponLabelText.SetWeaponLabelText(_params.Self, slot, isCvar ? _params.Self.GetCVar(text).ToString() : (isMetadata ? _params.ItemValue.GetMetadata(text).ToString() : text));
else if (!_params.Self.isEntityRemote)
NetPackageSyncWeaponLabelText.NetSyncSetWeaponLabelText(_params.Self, slot, isCvar ? _params.Self.GetCVar(text).ToString() : (isMetadata ? _params.ItemValue.GetMetadata(text).ToString() : text));
}
}

View File

@@ -0,0 +1,51 @@
using System.Xml.Linq;
using UnityEngine;
public class MinEventActionSetWeaponLabelColor : MinEventActionRemoteHoldingBase
{
private bool isText = true;
private int slot0 = 0;
private int slot1 = 0;
private Color color;
private int nameId;
public override bool ParseXmlAttribute(XAttribute _attribute)
{
bool flag = base.ParseXmlAttribute(_attribute);
if (!flag)
{
string value = _attribute.Value;
flag = true;
switch (_attribute.Name.LocalName)
{
case "is_text":
isText = bool.Parse(value);
break;
case "slot0":
slot0 = int.Parse(value);
break;
case "slot1":
slot1 = int.Parse(value);
break;
case "color":
flag = ColorUtility.TryParseHtmlString(value, out color);
break;
case "name":
nameId = Shader.PropertyToID(value);
break;
default:
flag = false;
break;
}
}
return flag;
}
public override void Execute(MinEventParams _params)
{
if (isRemoteHolding || localOnly)
NetPackageSyncWeaponLabelColor.SetWeaponLabelColor(_params.Self, isText, slot0, color, slot1, nameId);
else if (!_params.Self.isEntityRemote)
NetPackageSyncWeaponLabelColor.NetSyncSetWeaponLabelColor(_params.Self, isText, slot0, color, slot1, nameId);
}
}

View File

@@ -0,0 +1,49 @@
using KFCommonUtilityLib;
using KFCommonUtilityLib.Scripts.StaticManagers;
using System;
using System.Xml.Linq;
public class MinEventActionUpdateLocalCache : MinEventActionBase
{
private PassiveEffects passive;
private FastTags<TagGroup.Global> tags;
private int actionIndex = -1;
private int saveAs;
private string saveAsStr;
public override bool CanExecute(MinEventTypes _eventType, MinEventParams _params)
{
return !_params.Self.isEntityRemote && (actionIndex < 0 ? _params.ItemActionData : _params.ItemActionData.invData.actionData[actionIndex]) is IModuleContainerFor<ActionModuleLocalPassiveCache.LocalPassiveCacheData> && base.CanExecute(_eventType, _params);
}
public override void Execute(MinEventParams _params)
{
ActionModuleLocalPassiveCache.LocalPassiveCacheData _data = ((IModuleContainerFor<ActionModuleLocalPassiveCache.LocalPassiveCacheData>)(actionIndex < 0 ? _params.ItemActionData : _params.ItemActionData.invData.actionData[actionIndex])).Instance;
_data.CachePassive(passive, saveAs, saveAsStr, tags);
}
public override bool ParseXmlAttribute(XAttribute _attribute)
{
if (base.ParseXmlAttribute(_attribute))
return true;
switch (_attribute.Name.LocalName)
{
case "passive":
passive = CustomEffectEnumManager.RegisterOrGetEnum<PassiveEffects>(_attribute.Value, true);
return true;
case "tags":
tags = FastTags<TagGroup.Global>.Parse(_attribute.Value);
return true;
case "action_index":
actionIndex = int.Parse(_attribute.Value);
return true;
case "as":
saveAsStr = _attribute.Value;
saveAs = _attribute.Value.GetHashCode();
return true;
}
return false;
}
}

View File

@@ -0,0 +1,42 @@
using KFCommonUtilityLib.Scripts.StaticManagers;
namespace KFCommonUtilityLib.Scripts.NetPackages
{
public class NetPackageEntityActionIndex : NetPackage
{
private int entityID;
private int mode;
public NetPackageEntityActionIndex Setup(int entityID, int mode)
{
this.entityID = entityID;
this.mode = mode;
return this;
}
public override int GetLength()
{
return 5;
}
public override void ProcessPackage(World _world, GameManager _callbacks)
{
if (MultiActionManager.SetModeForEntity(entityID, mode) && ConnectionManager.Instance.IsServer)
{
ConnectionManager.Instance.SendPackage(NetPackageManager.GetPackage<NetPackageEntityActionIndex>().Setup(entityID, mode), false, -1, entityID);
}
}
public override void write(PooledBinaryWriter _writer)
{
base.write(_writer);
_writer.Write(entityID);
_writer.Write((byte)mode);
}
public override void read(PooledBinaryReader _reader)
{
entityID = _reader.ReadInt32();
mode = _reader.ReadByte();
}
}
}

View File

@@ -0,0 +1,81 @@
namespace KFCommonUtilityLib.Scripts.NetPackages
{
public class NetPackageEntitySpawnWithCVar : NetPackageEntitySpawn
{
byte[] cvarData;
public NetPackageEntitySpawnWithCVar Setup(EntityCreationData _es, EntityAlive _ea)
{
Setup(_es);
using (var bw = MemoryPools.poolBinaryWriter.AllocSync(true))
{
using (var ms = MemoryPools.poolMemoryStream.AllocSync(true))
{
bw.SetBaseStream(ms);
if (_ea && _ea.Buffs != null)
{
var buff = _ea.Buffs;
bw.Write(buff.CVars.Count);
foreach (var cvar in buff.CVars)
{
bw.Write(cvar.Key);
bw.Write(cvar.Value);
}
}
else
{
bw.Write(0);
}
cvarData = ms.ToArray();
}
}
return this;
}
public override int GetLength()
{
return base.GetLength() + 200;
}
public override void ProcessPackage(World _world, GameManager _callbacks)
{
base.ProcessPackage(_world, _callbacks);
if (_world == null || _callbacks == null || es.id == -1)
{
return;
}
EntityAlive ea = _world.GetEntity(es.id) as EntityAlive;
if (!ea)
{
return;
}
using (var ms = MemoryPools.poolMemoryStream.AllocSync(true))
{
ms.Write(cvarData, 0, cvarData.Length);
ms.Position = 0;
using (var br = MemoryPools.poolBinaryReader.AllocSync(true))
{
br.SetBaseStream(ms);
var count = br.ReadInt32();
for (int i = 0; i < count; i++)
{
ea.Buffs.SetCustomVar(br.ReadString(), br.ReadSingle(), false);
}
}
}
ea.FireEvent(CustomEnums.onSelfFirstCVarSync);
}
public override void read(PooledBinaryReader _reader)
{
base.read(_reader);
cvarData = _reader.ReadBytes(_reader.ReadInt32());
}
public override void write(PooledBinaryWriter _writer)
{
base.write(_writer);
_writer.Write(cvarData.Length);
_writer.Write(cvarData);
}
}
}

View File

@@ -0,0 +1,49 @@
using KFCommonUtilityLib.Scripts.Utilities;
class NetPackageFixedReload : NetPackage
{
private int entityId;
private byte actionIndex;
public NetPackageFixedReload Setup(int entityId, int actionIndex)
{
this.entityId = entityId;
this.actionIndex = (byte)actionIndex;
return this;
}
public override int GetLength()
{
return 5;
}
public override void ProcessPackage(World _world, GameManager _callbacks)
{
if (_world == null)
{
return;
}
if (!_world.IsRemote())
{
MultiActionUtils.FixedItemReloadServer(entityId, actionIndex);
}
else
{
MultiActionUtils.FixedItemReloadClient(entityId, actionIndex);
}
}
public override void read(PooledBinaryReader _reader)
{
entityId = _reader.ReadInt32();
actionIndex = _reader.ReadByte();
}
public override void write(PooledBinaryWriter _writer)
{
base.write(_writer);
_writer.Write(entityId);
_writer.Write(actionIndex);
}
}

View File

@@ -0,0 +1,69 @@
using UnityEngine;
namespace KFCommonUtilityLib.Scripts.NetPackages
{
public class NetPackageRemoteAttachPrefab : NetPackage
{
private int entityID;
private string prefab;
private string path;
private Vector3 localPosition;
private Vector3 localRotation;
private Vector3 localScale;
public NetPackageRemoteAttachPrefab Setup(int entityID, string prefab, string path, Vector3 localPosition, Vector3 localRotation, Vector3 localScale)
{
this.entityID = entityID;
this.prefab = prefab;
this.path = path;
this.localPosition = localPosition;
this.localRotation = localRotation;
this.localScale = localScale;
return this;
}
public override int GetLength()
{
return 200;
}
public override void ProcessPackage(World _world, GameManager _callbacks)
{
if (_world == null || _callbacks == null)
{
return;
}
var entity = _world.GetEntity(entityID) as EntityAlive;
if (!entity || !entity.isEntityRemote)
{
return;
}
if (ConnectionManager.Instance.IsServer)
{
ConnectionManager.Instance.SendPackage(NetPackageManager.GetPackage<NetPackageRemoteAttachPrefab>().Setup(entityID, prefab, path, localPosition, localRotation, localScale), false, -1, -1, entityID);
}
MinEventActionAttachPrefabToEntitySync.RemoteAttachPrefab(entity, prefab, path, localPosition, localRotation, localScale);
}
public override void read(PooledBinaryReader _reader)
{
entityID = _reader.ReadInt32();
prefab = _reader.ReadString();
path = _reader.ReadString();
localPosition = StreamUtils.ReadVector3(_reader);
localRotation = StreamUtils.ReadVector3(_reader);
localScale = StreamUtils.ReadVector3(_reader);
}
public override void write(PooledBinaryWriter _writer)
{
base.write(_writer);
_writer.Write(entityID);
_writer.Write(prefab);
_writer.Write(path);
StreamUtils.Write(_writer, localPosition);
StreamUtils.Write(_writer, localRotation);
StreamUtils.Write(_writer, localScale);
}
}
}

View File

@@ -0,0 +1,117 @@
using UnityEngine;
class NetPackageSyncWeaponLabelColor : NetPackage
{
public NetPackageSyncWeaponLabelColor Setup(int entityId, bool isText, Color color, int index0, int index1, int nameId)
{
this.entityId = entityId;
this.isText = isText;
this.color = color;
this.index0 = index0;
if (!isText)
{
this.index1 = index1;
this.nameId = nameId;
}
return this;
}
public override int GetLength()
{
return isText ? 20 : 28;
}
public override void ProcessPackage(World _world, GameManager _callbacks)
{
if (_world == null)
return;
NetSyncSetWeaponLabelColor(_world.GetEntity(entityId) as EntityAlive, isText, index0, color, index1, nameId, true);
}
public static void NetSyncSetWeaponLabelColor(EntityAlive holdingEntity, bool isText, int slot0, Color color, int slot1, int nameId, bool fromNet = false)
{
if (!holdingEntity || (holdingEntity.isEntityRemote && !fromNet))
{
if (holdingEntity)
Log.Out("netsync failed! isEntityRemote: " + holdingEntity.isEntityRemote + " fromNet: " + fromNet);
else
Log.Out("Entity not found!");
return;
}
if (SetWeaponLabelColor(holdingEntity, isText, slot0, color, slot1, nameId))
{
//Log.Out("trying to set weapon label on " + (SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer ? "server" : "client") + " color: " + color.ToString() + " entity: " + holdingEntity.entityId + " from net: " + fromNet);
if (SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer && SingletonMonoBehaviour<ConnectionManager>.Instance.ClientCount() > 0)
{
int allButAttachedToEntityId = holdingEntity.entityId;
if (holdingEntity && holdingEntity.AttachedMainEntity)
allButAttachedToEntityId = holdingEntity.AttachedMainEntity.entityId;
SingletonMonoBehaviour<ConnectionManager>.Instance.SendPackage(NetPackageManager.GetPackage<NetPackageSyncWeaponLabelColor>().Setup(holdingEntity.entityId, isText, color, slot0, slot1, nameId), false, -1, allButAttachedToEntityId, allButAttachedToEntityId, null, 15);
}
else if (SingletonMonoBehaviour<ConnectionManager>.Instance.IsClient && !fromNet)
SingletonMonoBehaviour<ConnectionManager>.Instance.SendToServer(NetPackageManager.GetPackage<NetPackageSyncWeaponLabelColor>().Setup(holdingEntity.entityId, isText, color, slot0, slot1, nameId));
}
}
public static bool SetWeaponLabelColor(EntityAlive holdingEntity, bool isText, int slot0, Color color, int slot1, int nameId)
{
if (GameManager.IsDedicatedServer)
return true;
if (isText)
{
WeaponLabelControllerBase controller = holdingEntity.inventory.GetHoldingItemTransform()?.GetComponent<WeaponLabelControllerBase>();
//if (holdingEntity.emodel.avatarController is AvatarMultiBodyController multiBody && multiBody.HeldItemTransform != null)
// controller = multiBody.HeldItemTransform.GetComponent<WeaponLabelControllerBase>();
//else if (holdingEntity.emodel.avatarController is LegacyAvatarController legacy && legacy.HeldItemTransform != null)
// controller = legacy.HeldItemTransform.GetComponent<WeaponLabelControllerBase>();
return controller && controller.setLabelColor(slot0, color);
}
else
{
WeaponColorControllerBase controller = holdingEntity.inventory.GetHoldingItemTransform()?.GetComponent<WeaponColorControllerBase>();
//if (holdingEntity.emodel.avatarController is AvatarMultiBodyController multiBody && multiBody.HeldItemTransform != null)
// controller = multiBody.HeldItemTransform.GetComponent<WeaponColorControllerBase>();
//else if (holdingEntity.emodel.avatarController is LegacyAvatarController legacy && legacy.HeldItemTransform != null)
// controller = legacy.HeldItemTransform.GetComponent<WeaponColorControllerBase>();
return controller && controller.setMaterialColor(slot0, slot1, nameId, color);
}
}
public override void read(PooledBinaryReader _reader)
{
entityId = _reader.ReadInt32();
isText = _reader.ReadBoolean();
color = StreamUtils.ReadColor(_reader);
index0 = _reader.ReadChar();
if (!isText)
{
index1 = _reader.ReadChar();
nameId = _reader.ReadInt32();
}
}
public override void write(PooledBinaryWriter _writer)
{
base.write(_writer);
_writer.Write(entityId);
_writer.Write(isText);
StreamUtils.Write(_writer, color);
_writer.Write((char)index0);
if (!isText)
{
_writer.Write((char)index1);
_writer.Write(nameId);
}
}
private int entityId;
private bool isText;
private Color color;
private int index0;
private int index1;
private int nameId;
}

View File

@@ -0,0 +1,82 @@
class NetPackageSyncWeaponLabelText : NetPackage
{
public NetPackageSyncWeaponLabelText Setup(int entityId, int slot, string data)
{
this.entityId = entityId;
this.slot = slot;
this.data = data;
return this;
}
public override int GetLength()
{
return 6 + data.Length;
}
public override void ProcessPackage(World _world, GameManager _callbacks)
{
if (_world == null)
return;
NetSyncSetWeaponLabelText(_world.GetEntity(entityId) as EntityAlive, slot, data, true);
}
public override void read(PooledBinaryReader _reader)
{
entityId = _reader.ReadInt32();
slot = (int)_reader.ReadChar();
data = _reader.ReadString();
}
public override void write(PooledBinaryWriter _writer)
{
base.write(_writer);
_writer.Write(entityId);
_writer.Write((char)slot);
_writer.Write(data);
}
public static void NetSyncSetWeaponLabelText(EntityAlive holdingEntity, int slot, string data, bool fromNet = false)
{
if (!holdingEntity || (holdingEntity.isEntityRemote && !fromNet))
{
if (holdingEntity)
Log.Out("netsync failed! isEntityRemote: " + holdingEntity.isEntityRemote + " fromNet: " + fromNet);
else
Log.Out("Entity not found!");
return;
}
if (SetWeaponLabelText(holdingEntity, slot, data))
{
//Log.Out("trying to set weapon label on " + (SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer ? "server" : "client") + " slot: " + slot + " text: " + data + " entity: " + holdingEntity.entityId + " from net: " + fromNet);
if (SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer && SingletonMonoBehaviour<ConnectionManager>.Instance.ClientCount() > 0)
{
int allButAttachedToEntityId = holdingEntity.entityId;
if (holdingEntity.AttachedMainEntity)
allButAttachedToEntityId = holdingEntity.AttachedMainEntity.entityId;
SingletonMonoBehaviour<ConnectionManager>.Instance.SendPackage(NetPackageManager.GetPackage<NetPackageSyncWeaponLabelText>().Setup(holdingEntity.entityId, slot, data), false, -1, allButAttachedToEntityId, allButAttachedToEntityId, null, 15);
}
else if (SingletonMonoBehaviour<ConnectionManager>.Instance.IsClient && !fromNet)
SingletonMonoBehaviour<ConnectionManager>.Instance.SendToServer(NetPackageManager.GetPackage<NetPackageSyncWeaponLabelText>().Setup(holdingEntity.entityId, slot, data));
}
}
public static bool SetWeaponLabelText(EntityAlive holdingEntity, int slot, string data)
{
if (GameManager.IsDedicatedServer)
return true;
WeaponLabelControllerBase controller = holdingEntity.inventory.GetHoldingItemTransform()?.GetComponent<WeaponLabelControllerBase>();
//if (holdingEntity.emodel.avatarController is AvatarMultiBodyController multiBody && multiBody.HeldItemTransform != null)
// controller = multiBody.HeldItemTransform.GetComponent<WeaponLabelControllerBase>();
//else if (holdingEntity.emodel.avatarController is LegacyAvatarController legacy && legacy.HeldItemTransform)
// controller = legacy.HeldItemTransform.GetComponent<WeaponLabelControllerBase>();
return controller && controller.setLabelText(slot, data);
}
private int entityId;
private int slot;
private string data;
}

View File

@@ -0,0 +1,52 @@
using KFCommonUtilityLib;
using System.Xml.Linq;
public class ActionHasTags : TargetedCompareRequirementBase
{
private FastTags<TagGroup.Global> actionTags;
private bool hasAllTags;
public override bool IsValid(MinEventParams _params)
{
if (!base.IsValid(_params))
{
return false;
}
bool flag = false;
if (_params.ItemActionData is IModuleContainerFor<ActionModuleTagged.TaggedData> tagged)
{
flag = (hasAllTags ? tagged.Instance.tags.Test_AllSet(actionTags) : tagged.Instance.tags.Test_AnySet(actionTags));
}
if (!invert)
{
return flag;
}
return !flag;
}
public override bool ParseXAttribute(XAttribute _attribute)
{
bool flag = base.ParseXAttribute(_attribute);
if (!flag)
{
string localName = _attribute.Name.LocalName;
if (localName == "tags")
{
actionTags = FastTags<TagGroup.Global>.Parse(_attribute.Value);
return true;
}
if (localName == "has_all_tags")
{
hasAllTags = StringParsers.ParseBool(_attribute.Value);
return true;
}
}
return flag;
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Xml.Linq;
public class ActionIndexIs : RequirementBase
{
protected int index;
public override bool IsValid(MinEventParams _params)
{
//if (!res)
//{
// Log.Out($"Action index is not {index} : {(_params.ItemActionData == null ? "null" : _params.ItemActionData.indexInEntityOfAction.ToString())}\n{StackTraceUtility.ExtractStackTrace()}");
//}
var res = (_params.ItemActionData == null && index == 0) || _params.ItemActionData?.indexInEntityOfAction == index;
return invert ? !res : res;
}
public override bool ParamsValid(MinEventParams _params)
{
return true;
}
public override bool ParseXAttribute(XAttribute _attribute)
{
if (_attribute.Name == "index")
{
index = Math.Max(int.Parse(_attribute.Value), 0);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,44 @@
using KFCommonUtilityLib.Scripts.Utilities;
using System.Xml.Linq;
public class AmmoIndexIs : RequirementBase
{
protected int ammoIndex;
protected int actionIndex;
protected ItemValue itemValueCache;
public override bool IsValid(MinEventParams _params)
{
bool res = false;
int parAmmoIndex = itemValueCache.GetSelectedAmmoIndexByActionIndex(actionIndex);
res = parAmmoIndex == ammoIndex;
itemValueCache = null;
if (invert)
{
return !res;
}
return res;
}
public override bool ParamsValid(MinEventParams _params)
{
itemValueCache = _params.ItemValue;
return itemValueCache != null;
}
public override bool ParseXAttribute(XAttribute _attribute)
{
switch(_attribute.Name.LocalName)
{
case "ammoIndex":
ammoIndex = int.Parse(_attribute.Value);
break;
case "actionIndex":
actionIndex = int.Parse(_attribute.Value);
break;
default:
return false;
}
return true;
}
}

View File

@@ -0,0 +1,32 @@
using KFCommonUtilityLib;
using System;
using System.Xml.Linq;
public class FireModeIs : RequirementBase
{
protected int index;
public override bool IsValid(MinEventParams _params)
{
bool res = false;
if (_params.ItemActionData is IModuleContainerFor<ActionModuleFireModeSelector.FireModeData> dataModule)
{
res = dataModule.Instance.currentFireMode == index;
}
return invert ? !res : res;
}
public override bool ParamsValid(MinEventParams _params)
{
return true;
}
public override bool ParseXAttribute(XAttribute _attribute)
{
if (_attribute.Name == "index")
{
index = Math.Max(int.Parse(_attribute.Value), 0);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,9 @@
using KFCommonUtilityLib.Scripts.StaticManagers;
public class HoldingActionIndexIs : ActionIndexIs
{
public override bool IsValid(MinEventParams _params)
{
return (MultiActionManager.GetActionIndexForEntity(_params.Self) == index) ^ invert;
}
}

View File

@@ -0,0 +1,8 @@
public class HoldingAmmoIndexIs : AmmoIndexIs
{
public override bool ParamsValid(MinEventParams _params)
{
itemValueCache = _params.Self?.inventory?.holdingItemItemValue;
return itemValueCache != null;
}
}

View File

@@ -0,0 +1,33 @@
using KFCommonUtilityLib;
using KFCommonUtilityLib.Scripts.StaticManagers;
using System;
using System.Xml.Linq;
public class HoldingFireModeIs : RequirementBase
{
protected int index;
public override bool IsValid(MinEventParams _params)
{
bool res = false;
if (_params.Self && _params.Self?.inventory?.holdingItemData?.actionData[MultiActionManager.GetActionIndexForEntity(_params.Self)] is IModuleContainerFor<ActionModuleFireModeSelector.FireModeData> dataModule)
{
res = dataModule.Instance.currentFireMode == index;
}
return invert ? !res : res;
}
public override bool ParamsValid(MinEventParams _params)
{
return true;
}
public override bool ParseXAttribute(XAttribute _attribute)
{
if (_attribute.Name == "index")
{
index = Math.Max(int.Parse(_attribute.Value), 0);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,28 @@
using KFCommonUtilityLib;
using System.Xml.Linq;
public class IsActionUnlocked : TargetedCompareRequirementBase
{
protected int actionIndex;
public override bool IsValid(MinEventParams _params)
{
return base.IsValid(_params) &&
((actionIndex == 0 ||
(_params.ItemActionData?.invData?.actionData?[0] is IModuleContainerFor<ActionModuleAlternative.AlternativeData> alt
&& alt.Instance.IsActionUnlocked(actionIndex))) ^ invert);
}
public override bool ParseXAttribute(XAttribute _attribute)
{
if (base.ParseXAttribute(_attribute))
return true;
if (_attribute.Name == "index")
{
actionIndex = int.Parse(_attribute.Value);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,64 @@
using UniLinq;
using System.Xml.Linq;
using UnityEngine;
public class IsHoldingItemModificationActivated : RequirementBase
{
private string modName;
private int modId = -1;
public override bool IsValid(MinEventParams _params)
{
if (modId < 0)
{
modId = ItemClass.GetItemClass(modName)?.Id ?? -1;
//Log.Out($"modId {modId}");
if (modId < 0)
return false;
}
ItemValue itemValue = _params.Self?.inventory?.holdingItemItemValue;
//Log.Out($"modName {modName} modId {modId} item {_params?.ItemValue?.ItemClass?.Name ?? "null"} mods{(_params?.ItemValue?.Modifications == null ? ": null" : itemValue.Modifications.Select(v => $"\n{(v == null || v.IsEmpty() ? "null" : $"item {v.ItemClass.Name} type {v.type} activated {v.Activated}")}").Join())} \ncos{(_params?.ItemValue?.CosmeticMods == null ? ": null" : itemValue.CosmeticMods.Select(v => $"\n{(v == null || v.IsEmpty() ? "null" : $"item {v.ItemClass.Name} type {v.type} activated {v.Activated}")}").Join())} \n{StackTraceUtility.ExtractStackTrace()}");
if (itemValue != null)
{
if (itemValue.Modifications != null)
{
foreach (var mod in itemValue.Modifications)
{
if (mod != null && mod.type == modId && mod.Activated > 0)
{
return !invert;
}
}
}
if (itemValue.CosmeticMods != null)
{
foreach (var cos in itemValue.CosmeticMods)
{
if (cos != null && cos.type == modId && cos.Activated > 0)
{
return !invert;
}
}
}
}
return invert;
}
public override bool ParseXAttribute(XAttribute _attribute)
{
if (base.ParseXAttribute(_attribute))
return true;
switch (_attribute.Name.LocalName)
{
case "mod":
modName = _attribute.Value;
return true;
}
return false;
}
}

View File

@@ -0,0 +1,15 @@
public class IsInJeep : IsAttachedToEntity
{
public override bool IsValid(MinEventParams _params)
{
if (!base.IsValid(_params))
{
return false;
}
if (invert)
{
return !(target.AttachedToEntity is EntityVJeep);
}
return target.AttachedToEntity is EntityVJeep;
}
}

View File

@@ -0,0 +1,7 @@
public class IsLocal : RequirementBase
{
public override bool IsValid(MinEventParams _params)
{
return base.IsValid(_params) && ((_params.IsLocal || (_params.Self && !_params.Self.isEntityRemote)) ^ invert);
}
}

View File

@@ -0,0 +1,63 @@
using UniLinq;
using System.Xml.Linq;
using UnityEngine;
public class IsModificationActivated : RequirementBase
{
private string modName;
private int modId = -1;
public override bool IsValid(MinEventParams _params)
{
if (modId < 0)
{
modId = ItemClass.GetItemClass(modName)?.Id ?? -1;
//Log.Out($"modId {modId}");
if (modId < 0)
return false;
}
//Log.Out($"modName {modName} modId {modId} item {_params?.ItemValue?.ItemClass?.Name ?? "null"} mods{(_params?.ItemValue?.Modifications == null ? ": null" : _params.ItemValue.Modifications.Select(v => $"\n{(v == null || v.IsEmpty() ? "null" : $"item {v.ItemClass.Name} type {v.type} activated {v.Activated}")}").Join())} \ncos{(_params?.ItemValue?.CosmeticMods == null ? ": null" : _params.ItemValue.CosmeticMods.Select(v => $"\n{(v == null || v.IsEmpty() ? "null" : $"item {v.ItemClass.Name} type {v.type} activated {v.Activated}")}").Join())} \n{StackTraceUtility.ExtractStackTrace()}");
if (_params.ItemValue != null)
{
if (_params.ItemValue.Modifications != null)
{
foreach (var mod in _params.ItemValue.Modifications)
{
if (mod != null && mod.type == modId && mod.Activated > 0)
{
return !invert;
}
}
}
if (_params.ItemValue.CosmeticMods != null)
{
foreach (var cos in _params.ItemValue.CosmeticMods)
{
if (cos != null && cos.type == modId && cos.Activated > 0)
{
return !invert;
}
}
}
}
return invert;
}
public override bool ParseXAttribute(XAttribute _attribute)
{
if (base.ParseXAttribute(_attribute))
return true;
switch (_attribute.Name.LocalName)
{
case "mod":
modName = _attribute.Value;
return true;
}
return false;
}
}

View File

@@ -0,0 +1,7 @@
//public class ItemActionIndexIs : ActionIndexIs
//{
// public override bool IsValid(MinEventParams _params)
// {
// return (_params.ItemValue == null && index == 0) || _params.ItemValue?.GetActionIndexForItemValue() == index;
// }
//}

View File

@@ -0,0 +1,33 @@
using System.Xml.Linq;
public class ItemInInventory : RequirementBase
{
private string itemName;
private ItemValue itemValueCache = null;
public override bool IsValid(MinEventParams _params)
{
return base.IsValid(_params) && compareValues(_params.Self.GetItemCount(itemValueCache), operation, value) ^ invert;
}
public override bool ParamsValid(MinEventParams _params)
{
if (itemValueCache == null)
itemValueCache = ItemClass.GetItem(itemName);
return base.ParamsValid(_params) && itemValueCache != null;
}
public override bool ParseXAttribute(XAttribute _attribute)
{
if (base.ParseXAttribute(_attribute))
return true;
string name = _attribute.Name.LocalName;
if (name == "item")
{
itemName = _attribute.Value;
return true;
}
return false;
}
}

View File

@@ -0,0 +1,17 @@
using KFCommonUtilityLib.Scripts.StaticManagers;
public class PercentInHoldingItem : RoundsInHoldingItem
{
public override bool IsValid(MinEventParams _params)
{
if (!ParamsValid(_params))
return false;
ItemValue holdingItemValue = _params.Self.inventory.holdingItemItemValue;
int actionIndex = MultiActionManager.GetActionIndexForEntity(_params.Self);
if (holdingItemValue.IsEmpty() || !(holdingItemValue.ItemClass.Actions[actionIndex] is ItemActionRanged _ranged))
return false;
return RequirementBase.compareValues((float)(roundsBeforeShot ? holdingItemValue.Meta + 1 : holdingItemValue.Meta) / _ranged.GetMaxAmmoCount(_params.Self.inventory.holdingItemData.actionData[actionIndex]), operation, value) ^ invert;
}
}

View File

@@ -0,0 +1,14 @@
public class PercentInMagazine : RoundsInMagazineBase
{
public override bool IsValid(MinEventParams _params)
{
if (!ParamsValid(_params))
return false;
if (_params.ItemValue.IsEmpty() || !(_params.ItemActionData is ItemActionRanged.ItemActionDataRanged _rangedData) || _params.ItemActionData.invData == null)
return false;
return RequirementBase.compareValues((float)(roundsBeforeShot ? _params.ItemValue.Meta + 1 : _params.ItemValue.Meta) / ((ItemActionRanged)_params.ItemValue.ItemClass.Actions[_rangedData.indexInEntityOfAction]).GetMaxAmmoCount(_params.ItemActionData), operation, value) ^ invert;
}
}

View File

@@ -0,0 +1,17 @@
using KFCommonUtilityLib.Scripts.StaticManagers;
public class RoundsInHoldingItem : RoundsInMagazineBase
{
public override bool IsValid(MinEventParams _params)
{
if (!ParamsValid(_params))
return false;
ItemValue holdingItemValue = _params.Self.inventory.holdingItemItemValue;
if (holdingItemValue.IsEmpty() || !(holdingItemValue.ItemClass.Actions[MultiActionManager.GetActionIndexForEntity(_params.Self)] is ItemActionRanged))
return false;
return RequirementBase.compareValues((float)(roundsBeforeShot ? holdingItemValue.Meta + 1 : holdingItemValue.Meta), operation, value) ^ invert;
}
}

View File

@@ -0,0 +1,32 @@
using System.Collections.Generic;
public class RoundsInInventory : RequirementBase
{
private static Dictionary<string, ItemValue> cached = new Dictionary<string, ItemValue>();
public static bool TryGetValue(string ammoName, out ItemValue ammoValue)
{
if (!cached.TryGetValue(ammoName, out ammoValue))
{
ammoValue = ItemClass.GetItem(ammoName, false);
if (ammoValue == null)
return false;
cached.Add(ammoName, ammoValue);
}
return true;
}
public override bool IsValid(MinEventParams _params)
{
if (!this.ParamsValid(_params))
return invert;
ItemValue itemValue = _params.ItemValue;
if (itemValue.IsEmpty() || !(_params.ItemActionData is ItemActionRanged.ItemActionDataRanged _rangedData))
return invert;
string ammoName = ((ItemActionRanged)itemValue.ItemClass.Actions[_rangedData.indexInEntityOfAction]).MagazineItemNames[itemValue.SelectedAmmoTypeIndex];
if (TryGetValue(ammoName, out var ammoValue))
return compareValues(_params.Self.GetItemCount(ammoValue), this.operation, this.value) ^ invert;
return invert;
}
}

View File

@@ -0,0 +1,21 @@
using System.Xml.Linq;
public class RoundsInMagazineBase : RoundsInMagazine
{
protected bool roundsBeforeShot = false;
public override bool ParseXAttribute(XAttribute _attribute)
{
if (base.ParseXAttribute(_attribute))
return true;
if (_attribute.Name.LocalName == "rounds_before_shot")
{
roundsBeforeShot = bool.Parse(_attribute.Value);
return true;
}
return false;
}
}

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;
}
}
}
}

View File

@@ -0,0 +1,125 @@
using System;
//https://gist.github.com/Kryzarel/bba64622057f21a1d6d44879f9cd7bd4
namespace Kryz.Tweening
{
// Made with the help of this great post: https://joshondesign.com/2013/03/01/improvedEasingEquations
// --------------------------------- Other Related Links --------------------------------------------------------------------
// Original equations, bad formulation: https://github.com/danro/jquery-easing/blob/master/jquery.easing.js
// A few equations, very simplified: https://gist.github.com/gre/1650294
// Easings.net equations, simplified: https://github.com/ai/easings.net/blob/master/src/easings/easingsFunctions.ts
public static class EasingFunctions
{
public static float Linear(float t) => t;
public static float InQuad(float t) => t * t;
public static float OutQuad(float t) => 1 - InQuad(1 - t);
public static float InOutQuad(float t)
{
if (t < 0.5) return InQuad(t * 2) / 2;
return 1 - InQuad((1 - t) * 2) / 2;
}
public static float InCubic(float t) => t * t * t;
public static float OutCubic(float t) => 1 - InCubic(1 - t);
public static float InOutCubic(float t)
{
if (t < 0.5) return InCubic(t * 2) / 2;
return 1 - InCubic((1 - t) * 2) / 2;
}
public static float InQuart(float t) => t * t * t * t;
public static float OutQuart(float t) => 1 - InQuart(1 - t);
public static float InOutQuart(float t)
{
if (t < 0.5) return InQuart(t * 2) / 2;
return 1 - InQuart((1 - t) * 2) / 2;
}
public static float InQuint(float t) => t * t * t * t * t;
public static float OutQuint(float t) => 1 - InQuint(1 - t);
public static float InOutQuint(float t)
{
if (t < 0.5) return InQuint(t * 2) / 2;
return 1 - InQuint((1 - t) * 2) / 2;
}
public static float InSine(float t) => 1 - (float)Math.Cos(t * Math.PI / 2);
public static float OutSine(float t) => (float)Math.Sin(t * Math.PI / 2);
public static float InOutSine(float t) => (float)(Math.Cos(t * Math.PI) - 1) / -2;
public static float InExpo(float t) => (float)Math.Pow(2, 10 * (t - 1));
public static float OutExpo(float t) => 1 - InExpo(1 - t);
public static float InOutExpo(float t)
{
if (t < 0.5) return InExpo(t * 2) / 2;
return 1 - InExpo((1 - t) * 2) / 2;
}
public static float InCirc(float t) => -((float)Math.Sqrt(1 - t * t) - 1);
public static float OutCirc(float t) => 1 - InCirc(1 - t);
public static float InOutCirc(float t)
{
if (t < 0.5) return InCirc(t * 2) / 2;
return 1 - InCirc((1 - t) * 2) / 2;
}
public static float InElastic(float t) => 1 - OutElastic(1 - t);
public static float OutElastic(float t)
{
float p = 0.3f;
return (float)Math.Pow(2, -10 * t) * (float)Math.Sin((t - p / 4) * (2 * Math.PI) / p) + 1;
}
public static float InOutElastic(float t)
{
if (t < 0.5) return InElastic(t * 2) / 2;
return 1 - InElastic((1 - t) * 2) / 2;
}
public static float InBack(float t)
{
float s = 1.70158f;
return t * t * ((s + 1) * t - s);
}
public static float OutBack(float t) => 1 - InBack(1 - t);
public static float InOutBack(float t)
{
if (t < 0.5) return InBack(t * 2) / 2;
return 1 - InBack((1 - t) * 2) / 2;
}
public static float InBounce(float t) => 1 - OutBounce(1 - t);
public static float OutBounce(float t)
{
float div = 2.75f;
float mult = 7.5625f;
if (t < 1 / div)
{
return mult * t * t;
}
else if (t < 2 / div)
{
t -= 1.5f / div;
return mult * t * t + 0.75f;
}
else if (t < 2.5 / div)
{
t -= 2.25f / div;
return mult * t * t + 0.9375f;
}
else
{
t -= 2.625f / div;
return mult * t * t + 0.984375f;
}
}
public static float InOutBounce(float t)
{
if (t < 0.5) return InBounce(t * 2) / 2;
return 1 - InBounce((1 - t) * 2) / 2;
}
}
}

View File

@@ -0,0 +1,67 @@
public static class EntityInventoryExtension
{
public static void TryStackItem(this EntityAlive self, ItemStack stack)
{
if (self.bag.TryStackItem(stack))
return;
self.inventory.TryStackItem(stack);
}
public static void TryRemoveItem(this EntityAlive self, int count, ItemValue value)
{
int decFromInv = self.bag.DecItem(value, count) - count;
if (decFromInv > 0)
self.inventory.DecItem(value, decFromInv);
}
public static int GetItemCount(this EntityAlive self, ItemValue value)
{
return self.inventory.GetItemCount(value) + self.bag.GetItemCount(value);
}
public static bool TryStackItem(this Bag self, ItemStack stack)
{
ItemStack[] slots = self.GetSlots();
TryStackItem(slots, stack);
self.SetSlots(slots);
return stack.count == 0;
}
public static bool TryStackItem(this Inventory self, ItemStack stack)
{
ItemStack[] slots = self.GetSlots();
TryStackItem(slots, stack);
self.CallOnToolbeltChangedInternal();
return stack.count == 0;
}
public static void TryStackWith(this ItemStack self, ItemStack other)
{
int maxStackCount = other.itemValue.ItemClass.Stacknumber.Value;
if (self.IsEmpty())
{
self.itemValue = other.itemValue.Clone();
self.count = Utils.FastMin(maxStackCount, other.count);
other.count -= self.count;
return;
}
if (self.itemValue.type != other.itemValue.type || self.itemValue.Texture != other.itemValue.Texture || self.count >= maxStackCount)
return;
int add = Utils.FastMin(maxStackCount - self.count, other.count);
self.count += add;
other.count -= add;
}
private static void TryStackItem(ItemStack[] slots, ItemStack stack)
{
foreach (var slot in slots)
{
slot.TryStackWith(stack);
if (stack.count == 0)
return;
}
}
}

View File

@@ -0,0 +1,424 @@
using System;
using System.Collections.Generic;
using UniLinq;
using Sprache;
using static Sprache.Parse;
namespace CodeWriter.ExpressionParser
{
public delegate T Expression<out T>();
public abstract class ExpressionParser<T>
{
private readonly Dictionary<string, ExprBuilder> _builderCached = new Dictionary<string, ExprBuilder>();
private Parser<ExprBuilder> _parserCached;
public Expression<bool> CompilePredicate(string input, ExpressionContext<T> context, bool cache)
{
var expr = Compile(input, context, cache);
return () => IsTrue(expr.Invoke());
}
public Expression<T> Compile(string input, ExpressionContext<T> context, bool cache)
{
context = context ?? new ExpressionContext<T>();
_parserCached = _parserCached ?? CreateParser();
ExprBuilder builder;
try
{
if (cache)
{
if (!_builderCached.TryGetValue(input, out builder))
{
builder = _parserCached.Parse(input);
_builderCached.Add(input, builder);
}
}
else
{
builder = _parserCached.Parse(input);
}
}
catch (ParseException parseException)
{
throw new ExpressionParseException(input, parseException);
}
return builder.Invoke(context);
}
private static Parser<BinaryFunc> Operator(string op, BinaryFunc fun)
{
return String(op).Token().Return(fun).Named(op);
}
private static Parser<ExprBuilder> BinaryOperator(Parser<BinaryFunc> op,
Parser<ExprBuilder> operand, Func<BinaryFunc, ExprBuilder, ExprBuilder, ExprBuilder> apply) =>
(
from l in operand
from rest in
(
from f in op
from r in operand
select apply(f, l, r)
).Optional()
select rest.IsEmpty ? l : rest.Get()
);
private Parser<ExprBuilder> CreateParser()
{
var letterOrUnderscore = Char(c => char.IsLetter(c) || c == '_',
"letter or underscore");
var letterOrDigitOrUnderscore = Char(c => char.IsLetterOrDigit(c) || c == '_',
"letter or digit or underscore");
var constant = (
from number in DecimalInvariant
select MakeConstant(number, Parse)
).Named("number");
var add = Operator("+", Add);
var sub = Operator("-", Sub);
var mul = Operator("*", Mul);
var div = Operator("/", Div);
var mod = Operator("%", Mod);
var pow = Operator("^", Pow);
var eq = Operator("=", Equal);
var neq = Operator("!=", NotEqual);
var and = Operator("AND", And);
var or = Operator("OR", Or);
var lt = Operator("<", LessThan);
var lte = Operator("<=", LessThanOrEqual);
var gt = Operator(">", GreaterThan);
var gte = Operator(">=", GreaterThanOrEqual);
var variable =
(
from nameHead in letterOrUnderscore.Once().Text()
from nameTail in letterOrDigitOrUnderscore.Many().Text()
select MakeVariable(nameHead + nameTail)
).Named("variable");
Parser<ExprBuilder> expression = null;
var function =
(
from name in Letter.AtLeastOnce().Text()
from lparen in Char('(')
// ReSharper disable once AccessToModifiedClosure
from expr in Ref(() => expression).DelimitedBy(Char(',').Token())
from rparen in Char(')')
select MakeFunction(name, expr.ToList())
).Named("function");
var factor =
(
from lparen in Char('(')
// ReSharper disable once AccessToModifiedClosure
from expr in Ref(() => expression)
from rparen in Char(')')
select expr
).Named("expression")
.XOr(constant)
.XOr(function)
.Or(variable);
var operand =
(
(
from sign in Char('-')
from fact in factor
select MakeUnary(Negate, fact)
)
.XOr(factor)
).Token();
var term1 = ChainRightOperator(pow, operand, MakeBinary);
var term2 = ChainOperator(mul.Or(div).Or(mod), term1, MakeBinary);
var term3 = ChainOperator(add.Or(sub), term2, MakeBinary);
var term4 = BinaryOperator(lte.Or(lt).Or(gte).Or(gt), term3, MakeBinary);
var term5 = BinaryOperator(eq.Or(neq), term4, MakeBinary);
var term6 = ChainOperator(and, term5, MakeBinary);
var term7 = ChainOperator(or, term6, MakeBinary);
expression = term7;
return expression.End();
}
protected abstract T False { get; }
protected abstract T True { get; }
protected abstract T Parse(string input);
protected abstract T Negate(T v);
protected abstract T Add(T a, T b);
protected abstract T Sub(T a, T b);
protected abstract T Mul(T a, T b);
protected abstract T Div(T a, T b);
protected abstract T Mod(T a, T b);
protected abstract T Pow(T a, T b);
protected abstract T Equal(T a, T b);
protected abstract T NotEqual(T a, T b);
protected abstract T LessThan(T a, T b);
protected abstract T LessThanOrEqual(T a, T b);
protected abstract T GreaterThan(T a, T b);
protected abstract T GreaterThanOrEqual(T a, T b);
protected abstract bool IsTrue(T v);
protected abstract T Round(T v);
protected abstract T Floor(T v);
protected abstract T Ceiling(T v);
protected abstract T Log10(T v);
protected virtual T Log(T v, T newBase) => Div(Log10(v), Log10(newBase));
private T Not(T v) => IsTrue(v) ? False : True;
private T And(T a, T b) => IsTrue(a) ? b : a;
private T Or(T a, T b) => IsTrue(a) ? a : b;
private T Min(T a, T b) => IsTrue(GreaterThan(a, b)) ? b : a;
private T Max(T a, T b) => IsTrue(GreaterThan(b, a)) ? b : a;
private delegate Expression<T> ExprBuilder(ExpressionContext<T> context);
private delegate T UnaryFunc(T a);
private delegate T BinaryFunc(T a, T b);
private ExprBuilder MakeFunction(string name, List<ExprBuilder> parameterBuilders)
{
switch (name)
{
case "NOT":
return MakeFunction1(Not);
case "ROUND":
return MakeFunction1(Round);
case "CEILING":
return MakeFunction1(Ceiling);
case "FLOOR":
return MakeFunction1(Floor);
case "LOG":
return parameterBuilders.Count == 2
? MakeBinary(Log, parameterBuilders[0], parameterBuilders[1])
: MakeFunction1(Log10);
case "MIN":
return MakeFunctionFold(Min);
case "MAX":
return MakeFunctionFold(Max);
case "IF":
if (parameterBuilders.Count < 3 ||
parameterBuilders.Count % 2 != 1)
{
throw new FunctionNotDefinedException(name, "Wrong parameters count");
}
return context =>
{
var conditions = new List<Expression<T>>();
var results = new List<Expression<T>>();
var defaultResult = parameterBuilders[parameterBuilders.Count - 1].Invoke(context);
for (var i = 0; i < parameterBuilders.Count - 1; i += 2)
{
conditions.Add(parameterBuilders[i].Invoke(context));
results.Add(parameterBuilders[i + 1].Invoke(context));
}
return () =>
{
for (var i = 0; i < conditions.Count; i++)
{
if (IsTrue(conditions[i].Invoke()))
{
return results[i].Invoke();
}
}
return defaultResult.Invoke();
};
};
default: throw new FunctionNotDefinedException(name, "Unknown name");
}
ExprBuilder MakeFunction1(Func<T, T> func)
{
if (parameterBuilders.Count != 1)
{
throw new FunctionNotDefinedException(name, "Wrong parameters count");
}
return context =>
{
var inner = parameterBuilders[0].Invoke(context);
return () => func(inner.Invoke());
};
}
ExprBuilder MakeFunctionFold(Func<T, T, T> func)
{
if (parameterBuilders.Count < 1)
{
throw new FunctionNotDefinedException(name, "Wrong parameters count");
}
return context =>
{
var inner = new List<Expression<T>>();
for (var i = 0; i < parameterBuilders.Count; i++)
{
inner.Add(parameterBuilders[i].Invoke(context));
}
return () =>
{
var result = inner[0].Invoke();
for (var i = 1; i < inner.Count; i++)
{
result = func(result, inner[i].Invoke());
}
return result;
};
};
}
}
private static ExprBuilder MakeUnary(UnaryFunc func, ExprBuilder innerBuilder)
{
return context =>
{
var inner = innerBuilder.Invoke(context);
return () => func(inner.Invoke());
};
}
private static ExprBuilder MakeBinary(BinaryFunc func, ExprBuilder l, ExprBuilder r)
{
return context =>
{
var left = l.Invoke(context);
var right = r.Invoke(context);
return () => func(left.Invoke(), right.Invoke());
};
}
private ExprBuilder MakeVariable(string name)
{
if (name.Equals("TRUE", StringComparison.Ordinal))
{
return context => () => True;
}
if (name.Equals("FALSE", StringComparison.Ordinal))
{
return context => () => False;
}
return context =>
{
var variable = context.GetVariable(name);
return variable;
};
}
private static ExprBuilder MakeConstant(string valueString, Func<string, T> parser)
{
var value = parser(valueString);
return context => () => value;
}
}
public class ExpressionContext<T>
{
private readonly ExpressionContext<T> _parent;
private readonly Func<string, Expression<T>> _unregisteredVariableResolver;
private readonly Dictionary<string, Expression<T>> _variables = new Dictionary<string, Expression<T>>();
public ExpressionContext(ExpressionContext<T> parent = null,
Func<string, Expression<T>> unregisteredVariableResolver = null)
{
_parent = parent;
_unregisteredVariableResolver = unregisteredVariableResolver;
}
public void RegisterVariable(string name, Expression<T> value)
{
if (_variables.ContainsKey(name))
{
throw new InvalidOperationException($"Variable {name} already registered");
}
_variables.Add(name, value);
}
public Expression<T> GetVariable(string name, bool nullIsOk = false)
{
if (_variables.TryGetValue(name, out var variable))
{
return variable;
}
if (_unregisteredVariableResolver != null)
{
variable = _unregisteredVariableResolver.Invoke(name);
if (variable != null)
{
return variable;
}
}
var parentVariable = _parent?.GetVariable(name, nullIsOk: true);
if (parentVariable != null)
{
return parentVariable;
}
if (nullIsOk)
{
return null;
}
throw new VariableNotDefinedException(name);
}
}
[Obsolete("ExpresionContext contains a typo. Use ExpressionContext instead", true)]
public class ExpresionContext<T> : ExpressionContext<T>
{
}
public class VariableNotDefinedException : Exception
{
public VariableNotDefinedException(string name)
: base($"Variable '{name}' not defined")
{
}
}
public class FunctionNotDefinedException : Exception
{
public FunctionNotDefinedException(string name, string reason) : base(
$"Function '{name}' not defined: {reason}")
{
}
}
public class ExpressionParseException : Exception
{
public ExpressionParseException(string expression, ParseException parseException)
: base($"Failed to parse expression '{expression}'{Environment.NewLine}{parseException.Message}")
{
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using System.Globalization;
using UnityEngine;
namespace CodeWriter.ExpressionParser
{
public class FloatExpressionParser : ExpressionParser<float>
{
public static readonly ExpressionParser<float> Instance = new FloatExpressionParser();
protected override float False { get; } = 0f;
protected override float True { get; } = 1f;
protected override float Parse(string input) =>
float.Parse(input, NumberStyles.Any, CultureInfo.InvariantCulture);
protected override float Negate(float v) => -v;
protected override float Add(float a, float b) => a + b;
protected override float Sub(float a, float b) => a - b;
protected override float Mul(float a, float b) => a * b;
protected override float Div(float a, float b) => a / b;
protected override float Mod(float a, float b) => a % b;
protected override float Pow(float a, float b) => Mathf.Pow(a, b);
protected override float Equal(float a, float b) => Mathf.Approximately(a, b) ? 1 : 0;
protected override float NotEqual(float a, float b) => !Mathf.Approximately(a, b) ? 1 : 0;
protected override float LessThan(float a, float b) => a < b ? 1 : 0;
protected override float LessThanOrEqual(float a, float b) => a <= b ? 1 : 0;
protected override float GreaterThan(float a, float b) => a > b ? 1 : 0;
protected override float GreaterThanOrEqual(float a, float b) => a >= b ? 1 : 0;
protected override bool IsTrue(float v) => !Mathf.Approximately(v, 0);
protected override float Round(float v) => (float) Math.Round(v);
protected override float Ceiling(float v) => (float) Math.Ceiling(v);
protected override float Floor(float v) => (float) Math.Floor(v);
protected override float Log10(float v) => (float) Math.Log10(v);
protected override float Log(float v, float newBase) => (float) Math.Log(v, newBase);
}
}

View File

@@ -0,0 +1,124 @@
namespace Sprache
{
/// <summary>
/// Constructs customizable comment parsers.
/// </summary>
public class CommentParser : IComment
{
///<summary>
///Single-line comment header.
///</summary>
public string Single { get; set; }
///<summary>
///Newline character preference.
///</summary>
public string NewLine { get; set; }
///<summary>
///Multi-line comment opener.
///</summary>
public string MultiOpen { get; set; }
///<summary>
///Multi-line comment closer.
///</summary>
public string MultiClose { get; set; }
/// <summary>
/// Initializes a Comment with C-style headers and Windows newlines.
/// </summary>
public CommentParser()
{
Single = "//";
MultiOpen = "/*";
MultiClose = "*/";
NewLine = "\n";
}
/// <summary>
/// Initializes a Comment with custom multi-line headers and newline characters.
/// Single-line headers are made null, it is assumed they would not be used.
/// </summary>
/// <param name="multiOpen"></param>
/// <param name="multiClose"></param>
/// <param name="newLine"></param>
public CommentParser(string multiOpen, string multiClose, string newLine)
{
Single = null;
MultiOpen = multiOpen;
MultiClose = multiClose;
NewLine = newLine;
}
/// <summary>
/// Initializes a Comment with custom headers and newline characters.
/// </summary>
/// <param name="single"></param>
/// <param name="multiOpen"></param>
/// <param name="multiClose"></param>
/// <param name="newLine"></param>
public CommentParser(string single, string multiOpen, string multiClose, string newLine)
{
Single = single;
MultiOpen = multiOpen;
MultiClose = multiClose;
NewLine = newLine;
}
///<summary>
///Parse a single-line comment.
///</summary>
public Parser<string> SingleLineComment
{
get
{
if (Single == null)
throw new ParseException("Field 'Single' is null; single-line comments not allowed.");
return from first in Parse.String(Single)
from rest in Parse.CharExcept(NewLine).Many().Text()
select rest;
}
private set { }
}
///<summary>
///Parse a multi-line comment.
///</summary>
public Parser<string> MultiLineComment
{
get
{
if (MultiOpen == null)
throw new ParseException("Field 'MultiOpen' is null; multi-line comments not allowed.");
else if (MultiClose == null)
throw new ParseException("Field 'MultiClose' is null; multi-line comments not allowed.");
return from first in Parse.String(MultiOpen)
from rest in Parse.AnyChar
.Until(Parse.String(MultiClose)).Text()
select rest;
}
private set { }
}
///<summary>
///Parse a comment.
///</summary>
public Parser<string> AnyComment
{
get
{
if (Single != null && MultiOpen != null && MultiClose != null)
return SingleLineComment.Or(MultiLineComment);
else if (Single != null && (MultiOpen == null || MultiClose == null))
return SingleLineComment;
else if (Single == null && (MultiOpen != null && MultiClose != null))
return MultiLineComment;
else throw new ParseException("Unable to parse comment; check values of fields 'MultiOpen' and 'MultiClose'.");
}
private set { }
}
}
}

View File

@@ -0,0 +1,43 @@
namespace Sprache
{
/// <summary>
/// Represents a customizable comment parser.
/// </summary>
public interface IComment
{
///<summary>
/// Single-line comment header.
///</summary>
string Single { get; set; }
///<summary>
/// Newline character preference.
///</summary>
string NewLine { get; set; }
///<summary>
/// Multi-line comment opener.
///</summary>
string MultiOpen { get; set; }
///<summary>
/// Multi-line comment closer.
///</summary>
string MultiClose { get; set; }
///<summary>
/// Parse a single-line comment.
///</summary>
Parser<string> SingleLineComment { get; }
///<summary>
/// Parse a multi-line comment.
///</summary>
Parser<string> MultiLineComment { get; }
///<summary>
/// Parse a comment.
///</summary>
Parser<string> AnyComment { get; }
}
}

View File

@@ -0,0 +1,26 @@
using System.Collections.Generic;
namespace Sprache
{
/// <summary>
/// Represents a commented result with its leading and trailing comments.
/// </summary>
/// <typeparam name="T">Type of the matched result.</typeparam>
public interface ICommented<T>
{
/// <summary>
/// Gets the leading comments.
/// </summary>
IEnumerable<string> LeadingComments { get; }
/// <summary>
/// Gets the resulting value.
/// </summary>
T Value { get; }
/// <summary>
/// Gets the trailing comments.
/// </summary>
IEnumerable<string> TrailingComments { get; }
}
}

View File

@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
namespace Sprache
{
/// <summary>
/// Represents an input for parsing.
/// </summary>
public interface IInput : IEquatable<IInput>
{
/// <summary>
/// Advances the input.
/// </summary>
/// <returns>A new <see cref="IInput" /> that is advanced.</returns>
/// <exception cref="System.InvalidOperationException">The input is already at the end of the source.</exception>
IInput Advance();
/// <summary>
/// Gets the whole source.
/// </summary>
string Source { get; }
/// <summary>
/// Gets the current <see cref="System.Char" />.
/// </summary>
char Current { get; }
/// <summary>
/// Gets a value indicating whether the end of the source is reached.
/// </summary>
bool AtEnd { get; }
/// <summary>
/// Gets the current positon.
/// </summary>
int Position { get; }
/// <summary>
/// Gets the current line number.
/// </summary>
int Line { get; }
/// <summary>
/// Gets the current column.
/// </summary>
int Column { get; }
/// <summary>
/// Memos used by this input
/// </summary>
IDictionary<object, object> Memos { get; }
}
}

View File

@@ -0,0 +1,18 @@
namespace Sprache
{
/// <summary>
/// An interface for objects that have a source <see cref="Position"/>.
/// </summary>
/// <typeparam name="T">Type of the matched result.</typeparam>
public interface IPositionAware<out T>
{
/// <summary>
/// Set the start <see cref="Position"/> and the matched length.
/// </summary>
/// <param name="startPos">The start position</param>
/// <param name="length">The matched length.</param>
/// <returns>The matched result.</returns>
T SetPos(Position startPos, int length);
}
}

View File

@@ -0,0 +1,36 @@
using System.Collections.Generic;
namespace Sprache
{
/// <summary>
/// Represents a parsing result.
/// </summary>
/// <typeparam name="T">The result type.</typeparam>
public interface IResult<out T>
{
/// <summary>
/// Gets the resulting value.
/// </summary>
T Value { get; }
/// <summary>
/// Gets a value indicating whether wether parsing was successful.
/// </summary>
bool WasSuccessful { get; }
/// <summary>
/// Gets the error message.
/// </summary>
string Message { get; }
/// <summary>
/// Gets the parser expectations in case of error.
/// </summary>
IEnumerable<string> Expectations { get; }
/// <summary>
/// Gets the remainder of the input.
/// </summary>
IInput Remainder { get; }
}
}

View File

@@ -0,0 +1,29 @@
namespace Sprache
{
/// <summary>
/// Represents a text span of the matched result.
/// </summary>
/// <typeparam name="T">Type of the matched result.</typeparam>
public interface ITextSpan<T>
{
/// <summary>
/// Gets the resulting value.
/// </summary>
T Value { get; }
/// <summary>
/// Gets the starting <see cref="Position"/>.
/// </summary>
Position Start { get; }
/// <summary>
/// Gets the ending <see cref="Position"/>.
/// </summary>
Position End { get; }
/// <summary>
/// Gets the length of the text span.
/// </summary>
int Length { get; }
}
}

View File

@@ -0,0 +1,156 @@
using System;
using System.Collections.Generic;
namespace Sprache
{
/// <summary>
/// Represents an input for parsing.
/// </summary>
public class Input : IInput
{
private readonly string _source;
private readonly int _position;
private readonly int _line;
private readonly int _column;
/// <summary>
/// Gets the list of memos assigned to the <see cref="Input" /> instance.
/// </summary>
public IDictionary<object, object> Memos { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="Input" /> class.
/// </summary>
/// <param name="source">The source.</param>
public Input(string source)
: this(source, 0)
{
}
internal Input(string source, int position, int line = 1, int column = 1)
{
_source = source;
_position = position;
_line = line;
_column = column;
Memos = new Dictionary<object, object>();
}
/// <summary>
/// Advances the input.
/// </summary>
/// <returns>A new <see cref="IInput" /> that is advanced.</returns>
/// <exception cref="System.InvalidOperationException">The input is already at the end of the source.</exception>
public IInput Advance()
{
if (AtEnd)
throw new InvalidOperationException("The input is already at the end of the source.");
return new Input(_source, _position + 1, Current == '\n' ? _line + 1 : _line, Current == '\n' ? 1 : _column + 1);
}
/// <summary>
/// Gets the whole source.
/// </summary>
public string Source { get { return _source; } }
/// <summary>
/// Gets the current <see cref="System.Char" />.
/// </summary>
public char Current { get { return _source[_position]; } }
/// <summary>
/// Gets a value indicating whether the end of the source is reached.
/// </summary>
public bool AtEnd { get { return _position == _source.Length; } }
/// <summary>
/// Gets the current positon.
/// </summary>
public int Position { get { return _position; } }
/// <summary>
/// Gets the current line number.
/// </summary>
public int Line { get { return _line; } }
/// <summary>
/// Gets the current column.
/// </summary>
public int Column { get { return _column; } }
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>
/// A string that represents the current object.
/// </returns>
public override string ToString()
{
return string.Format("Line {0}, Column {1}", _line, _column);
}
/// <summary>
/// Serves as a hash function for a particular type.
/// </summary>
/// <returns>
/// A hash code for the current <see cref="Input" />.
/// </returns>
public override int GetHashCode()
{
unchecked
{
return ((_source != null ? _source.GetHashCode() : 0) * 397) ^ _position;
}
}
/// <summary>
/// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="Input" />.
/// </summary>
/// <returns>
/// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="Input" />; otherwise, false.
/// </returns>
/// <param name="obj">The object to compare with the current object. </param>
public override bool Equals(object obj)
{
return Equals(obj as IInput);
}
/// <summary>
/// Indicates whether the current <see cref="Input" /> is equal to another object of the same type.
/// </summary>
/// <returns>
/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
public bool Equals(IInput other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return string.Equals(_source, other.Source) && _position == other.Position;
}
/// <summary>
/// Indicates whether the left <see cref="Input" /> is equal to the right <see cref="Input" />.
/// </summary>
/// <param name="left">The left <see cref="Input" />.</param>
/// <param name="right">The right <see cref="Input" />.</param>
/// <returns>true if both objects are equal.</returns>
public static bool operator ==(Input left, Input right)
{
return Equals(left, right);
}
/// <summary>
/// Indicates whether the left <see cref="Input" /> is not equal to the right <see cref="Input" />.
/// </summary>
/// <param name="left">The left <see cref="Input" />.</param>
/// <param name="right">The right <see cref="Input" />.</param>
/// <returns>true if the objects are not equal.</returns>
public static bool operator !=(Input left, Input right)
{
return !Equals(left, right);
}
}
}

View File

@@ -0,0 +1,145 @@
using System;
namespace Sprache
{
/// <summary>
/// Represents an optional result.
/// </summary>
/// <typeparam name="T">The result type.</typeparam>
public interface IOption<out T>
{
/// <summary>
/// Gets a value indicating whether this instance is empty.
/// </summary>
bool IsEmpty { get; }
/// <summary>
/// Gets a value indicating whether this instance is defined.
/// </summary>
bool IsDefined { get; }
/// <summary>
/// Gets the matched result or a default value.
/// </summary>
/// <returns></returns>
T GetOrDefault();
/// <summary>
/// Gets the matched result.
/// </summary>
T Get();
}
/// <summary>
/// Extensions for <see cref="IOption&lt;T&gt;"/>.
/// </summary>
public static class OptionExtensions
{
/// <summary>
/// Gets the value or else returns a default value.
/// </summary>
/// <typeparam name="T">The result type.</typeparam>
/// <param name="option"></param>
/// <param name="defaultValue">The default value.</param>
/// <returns></returns>
public static T GetOrElse<T>(this IOption<T> option, T defaultValue)
{
if (option == null) throw new ArgumentNullException(nameof(option));
return option.IsEmpty ? defaultValue : option.Get();
}
/// <summary>
/// Maps a function over the value or else returns an empty option.
/// </summary>
/// <typeparam name="T">The input type.</typeparam>
/// <typeparam name="U">The output type.</typeparam>
/// <param name="option">The option containing the value to apply <paramref name="map" /> to.</param>
/// <param name="map">The function to apply to the value of <paramref name="option" />.</param>
/// <returns>An options result containing the result if there was an input value.</returns>
public static IOption<U> Select<T, U>(this IOption<T> option, Func<T,U> map)
{
if (option == null) throw new ArgumentNullException(nameof(option));
return option.IsDefined ? (IOption<U>) new Some<U>(map(option.Get())) : new None<U>();
}
/// <summary>
/// Binds the value to a function with optional result and flattens the result to a single optional.
/// A result projection is applied aftherwards.
/// </summary>
/// <typeparam name="T">The input type.</typeparam>
/// <typeparam name="U">The output type of <paramref name="bind" />.</typeparam>
/// <typeparam name="V">The final output type.</typeparam>
/// <param name="option">The option containing the value to bind to.</param>
/// <param name="bind">The function that receives the input values and returns an optional value.</param>
/// <param name="project">The function that is projects the result of <paramref name="bind" />.</param>
/// <returns>An option result containing the result if there were was an input value and bind result.</returns>
public static IOption<V> SelectMany<T,U,V>(this IOption<T> option, Func<T,IOption<U>> bind, Func<T,U,V> project)
{
if (option == null) throw new ArgumentNullException(nameof(option));
if (option.IsEmpty) return new None<V>();
var t = option.Get();
return bind(t).Select(u => project(t,u));
}
/// <summary>
/// Binds the value to a function with optional result and flattens the result to a single optional.
/// </summary>
/// <typeparam name="T">The input type.</typeparam>
/// <typeparam name="U">The output type.</typeparam>
/// <param name="option">The option containing the value to bind to.</param>
/// <param name="bind">The function that receives the input values and returns an optional value.</param>
/// <returns>An option result containing the result if there were was an input value and bind result.</returns>
public static IOption<U> SelectMany<T,U>(this IOption<T> option, Func<T,IOption<U>> bind) => option.SelectMany(bind, (_,x) => x);
}
internal abstract class AbstractOption<T> : IOption<T>
{
public abstract bool IsEmpty { get; }
public bool IsDefined
{
get { return !IsEmpty; }
}
public T GetOrDefault()
{
return IsEmpty ? default(T) : Get();
}
public abstract T Get();
}
internal sealed class Some<T> : AbstractOption<T>
{
private readonly T _value;
public Some(T value)
{
_value = value;
}
public override bool IsEmpty
{
get { return false; }
}
public override T Get()
{
return _value;
}
}
internal sealed class None<T> : AbstractOption<T>
{
public override bool IsEmpty
{
get { return true; }
}
public override T Get()
{
throw new InvalidOperationException("Cannot get value from None.");
}
}
}

View File

@@ -0,0 +1,126 @@
using System;
using System.Collections.Generic;
using UniLinq;
namespace Sprache
{
partial class Parse
{
/// <summary>
/// Represents a text span of the matched result.
/// </summary>
/// <typeparam name="T">Type of the matched result.</typeparam>
private class TextSpan<T> : ITextSpan<T>
{
public T Value { get; set; }
public Position Start { get; set; }
public Position End { get; set; }
public int Length { get; set; }
}
/// <summary>
/// Constructs a parser that returns the <see cref="ITextSpan{T}"/> of the parsed value.
/// </summary>
/// <typeparam name="T">The result type of the given parser.</typeparam>
/// <param name="parser">The parser to wrap.</param>
/// <returns>A parser for the text span of the given parser.</returns>
public static Parser<ITextSpan<T>> Span<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return i =>
{
var r = parser(i);
if (r.WasSuccessful)
{
var span = new TextSpan<T>
{
Value = r.Value,
Start = Position.FromInput(i),
End = Position.FromInput(r.Remainder),
Length = r.Remainder.Position - i.Position,
};
return Result.Success(span, r.Remainder);
}
return Result.Failure<ITextSpan<T>>(r.Remainder, r.Message, r.Expectations);
};
}
/// <summary>
/// Represents a commented result with its leading and trailing comments.
/// </summary>
/// <typeparam name="T">Type of the matched result.</typeparam>
private class CommentedValue<T> : ICommented<T>
{
public CommentedValue(T value)
{
LeadingComments = TrailingComments = EmptyStringList;
Value = value;
}
public CommentedValue(IEnumerable<string> leading, T value, IEnumerable<string> trailing)
{
LeadingComments = leading ?? EmptyStringList;
Value = value;
TrailingComments = trailing ?? EmptyStringList;
}
public T Value { get; }
public IEnumerable<string> LeadingComments { get; }
public IEnumerable<string> TrailingComments { get; }
}
private static readonly string[] EmptyStringList = new string[0];
private static readonly IComment DefaultCommentParser = new CommentParser();
/// <summary>
/// Constructs a parser that consumes a whitespace and all comments
/// parsed by the commentParser.AnyComment parser, but parses only one trailing
/// comment that starts exactly on the last line of the parsed value.
/// </summary>
/// <typeparam name="T">The result type of the given parser.</typeparam>
/// <param name="parser">The parser to wrap.</param>
/// <param name="commentParser">The comment parser.</param>
/// <returns>An extended Token() version of the given parser.</returns>
public static Parser<ICommented<T>> Commented<T>(this Parser<T> parser, IComment commentParser = null)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
// consume any comment supported by the comment parser
var comment = (commentParser ?? DefaultCommentParser).AnyComment;
// parses any whitespace except for the new lines
var whiteSpaceExceptForNewLine = WhiteSpace.Except(Chars("\r\n")).Many().Text();
// returns true if the second span starts on the first span's last line
bool IsSameLine(ITextSpan<T> first, ITextSpan<string> second) =>
first.End.Line == second.Start.Line;
// single comment span followed by a whitespace
var commentSpan =
from cs in comment.Span()
from ws in whiteSpaceExceptForNewLine
select cs;
// add leading and trailing comments to the parser
return
from leadingWhiteSpace in WhiteSpace.Many()
from leadingComments in comment.Token().Many()
from valueSpan in parser.Span()
from trailingWhiteSpace in whiteSpaceExceptForNewLine
from trailingPreview in commentSpan.Many().Preview()
let trailingCount = trailingPreview.GetOrElse(Enumerable.Empty<ITextSpan<string>>())
.Where(c => IsSameLine(valueSpan, c)).Count()
from trailingComments in commentSpan.Repeat(trailingCount)
select new CommentedValue<T>(leadingComments, valueSpan.Value, trailingComments.Select(c => c.Value));
}
}
}

View File

@@ -0,0 +1,80 @@
using System;
namespace Sprache
{
partial class Parse
{
/// <summary>
/// Construct a parser that indicates that the given parser
/// is optional. The returned parser will succeed on
/// any input no matter whether the given parser
/// succeeds or not.
/// </summary>
/// <typeparam name="T">The result type of the given parser.</typeparam>
/// <param name="parser">The parser to wrap.</param>
/// <returns>An optional version of the given parser.</returns>
public static Parser<IOption<T>> Optional<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return i =>
{
var pr = parser(i);
if (pr.WasSuccessful)
return Result.Success(new Some<T>(pr.Value), pr.Remainder);
return Result.Success(new None<T>(), i);
};
}
/// <summary>
/// Constructs the eXclusive version of the Optional{T} parser.
/// </summary>
/// <typeparam name="T">The result type of the given parser</typeparam>
/// <param name="parser">The parser to wrap</param>
/// <returns>An eXclusive optional version of the given parser.</returns>
/// <seealso cref="XOr"/>
public static Parser<IOption<T>> XOptional<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return i =>
{
var result = parser(i);
if (result.WasSuccessful)
return Result.Success(new Some<T>(result.Value), result.Remainder);
if (result.Remainder.Equals(i))
return Result.Success(new None<T>(), i);
return Result.Failure<IOption<T>>(result.Remainder, result.Message, result.Expectations);
};
}
/// <summary>
/// Construct a parser that indicates that the given parser is optional
/// and non-consuming. The returned parser will succeed on
/// any input no matter whether the given parser succeeds or not.
/// In any case, it won't consume any input, like a positive look-ahead in regex.
/// </summary>
/// <typeparam name="T">The result type of the given parser.</typeparam>
/// <param name="parser">The parser to wrap.</param>
/// <returns>A non-consuming version of the given parser.</returns>
public static Parser<IOption<T>> Preview<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return i =>
{
var result = parser(i);
if (result.WasSuccessful)
return Result.Success(new Some<T>(result.Value), i);
return Result.Success(new None<T>(), i);
};
}
}
}

View File

@@ -0,0 +1,27 @@
namespace Sprache
{
partial class Parse
{
/// <summary>
/// Construct a parser that will set the position to the position-aware
/// T on succsessful match.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parser"></param>
/// <returns></returns>
public static Parser<T> Positioned<T>(this Parser<T> parser) where T : IPositionAware<T>
{
return i =>
{
var r = parser(i);
if (r.WasSuccessful)
{
return Result.Success(r.Value.SetPos(Position.FromInput(i), r.Remainder.Position - i.Position), r.Remainder);
}
return r;
};
}
}
}

View File

@@ -0,0 +1,34 @@
namespace Sprache
{
partial class Parse
{
/// <summary>
/// \n or \r\n
/// </summary>
public static Parser<string> LineEnd =
(from r in Char('\r').Optional()
from n in Char('\n')
select r.IsDefined ? r.Get().ToString() + n : n.ToString())
.Named("LineEnd");
/// <summary>
/// line ending or end of input
/// </summary>
public static Parser<string> LineTerminator =
Return("").End()
.Or(LineEnd.End())
.Or(LineEnd)
.Named("LineTerminator");
/// <summary>
/// Parser for identifier starting with <paramref name="firstLetterParser"/> and continuing with <paramref name="tailLetterParser"/>
/// </summary>
public static Parser<string> Identifier(Parser<char> firstLetterParser, Parser<char> tailLetterParser)
{
return
from firstLetter in firstLetterParser
from tail in tailLetterParser.Many().Text()
select firstLetter + tail;
}
}
}

View File

@@ -0,0 +1,158 @@
namespace Sprache
{
using System;
using System.Collections.Generic;
using UniLinq;
partial class Parse
{
/// <summary>
///
/// </summary>
/// <param name="parser"></param>
/// <param name="delimiter"></param>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static Parser<IEnumerable<T>> DelimitedBy<T, U>(this Parser<T> parser, Parser<U> delimiter)
{
return DelimitedBy(parser, delimiter, null, null);
}
/// <summary>
///
/// </summary>
/// <param name="parser"></param>
/// <param name="delimiter"></param>
/// <param name="minimumCount"></param>
/// <param name="maximumCount"></param>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static Parser<IEnumerable<T>> DelimitedBy<T, U>(this Parser<T> parser, Parser<U> delimiter, int? minimumCount, int? maximumCount)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (delimiter == null) throw new ArgumentNullException(nameof(delimiter));
return from head in parser.Once()
from tail in
(from separator in delimiter
from item in parser
select item).Repeat(minimumCount - 1, maximumCount - 1)
select head.Concat(tail);
}
/// <summary>
/// Fails on the first itemParser failure, if it reads at least one character.
/// </summary>
/// <param name="itemParser"></param>
/// <param name="delimiter"></param>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static Parser<IEnumerable<T>> XDelimitedBy<T, U>(this Parser<T> itemParser, Parser<U> delimiter)
{
if (itemParser == null) throw new ArgumentNullException(nameof(itemParser));
if (delimiter == null) throw new ArgumentNullException(nameof(delimiter));
return from head in itemParser.Once()
from tail in
(from separator in delimiter
from item in itemParser
select item).XMany()
select head.Concat(tail);
}
/// <summary>
///
/// </summary>
/// <param name="parser"></param>
/// <param name="count"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static Parser<IEnumerable<T>> Repeat<T>(this Parser<T> parser, int count)
{
return Repeat(parser, count, count);
}
/// <summary>
///
/// </summary>
/// <param name="parser"></param>
/// <param name="minimumCount"></param>
/// <param name="maximumCount"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static Parser<IEnumerable<T>> Repeat<T>(this Parser<T> parser, int? minimumCount, int? maximumCount)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return i =>
{
var remainder = i;
var result = new List<T>();
var count = 0;
var r = parser(remainder);
while (r.WasSuccessful && (maximumCount == null || count < maximumCount.Value))
{
count++;
result.Add(r.Value);
remainder = r.Remainder;
r = parser(remainder);
}
if (minimumCount.HasValue && count < minimumCount.Value)
{
var what = r.Remainder.AtEnd
? "end of input"
: r.Remainder.Current.ToString();
var msg = $"Unexpected '{what}'";
string exp;
if (minimumCount == maximumCount)
exp = $"'{StringExtensions.Join(", ", r.Expectations)}' {minimumCount.Value} times, but found {count}";
else if (maximumCount == null)
exp = $"'{StringExtensions.Join(", ", r.Expectations)}' minimum {minimumCount.Value} times, but found {count}";
else
exp = $"'{StringExtensions.Join(", ", r.Expectations)}' between {minimumCount.Value} and {maximumCount.Value} times, but found {count}";
return Result.Failure<IEnumerable<T>>(i, msg, new[] { exp });
}
return Result.Success<IEnumerable<T>>(result, remainder);
};
}
/// <summary>
///
/// </summary>
/// <param name="parser"></param>
/// <param name="open"></param>
/// <param name="close"></param>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <typeparam name="V"></typeparam>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static Parser<T> Contained<T, U, V>(this Parser<T> parser, Parser<U> open, Parser<V> close)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (open == null) throw new ArgumentNullException(nameof(open));
if (close == null) throw new ArgumentNullException(nameof(close));
return from o in open
from item in parser
from c in close
select item;
}
}
}

View File

@@ -0,0 +1,782 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using UniLinq;
namespace Sprache
{
/// <summary>
/// Parsers and combinators.
/// </summary>
public static partial class Parse
{
/// <summary>
/// TryParse a single character matching 'predicate'
/// </summary>
/// <param name="predicate"></param>
/// <param name="description"></param>
/// <returns></returns>
public static Parser<char> Char(Predicate<char> predicate, string description)
{
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
if (description == null) throw new ArgumentNullException(nameof(description));
return i =>
{
if (!i.AtEnd)
{
if (predicate(i.Current))
return Result.Success(i.Current, i.Advance());
return Result.Failure<char>(i,
$"unexpected '{i.Current}'",
new[] { description });
}
return Result.Failure<char>(i,
"Unexpected end of input reached",
new[] { description });
};
}
/// <summary>
/// Parse a single character except those matching <paramref name="predicate"/>.
/// </summary>
/// <param name="predicate">Characters not to match.</param>
/// <param name="description">Description of characters that don't match.</param>
/// <returns>A parser for characters except those matching <paramref name="predicate"/>.</returns>
public static Parser<char> CharExcept(Predicate<char> predicate, string description)
{
return Char(c => !predicate(c), "any character except " + description);
}
/// <summary>
/// Parse a single character c.
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public static Parser<char> Char(char c)
{
return Char(ch => c == ch, char.ToString(c));
}
/// <summary>
/// Parse a single character of any in c
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public static Parser<char> Chars(params char[] c)
{
return Char(c.Contains, StringExtensions.Join("|", c));
}
/// <summary>
/// Parse a single character of any in c
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public static Parser<char> Chars(string c)
{
return Char(c.ToEnumerable().Contains, StringExtensions.Join("|", c.ToEnumerable()));
}
/// <summary>
/// Parse a single character except c.
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public static Parser<char> CharExcept(char c)
{
return CharExcept(ch => c == ch, char.ToString(c));
}
/// <summary>
/// Parses a single character except for those in the given parameters
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public static Parser<char> CharExcept(IEnumerable<char> c)
{
var chars = c as char[] ?? c.ToArray();
return CharExcept(chars.Contains, StringExtensions.Join("|", chars));
}
/// <summary>
/// Parses a single character except for those in c
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public static Parser<char> CharExcept(string c)
{
return CharExcept(c.ToEnumerable().Contains, StringExtensions.Join("|", c.ToEnumerable()));
}
/// <summary>
/// Parse a single character in a case-insensitive fashion.
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public static Parser<char> IgnoreCase(char c)
{
return Char(ch => char.ToLower(c) == char.ToLower(ch), char.ToString(c));
}
/// <summary>
/// Parse a string in a case-insensitive fashion.
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static Parser<IEnumerable<char>> IgnoreCase(string s)
{
if (s == null) throw new ArgumentNullException(nameof(s));
return s
.ToEnumerable()
.Select(IgnoreCase)
.Aggregate(Return(Enumerable.Empty<char>()),
(a, p) => a.Concat(p.Once()))
.Named(s);
}
/// <summary>
/// Parse any character.
/// </summary>
public static readonly Parser<char> AnyChar = Char(c => true, "any character");
/// <summary>
/// Parse a whitespace.
/// </summary>
public static readonly Parser<char> WhiteSpace = Char(char.IsWhiteSpace, "whitespace");
/// <summary>
/// Parse a digit.
/// </summary>
public static readonly Parser<char> Digit = Char(char.IsDigit, "digit");
/// <summary>
/// Parse a letter.
/// </summary>
public static readonly Parser<char> Letter = Char(char.IsLetter, "letter");
/// <summary>
/// Parse a letter or digit.
/// </summary>
public static readonly Parser<char> LetterOrDigit = Char(char.IsLetterOrDigit, "letter or digit");
/// <summary>
/// Parse a lowercase letter.
/// </summary>
public static readonly Parser<char> Lower = Char(char.IsLower, "lowercase letter");
/// <summary>
/// Parse an uppercase letter.
/// </summary>
public static readonly Parser<char> Upper = Char(char.IsUpper, "uppercase letter");
/// <summary>
/// Parse a numeric character.
/// </summary>
public static readonly Parser<char> Numeric = Char(char.IsNumber, "numeric character");
/// <summary>
/// Parse a string of characters.
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static Parser<IEnumerable<char>> String(string s)
{
if (s == null) throw new ArgumentNullException(nameof(s));
return s
.ToEnumerable()
.Select(Char)
.Aggregate(Return(Enumerable.Empty<char>()),
(a, p) => a.Concat(p.Once()))
.Named(s);
}
/// <summary>
/// Constructs a parser that will fail if the given parser succeeds,
/// and will succeed if the given parser fails. In any case, it won't
/// consume any input. It's like a negative look-ahead in regex.
/// </summary>
/// <typeparam name="T">The result type of the given parser</typeparam>
/// <param name="parser">The parser to wrap</param>
/// <returns>A parser that is the opposite of the given parser.</returns>
public static Parser<object> Not<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return i =>
{
var result = parser(i);
if (result.WasSuccessful)
{
var msg = $"`{StringExtensions.Join(", ", result.Expectations)}' was not expected";
return Result.Failure<object>(i, msg, new string[0]);
}
return Result.Success<object>(null, i);
};
}
/// <summary>
/// Parse first, and if successful, then parse second.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <param name="first"></param>
/// <param name="second"></param>
/// <returns></returns>
public static Parser<U> Then<T, U>(this Parser<T> first, Func<T, Parser<U>> second)
{
if (first == null) throw new ArgumentNullException(nameof(first));
if (second == null) throw new ArgumentNullException(nameof(second));
return i => first(i).IfSuccess(s => second(s.Value)(s.Remainder));
}
/// <summary>
/// Parse a stream of elements.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parser"></param>
/// <returns></returns>
/// <remarks>Implemented imperatively to decrease stack usage.</remarks>
public static Parser<IEnumerable<T>> Many<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return i =>
{
var remainder = i;
var result = new List<T>();
var r = parser(i);
while (r.WasSuccessful)
{
if (remainder.Equals(r.Remainder))
break;
result.Add(r.Value);
remainder = r.Remainder;
r = parser(remainder);
}
return Result.Success<IEnumerable<T>>(result, remainder);
};
}
/// <summary>
/// Parse a stream of elements, failing if any element is only partially parsed.
/// </summary>
/// <typeparam name="T">The type of element to parse.</typeparam>
/// <param name="parser">A parser that matches a single element.</param>
/// <returns>A <see cref="Parser{T}"/> that matches the sequence.</returns>
/// <remarks>
/// <para>
/// Using <seealso cref="XMany{T}(Parser{T})"/> may be preferable to <seealso cref="Many{T}(Parser{T})"/>
/// where the first character of each match identified by <paramref name="parser"/>
/// is sufficient to determine whether the entire match should succeed. The X*
/// methods typically give more helpful errors and are easier to debug than their
/// unqualified counterparts.
/// </para>
/// </remarks>
/// <seealso cref="XOr"/>
public static Parser<IEnumerable<T>> XMany<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return parser.Many().Then(m => parser.Once().XOr(Return(m)));
}
/// <summary>
/// TryParse a stream of elements with at least one item.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parser"></param>
/// <returns></returns>
public static Parser<IEnumerable<T>> AtLeastOnce<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return parser.Once().Then(t1 => parser.Many().Select(ts => t1.Concat(ts)));
}
/// <summary>
/// TryParse a stream of elements with at least one item. Except the first
/// item, all other items will be matched with the <code>XMany</code> operator.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parser"></param>
/// <returns></returns>
public static Parser<IEnumerable<T>> XAtLeastOnce<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return parser.Once().Then(t1 => parser.XMany().Select(ts => t1.Concat(ts)));
}
/// <summary>
/// Parse end-of-input.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parser"></param>
/// <returns></returns>
public static Parser<T> End<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return i => parser(i).IfSuccess(s =>
s.Remainder.AtEnd
? s
: Result.Failure<T>(
s.Remainder,
string.Format("unexpected '{0}'", s.Remainder.Current),
new[] { "end of input" }));
}
/// <summary>
/// Take the result of parsing, and project it onto a different domain.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <param name="parser"></param>
/// <param name="convert"></param>
/// <returns></returns>
public static Parser<U> Select<T, U>(this Parser<T> parser, Func<T, U> convert)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (convert == null) throw new ArgumentNullException(nameof(convert));
return parser.Then(t => Return(convert(t)));
}
/// <summary>
/// Parse the token, embedded in any amount of whitespace characters.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parser"></param>
/// <returns></returns>
public static Parser<T> Token<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return from leading in WhiteSpace.Many()
from item in parser
from trailing in WhiteSpace.Many()
select item;
}
/// <summary>
/// Refer to another parser indirectly. This allows circular compile-time dependency between parsers.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="reference"></param>
/// <returns></returns>
public static Parser<T> Ref<T>(Func<Parser<T>> reference)
{
if (reference == null) throw new ArgumentNullException(nameof(reference));
Parser<T> p = null;
return i =>
{
if (p == null)
p = reference();
if (i.Memos.ContainsKey(p))
{
var pResult = i.Memos[p] as IResult<T>;
if (pResult.WasSuccessful)
return pResult;
throw new ParseException(pResult.ToString());
}
i.Memos[p] = Result.Failure<T>(i,
"Left recursion in the grammar.",
new string[0]);
var result = p(i);
i.Memos[p] = result;
return result;
};
}
/// <summary>
/// Convert a stream of characters to a string.
/// </summary>
/// <param name="characters"></param>
/// <returns></returns>
public static Parser<string> Text(this Parser<IEnumerable<char>> characters)
{
return characters.Select(chs => new string(chs.ToArray()));
}
/// <summary>
/// Parse first, if it succeeds, return first, otherwise try second.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="first"></param>
/// <param name="second"></param>
/// <returns></returns>
public static Parser<T> Or<T>(this Parser<T> first, Parser<T> second)
{
if (first == null) throw new ArgumentNullException(nameof(first));
if (second == null) throw new ArgumentNullException(nameof(second));
return i =>
{
var fr = first(i);
if (!fr.WasSuccessful)
{
return second(i).IfFailure(sf => DetermineBestError(fr, sf));
}
if (fr.Remainder.Equals(i))
return second(i).IfFailure(sf => fr);
return fr;
};
}
/// <summary>
/// Names part of the grammar for help with error messages.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parser"></param>
/// <param name="name"></param>
/// <returns></returns>
public static Parser<T> Named<T>(this Parser<T> parser, string name)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (name == null) throw new ArgumentNullException(nameof(name));
return i => parser(i).IfFailure(f => f.Remainder.Equals(i) ?
Result.Failure<T>(f.Remainder, f.Message, new[] { name }) :
f);
}
/// <summary>
/// Parse first, if it succeeds, return first, otherwise try second.
/// Assumes that the first parsed character will determine the parser chosen (see Try).
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="first"></param>
/// <param name="second"></param>
/// <returns></returns>
public static Parser<T> XOr<T>(this Parser<T> first, Parser<T> second)
{
if (first == null) throw new ArgumentNullException(nameof(first));
if (second == null) throw new ArgumentNullException(nameof(second));
return i => {
var fr = first(i);
if (!fr.WasSuccessful)
{
// The 'X' part
if (!fr.Remainder.Equals(i))
return fr;
return second(i).IfFailure(sf => DetermineBestError(fr, sf));
}
// This handles a zero-length successful application of first.
if (fr.Remainder.Equals(i))
return second(i).IfFailure(sf => fr);
return fr;
};
}
// Examines two results presumably obtained at an "Or" junction; returns the result with
// the most information, or if they apply at the same input position, a union of the results.
static IResult<T> DetermineBestError<T>(IResult<T> firstFailure, IResult<T> secondFailure)
{
if (secondFailure.Remainder.Position > firstFailure.Remainder.Position)
return secondFailure;
if (secondFailure.Remainder.Position == firstFailure.Remainder.Position)
return Result.Failure<T>(
firstFailure.Remainder,
firstFailure.Message,
firstFailure.Expectations.Union(secondFailure.Expectations));
return firstFailure;
}
/// <summary>
/// Parse a stream of elements containing only one item.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parser"></param>
/// <returns></returns>
public static Parser<IEnumerable<T>> Once<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return parser.Select(r => (IEnumerable<T>)new[] { r });
}
/// <summary>
/// Concatenate two streams of elements.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="first"></param>
/// <param name="second"></param>
/// <returns></returns>
public static Parser<IEnumerable<T>> Concat<T>(this Parser<IEnumerable<T>> first, Parser<IEnumerable<T>> second)
{
if (first == null) throw new ArgumentNullException(nameof(first));
if (second == null) throw new ArgumentNullException(nameof(second));
return first.Then(f => second.Select(f.Concat));
}
/// <summary>
/// Succeed immediately and return value.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <returns></returns>
public static Parser<T> Return<T>(T value)
{
return i => Result.Success(value, i);
}
/// <summary>
/// Version of Return with simpler inline syntax.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <param name="parser"></param>
/// <param name="value"></param>
/// <returns></returns>
public static Parser<U> Return<T, U>(this Parser<T> parser, U value)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return parser.Select(t => value);
}
/// <summary>
/// Attempt parsing only if the <paramref name="except"/> parser fails.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <param name="parser"></param>
/// <param name="except"></param>
/// <returns></returns>
public static Parser<T> Except<T, U>(this Parser<T> parser, Parser<U> except)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (except == null) throw new ArgumentNullException(nameof(except));
// Could be more like: except.Then(s => s.Fail("..")).XOr(parser)
return i =>
{
var r = except(i);
if (r.WasSuccessful)
return Result.Failure<T>(i, "Excepted parser succeeded.", new[] { "other than the excepted input" });
return parser(i);
};
}
/// <summary>
/// Parse a sequence of items until a terminator is reached.
/// Returns the sequence, discarding the terminator.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <param name="parser"></param>
/// <param name="until"></param>
/// <returns></returns>
public static Parser<IEnumerable<T>> Until<T, U>(this Parser<T> parser, Parser<U> until)
{
return parser.Except(until).Many().Then(r => until.Return(r));
}
/// <summary>
/// Succeed if the parsed value matches predicate.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parser"></param>
/// <param name="predicate"></param>
/// <returns></returns>
public static Parser<T> Where<T>(this Parser<T> parser, Func<T, bool> predicate)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
return i => parser(i).IfSuccess(s =>
predicate(s.Value) ? s : Result.Failure<T>(i,
string.Format("Unexpected {0}.", s.Value),
new string[0]));
}
/// <summary>
/// Monadic combinator Then, adapted for Linq comprehension syntax.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <typeparam name="V"></typeparam>
/// <param name="parser"></param>
/// <param name="selector"></param>
/// <param name="projector"></param>
/// <returns></returns>
public static Parser<V> SelectMany<T, U, V>(
this Parser<T> parser,
Func<T, Parser<U>> selector,
Func<T, U, V> projector)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (selector == null) throw new ArgumentNullException(nameof(selector));
if (projector == null) throw new ArgumentNullException(nameof(projector));
return parser.Then(t => selector(t).Select(u => projector(t, u)));
}
/// <summary>
/// Chain a left-associative operator.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TOp"></typeparam>
/// <param name="op"></param>
/// <param name="operand"></param>
/// <param name="apply"></param>
/// <returns></returns>
public static Parser<T> ChainOperator<T, TOp>(
Parser<TOp> op,
Parser<T> operand,
Func<TOp, T, T, T> apply)
{
if (op == null) throw new ArgumentNullException(nameof(op));
if (operand == null) throw new ArgumentNullException(nameof(operand));
if (apply == null) throw new ArgumentNullException(nameof(apply));
return operand.Then(first => ChainOperatorRest(first, op, operand, apply, Or));
}
/// <summary>
/// Chain a left-associative operator.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TOp"></typeparam>
/// <param name="op"></param>
/// <param name="operand"></param>
/// <param name="apply"></param>
/// <returns></returns>
public static Parser<T> XChainOperator<T, TOp>(
Parser<TOp> op,
Parser<T> operand,
Func<TOp, T, T, T> apply)
{
if (op == null) throw new ArgumentNullException(nameof(op));
if (operand == null) throw new ArgumentNullException(nameof(operand));
if (apply == null) throw new ArgumentNullException(nameof(apply));
return operand.Then(first => ChainOperatorRest(first, op, operand, apply, XOr));
}
static Parser<T> ChainOperatorRest<T, TOp>(
T firstOperand,
Parser<TOp> op,
Parser<T> operand,
Func<TOp, T, T, T> apply,
Func<Parser<T>, Parser<T>, Parser<T>> or)
{
if (op == null) throw new ArgumentNullException(nameof(op));
if (operand == null) throw new ArgumentNullException(nameof(operand));
if (apply == null) throw new ArgumentNullException(nameof(apply));
return or(op.Then(opvalue =>
operand.Then(operandValue =>
ChainOperatorRest(apply(opvalue, firstOperand, operandValue), op, operand, apply, or))),
Return(firstOperand));
}
/// <summary>
/// Chain a right-associative operator.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TOp"></typeparam>
/// <param name="op"></param>
/// <param name="operand"></param>
/// <param name="apply"></param>
/// <returns></returns>
public static Parser<T> ChainRightOperator<T, TOp>(
Parser<TOp> op,
Parser<T> operand,
Func<TOp, T, T, T> apply)
{
if (op == null) throw new ArgumentNullException(nameof(op));
if (operand == null) throw new ArgumentNullException(nameof(operand));
if (apply == null) throw new ArgumentNullException(nameof(apply));
return operand.Then(first => ChainRightOperatorRest(first, op, operand, apply, Or));
}
/// <summary>
/// Chain a right-associative operator.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TOp"></typeparam>
/// <param name="op"></param>
/// <param name="operand"></param>
/// <param name="apply"></param>
/// <returns></returns>
public static Parser<T> XChainRightOperator<T, TOp>(
Parser<TOp> op,
Parser<T> operand,
Func<TOp, T, T, T> apply)
{
if (op == null) throw new ArgumentNullException(nameof(op));
if (operand == null) throw new ArgumentNullException(nameof(operand));
if (apply == null) throw new ArgumentNullException(nameof(apply));
return operand.Then(first => ChainRightOperatorRest(first, op, operand, apply, XOr));
}
static Parser<T> ChainRightOperatorRest<T, TOp>(
T lastOperand,
Parser<TOp> op,
Parser<T> operand,
Func<TOp, T, T, T> apply,
Func<Parser<T>, Parser<T>, Parser<T>> or)
{
if (op == null) throw new ArgumentNullException(nameof(op));
if (operand == null) throw new ArgumentNullException(nameof(operand));
if (apply == null) throw new ArgumentNullException(nameof(apply));
return or(op.Then(opvalue =>
operand.Then(operandValue =>
ChainRightOperatorRest(operandValue, op, operand, apply, or)).Then(r =>
Return(apply(opvalue, lastOperand, r)))),
Return(lastOperand));
}
/// <summary>
/// Parse a number.
/// </summary>
public static readonly Parser<string> Number = Numeric.AtLeastOnce().Text();
static Parser<string> DecimalWithoutLeadingDigits(CultureInfo ci = null)
{
return from nothing in Return("")
// dummy so that CultureInfo.CurrentCulture is evaluated later
from dot in String((ci ?? CultureInfo.CurrentCulture).NumberFormat.NumberDecimalSeparator).Text()
from fraction in Number
select dot + fraction;
}
static Parser<string> DecimalWithLeadingDigits(CultureInfo ci = null)
{
return Number.Then(n => DecimalWithoutLeadingDigits(ci).XOr(Return("")).Select(f => n + f));
}
/// <summary>
/// Parse a decimal number using the current culture's separator character.
/// </summary>
public static readonly Parser<string> Decimal = DecimalWithLeadingDigits().XOr(DecimalWithoutLeadingDigits());
/// <summary>
/// Parse a decimal number with separator '.'.
/// </summary>
public static readonly Parser<string> DecimalInvariant = DecimalWithLeadingDigits(CultureInfo.InvariantCulture)
.XOr(DecimalWithoutLeadingDigits(CultureInfo.InvariantCulture));
}
}

View File

@@ -0,0 +1,50 @@
using System;
namespace Sprache
{
/// <summary>
/// Represents an error that occurs during parsing.
/// </summary>
public class ParseException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="ParseException" /> class.
/// </summary>
public ParseException() { }
/// <summary>
/// Initializes a new instance of the <see cref="ParseException" /> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public ParseException(string message) : base(message) { }
/// <summary>
/// Initializes a new instance of the <see cref="ParseException" /> class with a specified error message
/// and the position where the error occured.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="position">The position where the error occured.</param>
public ParseException(string message, Position position) : base(message)
{
if (position == null) throw new ArgumentNullException(nameof(position));
Position = position;
}
/// <summary>
/// Initializes a new instance of the <see cref="ParseException" /> class with a specified error message
/// and a reference to the inner exception that is the cause of this exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception,
/// or a null reference (Nothing in Visual Basic) if no inner exception is specified.</param>
public ParseException(string message, Exception innerException) : base(message, innerException) { }
/// <summary>
/// Gets the position of the parsing failure if one is available; otherwise, null.
/// </summary>
public Position Position {
get;
}
}
}

View File

@@ -0,0 +1,54 @@
using System;
namespace Sprache
{
/// <summary>
/// Represents a parser.
/// </summary>
/// <typeparam name="T">The type of the result.</typeparam>
/// <param name="input">The input to parse.</param>
/// <returns>The result of the parser.</returns>
public delegate IResult<T> Parser<out T>(IInput input);
/// <summary>
/// Contains some extension methods for <see cref="Parser&lt;T&gt;" />.
/// </summary>
public static class ParserExtensions
{
/// <summary>
/// Tries to parse the input without throwing an exception.
/// </summary>
/// <typeparam name="T">The type of the result.</typeparam>
/// <param name="parser">The parser.</param>
/// <param name="input">The input.</param>
/// <returns>The result of the parser</returns>
public static IResult<T> TryParse<T>(this Parser<T> parser, string input)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (input == null) throw new ArgumentNullException(nameof(input));
return parser(new Input(input));
}
/// <summary>
/// Parses the specified input string.
/// </summary>
/// <typeparam name="T">The type of the result.</typeparam>
/// <param name="parser">The parser.</param>
/// <param name="input">The input.</param>
/// <returns>The result of the parser.</returns>
/// <exception cref="Sprache.ParseException">It contains the details of the parsing error.</exception>
public static T Parse<T>(this Parser<T> parser, string input)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (input == null) throw new ArgumentNullException(nameof(input));
var result = parser.TryParse(input);
if(result.WasSuccessful)
return result.Value;
throw new ParseException(result.ToString(), Position.FromInput(result.Remainder));
}
}
}

View File

@@ -0,0 +1,136 @@
using System;
namespace Sprache
{
/// <summary>
/// Represents a position in the input.
/// </summary>
public class Position : IEquatable<Position>
{
/// <summary>
/// Initializes a new instance of the <see cref="Position" /> class.
/// </summary>
/// <param name="pos">The position.</param>
/// <param name="line">The line number.</param>
/// <param name="column">The column.</param>
public Position(int pos, int line, int column)
{
Pos = pos;
Line = line;
Column = column;
}
/// <summary>
/// Creates an new <see cref="Position"/> instance from a given <see cref="IInput"/> object.
/// </summary>
/// <param name="input">The current input.</param>
/// <returns>A new <see cref="Position"/> instance.</returns>
public static Position FromInput(IInput input)
{
return new Position(input.Position, input.Line, input.Column);
}
/// <summary>
/// Gets the current positon.
/// </summary>
public int Pos
{
get;
private set;
}
/// <summary>
/// Gets the current line number.
/// </summary>
public int Line
{
get;
private set;
}
/// <summary>
/// Gets the current column.
/// </summary>
public int Column
{
get;
private set;
}
/// <summary>
/// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="Position" />.
/// </summary>
/// <returns>
/// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="Position" />; otherwise, false.
/// </returns>
/// <param name="obj">The object to compare with the current object. </param>
public override bool Equals(object obj)
{
return Equals(obj as Position);
}
/// <summary>
/// Indicates whether the current <see cref="Position" /> is equal to another object of the same type.
/// </summary>
/// <returns>
/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
public bool Equals(Position other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Pos == other.Pos
&& Line == other.Line
&& Column == other.Column;
}
/// <summary>
/// Indicates whether the left <see cref="Position" /> is equal to the right <see cref="Position" />.
/// </summary>
/// <param name="left">The left <see cref="Position" />.</param>
/// <param name="right">The right <see cref="Position" />.</param>
/// <returns>true if both objects are equal.</returns>
public static bool operator ==(Position left, Position right)
{
return Equals(left, right);
}
/// <summary>
/// Indicates whether the left <see cref="Position" /> is not equal to the right <see cref="Position" />.
/// </summary>
/// <param name="left">The left <see cref="Position" />.</param>
/// <param name="right">The right <see cref="Position" />.</param>
/// <returns>true if the objects are not equal.</returns>
public static bool operator !=(Position left, Position right)
{
return !Equals(left, right);
}
/// <summary>
/// Serves as a hash function for a particular type.
/// </summary>
/// <returns>
/// A hash code for the current <see cref="Position" />.
/// </returns>
public override int GetHashCode()
{
var h = 31;
h = h * 13 + Pos;
h = h * 13 + Line;
h = h * 13 + Column;
return h;
}
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>
/// A string that represents the current object.
/// </returns>
public override string ToString()
{
return string.Format("Line {0}, Column {1}", Line, Column);
}
}
}

View File

@@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using UniLinq;
namespace Sprache
{
/// <summary>
/// Contains helper functions to create <see cref="IResult&lt;T&gt;"/> instances.
/// </summary>
public static class Result
{
/// <summary>
/// Creates a success result.
/// </summary>
/// <typeparam name="T">The type of the result (value).</typeparam>
/// <param name="value">The sucessfully parsed value.</param>
/// <param name="remainder">The remainder of the input.</param>
/// <returns>The new <see cref="IResult&lt;T&gt;"/>.</returns>
public static IResult<T> Success<T>(T value, IInput remainder)
{
return new Result<T>(value, remainder);
}
/// <summary>
/// Creates a failure result.
/// </summary>
/// <typeparam name="T">The type of the result.</typeparam>
/// <param name="remainder">The remainder of the input.</param>
/// <param name="message">The error message.</param>
/// <param name="expectations">The parser expectations.</param>
/// <returns>The new <see cref="IResult&lt;T&gt;"/>.</returns>
public static IResult<T> Failure<T>(IInput remainder, string message, IEnumerable<string> expectations)
{
return new Result<T>(remainder, message, expectations);
}
}
internal class Result<T> : IResult<T>
{
private readonly T _value;
private readonly IInput _remainder;
private readonly bool _wasSuccessful;
private readonly string _message;
private readonly IEnumerable<string> _expectations;
public Result(T value, IInput remainder)
{
_value = value;
_remainder = remainder;
_wasSuccessful = true;
_message = null;
_expectations = Enumerable.Empty<string>();
}
public Result(IInput remainder, string message, IEnumerable<string> expectations)
{
_value = default(T);
_remainder = remainder;
_wasSuccessful = false;
_message = message;
_expectations = expectations;
}
public T Value
{
get
{
if (!WasSuccessful)
throw new InvalidOperationException("No value can be computed.");
return _value;
}
}
public bool WasSuccessful { get { return _wasSuccessful; } }
public string Message { get { return _message; } }
public IEnumerable<string> Expectations { get { return _expectations; } }
public IInput Remainder { get { return _remainder; } }
public override string ToString()
{
if (WasSuccessful)
return string.Format("Successful parsing of {0}.", Value);
var expMsg = "";
if (Expectations.Any())
expMsg = " expected " + Expectations.Aggregate((e1, e2) => e1 + " or " + e2);
var recentlyConsumed = CalculateRecentlyConsumed();
return string.Format("Parsing failure: {0};{1} ({2}); recently consumed: {3}", Message, expMsg, Remainder, recentlyConsumed);
}
private string CalculateRecentlyConsumed()
{
const int windowSize = 10;
var totalConsumedChars = Remainder.Position;
var windowStart = totalConsumedChars - windowSize;
windowStart = windowStart < 0 ? 0 : windowStart;
var numberOfRecentlyConsumedChars = totalConsumedChars - windowStart;
return Remainder.Source.Substring(windowStart, numberOfRecentlyConsumedChars);
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
namespace Sprache
{
internal static class ResultHelper
{
public static IResult<U> IfSuccess<T, U>(this IResult<T> result, Func<IResult<T>, IResult<U>> next)
{
if(result == null) throw new ArgumentNullException(nameof(result));
if (result.WasSuccessful)
return next(result);
return Result.Failure<U>(result.Remainder, result.Message, result.Expectations);
}
public static IResult<T> IfFailure<T>(this IResult<T> result, Func<IResult<T>, IResult<T>> next)
{
if (result == null) throw new ArgumentNullException(nameof(result));
return result.WasSuccessful
? result
: next(result);
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using UniLinq;
namespace Sprache
{
internal static class StringExtensions
{
public static IEnumerable<char> ToEnumerable(this string @this)
{
#if STRING_IS_ENUMERABLE
return @this;
#else
if (@this == null) throw new ArgumentNullException(nameof(@this));
for (var i = 0; i < @this.Length; ++i)
{
yield return @this[i];
}
#endif
}
public static string Join<T>(string separator, IEnumerable<T> values)
{
#if STRING_JOIN_ENUMERABLE
return string.Join(separator, values);
#else
return string.Join(separator, values.Select(v => v.ToString()).ToArray());
#endif
}
}
}

View File

@@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2011 Nicholas Blumhardt
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,19 @@
using KFCommonUtilityLib.Scripts.StaticManagers;
using Mono.Cecil.Cil;
using Mono.Cecil;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace KFCommonUtilityLib
{
public interface IModuleProcessor
{
void InitModules(ModuleManipulator manipulator, Type targetType, Type baseType, params Type[] moduleTypes);
bool BuildConstructor(ModuleManipulator manipulator, MethodDefinition mtddef_ctor);
Type GetModuleTypeByName(string name);
bool MatchSpecialArgs(ParameterDefinition par, MethodDefinition mtddef_target, MethodPatchInfo mtdpinf_derived, int moduleIndex, List<Instruction> list_inst_pars, ILProcessor il);
}
}

View File

@@ -0,0 +1,80 @@
using Mono.Cecil;
using Mono.Cecil.Cil;
using System;
using System.Collections.Generic;
namespace KFCommonUtilityLib
{
public class ItemActionDataModuleProcessor : IModuleProcessor
{
TypeDefinition typedef_newAction;
Type[] arr_type_actions;
FieldDefinition[] arr_flddef_actions;
bool[] arr_hasdata;
FieldDefinition[] arr_flddef_actiondatas;
public ItemActionDataModuleProcessor(TypeDefinition typedef_newAction, Type[] arr_type_actions, FieldDefinition[] arr_flddef_actions, bool[] arr_hasdata, out FieldDefinition[] arr_flddef_actiondatas)
{
this.typedef_newAction = typedef_newAction;
this.arr_type_actions = arr_type_actions;
this.arr_flddef_actions = arr_flddef_actions;
this.arr_hasdata = arr_hasdata;
this.arr_flddef_actiondatas = new FieldDefinition[arr_type_actions.Length];
arr_flddef_actiondatas = this.arr_flddef_actiondatas;
}
public bool BuildConstructor(ModuleManipulator manipulator, MethodDefinition mtddef_ctor)
{
mtddef_ctor.Parameters.Add(new ParameterDefinition("_inventoryData", Mono.Cecil.ParameterAttributes.None, manipulator.module.ImportReference(typeof(ItemInventoryData))));
mtddef_ctor.Parameters.Add(new ParameterDefinition("_indexInEntityOfAction", Mono.Cecil.ParameterAttributes.None, manipulator.module.TypeSystem.Int32));
FieldReference fldref_invdata_item = manipulator.module.ImportReference(typeof(ItemInventoryData).GetField(nameof(ItemInventoryData.item)));
FieldReference fldref_item_actions = manipulator.module.ImportReference(typeof(ItemClass).GetField(nameof(ItemClass.Actions)));
var il = mtddef_ctor.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, manipulator.module.ImportReference(manipulator.targetType.GetConstructor(new Type[] { typeof(ItemInventoryData), typeof(int) }))));
il.Append(il.Create(OpCodes.Nop));
for (int i = 0, j = 0; i < arr_type_actions.Length; i++)
{
if (!arr_hasdata[i])
{
arr_flddef_actiondatas[i] = null;
continue;
}
arr_flddef_actiondatas[i] = manipulator.arr_flddef_modules[j];
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_actions[i]);
Log.Out($"data module {j} {manipulator.moduleTypes[j].FullName} action module {i} {arr_type_actions[i].FullName}");
il.Append(il.Create(OpCodes.Newobj, manipulator.module.ImportReference(manipulator.moduleTypes[j].GetConstructor(new Type[] { typeof(ItemInventoryData), typeof(int), arr_type_actions[i] }))));
il.Append(il.Create(OpCodes.Stfld, manipulator.arr_flddef_modules[j]));
il.Append(il.Create(OpCodes.Nop));
j++;
}
il.Append(il.Create(OpCodes.Ret));
return true;
}
public Type GetModuleTypeByName(string name)
{
throw new NotImplementedException();
}
public void InitModules(ModuleManipulator manipulator, Type targetType, Type baseType, params Type[] moduleTypes)
{
}
public bool MatchSpecialArgs(ParameterDefinition par, MethodDefinition mtddef_target, MethodPatchInfo mtdpinf_derived, int moduleIndex, List<Instruction> list_inst_pars, ILProcessor il)
{
return false;
}
}
}

View File

@@ -0,0 +1,104 @@
using KFCommonUtilityLib.Scripts.Attributes;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using UniLinq;
namespace KFCommonUtilityLib
{
//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 static class ItemActionModuleManager
{
private static readonly Dictionary<string, List<(string typename, int indexOfAction)>> dict_replacement_mapping = new Dictionary<string, List<(string typename, int indexOfAction)>>();
internal static void Init()
{
ModuleManagers.OnAssemblyCreated += static () => dict_replacement_mapping.Clear();
ModuleManagers.OnAssemblyLoaded += static () =>
{
//replace item actions
foreach (var pair in dict_replacement_mapping)
{
ItemClass item = ItemClass.GetItemClass(pair.Key, true);
foreach ((string typename, int indexOfAction) in pair.Value)
if (ModuleManagers.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);
}
}
dict_replacement_mapping.Clear();
};
}
internal static void CheckItem(ItemClass item)
{
if (!ModuleManagers.Inited)
{
return;
}
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))
{
try
{
if (ModuleManagers.PatchType<ItemActionModuleProcessor>(itemAction.GetType(), typeof(ItemAction), str_modules, out string typename))
{
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));
}
}
catch(Exception e)
{
Log.Error($"Error parsing ItemActionModules for {item.Name} action{i}:\n{e}");
continue;
}
}
}
}
}
}

View File

@@ -0,0 +1,157 @@
using Mono.Cecil;
using Mono.Cecil.Cil;
using System;
using System.Collections.Generic;
using UniLinq;
using System.Reflection;
using UnityEngine.Scripting;
using FieldAttributes = Mono.Cecil.FieldAttributes;
using MethodAttributes = Mono.Cecil.MethodAttributes;
using TypeAttributes = Mono.Cecil.TypeAttributes;
using KFCommonUtilityLib.Scripts.Attributes;
using Mono.Cecil.Rocks;
namespace KFCommonUtilityLib
{
public class ItemActionModuleProcessor : IModuleProcessor
{
TypeDefinition typedef_newActionData;
FieldDefinition[] arr_flddef_data;
public Type GetModuleTypeByName(string name)
{
return ReflectionHelpers.GetTypeWithPrefix("ActionModule", name);
}
public bool BuildConstructor(ModuleManipulator manipulator, MethodDefinition mtddef_ctor)
{
return false;
}
public void InitModules(ModuleManipulator manipulator, Type targetType, Type baseType, params Type[] moduleTypes)
{
ModuleDefinition module = manipulator.module;
//Find ItemActionData subtype
MethodInfo mtdinf_create_data = null;
{
Type type_itemActionBase = targetType;
while (baseType.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();
}
}
//ACTION MODULE DATA TYPES
var arr_type_data = moduleTypes.Select(m => m.GetCustomAttribute<ActionDataTargetAttribute>()?.DataType).ToArray();
//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));
MethodReference mtdref_data_ctor;
if (ModuleManagers.PatchType(type_itemActionData, typeof(ItemActionData), arr_type_data.Where(t => t != null).ToArray(), new ItemActionDataModuleProcessor(manipulator.typedef_newTarget, moduleTypes, manipulator.arr_flddef_modules, arr_type_data.Select(t => t != null).ToArray(), out arr_flddef_data), out var str_data_type_name) && ModuleManagers.TryFindInCur(str_data_type_name, out typedef_newActionData))
{
module.Types.Remove(typedef_newActionData);
manipulator.typedef_newTarget.NestedTypes.Add(typedef_newActionData);
mtdref_data_ctor = typedef_newActionData.GetConstructors().FirstOrDefault();
}
else
{
mtdref_data_ctor = module.ImportReference(type_itemActionData.GetConstructor(new Type[] { typeof(ItemInventoryData), typeof(int) }));
}
//typedef_newActionData = new TypeDefinition(null, ModuleUtils.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>()))));
//manipulator.typedef_newTarget.NestedTypes.Add(typedef_newActionData);
////Create ItemActionData field
//arr_flddef_data = new FieldDefinition[moduleTypes.Length];
//for (int i = 0; i < moduleTypes.Length; i++)
//{
// if (arr_type_data[i] != null)
// {
// Type type_data = arr_type_data[i];
// manipulator.MakeContainerFor(typedef_newActionData, type_data, out var flddef_data);
// arr_flddef_data[i] = flddef_data;
// }
//}
////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)));
//var 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, manipulator.typedef_newTarget);
// il.Emit(OpCodes.Ldfld, manipulator.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));
var il = mtddef_create_modifier_data.Body.GetILProcessor();
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Newobj, mtdref_data_ctor);
il.Emit(OpCodes.Ret);
manipulator.typedef_newTarget.Methods.Add(mtddef_create_modifier_data);
}
public bool MatchSpecialArgs(ParameterDefinition par, MethodDefinition mtddef_target, MethodPatchInfo mtdpinf_derived, int moduleIndex, List<Instruction> list_inst_pars, ILProcessor il)
{
switch (par.Name)
{
//load injected data instance
case "__customData":
var flddef_data = arr_flddef_data[moduleIndex];
if (flddef_data == null)
throw new ArgumentNullException($"No Injected ItemActionData in {mtddef_target.DeclaringType.FullName}! module index {moduleIndex}");
int index = -1;
for (int j = 0; j < mtdpinf_derived.Method.Parameters.Count; j++)
{
if (mtdpinf_derived.Method.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, mtdpinf_derived.Method.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));
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
namespace KFCommonUtilityLib
{
public static class ItemClassModuleManager
{
private static readonly Dictionary<string, string> dict_classtypes = new Dictionary<string, string>();
internal static void Init()
{
ModuleManagers.OnAssemblyCreated += static () => dict_classtypes.Clear();
ModuleManagers.OnAssemblyLoaded += static () =>
{
foreach (var pair in dict_classtypes)
{
if (ModuleManagers.TryFindType(pair.Value, out Type classType))
{
var item = ItemClass.GetItemClass(pair.Key);
if (item != null)
{
var itemNew = (ItemClass)Activator.CreateInstance(classType);
item.PreInitCopyTo(itemNew);
if (item is ItemClassModifier mod)
{
mod.PreInitCopyToModifier((ItemClassModifier)itemNew);
}
itemNew.Init();
ItemClass.itemNames.RemoveAt(ItemClass.itemNames.Count - 1);
ItemClass.list[itemNew.Id] = itemNew;
}
}
}
dict_classtypes.Clear();
};
}
internal static void CheckItem(ItemClass item)
{
if (!ModuleManagers.Inited)
{
return;
}
if (item != null && item.Properties.Values.TryGetValue("ItemClassModules", out string str_modules))
{
if (ModuleManagers.PatchType<ItemClassModuleProcessor>(item.GetType(), typeof(ItemClass), str_modules, out string typename))
{
dict_classtypes[item.Name] = typename;
}
}
}
private static void PreInitCopyTo(this ItemClass from, ItemClass to)
{
to.Actions = from.Actions;
foreach (var action in to.Actions)
{
if (action != null)
{
action.item = to;
}
}
to.SetName(from.Name);
to.pId = from.pId;
to.Properties = from.Properties;
to.Effects = from.Effects;
to.setLocalizedItemName(from.localizedName);
to.Stacknumber = from.Stacknumber;
to.SetCanHold(from.bCanHold);
to.SetCanDrop(from.bCanDrop);
to.MadeOfMaterial = from.MadeOfMaterial;
to.MeshFile = from.MeshFile;
to.StickyOffset = from.StickyOffset;
to.StickyColliderRadius = from.StickyColliderRadius;
to.StickyColliderUp = from.StickyColliderUp;
to.StickyColliderLength = from.StickyColliderLength;
to.StickyMaterial = from.StickyMaterial;
to.ImageEffectOnActive = from.ImageEffectOnActive;
to.Active = from.Active;
to.IsSticky = from.IsSticky;
to.DropMeshFile = from.DropMeshFile;
to.HandMeshFile = from.HandMeshFile;
to.HoldType = from.HoldType;
to.RepairTools = from.RepairTools;
to.RepairAmount = from.RepairAmount;
to.RepairTime = from.RepairTime;
to.MaxUseTimes = from.MaxUseTimes;
to.MaxUseTimesBreaksAfter = from.MaxUseTimesBreaksAfter;
to.EconomicValue = from.EconomicValue;
to.Preview = from.Preview;
}
private static void PreInitCopyToModifier(this ItemClassModifier from, ItemClassModifier to)
{
to.CosmeticInstallChance = from.CosmeticInstallChance;
to.PropertyOverrides = from.PropertyOverrides;
to.InstallableTags = from.InstallableTags;
to.DisallowedTags = from.DisallowedTags;
to.ItemTags = from.ItemTags;
to.Type = from.Type;
}
}
}

View File

@@ -0,0 +1,33 @@
using Mono.Cecil;
using Mono.Cecil.Cil;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace KFCommonUtilityLib
{
public struct ItemClassModuleProcessor : IModuleProcessor
{
public Type GetModuleTypeByName(string name)
{
return ReflectionHelpers.GetTypeWithPrefix("ItemModule", name);
}
public bool BuildConstructor(ModuleManipulator manipulator, MethodDefinition mtddef_ctor)
{
return false;
}
public void InitModules(ModuleManipulator manipulator, Type targetType, Type baseType, params Type[] moduleTypes)
{
}
public bool MatchSpecialArgs(ParameterDefinition par, MethodDefinition mtddef_target, MethodPatchInfo mtdpinf_derived, int moduleIndex, List<Instruction> list_inst_pars, ILProcessor il)
{
return false;
}
}
}

View File

@@ -0,0 +1,262 @@
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using Mono.Cecil;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Security.Permissions;
using UniLinq;
using UnityEngine;
namespace KFCommonUtilityLib
{
public static class ModuleManagers
{
private static class ModuleExtensions<T>
{
public readonly static List<Type> extensions = new List<Type>();
}
public static AssemblyDefinition WorkingAssembly { get; private set; } = null;
public static event Action OnAssemblyCreated;
public static event Action OnAssemblyLoaded;
public static bool Inited { get; private set; }
private static bool extensionScanned;
private static readonly HashSet<string> list_registered_path = new HashSet<string>();
private static readonly List<Assembly> list_created = new List<Assembly>();
private static DefaultAssemblyResolver resolver;
private static ModuleAttributes moduleAttributes;
private static ModuleCharacteristics moduleCharacteristics;
private static MethodInfo mtdinf = AccessTools.Method(typeof(ModuleManagers), nameof(ModuleManagers.AddModuleExtension));
public static void InitModuleExtensions()
{
if (extensionScanned)
{
return;
}
var assemblies = ModManager.GetLoadedAssemblies();
foreach (var assembly in assemblies)
{
foreach (var type in assembly.GetTypes())
{
var attr = type.GetCustomAttribute<TypeTargetExtensionAttribute>();
if (attr != null)
{
if ((bool)mtdinf.MakeGenericMethod(attr.ModuleType).Invoke(null, new object[] { type }))
{
Log.Out($"Found Module Extension {type.FullName}");
}
}
}
}
extensionScanned = true;
}
public static bool AddModuleExtension<T>(Type extType)
{
if (typeof(T).GetCustomAttribute<TypeTargetAttribute>() == null)
{
return false;
}
if (!ModuleExtensions<T>.extensions.Contains(extType))
ModuleExtensions<T>.extensions.Add(extType);
return true;
}
public static Type[] GetModuleExtensions<T>()
{
if (typeof(T).GetCustomAttribute<TypeTargetAttribute>() == null)
{
return Array.Empty<Type>();
}
return ModuleExtensions<T>.extensions.ToArray();
}
public static void AddAssemblySearchPath(string path)
{
if (Directory.Exists(path))
{
list_registered_path.Add(path);
}
}
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()
{
if (Inited)
{
return;
}
InitModuleExtensions();
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);
}
foreach (var path in list_registered_path)
{
resolver.AddSearchDirectory(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 = "RuntimeAssembled" + 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);
OnAssemblyCreated?.Invoke();
Inited = true;
Log.Out("======Init New======");
}
public static bool PatchType<T>(Type targetType, Type baseType, string moduleNames, out string typename) where T : IModuleProcessor, new()
{
Type[] moduleTypes = moduleNames.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
.Select(s => new T().GetModuleTypeByName(s.Trim()))
.Where(t => t.GetCustomAttribute<TypeTargetAttribute>().BaseType.IsAssignableFrom(targetType)).ToArray();
return PatchType(targetType, baseType, moduleTypes, new T(), out typename);
}
public static bool PatchType<T>(Type targetType, Type baseType, Type[] moduleTypes, out string typename) where T : IModuleProcessor, new()
{
return PatchType(targetType, baseType, moduleTypes, new T(), out typename);
}
public static bool PatchType<T>(Type targetType, Type baseType, Type[] moduleTypes, T processor, out string typename) where T : IModuleProcessor
{
if (moduleTypes.Length == 0)
{
typename = string.Empty;
return false;
}
typename = ModuleUtils.CreateTypeName(targetType, moduleTypes);
//Log.Out(typename);
if (!ModuleManagers.TryFindType(typename, out _) && !ModuleManagers.TryFindInCur(typename, out _))
_ = new ModuleManipulator(ModuleManagers.WorkingAssembly, processor, targetType, baseType, moduleTypes);
return true;
}
internal static void FinishAndLoad()
{
if (!Inited)
{
return;
}
//output assembly
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)
{
if (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);
}
}
Log.Out("======Finish and Load======");
Inited = false;
OnAssemblyLoaded?.Invoke();
Cleanup();
}
}
//cleanup
internal static void Cleanup()
{
Inited = false;
WorkingAssembly?.Dispose();
WorkingAssembly = null;
}
/// <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>
public 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>
public static bool TryFindInCur(string name, out TypeDefinition typedef)
{
typedef = WorkingAssembly?.MainModule.GetType(name);
return typedef != null;
}
}
}

View File

@@ -0,0 +1,646 @@
using HarmonyLib.Public.Patching;
using HarmonyLib;
using KFCommonUtilityLib.Scripts.Attributes;
using Mono.Cecil;
using Mono.Cecil.Cil;
using MonoMod.Cil;
using MonoMod.Utils;
using System;
using System.Collections.Generic;
using UniLinq;
using System.Reflection;
using KFCommonUtilityLib.Harmony;
using UnityEngine.Scripting;
using TypeAttributes = Mono.Cecil.TypeAttributes;
using MethodAttributes = Mono.Cecil.MethodAttributes;
using FieldAttributes = Mono.Cecil.FieldAttributes;
using Mono.Cecil.Rocks;
namespace KFCommonUtilityLib
{
public interface IModuleContainerFor<out T> where T : class
{
T Instance { get; }
}
public class TranspilerTarget
{
public TranspilerTarget(Type type_action, MethodInfo mtdinf_original, PatchInfo patchinf_harmony)
{
this.type_action = type_action;
this.mtdinf_original = mtdinf_original;
this.patchinf_harmony = patchinf_harmony;
}
public Type type_action;
public MethodInfo mtdinf_original;
public PatchInfo patchinf_harmony;
}
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;
}
}
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;
}
}
public class ModuleManipulator
{
public ModuleDefinition module;
public IModuleProcessor processor;
public Type targetType;
public Type baseType;
public Type[] moduleTypes;
public Type[][] moduleExtensionTypes;
public TypeDefinition typedef_newTarget;
public TypeReference typeref_interface;
public FieldDefinition[] arr_flddef_modules;
private static MethodInfo mtdinf = AccessTools.Method(typeof(ModuleManagers), nameof(ModuleManagers.GetModuleExtensions));
public ModuleManipulator(AssemblyDefinition workingAssembly, IModuleProcessor processor, Type targetType, Type baseType, params Type[] moduleTypes)
{
module = workingAssembly.MainModule;
this.processor = processor;
this.targetType = targetType;
this.baseType = baseType;
this.moduleTypes = moduleTypes;
moduleExtensionTypes = moduleTypes.Select(t => (Type[])mtdinf.MakeGenericMethod(t).Invoke(null, null)).ToArray();
Patch();
}
private void Patch()
{
typeref_interface = module.ImportReference(typeof(IModuleContainerFor<>));
//Create new override type
TypeReference typeref_target = module.ImportReference(targetType);
typedef_newTarget = new TypeDefinition(null, ModuleUtils.CreateTypeName(targetType, moduleTypes), TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.Public | TypeAttributes.Sealed, typeref_target);
typedef_newTarget.CustomAttributes.Add(new CustomAttribute(module.ImportReference(typeof(PreserveAttribute).GetConstructor(Array.Empty<Type>()))));
module.Types.Add(typedef_newTarget);
//Create fields
arr_flddef_modules = new FieldDefinition[moduleTypes.Length];
for (int i = 0; i < moduleTypes.Length; i++)
{
//Create ItemAction field
Type type_module = moduleTypes[i];
MakeContainerFor(typedef_newTarget, type_module, out var flddef_module);
arr_flddef_modules[i] = flddef_module;
}
//Create ItemAction constructor
MethodDefinition mtddef_ctor = new MethodDefinition(".ctor", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, module.TypeSystem.Void);
if (processor == null || !processor.BuildConstructor(this, mtddef_ctor))
{
var il = mtddef_ctor.Body.GetILProcessor();
il.Append(il.Create(OpCodes.Ldarg_0));
il.Append(il.Create(OpCodes.Call, module.ImportReference(targetType.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_newTarget.Methods.Add(mtddef_ctor);
processor?.InitModules(this, targetType, baseType, moduleTypes);
//<derived method name, method patch info>
Dictionary<string, MethodPatchInfo> dict_overrides = new Dictionary<string, MethodPatchInfo>();
//<derived method name, transpiler stub methods in inheritance order>
//TODO: USE TREE INSTEAD OF LIST
Dictionary<string, List<TranspilerTarget>> dict_transpilers = new Dictionary<string, List<TranspilerTarget>>();
//<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
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).Concat(moduleExtensionTypes[i].SelectMany(t => t.GetMethods(searchFlags))))
{
var attr = mtd.GetCustomAttribute<MethodTargetTranspilerAttribute>();
foreach (var hp in mtd.GetCustomAttributes<HarmonyPatch>())
{
//make sure the transpiler has a target method to apply, otherwise skip it
if (attr != null && hp != null && hp.info.declaringType != null)
{
var hm = hp.info;
hm.methodType = hm.methodType ?? MethodType.Normal;
var mtdinf_target = hm.GetOriginalMethod() as MethodInfo;
if (mtdinf_target == null || mtdinf_target.IsAbstract || !mtdinf_target.IsVirtual)
{
continue;
}
string id = hm.GetTargetMethodIdentifier();
if (!dict_transpilers.TryGetValue(id, out var list))
{
dict_transpilers[id] = (list = new List<TranspilerTarget>());
Type nextType = targetType;
TranspilerTarget curNode = null;
var hm_next = hm.Clone();
while (hm.declaringType.IsAssignableFrom(nextType))
{
hm_next.declaringType = nextType;
var mtdinfo_cur = hm_next.GetOriginalMethod() as MethodInfo;
if (mtdinfo_cur != null)
{
var patchinf_harmony = mtdinfo_cur.ToPatchInfoDontAdd().Copy();
curNode = new TranspilerTarget(mtdinfo_cur.DeclaringType, mtdinfo_cur, patchinf_harmony);
list.Add(curNode);
}
nextType = nextType.BaseType;
}
if (curNode != null)
{
curNode.patchinf_harmony.AddTranspilers(CommonUtilityLibInit.HarmonyInstance.Id, new HarmonyMethod(mtd));
Log.Out($"Adding transpiler {mtd.FullDescription()}\nCurrent transpilers:\n{string.Join('\n', curNode.patchinf_harmony.transpilers.Select(p => p.PatchMethod.FullDescription()))}");
}
}
else
{
bool childFound = false;
foreach (var node in ((IEnumerable<TranspilerTarget>)list).Reverse())
{
if (node.type_action.Equals(hm.declaringType))
{
childFound = true;
node.patchinf_harmony.AddTranspilers(CommonUtilityLibInit.HarmonyInstance.Id, mtd);
Log.Out($"Adding transpiler {mtd.FullDescription()}\nCurrent transpilers:\n{string.Join('\n', node.patchinf_harmony.transpilers.Select(p => p.PatchMethod.FullDescription()))}");
break;
}
}
if (!childFound)
{
Type nextType = list[list.Count - 1].type_action.BaseType;
TranspilerTarget curNode = null;
var hm_next = hm.Clone();
while (hm.declaringType.IsAssignableFrom(nextType))
{
hm_next.declaringType = nextType;
var mtdinfo_cur = hm_next.GetOriginalMethod() as MethodInfo;
if (mtdinfo_cur != null)
{
var patchinf_harmony = mtdinfo_cur.ToPatchInfoDontAdd().Copy();
curNode = new TranspilerTarget(mtdinfo_cur.DeclaringType, mtdinfo_cur, patchinf_harmony);
list.Add(curNode);
}
nextType = nextType.BaseType;
}
if (curNode != null)
{
curNode.patchinf_harmony.AddTranspilers(CommonUtilityLibInit.HarmonyInstance.Id, new HarmonyMethod(mtd));
Log.Out($"Adding transpiler {mtd.FullDescription()}\nCurrent transpilers:\n{string.Join('\n', curNode.patchinf_harmony.transpilers.Select(p => p.PatchMethod.FullDescription()))}");
}
}
}
}
}
}
}
//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)
{
List<TranspilerTarget> list = pair.Value;
//the top copy to call in the override method
MethodDefinition mtddef_override_copy = null;
MethodReference mtdref_override_base = null;
for (int i = list.Count - 1; i >= 0; i--)
{
TranspilerTarget curNode = list[i];
MethodPatcher patcher = curNode.mtdinf_original.GetMethodPatcher();
DynamicMethodDefinition dmd = patcher.CopyOriginal();
ILContext context = new ILContext(dmd.Definition);
HarmonyManipulator.Manipulate(curNode.mtdinf_original, curNode.patchinf_harmony, context);
var mtdref_original = module.ImportReference(curNode.mtdinf_original);
var mtddef_copy = mtdref_original.Resolve().CloneToModuleAsStatic(context.Body, module.ImportReference(curNode.type_action), module);
dmd.Dispose();
context.Dispose();
if (mtddef_override_copy != null && mtdref_override_base != null)
{
//replace calls to the base
foreach (var ins in mtddef_copy.Body.Instructions)
{
if (ins.OpCode == OpCodes.Call && ((MethodReference)ins.Operand).FullName.Equals(mtdref_override_base.FullName))
{
Log.Out($"replacing call to {mtdref_override_base.FullName} to {mtddef_override_copy.FullName}");
ins.Operand = mtddef_override_copy;
}
}
}
//add patched copy to the class
typedef_newTarget.Methods.Add(mtddef_copy);
//the iteration is reversed so make sure we grab the latest method
mtddef_override_copy = mtddef_copy;
mtdref_override_base = mtdref_original;
}
//create the method override that calls the patched copy
if (mtddef_override_copy != null && mtdref_override_base != null)
{
GetOrCreateOverride(dict_overrides, pair.Key, mtdref_override_base, 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>(i);
string moduleID = ModuleUtils.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);
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, i, true, dict_states, moduleID);
//insert invocation
var 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>(i);
string moduleID = ModuleUtils.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);
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, i, false, dict_states, moduleID);
//insert invocation
var 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_newTarget.Methods.Add(mtd.Method);
//Log.Out($"Add method override to new action: {mtd.Method.Name}");
}
}
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="targetType"></param>
/// <param name="moduleType"></param>
/// <param name="module"></param>
/// <returns></returns>
private Dictionary<string, MethodOverrideInfo> GetMethodOverrideTargets<T>(int moduleIndex) where T : Attribute, IMethodTarget
{
Type moduleType = moduleTypes[moduleIndex];
Dictionary<string, MethodOverrideInfo> dict_overrides = new Dictionary<string, MethodOverrideInfo>();
const BindingFlags searchFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
const BindingFlags extensionFlags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
foreach (var mtd in moduleType.GetMethods(searchFlags).Concat(moduleExtensionTypes[moduleIndex].SelectMany(t => t.GetMethods(extensionFlags))))
{
if (mtd.GetCustomAttribute<T>() != null)
{
foreach (HarmonyPatch hp in mtd.GetCustomAttributes<HarmonyPatch>())
{
if (hp != null && (hp.info.declaringType == null || hp.info.declaringType.IsAssignableFrom(targetType)))
{
var hm = hp.info;
hm.methodType = hm.methodType ?? MethodType.Normal;
var hmclone = hm.Clone();
hmclone.declaringType = targetType;
string id = hm.GetTargetMethodIdentifier();
MethodInfo mtdinf_base = hmclone.GetBaseMethod() as MethodInfo;
if (mtdinf_base == null || !mtdinf_base.IsVirtual || mtdinf_base.IsFinal)
{
Log.Error($"Method not found: {moduleType.FullName} on {hmclone.methodName}\n{hmclone.ToString()}");
continue;
}
MethodReference mtdref_base = module.ImportReference(mtdinf_base);
//Find preferred patch
if (dict_overrides.TryGetValue(id, out var info))
{
if (hm.declaringType == 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 (hm.declaringType.IsAssignableFrom(targetType) && (info.prefType == null || hm.declaringType.IsSubclassOf(info.prefType)))
{
dict_overrides[id] = new MethodOverrideInfo(mtd, mtdinf_base, mtdref_base, hm.declaringType);
}
}
else
{
dict_overrides[id] = new MethodOverrideInfo(mtd, mtdinf_base, mtdref_base, hm.declaringType);
}
//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 MethodPatchInfo GetOrCreateOverride(Dictionary<string, MethodPatchInfo> dict_overrides, string id, MethodReference mtdref_base, 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_base = mtdref_base.Resolve();
MethodDefinition mtddef_derived = new MethodDefinition(mtddef_base.Name, (mtddef_base.Attributes | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.ReuseSlot) & ~MethodAttributes.NewSlot, module.ImportReference(mtddef_base.ReturnType));
//Log.Out($"Create method override: {id} for {mtdref_base.FullName}");
foreach (var par in mtddef_base_override?.Parameters?.Skip(1) ?? mtddef_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(mtddef_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_root">The root definition of this method.</param>
/// <param name="mtdpinf_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 List<Instruction> MatchArguments(MethodDefinition mtddef_root, MethodPatchInfo mtdpinf_derived, MethodDefinition mtddef_target, int moduleIndex, bool isPostfix, Dictionary<string, VariableDefinition> dict_states, string moduleID)
{
FieldDefinition flddef_module = arr_flddef_modules[moduleIndex];
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.IsStatic ? mtddef_target.Parameters.Skip(1) : mtddef_target.Parameters)
{
if (par.Name.StartsWith("___"))
{
//___ means non public fields
string str_fldname = par.Name.Substring(3);
FieldDefinition flddef_target = module.ImportReference(targetType.GetField(str_fldname, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | 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, mtddef_target, mtdpinf_derived, moduleIndex, list_inst_pars, il, 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 bool MatchSpecialParameters(ParameterDefinition par, MethodDefinition mtddef_target, MethodPatchInfo mtdpinf_derived, int moduleIndex, List<Instruction> list_inst_pars, ILProcessor il, bool isPostfix, Dictionary<string, VariableDefinition> dict_states, string moduleID)
{
MethodDefinition mtddef_derived = mtdpinf_derived.Method;
switch (par.Name)
{
//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 processor != null ? processor.MatchSpecialArgs(par, mtddef_target, mtdpinf_derived, moduleIndex, list_inst_pars, il) : false;
}
return true;
}
public void MakeContainerFor(TypeDefinition typedef_container, Type type_module, out FieldDefinition flddef_module)
{
var typeref_module = module.ImportReference(type_module);
flddef_module = new FieldDefinition(ModuleUtils.CreateFieldName(type_module), FieldAttributes.Public, typeref_module);
typedef_container.Fields.Add(flddef_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);
}
}
}

View File

@@ -0,0 +1,67 @@
using HarmonyLib;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace KFCommonUtilityLib
{
public static class ModuleUtils
{
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();
}
}
}

View File

@@ -0,0 +1,1685 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;
using MethodBody = Mono.Cecil.Cil.MethodBody;
/// <summary>
/// https://github.com/snaphat/MonoCecilExtensions
/// Provides extension methods for classes from the Mono.Cecil library, a library for reading and writing Intermediate Language (IL) code.
/// These extensions facilitate manipulation of IL code, providing functionality to clone, merge, and update types in collections, methods, fields, and other components of a .NET assembly.
/// </summary>
public static class MonoCecilExtensions
{
/// <summary>
/// Represents an information container for updating Mono.Cecil definitions.
/// </summary>
public class UpdateInfo
{
/// <summary>
/// A collection of CustomAttribute objects that have been updated.
/// </summary>
public readonly Collection<CustomAttribute> updatedAttributes = new Collection<CustomAttribute>();
/// <summary>
/// A collection of InterfaceImplementation objects that have been updated.
/// </summary>
public readonly Collection<InterfaceImplementation> updatedInterfaces = new Collection<InterfaceImplementation>();
/// <summary>
/// A collection of FieldDefinition objects that have been updated.
/// </summary>
public readonly Collection<FieldDefinition> updatedFields = new Collection<FieldDefinition>();
/// <summary>
/// A collection of PropertyDefinition objects that have been updated.
/// </summary>
public readonly Collection<PropertyDefinition> updatedProperties = new Collection<PropertyDefinition>();
/// <summary>
/// A collection of MethodDefinition objects that have been updated.
/// </summary>
public readonly Collection<MethodDefinition> updatedMethods = new Collection<MethodDefinition>();
/// <summary>
/// A collection of source TypeDefinition objects that are being merged.
/// </summary>
public readonly Collection<TypeDefinition> srcTypes = new Collection<TypeDefinition>();
/// <summary>
/// A collection of destination TypeDefinition objects where source objects are merged into.
/// </summary>
public readonly Collection<TypeDefinition> destTypes = new Collection<TypeDefinition>();
};
/// <summary>
/// A dictionary mapping from AssemblyDefinition objects to their corresponding UpdateInfo objects.
/// Used to keep track of the updates made to each assembly.
/// </summary>
public static readonly Dictionary<AssemblyDefinition, UpdateInfo> assemblyUpdateInfo = new Dictionary<AssemblyDefinition, UpdateInfo> ();
/// <summary>
/// Additional search directories for resolving assembly types.
/// </summary>
public static readonly Collection<string> additionalSearchDirectories = new Collection<string>();
// Basic extension methods for loading assemblies, adding elements to collections, and finding types, fields, and methods in Mono.Cecil objects.
#region Base
/// <summary>
/// This extension method loads an assembly from a given location.
/// </summary>
/// <param name="location">The location of the assembly to be loaded.</param>
/// <param name="readWrite">A boolean value to determine if the assembly is read-only or writable.</param>
/// <returns>The AssemblyDefinition object of the loaded assembly if successful</returns>
public static AssemblyDefinition LoadAssembly(this string location, bool readWrite = false)
{
// Create a new instance of the DefaultAssemblyResolver.
var resolver = new DefaultAssemblyResolver();
// Add search directories to the resolver.
foreach (var directory in additionalSearchDirectories)
resolver.AddSearchDirectory(directory);
resolver.AddSearchDirectory(Path.GetDirectoryName(typeof(int).Assembly.Location));
resolver.AddSearchDirectory(Path.Combine(Path.GetDirectoryName(typeof(int).Assembly.Location), "Facades"));
// Read and return the assembly using the provided location and reader parameters.
return AssemblyDefinition.ReadAssembly(location, new ReaderParameters()
{
AssemblyResolver = resolver,
ReadWrite = readWrite,
});
}
/// <summary>
/// This extension method finds a method of a given type in an assembly.
/// </summary>
/// <param name="assembly">The assembly where the type and method are located.</param>
/// <param name="typeSignature">The full or simple name of the type.</param>
/// <param name="methodSignature">The full or simple name of the method.</param>
/// <returns>The MethodDefinition object of the found method. Null if not found.</returns>
public static MethodDefinition FindMethodOfType(this AssemblyDefinition assembly, string typeSignature, string methodSignature)
{
// Find and return the method of the given type in the assembly.
return assembly.FindType(typeSignature)?.FindMethod(methodSignature);
}
/// <summary>
/// This extension method finds a type in an assembly using its full name or simple name.
/// </summary>
/// <param name="assembly">The assembly where the type is located.</param>
/// <param name="typeSignature">The full or simple name of the type.</param>
/// <returns>The TypeDefinition object of the found type. Null if not found.</returns>
public static TypeDefinition FindType(this AssemblyDefinition assembly, string typeSignature)
{
// Return the first type that matches the provided type signature.
return assembly.MainModule.Types.FirstOrDefault(type => type.FullName == typeSignature || type.Name == typeSignature);
}
/// <summary>
/// This extension method finds a type in an assembly using its full name or simple name.
/// </summary>
/// <param name="assembly">The assembly where the type is located.</param>
/// <param name="type">The type to locate.</param>
/// <returns>The TypeDefinition object of the found type. Null if not found.</returns>
public static TypeDefinition FindType(this AssemblyDefinition assembly, Type type)
{
// Return the first type that matches the provided type signature.
return assembly.MainModule.Types.FirstOrDefault(_type => _type.FullName == type.FullName || _type.Name == type.Name);
}
/// <summary>
/// This extension method finds a field in a type.
/// </summary>
/// <param name="type">The type where the field is located.</param>
/// <param name="fieldSignature">The full or simple name of the field.</param>
/// <returns>The FieldDefinition object of the found field. Null if not found.</returns>
public static FieldDefinition FindField(this TypeDefinition type, string fieldSignature)
{
// Return the first field that matches the provided field signature.
return type.Fields.FirstOrDefault(m => m.FullName == fieldSignature || m.Name == fieldSignature);
}
/// <summary>
/// This extension method finds a method in a type.
/// </summary>
/// <param name="type">The type where the method is located.</param>
/// <param name="methodSignature">The full or simple name of the method.</param>
/// <returns>The MethodDefinition object of the found method. Null if not found.</returns>
public static MethodDefinition FindMethod(this TypeDefinition type, string methodSignature)
{
// The function checks each method in the type's Methods collection,
// and returns the first method whose full name or simple name matches the provided method signature.
return type.Methods.FirstOrDefault(m => m.FullName == methodSignature || m.Name == methodSignature);
}
/// <summary>
/// This extension method finds all methods in a type that match a given method signature.
/// </summary>
/// <param name="type">The type where the methods are located.</param>
/// <param name="methodSignature">The full or simple name of the methods.</param>
/// <returns>A collection of MethodDefinition objects for the found methods. Empty collection if none found.</returns>
public static Collection<MethodDefinition> FindMethods(this TypeDefinition type, string methodSignature)
{
var collection = new Collection<MethodDefinition>();
// This function checks each method in the type's Methods collection,
// and adds those methods to the collection whose full name or simple name matches the provided method signature.
foreach (var item in type.Methods.Where(m => m.FullName == methodSignature || m.Name == methodSignature))
collection.Add(item);
return collection;
}
#endregion Base
// Extension method that handles adding types to an assembly.
#region AddType
/// <summary>
/// Adds a type to an assembly. This includes adding the type's fields, properties, and methods.
/// If the source type is nested, it will be added as a nested type within the parent type in the destination assembly.
/// </summary>
/// <param name="assembly">The assembly to which the type will be added.</param>
/// <param name="src">The source type that will be added to the assembly.</param>
/// <param name="avoidSignatureConflicts">Avoid name conflicts by adding a '_' suffix to the copied class name.</param>
public static void AddType(this AssemblyDefinition assembly, TypeDefinition src, bool avoidSignatureConflicts = false)
{
// Check for signature conflict avoidance
var srcName = src.Name;
if (avoidSignatureConflicts) src.Name += "_";
// Create a new TypeDefinition with the same properties as the source type
var dest = new TypeDefinition(src.Namespace, src.Name, src.Attributes);
// If the source type isn't nested, add the new type directly to the assembly's types
// Otherwise, find the declaring type in the assembly and add the new type as a nested type
if (!src.IsNested)
assembly.MainModule.Types.Add(dest);
else
assembly.FindType(src.DeclaringType.FullName).NestedTypes.Add(dest);
// Set the base type of the new type to match the base type of the source type
dest.BaseType = src.BaseType;
// Add the fields, properties, and methods from the source type to the new type
dest.AddFieldsPropertiesAndMethods(src);
// Restore name
if (avoidSignatureConflicts) src.Name = srcName;
}
#endregion AddType
// Extension method that handles the addition of fields, properties, and methods from a source type to a destination type.
// This is a key part of merging two types, ensuring the destination type includes all necessary components from the source type.
#region AddFieldsPropertiesAndMethods
/// <summary>
/// Merges the source type into the destination type by cloning the fields, properties, and methods of the source, updating their types and adding them to the destination.
/// </summary>
/// <param name="dest">The destination type definition where fields, properties, and methods from source will be added.</param>
/// <param name="src">The source type definition whose fields, properties, and methods will be cloned and added to the destination.</param>
public static void AddFieldsPropertiesAndMethods(this TypeDefinition dest, TypeDefinition src)
{
// Add nested types to the module
foreach (var subtype in src.NestedTypes)
dest.Module.Assembly.AddType(subtype);
// Clone attributes from the source and add to the destination
var clonedAttributes = new Collection<CustomAttribute>();
foreach (var attribute in src.CustomAttributes)
{
var clonedAttribute = attribute.Clone();
dest.CustomAttributes.Add(clonedAttribute);
clonedAttributes.Add(clonedAttribute);
}
// Clone interfaces from the source and add to the destination
var clonedInterfaces = new Collection<InterfaceImplementation>();
foreach (var @interface in src.Interfaces)
{
var clonedInterface = @interface.Clone();
dest.Interfaces.Add(clonedInterface);
clonedInterfaces.Add(clonedInterface);
}
// Clone fields from the source and add to the destination
var clonedFields = new Collection<FieldDefinition>();
foreach (var field in src.Fields)
{
var clonedField = field.Clone();
clonedFields.Add(clonedField);
dest.Fields.Add(clonedField);
}
// Clone properties from the source and add to the destination
var clonedProperties = new Collection<PropertyDefinition>();
foreach (var property in src.Properties)
{
var clonedProperty = property.Clone();
clonedProperties.Add(clonedProperty);
dest.Properties.Add(clonedProperty);
}
// Clone methods from the source (don't add to the destination yet)
var clonedMethods = new Collection<MethodDefinition>();
foreach (var method in src.Methods)
{
var clonedMethod = method.Clone();
clonedMethods.Add(clonedMethod);
}
// List for keeping track of methods that need further processing
var updatedMethods = new Collection<MethodDefinition>();
// Process each method
foreach (var clonedMethod in clonedMethods.ToList())
{
// Special handling for constructors
if (clonedMethod.Name is ".ctor" || clonedMethod.Name is ".cctor" || clonedMethod.Name is "Finalize")
{
// Temporarily set the declaring type of the cloned method to the destination type
// This is required to get the correct full name of the method for the FindMethod call
clonedMethod.DeclaringType = dest;
// Find an existing method in the destination type that matches the full name of the cloned method
// Note that the full name of a method includes the name of its declaring type
var destMethod = dest.FindMethod(clonedMethod.FullName);
// Reset the declaring type of the cloned method to null
// This is done because the cloned method hasn't been added to the destination type yet,
// and leaving the declaring type set will cause failures to add the method to the destination type
clonedMethod.DeclaringType = null;
// If destination already contains a constructor/destructor, merge the instructions
if (destMethod != null)
{
var clonedInstructions = clonedMethod.Body.Instructions;
var trimmedClonedInstructions = clonedInstructions.ToList();
// For constructors
if (clonedMethod.Name is ".ctor")
{
// Find the constructor call instruction and remove the instructions before it
// This is done to prevent calling the base class constructor twice when merging
var callIndex = trimmedClonedInstructions.FindIndex(x => x.OpCode == OpCodes.Call);
// Check if callIndex is within valid range
if (callIndex < 0 || callIndex >= trimmedClonedInstructions.Count)
throw new Exception("Invalid Call Instruction Index in cloned method.");
// Remove starting instructions
trimmedClonedInstructions.RemoveRange(0, callIndex + 1);
trimmedClonedInstructions.RemoveAt(trimmedClonedInstructions.Count - 1);
// Insert the trimmed instructions to the existing constructor, just before the last instruction (ret)
int insertIndex = destMethod.Body.Instructions.Count - 1;
foreach (var clonedInstruction in trimmedClonedInstructions)
{
destMethod.Body.Instructions.Insert(insertIndex, clonedInstruction);
insertIndex++;
}
}
// For static constructors
else if (clonedMethod.Name is ".cctor")
{
// Remove the last instruction (ret)
trimmedClonedInstructions.RemoveAt(trimmedClonedInstructions.Count - 1);
// Insert the trimmed instructions to the existing static constructor, just before the last instruction (ret)
int insertIndex = destMethod.Body.Instructions.Count - 1;
foreach (var clonedInstruction in trimmedClonedInstructions)
{
destMethod.Body.Instructions.Insert(insertIndex, clonedInstruction);
insertIndex++;
}
}
// For destructors
else if (clonedMethod.Name is "Finalize")
{
// Find the leave.s instruction and remove the instructions after it.
// This is done to prevent calling the base class destructor twice when merging.
var trimIndex = trimmedClonedInstructions.FindIndex(x => x.OpCode == OpCodes.Leave_S);
// Check if trimIndex is within valid range
if (trimIndex < 0 || trimIndex >= trimmedClonedInstructions.Count)
throw new Exception("Invalid trim index in cloned method.");
// Remove instructions after leave.s (inclusive)
trimmedClonedInstructions.RemoveRange(trimIndex, trimmedClonedInstructions.Count - trimIndex);
// Insert the trimmed instructions to the existing destructor, at the beginning
int insertionIndex = 0;
foreach (var clonedInstruction in trimmedClonedInstructions)
{
destMethod.Body.Instructions.Insert(insertionIndex, clonedInstruction);
insertionIndex++;
}
}
// Remove the cloned constructor or destructor from the list of methods to add to the destination type
_ = clonedMethods.Remove(clonedMethod);
// Add the method to the list of methods to update since it has been modified
updatedMethods.Add(destMethod);
}
else
{
// Add the cloned constructor to the destination type
updatedMethods.Add(clonedMethod);
}
}
else
{
// For non-constructor/non-destructor methods
updatedMethods.Add(clonedMethod);
}
}
// Add updated methods to the destination type
foreach (var method in clonedMethods) dest.Methods.Add(method);
// Add updated attributes, interfaces, fields, properties and methods to the update info
if (!assemblyUpdateInfo.TryGetValue(dest.Module.Assembly, out var updateInfo))
updateInfo = assemblyUpdateInfo[dest.Module.Assembly] = new UpdateInfo();
foreach (var attribute in clonedAttributes) updateInfo.updatedAttributes.Add(attribute);
foreach (var @interface in clonedInterfaces) updateInfo.updatedInterfaces.Add(@interface);
foreach (var field in clonedFields) updateInfo.updatedFields.Add(field);
foreach (var property in clonedProperties) updateInfo.updatedProperties.Add(property);
foreach (var method in updatedMethods) updateInfo.updatedMethods.Add(method);
// Add source and destination types to the update info
updateInfo.srcTypes.Add(src);
updateInfo.destTypes.Add(dest);
}
#endregion AddFieldsPropertiesAndMethods
// Extension methods that handle the updating of fields, properties, and methods within a destination type after they have been cloned from a source type.
// These methods ensure that the newly added components in the destination type correctly reference the destination type, rather than the original source type.
#region UpdateFieldsPropertiesAndMethods
/// <summary>
/// Updates the types of attributes, interfaces, fields, properties, and methods within a given assembly.
/// This includes updating the types in interfaces, fields, properties, and methods. It also updates the getter and setter methods for properties,
/// updates the instruction types for methods, imports references for attributes, interfaces, fields, properties, and methods,
/// imports base types of each destination type, and swaps any duplicate methods in the destination types.
/// </summary>
/// <param name="assembly">The assembly to be updated. This assembly's types are matched against the source types and replaced with the corresponding destination types, based on previously registered update information.</param>
/// <param name="avoidSignatureConflicts">Avoid signature conflicts by changing original method parameters to be base object types for duplicate methods</param>
public static void UpdateFieldsPropertiesAndMethods(this AssemblyDefinition assembly, bool avoidSignatureConflicts = false)
{
// Check if update information exists for the assembly
if (assemblyUpdateInfo.TryGetValue(assembly, out var updateInfo))
{
// Update types in interfaces, fields, properties, and methods
for (int i = 0; i < updateInfo.destTypes.Count; ++i)
foreach (var @interface in updateInfo.updatedInterfaces) @interface.UpdateTypes(updateInfo.srcTypes[i], updateInfo.destTypes[i]);
for (int i = 0; i < updateInfo.destTypes.Count; ++i)
foreach (var field in updateInfo.updatedFields) field.UpdateTypes(updateInfo.srcTypes[i], updateInfo.destTypes[i]);
for (int i = 0; i < updateInfo.destTypes.Count; ++i)
foreach (var property in updateInfo.updatedProperties) property.UpdateTypes(updateInfo.srcTypes[i], updateInfo.destTypes[i]);
for (int i = 0; i < updateInfo.destTypes.Count; ++i)
foreach (var method in updateInfo.updatedMethods) method.UpdateTypes(updateInfo.srcTypes[i], updateInfo.destTypes[i]);
// Update getter and setter methods for properties
for (int i = 0; i < updateInfo.destTypes.Count; ++i)
foreach (var property in updateInfo.updatedProperties) property.UpdateGettersAndSetters(updateInfo.srcTypes[i], updateInfo.destTypes[i]);
// Update instruction types for methods
for (int i = 0; i < updateInfo.destTypes.Count; ++i)
foreach (var method in updateInfo.updatedMethods) method.UpdateInstructionTypes(updateInfo.srcTypes[i], updateInfo.destTypes[i]);
// Check for optimization opportunities for methods
foreach (var method in updateInfo.updatedMethods) method.OptimizeInstructions();
// Import references for attributes, interfaces, fields, properties, and methods
foreach (var attribute in updateInfo.updatedAttributes) attribute.ImportReferences(assembly.MainModule);
foreach (var @interface in updateInfo.updatedInterfaces) @interface.ImportReferences(assembly.MainModule);
foreach (var field in updateInfo.updatedFields) field.ImportReferences(assembly.MainModule);
foreach (var property in updateInfo.updatedProperties) property.ImportReferences(assembly.MainModule);
foreach (var method in updateInfo.updatedMethods) method.ImportReferences(assembly.MainModule);
// Import base type of each dest type
foreach (var type in updateInfo.destTypes) type.BaseType = assembly.MainModule.ImportReference(type.BaseType);
// Swap any duplicate methods in the destination types
foreach (var type in updateInfo.destTypes) type.SwapDuplicateMethods(avoidSignatureConflicts);
// Remove the assembly from the update information collection
_ = assemblyUpdateInfo.Remove(assembly);
}
}
#endregion UpdateFieldsPropertiesAndMethods
// Extension methods for cloning various Mono.Cecil objects.
#region Clone
/// <summary>
/// Clones a CustomAttribute.
/// </summary>
/// <param name="attribute">The attribute to be cloned.</param>
/// <returns>A clone of the original attribute.</returns>
public static CustomAttribute Clone(this CustomAttribute attribute)
{
// Create a new CustomAttribute with the constructor of the original attribute.
var clonedAttribute = new CustomAttribute(attribute.Constructor);
// Add all constructor arguments from the original attribute to the cloned attribute.
foreach (var argument in attribute.ConstructorArguments) clonedAttribute.ConstructorArguments.Add(argument);
// Add all properties from the original attribute to the cloned attribute.
foreach (var property in attribute.Properties) clonedAttribute.Properties.Add(property);
// Add all fields from the original attribute to the cloned attribute.
foreach (var field in attribute.Fields) clonedAttribute.Fields.Add(field);
// Return the cloned attribute.
return clonedAttribute;
}
/// <summary>
/// Clones a InterfaceImplementation.
/// </summary>
/// <param name="interface">The interface to be cloned.</param>
/// <returns>A clone of the original interface.</returns>
public static InterfaceImplementation Clone(this InterfaceImplementation @interface)
{
// Create a new InterfaceImplementation with the type the original interface.
var clonedInterface = new InterfaceImplementation(@interface.InterfaceType);
// Copy all custom attributes from the original interface to the cloned interface.
foreach (var attribute in @interface.CustomAttributes) clonedInterface.CustomAttributes.Add(attribute.Clone());
// Return the cloned interface.
return clonedInterface;
}
/// <summary>
/// Clones a FieldDefinition.
/// </summary>
/// <param name="field">The field to be cloned.</param>
/// <returns>A clone of the original field.</returns>
public static FieldDefinition Clone(this FieldDefinition field)
{
// Create a new FieldDefinition with the same properties as the original field.
var clonedField = new FieldDefinition(field.Name, field.Attributes, field.FieldType);
// Copy all custom attributes from the original field to the cloned field.
foreach (var attribute in field.CustomAttributes) clonedField.CustomAttributes.Add(attribute.Clone());
// Copy the MarshalInfo if it exists.
clonedField.MarshalInfo = field.MarshalInfo != null ? new MarshalInfo(field.MarshalInfo.NativeType) : null;
// Copy the initial value of the field.
clonedField.InitialValue = field.InitialValue;
// Return the cloned field.
return clonedField;
}
/// <summary>
/// Clones a PropertyDefinition.
/// </summary>
/// <param name="property">The property to be cloned.</param>
/// <returns>A clone of the original property.</returns>
public static PropertyDefinition Clone(this PropertyDefinition property)
{
// Create a new PropertyDefinition with the same properties as the original property.
var clonedProperty = new PropertyDefinition(property.Name, property.Attributes, property.PropertyType);
// Copy all custom attributes from the original property to the cloned property.
foreach (var attribute in property.CustomAttributes) clonedProperty.CustomAttributes.Add(attribute.Clone());
// Clone the get and set methods if they exist.
clonedProperty.GetMethod = property.GetMethod?.Clone();
clonedProperty.SetMethod = property.SetMethod?.Clone();
// Return the cloned property.
return clonedProperty;
}
/// <summary>
/// Clones a ParameterDefinition.
/// </summary>
/// <param name="parameter">The parameter to be cloned.</param>
/// <returns>A clone of the original parameter.</returns>
public static ParameterDefinition Clone(this ParameterDefinition parameter)
{
// Create a new ParameterDefinition with the same properties as the original parameter.
var clonedParameter = new ParameterDefinition(parameter.Name, parameter.Attributes, parameter.ParameterType);
// Copy all custom attributes from the original parameter to the cloned parameter.
foreach (var attribute in parameter.CustomAttributes) clonedParameter.CustomAttributes.Add(attribute.Clone());
// Return the cloned parameter.
return clonedParameter;
}
/// <summary>
/// Clones a VariableDefinition.
/// </summary>
/// <param name="variable">The variable to be cloned.</param>
/// <returns>A clone of the original variable.</returns>
public static VariableDefinition Clone(this VariableDefinition variable)
{
// Create and return a new VariableDefinition with the same type as the original variable.
return new VariableDefinition(variable.VariableType);
}
/// <summary>
/// Clones an Instruction.
/// </summary>
/// <param name="instruction">The instruction to be cloned.</param>
/// <returns>A clone of the original instruction.</returns>
public static Instruction Clone(this Instruction instruction)
{
// Create a new Instruction with a default opcode.
var clonedInstruction = Instruction.Create(OpCodes.Nop);
// Copy the opcode and operand from the original instruction to the cloned instruction.
clonedInstruction.OpCode = instruction.OpCode;
clonedInstruction.Operand = instruction.Operand;
// Return the cloned instruction.
return clonedInstruction;
}
/// <summary>
/// Clones a MethodDefinition.
/// </summary>
/// <param name="method">The method to be cloned.</param>
/// <returns>A clone of the original method.</returns>
public static MethodDefinition Clone(this MethodDefinition method)
{
// Create a new MethodDefinition with the same properties as the original method.
var clonedMethod = new MethodDefinition(method.Name, method.Attributes, method.ReturnType)
{
ImplAttributes = method.ImplAttributes,
SemanticsAttributes = method.SemanticsAttributes
};
// Add all overides from the original method to the cloned method (references).
foreach (var @override in method.Overrides) clonedMethod.Overrides.Add(@override);
// Copy all custom attributes from the original method to the cloned method.
foreach (var attribute in method.CustomAttributes) clonedMethod.CustomAttributes.Add(attribute.Clone());
// Clone all parameters and add them to the cloned method.
foreach (var parameter in method.Parameters) clonedMethod.Parameters.Add(parameter.Clone());
// Create a new method body for the cloned method.
clonedMethod.Body = new MethodBody(clonedMethod);
// If the original method has a body, copy the relevant properties to the cloned method's body.
if (method.HasBody)
{
clonedMethod.Body.MaxStackSize = method.Body.MaxStackSize;
clonedMethod.Body.InitLocals = method.Body.InitLocals;
// Clone all variables and add them to the cloned method's body.
foreach (var variable in method.Body.Variables) clonedMethod.Body.Variables.Add(variable.Clone());
// Instruction mapping from old to new instructions used to update branch targets which is necessary after cloning
var instructionMapping = new Dictionary<Instruction, Instruction>();
// Clone all the instructions and create the mapping.
foreach (var instruction in method.Body.Instructions)
{
var clonedInstruction = instruction.Clone();
instructionMapping[instruction] = clonedInstruction;
clonedMethod.Body.Instructions.Add(clonedInstruction);
}
// Now fix up the branch targets.
foreach (var instruction in clonedMethod.Body.Instructions)
{
// If the instruction is a branch instruction, fix up its target.
if (instruction.OpCode.FlowControl == FlowControl.Branch ||
instruction.OpCode.FlowControl == FlowControl.Cond_Branch)
{
instruction.Operand = instructionMapping[(Instruction)instruction.Operand];
}
// If the instruction is a switch instruction, fix up its targets.
if (instruction.OpCode == OpCodes.Switch)
{
var oldTargets = (Instruction[])instruction.Operand;
var newTargets = new Instruction[oldTargets.Length];
for (int i = 0; i < oldTargets.Length; ++i)
{
newTargets[i] = instructionMapping[oldTargets[i]];
}
instruction.Operand = newTargets;
}
}
}
// Return the cloned method.
return clonedMethod;
}
public static MethodDefinition CloneToModuleAsStatic(this MethodDefinition method, TypeReference originalType, ModuleDefinition module)
{
// Create a new MethodDefinition with the same properties as the original method.
var clonedMethod = new MethodDefinition(method.Name, MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.HideBySig, module.ImportReference(method.ReturnType))
{
ImplAttributes = method.ImplAttributes,
SemanticsAttributes = method.SemanticsAttributes
};
// Copy all custom attributes from the original method to the cloned method.
foreach (var attribute in method.CustomAttributes)
clonedMethod.CustomAttributes.Add(attribute.CloneToModule(module));
if (!method.Attributes.HasFlag(MethodAttributes.Static))
clonedMethod.Parameters.Add(new ParameterDefinition("self", ParameterAttributes.None, originalType));
// Clone all parameters and add them to the cloned method.
foreach (var parameter in method.Parameters)
clonedMethod.Parameters.Add(parameter.CloneToModule(module));
// Create a new method body for the cloned method.
clonedMethod.Body = new MethodBody(clonedMethod);
// If the original method has a body, copy the relevant properties to the cloned method's body.
if (method.HasBody)
{
clonedMethod.Body.MaxStackSize = method.Body.MaxStackSize;
clonedMethod.Body.InitLocals = method.Body.InitLocals;
// Clone all variables and add them to the cloned method's body.
foreach (var variable in method.Body.Variables) clonedMethod.Body.Variables.Add(variable.CloneToModule(module));
// Instruction mapping from old to new instructions used to update branch targets which is necessary after cloning
var instructionMapping = new Dictionary<Instruction, Instruction>();
// Clone all the instructions and create the mapping.
foreach (var instruction in method.Body.Instructions)
{
var clonedInstruction = instruction.Clone();
clonedInstruction.Resolve(clonedMethod, method, module);
instructionMapping[instruction] = clonedInstruction;
clonedMethod.Body.Instructions.Add(clonedInstruction);
}
// Now fix up the branch targets.
foreach (var instruction in clonedMethod.Body.Instructions)
{
// If the instruction is a branch instruction, fix up its target.
if (instruction.OpCode.FlowControl == FlowControl.Branch ||
instruction.OpCode.FlowControl == FlowControl.Cond_Branch)
{
instruction.Operand = instructionMapping[(Instruction)instruction.Operand];
}
// If the instruction is a switch instruction, fix up its targets.
if (instruction.OpCode == OpCodes.Switch)
{
var oldTargets = (Instruction[])instruction.Operand;
var newTargets = new Instruction[oldTargets.Length];
for (int i = 0; i < oldTargets.Length; ++i)
{
newTargets[i] = instructionMapping[oldTargets[i]];
}
instruction.Operand = newTargets;
}
}
// copy the exception handler blocks
foreach (ExceptionHandler eh in method.Body.ExceptionHandlers)
{
ExceptionHandler neh = new ExceptionHandler(eh.HandlerType);
neh.CatchType = module.ImportReference(eh.CatchType);
// we need to setup neh.Start and End; these are instructions; we need to locate it in the source by index
if (eh.TryStart != null)
{
int idx = method.Body.Instructions.IndexOf(eh.TryStart);
neh.TryStart = clonedMethod.Body.Instructions[idx];
}
if (eh.TryEnd != null)
{
int idx = method.Body.Instructions.IndexOf(eh.TryEnd);
neh.TryEnd = clonedMethod.Body.Instructions[idx];
}
clonedMethod.Body.ExceptionHandlers.Add(neh);
}
}
// Return the cloned method.
return clonedMethod;
}
public static MethodDefinition CloneToModuleAsStatic(this MethodDefinition method, MethodBody body, TypeReference originalType, ModuleDefinition module)
{
// Create a new MethodDefinition with the same properties as the original method.
var clonedMethod = new MethodDefinition(method.Name, MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.HideBySig, module.ImportReference(method.ReturnType))
{
ImplAttributes = method.ImplAttributes,
SemanticsAttributes = method.SemanticsAttributes
};
// Copy all custom attributes from the original method to the cloned method.
foreach (var attribute in method.CustomAttributes)
clonedMethod.CustomAttributes.Add(attribute.CloneToModule(module));
if (!method.Attributes.HasFlag(MethodAttributes.Static))
clonedMethod.Parameters.Add(new ParameterDefinition("self", ParameterAttributes.None, originalType));
// Clone all parameters and add them to the cloned method.
foreach (var parameter in method.Parameters)
clonedMethod.Parameters.Add(parameter.CloneToModule(module));
// Create a new method body for the cloned method.
clonedMethod.Body = new MethodBody(clonedMethod);
// If the original method has a body, copy the relevant properties to the cloned method's body.
clonedMethod.Body.MaxStackSize = body.MaxStackSize;
clonedMethod.Body.InitLocals = body.InitLocals;
// Clone all variables and add them to the cloned method's body.
foreach (var variable in body.Variables) clonedMethod.Body.Variables.Add(variable.CloneToModule(module));
// Instruction mapping from old to new instructions used to update branch targets which is necessary after cloning
var instructionMapping = new Dictionary<Instruction, Instruction>();
// Clone all the instructions and create the mapping.
foreach (var instruction in body.Instructions)
{
var clonedInstruction = instruction.Clone();
clonedInstruction.Resolve(clonedMethod, method, module);
instructionMapping[instruction] = clonedInstruction;
clonedMethod.Body.Instructions.Add(clonedInstruction);
}
// Now fix up the branch targets.
foreach (var instruction in clonedMethod.Body.Instructions)
{
// If the instruction is a branch instruction, fix up its target.
if (instruction.OpCode.FlowControl == FlowControl.Branch ||
instruction.OpCode.FlowControl == FlowControl.Cond_Branch)
{
instruction.Operand = instructionMapping[(Instruction)instruction.Operand];
}
// If the instruction is a switch instruction, fix up its targets.
if (instruction.OpCode == OpCodes.Switch)
{
var oldTargets = (Instruction[])instruction.Operand;
var newTargets = new Instruction[oldTargets.Length];
for (int i = 0; i < oldTargets.Length; ++i)
{
newTargets[i] = instructionMapping[oldTargets[i]];
}
instruction.Operand = newTargets;
}
}
// copy the exception handler blocks
foreach (ExceptionHandler eh in body.ExceptionHandlers)
{
ExceptionHandler neh = new ExceptionHandler(eh.HandlerType);
neh.CatchType = module.ImportReference(eh.CatchType);
// we need to setup neh.Start and End; these are instructions; we need to locate it in the source by index
if (eh.TryStart != null)
{
int idx = body.Instructions.IndexOf(eh.TryStart);
neh.TryStart = clonedMethod.Body.Instructions[idx];
}
if (eh.TryEnd != null)
{
int idx = body.Instructions.IndexOf(eh.TryEnd);
neh.TryEnd = clonedMethod.Body.Instructions[idx];
}
clonedMethod.Body.ExceptionHandlers.Add(neh);
}
// Return the cloned method.
return clonedMethod;
}
public static VariableDefinition CloneToModule(this VariableDefinition variable, ModuleDefinition module)
{
// Create and return a new VariableDefinition with the same type as the original variable.
return new VariableDefinition(module.ImportReference(variable.VariableType));
}
public static ParameterDefinition CloneToModule(this ParameterDefinition parameter, ModuleDefinition module)
{
// Create a new ParameterDefinition with the same properties as the original parameter.
var clonedParameter = new ParameterDefinition(parameter.Name, parameter.Attributes, module.ImportReference(parameter.ParameterType));
// Copy all custom attributes from the original parameter to the cloned parameter.
foreach (var attribute in parameter.CustomAttributes)
{
module.ImportReference(attribute.GetType());
clonedParameter.CustomAttributes.Add(attribute.CloneToModule(module));
}
// Return the cloned parameter.
return clonedParameter;
}
public static CustomAttribute CloneToModule(this CustomAttribute attribute, ModuleDefinition module)
{
// Create a new CustomAttribute with the constructor of the original attribute.
var clonedAttribute = new CustomAttribute(module.ImportReference(attribute.Constructor));
// Add all constructor arguments from the original attribute to the cloned attribute.
foreach (var argument in attribute.ConstructorArguments)
{
module.ImportReference(argument.Type);
if (argument.Value != null)
{
module.ImportReference(argument.Value?.GetType());
}
clonedAttribute.ConstructorArguments.Add(argument);
}
// Add all properties from the original attribute to the cloned attribute.
foreach (var property in attribute.Properties)
{
module.ImportReference(property.Argument.Type);
if (property.Argument.Value != null)
{
module.ImportReference(property.Argument.Value?.GetType());
}
clonedAttribute.Properties.Add(property);
}
// Add all fields from the original attribute to the cloned attribute.
foreach (var field in attribute.Fields)
{
module.ImportReference(field.Argument.Type);
if (field.Argument.Value != null)
{
module.ImportReference(field.Argument.Value?.GetType());
}
clonedAttribute.Fields.Add(field);
}
// Return the cloned attribute.
return clonedAttribute;
}
private static void Resolve(this Instruction instruction, MethodDefinition method_new, MethodDefinition method_original, ModuleDefinition module)
{
var operand = instruction.Operand;
if (operand == null)
return;
if (operand is MethodDefinition opmtddef)
{
if (opmtddef.FullName.Equals(method_original.FullName))
instruction.Operand = method_new;
else
instruction.Operand = module.ImportReference(opmtddef);
return;
}
if (operand is MethodReference opmtdref)
{
instruction.Operand = module.ImportReference(opmtdref);
return;
}
if (operand is FieldReference opfldref)
{
instruction.Operand = module.ImportReference(opfldref);
return;
}
if (operand is TypeReference optyperef)
{
instruction.Operand = module.ImportReference(optyperef);
return;
}
}
#endregion Clone
// Extension methods for replacing references to a source type with references to a destination type within Mono.Cecil objects.
// This is used to ensure that copied fields, properties, and methods reference copied types instead of the originals.
#region UpdateTypes
/// <summary>
/// Updates the InterfaceType of the given InterfaceImplementation, if it matches the source type, to the destination type.
/// </summary>
/// <param name="interface">InterfaceImplementation that may have its InterfaceType updated.</param>
/// <param name="src">The source type which could be replaced.</param>
/// <param name="dest">The destination type which could replace the source type.</param>
public static void UpdateTypes(this InterfaceImplementation @interface, TypeDefinition src, TypeDefinition dest)
{
// If the interface's type matches the source type, update it to the destination type
if (@interface.InterfaceType == src) @interface.InterfaceType = dest;
}
/// <summary>
/// Updates the FieldType of the given FieldDefinition, if it matches the source type, to the destination type.
/// </summary>
/// <param name="field">FieldDefinition that may have its FieldType updated.</param>
/// <param name="src">The source type which could be replaced.</param>
/// <param name="dest">The destination type which could replace the source type.</param>
public static void UpdateTypes(this FieldDefinition field, TypeDefinition src, TypeDefinition dest)
{
// If the field's type matches the source type, update it to the destination type
if (field.FieldType == src) field.FieldType = dest;
}
/// <summary>
/// Updates the FieldReference and DeclaringType of the given FieldReference, if they match the source type, to the destination type.
/// If a matching field definition is found in the destination type, a reference to it is returned.
/// Otherwise, the original field reference is returned.
/// </summary>
/// <param name="field">FieldReference that may have its FieldType, and DeclaringType updated.</param>
/// <param name="src">The source type which could be replaced.</param>
/// <param name="dest">The destination type which could replace the source type.</param>
/// <returns>A FieldReference with updated types, or the original FieldReference if no updates were made.</returns>
public static FieldReference UpdateTypes(this FieldReference field, TypeDefinition src, TypeDefinition dest)
{
// Check if the field's FieldType or DeclaringType matches the source type, and if so, replace them with the destination type
if (field.FieldType == src) field.FieldType = dest;
if (field.DeclaringType == src) field.DeclaringType = dest;
// Attempt to find a field in the destination type that matches the field's full name
// If a matching definition is found, return a reference to it otherwise return original reference
return dest.FindField(field.FullName) ?? field;
}
/// <summary>
/// Updates the PropertyType of the given PropertyDefinition, if it matches the source type, to the destination type.
/// </summary>
/// <param name="property">PropertyDefinition that may have its PropertyType updated.</param>
/// <param name="src">The source type which could be replaced.</param>
/// <param name="dest">The destination type which could replace the source type.</param>
public static void UpdateTypes(this PropertyDefinition property, TypeDefinition src, TypeDefinition dest)
{
// If the property's type matches the source type, update it to the destination type
if (property.PropertyType == src) property.PropertyType = dest;
}
/// <summary>
/// Updates the ParameterType of the given ParameterDefinition, if it matches the source type, to the destination type.
/// </summary>
/// <param name="parameter">ParameterDefinition that may have its ParameterType updated.</param>
/// <param name="src">The source type which could be replaced.</param>
/// <param name="dest">The destination type which could replace the source type.</param>
public static void UpdateTypes(this ParameterDefinition parameter, TypeDefinition src, TypeDefinition dest)
{
// If the parameter's type matches the source type, update it to the destination type
if (parameter.ParameterType == src) parameter.ParameterType = dest;
}
/// <summary>
/// Updates the VariableType of the given VariableDefinition, if it matches the source type, to the destination type.
/// </summary>
/// <param name="variable">VariableDefinition that may have its VariableType updated.</param>
/// <param name="src">The source type which could be replaced.</param>
/// <param name="dest">The destination type which could replace the source type.</param>
public static void UpdateTypes(this VariableDefinition variable, TypeDefinition src, TypeDefinition dest)
{
// If the variable's type matches the source type, update it to the destination type
if (variable.VariableType == src) variable.VariableType = dest;
}
/// <summary>
/// Updates the ReturnType of the given MethodDefinition, if it matches the source type, to the destination type.
/// Also updates ParameterTypes and VariableTypes of the MethodDefinition using the same rule.
/// </summary>
/// <param name="method">MethodDefinition that may have its ReturnType, ParameterTypes, and VariableTypes updated.</param>
/// <param name="src">The source type which could be replaced.</param>
/// <param name="dest">The destination type which could replace the source type.</param>
public static void UpdateTypes(this MethodDefinition method, TypeDefinition src, TypeDefinition dest)
{
// Update method overrides if they match the source type
for (int i = 0; i < method.Overrides.Count; i++) method.Overrides[i] = method.Overrides[i].UpdateTypes(src, dest);
// If the method's return type matches the source type, update it to the destination type
if (method.ReturnType == src) method.ReturnType = dest;
// Update method parameters and variables if they match the source type
foreach (var parameter in method.Parameters) parameter.UpdateTypes(src, dest);
if (method.HasBody) foreach (var variable in method.Body.Variables) variable.UpdateTypes(src, dest);
}
/// <summary>
/// Updates the ReturnType and DeclaringType of the given MethodReference, if they match the source type, to the destination type.
/// Also updates the ParameterTypes of the MethodReference using the same rule.
/// If a matching method definition is found in the destination type, a reference to it is returned.
/// Otherwise, the original method reference is returned.
/// </summary>
/// <param name="method">MethodReference that may have its ReturnType, DeclaringType and ParameterTypes updated.</param>
/// <param name="src">The source type which could be replaced.</param>
/// <param name="dest">The destination type which could replace the source type.</param>
/// <returns>A MethodReference with updated types, or the original MethodReference if no updates were made.</returns>
public static MethodReference UpdateTypes(this MethodReference method, TypeDefinition src, TypeDefinition dest)
{
// Update method parameters to destination type
foreach (var parameter in method.Parameters) parameter.UpdateTypes(src, dest);
// Check if the method's ReturnType or DeclaringType matches the source type, and if so, replace them with the destination type
if (method.ReturnType == src) method.ReturnType = dest;
if (method.DeclaringType == src) method.DeclaringType = dest;
// Attempt to find a method in the destination type that matches the method's full name
// If a matching definition is found, return a reference to it otherwise return original reference
return dest.FindMethod(method.FullName) ?? method;
}
/// <summary>
/// Updates the ReturnType and Parameters of the CallSite to the destination type, if they match the source type, to the destination type.
/// </summary>
/// <param name="callSite">CallSite that needs its return type and parameters updated.</param>
/// <param name="src">The original type which is being replaced.</param>
/// <param name="dest">The new type which is replacing the original type.</param>
public static void UpdateTypes(this CallSite callSite, TypeDefinition src, TypeDefinition dest)
{
// Update callsite parameters to destination type
foreach (var parameter in callSite.Parameters) parameter.UpdateTypes(src, dest);
// If the current return type is the source type, update it to destination type
if (callSite.ReturnType == src) callSite.ReturnType = dest;
}
#endregion UpdateTypes
// Extension methods for replacing references to a source type with references to a destination type within Mono.Cecil.Instruction objects.
// This is crucial for ensuring that the instructions within methods correctly reference the fields, properties, and methods of the destination type after cloning from the source type.
#region UpdateInstructionTypes
/// <summary>
/// Updates the Operand of an instruction when merging classes.
/// The update strategy depends on the type of the operand.
/// If the operand is a ParameterDefinition, VariableDefinition, FieldReference, MethodReference, CallSite, or TypeReference, it's updated accordingly.
/// </summary>
/// <param name="instruction">Instruction that needs its operand updated.</param>
/// <param name="src">The original type which is being replaced.</param>
/// <param name="dest">The new type which is replacing the original type.</param>
public static void UpdateInstructionTypes(this Instruction instruction, TypeDefinition src, TypeDefinition dest)
{
// Check operand type and update accordingly
if (instruction.Operand is ParameterDefinition parameter)
parameter.UpdateTypes(src, dest); // Update types in ParameterDefinition
else if (instruction.Operand is VariableDefinition variable)
variable.UpdateTypes(src, dest); // Update types in VariableDefinition
else if (instruction.Operand is TypeReference type && type == src)
instruction.Operand = dest; // Update type in TypeReference
else if (instruction.Operand is FieldReference field)
instruction.Operand = field.UpdateTypes(src, dest); // Update types in FieldReference
else if (instruction.Operand is MethodReference method)
instruction.Operand = method.UpdateTypes(src, dest); // Update types in MethodReference
else if (instruction.Operand is CallSite callSite)
callSite.UpdateTypes(src, dest); // Update types in CallSite
}
/// <summary>
/// Updates all instructions in the method's body.
/// </summary>
/// <param name="method">Method whose instructions are to be updated.</param>
/// <param name="src">The original type which is being replaced.</param>
/// <param name="dest">The new type which is replacing the original type.</param>
public static void UpdateInstructionTypes(this MethodDefinition method, TypeDefinition src, TypeDefinition dest)
{
// Update instructions in the method body to the destination type
if (method.HasBody) foreach (var instruction in method.Body.Instructions) UpdateInstructionTypes(instruction, src, dest);
}
#endregion UpdateInstructionTypes
// Extension methods for replacing references to a source type with references to a destination type within Mono.Cecil.Property getter and setter methods.
// This ensures that the properties of the destination type reference copied getters and setters instead of the originals.
#region UpdateGettersAndSetters
/// <summary>
/// Updates the getter and setter methods of a property to reference the destination type when merging classes.
/// This method does the following:
/// - Clones the existing getter/setter methods, so that any modifications do not affect the original methods
/// - Calls UpdateTypes to update all type references within the methods' bodies from src to dest
/// - Updates the declaring type of the methods to be dest
/// - Finds the equivalent methods in dest (if they exist), and updates the property's getter/setter methods to reference them
/// This ensures that the property correctly interacts with the destination type after merging.
/// </summary>
/// <param name="property">PropertyDefinition whose getter and setter need to be updated.</param>
/// <param name="src">The original type which is being replaced.</param>
/// <param name="dest">The new type which is replacing the original type.</param>
public static void UpdateGettersAndSetters(this PropertyDefinition property, TypeDefinition src, TypeDefinition dest)
{
// If the declaring type of the property is the destination type
if (property.DeclaringType == dest)
{
// If the property has a getter, clone and update it
if (property.GetMethod != null)
{
// Clone the getter
var clonedGetter = property.GetMethod.Clone();
// Update all type references within the getter from src to dest
clonedGetter.UpdateTypes(src, dest);
// Update the declaring type of the getter to be dest
clonedGetter.DeclaringType = dest;
// If an equivalent method exists in dest, update the property's getter to reference it
if (dest.FindMethod(clonedGetter.FullName) is MethodDefinition getMethod)
property.GetMethod = getMethod;
}
// If the property has a setter, clone and update it
if (property.SetMethod != null)
{
// Clone the setter
var clonedSetter = property.SetMethod.Clone();
// Update all type references within the setter from src to dest
clonedSetter.UpdateTypes(src, dest);
// Update the declaring type of the setter to be dest
clonedSetter.DeclaringType = dest;
// If an equivalent method exists in dest, update the property's setter to reference it
if (dest.FindMethod(clonedSetter.FullName) is MethodDefinition setMethod)
property.SetMethod = setMethod;
}
}
}
#endregion UpdateGettersAndSetters
// Extension methods to import references from one module to another.
// This is important when merging assemblies classes as it allows the destination to access types that may not have been referenced prior.
#region ImportReferences
/// <summary>
/// Imports the constructor reference for a given attribute into a module.
/// </summary>
/// <param name="attribute">The custom attribute whose constructor reference needs to be imported.</param>
/// <param name="module">The module type into whose module the reference should be imported.</param>
public static void ImportReferences(this CustomAttribute attribute, ModuleDefinition module)
{
// Import the constructor reference into the module
attribute.Constructor = module.ImportReference(attribute.Constructor);
}
/// <summary>
/// Imports the interface type and custom attributes references of an interface into a module.
/// </summary>
/// <param name="interface">The interface whose references need to be imported.</param>
/// <param name="module">The module type into whose module the references should be imported.</param>
public static void ImportReferences(this InterfaceImplementation @interface, ModuleDefinition module)
{
// Import the custom attributes references into the module
foreach (var attribute in @interface.CustomAttributes) attribute.ImportReferences(module);
// Import the interface type reference into the module
@interface.InterfaceType = module.ImportReference(@interface.InterfaceType);
}
/// <summary>
/// Imports the field type and custom attributes references of a field into a module.
/// </summary>
/// <param name="field">The field whose references need to be imported.</param>
/// <param name="module">The module type into whose module the references should be imported.</param>
public static void ImportReferences(this FieldDefinition field, ModuleDefinition module)
{
// Import the custom attributes references into the module
foreach (var attribute in field.CustomAttributes) attribute.ImportReferences(module);
// Import the field type reference into the module
field.FieldType = module.ImportReference(field.FieldType);
// Import the declaring type definition into the module
field.DeclaringType = module.ImportReference(field.DeclaringType).Resolve();
}
/// <summary>
/// Imports the property type and custom attributes references of a property into a module.
/// </summary>
/// <param name="property">The property whose references need to be imported.</param>
/// <param name="module">The module type into whose module the references should be imported.</param>
public static void ImportReferences(this PropertyDefinition property, ModuleDefinition module)
{
// Import the custom attributes references into the module
foreach (var attribute in property.CustomAttributes) attribute.ImportReferences(module);
// Import the property type reference into the module
property.PropertyType = module.ImportReference(property.PropertyType);
// Import the declaring type definition into the module
property.DeclaringType = module.ImportReference(property.DeclaringType).Resolve();
}
/// <summary>
/// Imports the parameter type and custom attributes references of a parameter into a module.
/// </summary>
/// <param name="parameter">The parameter whose references need to be imported.</param>
/// <param name="module">The module type into whose module the references should be imported.</param>
public static void ImportReferences(this ParameterDefinition parameter, ModuleDefinition module)
{
// Import the custom attributes references into the module
foreach (var attribute in parameter.CustomAttributes) attribute.ImportReferences(module);
// Import the parameter type reference into the module
parameter.ParameterType = module.ImportReference(parameter.ParameterType);
}
/// <summary>
/// Imports the variable type references of a variable into a module.
/// </summary>
/// <param name="variable">The variable whose type references need to be imported.</param>
/// <param name="module">The module type into whose module the references should be imported.</param>
public static void ImportReferences(this VariableDefinition variable, ModuleDefinition module)
{
// Import the variable type reference into the module
variable.VariableType = module.ImportReference(variable.VariableType);
}
/// <summary>
/// Imports the method type references and the custom attributes of a method into a module.
/// </summary>
/// <param name="method">The method whose references need to be imported.</param>
/// <param name="module">The module type into whose module the references should be imported.</param>
public static void ImportReferences(this MethodDefinition method, ModuleDefinition module)
{
// Import method overrides into the module
for (int i = 0; i < method.Overrides.Count; ++i) method.Overrides[i] = module.ImportReference(method.Overrides[i]);
// Import the custom attributes references into the module
foreach (var attribute in method.CustomAttributes) attribute.ImportReferences(module);
// Import the parameter type references into the module
foreach (var parameter in method.Parameters) parameter.ImportReferences(module);
// Import the return type reference into the module
method.ReturnType = module.ImportReference(method.ReturnType);
// Import the declaring type definition into the module
method.DeclaringType = module.ImportReference(method.DeclaringType).Resolve();
// If the method has a body, import references for each variable and instruction
if (method.HasBody)
{
// Import the variable type references in the method body into the module
foreach (var variable in method.Body.Variables) variable.ImportReferences(module);
// Import the instruction type references in the method body into the module
foreach (var instruction in method.Body.Instructions) instruction.ImportReferences(module);
}
}
/// <summary>
/// Imports the return type references of a CallSite into a module.
/// </summary>
/// <param name="callSite">The CallSite whose return type references need to be imported.</param>
/// <param name="module">The module type into whose module the references should be imported.</param>
public static void ImportReferences(this CallSite callSite, ModuleDefinition module)
{
// Import the return type reference of the callSite into the module
callSite.ReturnType = module.ImportReference(callSite.ReturnType);
}
/// <summary>
/// Imports the operand type references of an instruction into a module.
/// </summary>
/// <param name="instruction">The instruction whose operand references need to be imported.</param>
/// <param name="module">The module type into whose module the references should be imported.</param>
public static void ImportReferences(this Instruction instruction, ModuleDefinition module)
{
// Import the operand references of the instruction into the module
if (instruction.Operand is ParameterDefinition parameter)
parameter.ImportReferences(module);
else if (instruction.Operand is VariableDefinition variable)
variable.ImportReferences(module);
else if (instruction.Operand is TypeReference type)
instruction.Operand = module.ImportReference(type);
else if (instruction.Operand is FieldReference field)
instruction.Operand = module.ImportReference(field);
else if (instruction.Operand is MethodReference method)
instruction.Operand = module.ImportReference(method);
else if (instruction.Operand is CallSite callSite)
callSite.ImportReferences(module);
}
#endregion ImportReferences
// Extension methods for swapping method implementations between different types.
// This can be used when wanting to replace method functionality in the destination type with the corresponding functionality from the source type.
#region SwapMethods
/// <summary>
/// Swaps the method references within the provided instruction between two given methods.
/// </summary>
/// <param name="instruction">The instruction to modify.</param>
/// <param name="leftMethod">The first method to swap.</param>
/// <param name="rightMethod">The second method to swap.</param>
public static void SwapMethodReferences(this Instruction instruction, MethodDefinition leftMethod, MethodDefinition rightMethod)
{
// If the instruction's operand is a method reference
if (instruction.Operand is MethodReference method)
{
// If the operand matches the left method, replace it with the right method
if (method == leftMethod)
instruction.Operand = rightMethod;
// If the operand matches the right method, replace it with the left method
else if (method == rightMethod)
instruction.Operand = leftMethod;
}
}
/// <summary>
/// Swaps the method references within the provided collection of instructions between two given methods.
/// </summary>
/// <param name="instructions">The collection of instructions to modify.</param>
/// <param name="leftMethod">The first method to swap.</param>
/// <param name="rightMethod">The second method to swap.</param>
public static void SwapMethodReferences(this Collection<Instruction> instructions, MethodDefinition leftMethod, MethodDefinition rightMethod)
{
// Swap method references for each instruction in the collection
foreach (var instruction in instructions)
instruction.SwapMethodReferences(leftMethod, rightMethod);
}
/// <summary>
/// Swaps the method references within the body of the provided method between two given methods.
/// </summary>
/// <param name="method">The method to modify.</param>
/// <param name="leftMethod">The first method to swap.</param>
/// <param name="rightMethod">The second method to swap.</param>
public static void SwapMethodReferences(this MethodDefinition method, MethodDefinition leftMethod, MethodDefinition rightMethod)
{
// Swap method references for each instruction in the method's body
if (method.HasBody) method.Body.Instructions.SwapMethodReferences(leftMethod, rightMethod);
}
/// <summary>
/// Swaps the attributes, parameters, custom attributes, and generic parameters between two given methods.
/// </summary>
/// <param name="leftMethod">The first method to swap.</param>
/// <param name="rightMethod">The second method to swap.</param>
public static void SwapMethods(this MethodDefinition leftMethod, MethodDefinition rightMethod)
{
// Save the left method's original details
var leftBody = leftMethod.Body;
var leftAttributes = leftMethod.Attributes;
var leftImplAttributes = leftMethod.ImplAttributes;
var leftSemanticsAttributes = leftMethod.SemanticsAttributes;
var leftParameters = new Collection<ParameterDefinition>(leftMethod.Parameters);
var leftCustomAttributes = new Collection<CustomAttribute>(leftMethod.CustomAttributes);
var leftGenericParameters = new Collection<GenericParameter>(leftMethod.GenericParameters);
// Swap the details from the right method to the left
leftMethod.Body = rightMethod.Body;
leftMethod.Body = rightMethod.Body;
leftMethod.Attributes = rightMethod.Attributes;
leftMethod.ImplAttributes = rightMethod.ImplAttributes;
leftMethod.SemanticsAttributes = rightMethod.SemanticsAttributes;
leftMethod.Parameters.Clear();
leftMethod.CustomAttributes.Clear();
leftMethod.GenericParameters.Clear();
foreach (var parameter in rightMethod.Parameters) leftMethod.Parameters.Add(parameter);
foreach (var attribute in rightMethod.CustomAttributes) leftMethod.CustomAttributes.Add(attribute);
foreach (var parameter in rightMethod.GenericParameters) leftMethod.GenericParameters.Add(parameter);
// Swap the details from the left method (which were saved) to the right
rightMethod.Body = leftBody;
rightMethod.Body = leftBody;
rightMethod.Attributes = leftAttributes;
rightMethod.ImplAttributes = leftImplAttributes;
rightMethod.SemanticsAttributes = leftSemanticsAttributes;
rightMethod.Parameters.Clear();
rightMethod.CustomAttributes.Clear();
rightMethod.GenericParameters.Clear();
foreach (var parameter in leftParameters) rightMethod.Parameters.Add(parameter);
foreach (var attribute in leftCustomAttributes) rightMethod.CustomAttributes.Add(attribute);
foreach (var parameter in leftGenericParameters) rightMethod.GenericParameters.Add(parameter);
// Swap method references within each method body
leftMethod.SwapMethodReferences(leftMethod, rightMethod);
rightMethod.SwapMethodReferences(rightMethod, leftMethod);
}
/// <summary>
/// Finds and swaps methods with the same full name within the given type.
/// </summary>
/// <param name="type">The type to modify.</param>
/// <param name="avoidSignatureConflicts">Avoid signature conflicts by changing original method parameters to be base object types</param>
public static void SwapDuplicateMethods(this TypeDefinition type, bool avoidSignatureConflicts = false)
{
// This HashSet is used for tracking the methods that have already been swapped.
var alreadySwapped = new HashSet<string>();
// Convert the method collection to list for efficient index-based access.
var methods = type.Methods.ToList();
// Iterate over each pair of methods in the type
for (int i = 0; i < methods.Count; i++)
{
for (int j = i + 1; j < methods.Count; j++)
{
var methodLeft = methods[i];
var methodRight = methods[j];
// If two methods have the same full name and haven't been swapped yet
if (methodLeft.FullName == methodRight.FullName && !alreadySwapped.Contains(methodLeft.FullName))
{
// Add the method full name to the set of already swapped methods
_ = alreadySwapped.Add(methodLeft.FullName);
// Swap the two methods
methodLeft.SwapMethods(methodRight);
// Change the original method types to be generic to avoid signature conflicts
if (avoidSignatureConflicts)
{
foreach (var parameter in methodRight.Parameters)
if (!parameter.ParameterType.IsValueType) parameter.ParameterType = type.Module.ImportReference(typeof(object));
}
}
}
}
}
#endregion SwapMethods
// Methods to do with instruction optimizations
#region InstructionOptimizations
#pragma warning disable RCS1003
/// <summary>
/// Determines if a given instruction within a method can be optimized out.
/// Specifically, this method looks for type conversion instructions (Isinst or Castclass)
/// that are unnecessary because the type of the value at the top of the stack is
/// already the target conversion type.
/// </summary>
/// <param name="instruction">The instruction to be checked for optimization.</param>
/// <param name="method">The method definition that contains the instruction.</param>
/// <returns>Returns true if the instruction can be optimized out. Otherwise, returns false.</returns>
/// <remarks>
/// This method works by examining the instructions before the given instruction in the method,
/// maintaining a conceptual "stack balance" and tracking the type of the value at the top of the stack.
/// The stack balance is a measure of the net effect of the instructions on the stack,
/// with a positive balance indicating more values have been pushed than popped,
/// and a negative balance indicating more values have been popped than pushed.
/// If the stack balance is zero and the type of the value at the top of the stack
/// matches the type conversion, the conversion is unnecessary and the method returns true.
/// </remarks>
public static bool CanBeOptimizedOut(this Instruction instruction, MethodDefinition method)
{
// Check if the instruction is a type conversion instruction (instance cast or class cast)
if (instruction.OpCode == OpCodes.Isinst || instruction.OpCode == OpCodes.Castclass)
{
// Get the type to which the conversion is being made
var typeConversionType = instruction.Operand as TypeReference;
// Initialize stack balance. This will help to determine the net stack effect of the instructions
int stackBalance = 0;
// Move to the previous instruction
instruction = instruction.Previous;
// Process previous instructions
while (instruction != null)
{
// Determine how the current instruction modifies the stack
var pushBehaviour = instruction.OpCode.StackBehaviourPush;
var popBehaviour = instruction.OpCode.StackBehaviourPop;
// Fullname of any type extracted from the instruction
string extractedFullName = null;
// This is an exhaustive check for control flow change instructions. These instructions will cause a jump
// in execution or a termination of the function, thus ending our analysis.
if (instruction.OpCode == OpCodes.Ret || // Return from the current method.
instruction.OpCode == OpCodes.Throw || // Throw an exception.
instruction.OpCode == OpCodes.Rethrow || // Rethrow the current exception.
instruction.OpCode == OpCodes.Endfilter || // End the filter clause of an exception block and branch to the exception handler.
instruction.OpCode == OpCodes.Endfinally || // Transfer control from the exception block of a try or catch block.
instruction.OpCode == OpCodes.Leave || instruction.OpCode == OpCodes.Leave_S || // Exit a protected region of code.
instruction.OpCode == OpCodes.Jmp || // Jump to the method pointed to by the method pointer loaded on the stack.
instruction.OpCode == OpCodes.Switch || // Switch control to one of several locations.
instruction.OpCode == OpCodes.Br || instruction.OpCode == OpCodes.Br_S || // Unconditional branch to target.
instruction.OpCode == OpCodes.Brfalse || instruction.OpCode == OpCodes.Brfalse_S || // Branch to target if value is zero (false).
instruction.OpCode == OpCodes.Brtrue || instruction.OpCode == OpCodes.Brtrue_S || // Branch to target if value is non-zero (true).
instruction.OpCode == OpCodes.Beq || instruction.OpCode == OpCodes.Beq_S || // Branch to target if two values are equal.
instruction.OpCode == OpCodes.Bne_Un || instruction.OpCode == OpCodes.Bne_Un_S || // Branch to target if two values are not equal.
instruction.OpCode == OpCodes.Bge || instruction.OpCode == OpCodes.Bge_S || instruction.OpCode == OpCodes.Bge_Un || instruction.OpCode == OpCodes.Bge_Un_S || // Branch to target if value1 >= value2 (unsigned or unordered).
instruction.OpCode == OpCodes.Bgt || instruction.OpCode == OpCodes.Bgt_S || instruction.OpCode == OpCodes.Bgt_Un || instruction.OpCode == OpCodes.Bgt_Un_S || // Branch to target if value1 > value2 (unsigned or unordered).
instruction.OpCode == OpCodes.Ble || instruction.OpCode == OpCodes.Ble_S || instruction.OpCode == OpCodes.Ble_Un || instruction.OpCode == OpCodes.Ble_Un_S || // Branch to target if value1 <= value2 (unsigned or unordered).
instruction.OpCode == OpCodes.Blt || instruction.OpCode == OpCodes.Blt_S || instruction.OpCode == OpCodes.Blt_Un || instruction.OpCode == OpCodes.Blt_Un_S) // Branch to target if value1 < value2 (unsigned or unordered).
return false; // Return from method
// Check if instruction is for loading a field onto the stack
// In this case, the type of the value is the type of the field.
else if (instruction.OpCode == OpCodes.Ldfld || // load field value onto stack
instruction.OpCode == OpCodes.Ldflda || // load field address onto stack
instruction.OpCode == OpCodes.Ldsfld || // load static field value onto stack
instruction.OpCode == OpCodes.Ldsflda) // load static field address onto stack
extractedFullName = ((FieldReference)instruction.Operand).FieldType.FullName;
// Check if instruction is for loading an argument onto the stack
// In this case, the type of the value is the type of the argument.
else if (instruction.OpCode == OpCodes.Ldarg || // load argument onto stack
instruction.OpCode == OpCodes.Ldarg_S) // short form for loading argument onto stack
extractedFullName = ((ParameterReference)instruction.Operand).ParameterType.FullName;
// Check for loading argument at index 0 onto stack
else if (instruction.OpCode == OpCodes.Ldarg_0) // load argument at index 0 onto stack
extractedFullName = (method.IsStatic ? method.Parameters[0].ParameterType : method.DeclaringType).FullName;
// Check for loading argument at index 1 onto stack
else if (instruction.OpCode == OpCodes.Ldarg_1) // load argument at index 1 onto stack
extractedFullName = (method.IsStatic ? method.Parameters[1].ParameterType : method.Parameters[0].ParameterType).FullName;
// Check for loading argument at index 2 onto stack
else if (instruction.OpCode == OpCodes.Ldarg_2) // load argument at index 2 onto stack
extractedFullName = (method.IsStatic ? method.Parameters[2].ParameterType : method.Parameters[1].ParameterType).FullName;
// Check for loading argument at index 3 onto stack
else if (instruction.OpCode == OpCodes.Ldarg_3) // load argument at index 3 onto stack
extractedFullName = (method.IsStatic ? method.Parameters[3].ParameterType : method.Parameters[2].ParameterType).FullName;
// Check for loading local variable onto stack
else if (instruction.OpCode == OpCodes.Ldloc || // load local variable onto stack
instruction.OpCode == OpCodes.Ldloc_S) // short form for loading local variable onto stack
extractedFullName = ((VariableReference)instruction.Operand).VariableType.FullName;
// Check for loading local variable at index 0 onto stack
else if (instruction.OpCode == OpCodes.Ldloc_0) // load local variable at index 0 onto stack
extractedFullName = method.Body.Variables[0].VariableType.FullName;
// Check for loading local variable at index 1 onto stack
else if (instruction.OpCode == OpCodes.Ldloc_1)// load local variable at index 1 onto stack
extractedFullName = method.Body.Variables[1].VariableType.FullName;
// Check for loading local variable at index 2 onto stack
else if (instruction.OpCode == OpCodes.Ldloc_2)// load local variable at index 2 onto stack
extractedFullName = method.Body.Variables[2].VariableType.FullName;
// Check for loading local variable at index 3 onto stack
else if (instruction.OpCode == OpCodes.Ldloc_3)// load local variable at index 3 onto stack
extractedFullName = method.Body.Variables[3].VariableType.FullName;
// Check for calling a method and pushing return value onto the stack, loading function pointer onto the stack
else if (instruction.OpCode == OpCodes.Callvirt || // call method virtually and push return value onto stack
instruction.OpCode == OpCodes.Call || // call method and push return value onto stack
instruction.OpCode == OpCodes.Ldftn || // load method pointer onto stack
instruction.OpCode == OpCodes.Ldvirtftn) // load virtual method pointer onto stack
extractedFullName = ((MethodReference)instruction.Operand).ReturnType.FullName;
// Check for calling a method indicated on the stack with arguments, pushing return value onto stack
else if (instruction.OpCode == OpCodes.Calli) // call method indicated on the stack with arguments, pushing return value onto stack
extractedFullName = ((CallSite)instruction.Operand).ReturnType.FullName;
// Check for creating a new object and pushing object reference onto stack
else if (instruction.OpCode == OpCodes.Newobj) // create a new object and push object reference onto stack
extractedFullName = ((MethodReference)instruction.Operand).DeclaringType.FullName;
// Check for loading an object, array element, or pointer onto stack, creating a new array, or creating a typed reference
else if (instruction.OpCode == OpCodes.Ldobj || // load object onto stack
instruction.OpCode == OpCodes.Ldelem_Any || // load element of an object array onto stack
instruction.OpCode == OpCodes.Newarr || // create a new array and push reference onto stack
instruction.OpCode == OpCodes.Mkrefany) // push a typed reference onto stack
extractedFullName = ((TypeReference)instruction.Operand).FullName;
// Check for loading a string onto stack
else if (instruction.OpCode == OpCodes.Ldstr) // load a string onto stack
extractedFullName = typeof(string).FullName;
// If the type of the value currently at the top of the stack matches the type conversion
// and the stack is balanced, the conversion is unnecessary
if (stackBalance == 0 && extractedFullName == typeConversionType.FullName)
return true;
// Dup doesn't change the type of the top of the stack, so adjust stack balance to ignore it
if (instruction.OpCode == OpCodes.Dup)
stackBalance--;
// Adjust stack balance according to the current instruction's push behavior
//if (pushBehaviour == StackBehaviour.Push0)
if (pushBehaviour == StackBehaviour.Push1 || pushBehaviour == StackBehaviour.Pushi || pushBehaviour == StackBehaviour.Pushref ||
pushBehaviour == StackBehaviour.Pushi8 || pushBehaviour == StackBehaviour.Pushr4 || pushBehaviour == StackBehaviour.Pushr8 ||
pushBehaviour == StackBehaviour.Varpush)
stackBalance++;
else if (pushBehaviour == StackBehaviour.Push1_push1)
stackBalance += 2;
// Adjust stack balance according to the current instruction's pop behavior
//if (popBehaviour == StackBehaviour.Pop0)
if (popBehaviour == StackBehaviour.Pop1 || popBehaviour == StackBehaviour.Popi || popBehaviour == StackBehaviour.Popref ||
popBehaviour == StackBehaviour.Varpop)
stackBalance--;
else if (popBehaviour == StackBehaviour.Pop1_pop1 || popBehaviour == StackBehaviour.Popi_popi || popBehaviour == StackBehaviour.Popi_pop1 ||
popBehaviour == StackBehaviour.Popi_popi8 || popBehaviour == StackBehaviour.Popi_popr4 || popBehaviour == StackBehaviour.Popi_popr8 ||
popBehaviour == StackBehaviour.Popref_pop1 || popBehaviour == StackBehaviour.Popref_popi)
stackBalance -= 2;
else if (popBehaviour == StackBehaviour.Popi_popi_popi || popBehaviour == StackBehaviour.Popref_popi_popi || popBehaviour == StackBehaviour.Popref_popi_popi8 ||
popBehaviour == StackBehaviour.Popref_popi_popr4 || popBehaviour == StackBehaviour.Popref_popi_popr8 || popBehaviour == StackBehaviour.Popref_popi_popref)
stackBalance -= 3;
// Move to previous instruction
instruction = instruction.Previous;
}
}
// Return that the instruction cannot be optimized
return false;
}
#pragma warning restore RCS1003
/// <summary>
/// Optimizes a given method by removing any instructions
/// that can be optimized out.
/// </summary>
/// <param name="method">
/// The MethodDefinition object to be optimized. This method contains a list
/// of instructions that are to be checked and potentially removed if they can be optimized out.
/// </param>
public static void OptimizeInstructions(this MethodDefinition method)
{
// If the method doesn't have a body (i.e., it's an abstract or external method), then exit
if (!method.HasBody) return;
// Iterate over each instruction in the method body
for (int i = 0; i < method.Body.Instructions.Count - 1; ++i)
{
// If the current instruction can be optimized out according to the CanBeOptimizedOut method, remove it
if (method.Body.Instructions[i].CanBeOptimizedOut(method))
{
// Remove the current instruction from the method body
method.Body.Instructions.RemoveAt(i);
// Decrement the loop index to account for the removal. This ensures that the next iteration doesn't skip any instructions.
i--;
}
}
}
#endregion InstructionOptimizations
}

View File

@@ -0,0 +1,738 @@
using KFCommonUtilityLib.Scripts.StaticManagers;
using System.Collections.Generic;
using UnityEngine;
namespace KFCommonUtilityLib.Scripts.Utilities
{
/// <summary>
/// This new script make following changes:
/// - projectile ItemValue fields are reused for custom passive calculation:
/// -- Meta => launcher ItemClass id
/// -- SelectedAmmoIndex => launcher action index
/// -- Activated => launcher data strain perc
/// -- cosmetics and mods reference launcher ItemValue
/// -- Quality and Durability is copied from launcher ItemValue
/// - MinEventParams.itemActionData is set to correct launcher data.
/// </summary>
public class CustomProjectileMoveScript : ProjectileMoveScript
{
public override void checkCollision()
{
if (this.firingEntity == null || state != State.Active || gameManager == null)
return;
World world = gameManager?.World;
if (world == null)
{
return;
}
Vector3 checkPos;
if (bOnIdealPos)
{
checkPos = transform.position + Origin.position;
}
else
{
checkPos = idealPosition;
}
Vector3 dir = checkPos - previousPosition;
float magnitude = dir.magnitude;
if (magnitude < 0.04f)
{
return;
}
EntityAlive firingEntity = (EntityAlive)this.firingEntity;
Ray ray = new Ray(previousPosition, dir.normalized);
waterCollisionParticles.CheckCollision(ray.origin, ray.direction, magnitude, (firingEntity != null) ? firingEntity.entityId : (-1));
int prevLayer = 0;
if (firingEntity != null && firingEntity.emodel != null)
{
prevLayer = firingEntity.GetModelLayer();
firingEntity.SetModelLayer(2);
}
int hitmask = ((hmOverride == 0) ? 80 : hmOverride);
bool flag = Voxel.Raycast(world, ray, magnitude, -538750997, hitmask, 0);
if (firingEntity != null && firingEntity.emodel != null)
{
firingEntity.SetModelLayer(prevLayer);
}
if (flag && (GameUtils.IsBlockOrTerrain(Voxel.voxelRayHitInfo.tag) || Voxel.voxelRayHitInfo.tag.StartsWith("E_")))
{
if (firingEntity != null && !firingEntity.isEntityRemote)
{
firingEntity.MinEventContext.Other = ItemActionAttack.FindHitEntity(Voxel.voxelRayHitInfo) as EntityAlive;
firingEntity.MinEventContext.ItemActionData = actionData;
firingEntity.MinEventContext.ItemValue = itemValueLauncher;
firingEntity.MinEventContext.Position = Voxel.voxelRayHitInfo.hit.pos;
ItemActionAttack.AttackHitInfo attackHitInfo = new ItemActionAttack.AttackHitInfo
{
WeaponTypeTag = ItemActionAttack.RangedTag
};
float strainPerc = itemValueProjectile.Activated / (float)byte.MaxValue;
MultiActionProjectileRewrites.ProjectileHit(Voxel.voxelRayHitInfo,
ProjectileOwnerID,
EnumDamageTypes.Piercing,
Mathf.Lerp(1f, MultiActionProjectileRewrites.GetProjectileDamageBlock(itemActionProjectile, itemValueProjectile, ItemActionAttack.GetBlockHit(world, Voxel.voxelRayHitInfo), firingEntity, actionData.indexInEntityOfAction), strainPerc),
Mathf.Lerp(1f, MultiActionProjectileRewrites.GetProjectileDamageEntity(itemActionProjectile, itemValueProjectile, firingEntity, actionData.indexInEntityOfAction), strainPerc),
1f,
1f,
MultiActionReversePatches.ProjectileGetValue(PassiveEffects.CriticalChance, itemValueProjectile, itemProjectile.CritChance.Value, firingEntity, null, itemProjectile.ItemTags, true, false),
ItemAction.GetDismemberChance(actionData, Voxel.voxelRayHitInfo),
itemProjectile.MadeOfMaterial.SurfaceCategory,
itemActionProjectile.GetDamageMultiplier(),
getBuffActions(),
attackHitInfo,
1,
itemActionProjectile.ActionExp,
itemActionProjectile.ActionExpBonusMultiplier,
null,
null,
ItemActionAttack.EnumAttackMode.RealNoHarvesting,
null,
-1,
itemValueProjectile,
itemValueLauncher);
if (firingEntity.MinEventContext.Other == null)
{
firingEntity.FireEvent(MinEventTypes.onSelfPrimaryActionMissEntity, false);
}
firingEntity.FireEvent(MinEventTypes.onProjectileImpact, false);
MinEventParams.CachedEventParam.Self = firingEntity;
MinEventParams.CachedEventParam.Position = Voxel.voxelRayHitInfo.hit.pos;
MinEventParams.CachedEventParam.ItemValue = itemValueProjectile;
MinEventParams.CachedEventParam.ItemActionData = actionData;
MinEventParams.CachedEventParam.Other = firingEntity.MinEventContext.Other;
itemProjectile.FireEvent(MinEventTypes.onProjectileImpact, MinEventParams.CachedEventParam);
if (itemActionProjectile.Explosion.ParticleIndex > 0)
{
Vector3 vector3 = Voxel.voxelRayHitInfo.hit.pos - dir.normalized * 0.1f;
Vector3i vector3i = World.worldToBlockPos(vector3);
if (!world.GetBlock(vector3i).isair)
{
BlockFace blockFace;
vector3i = Voxel.OneVoxelStep(vector3i, vector3, -dir.normalized, out vector3, out blockFace);
}
gameManager.ExplosionServer(Voxel.voxelRayHitInfo.hit.clrIdx, vector3, vector3i, Quaternion.identity, itemActionProjectile.Explosion, ProjectileOwnerID, 0f, false, itemValueProjectile);
SetState(State.Dead);
return;
}
if (itemProjectile.IsSticky)
{
GameRandom gameRandom = world.GetGameRandom();
if (GameUtils.IsBlockOrTerrain(Voxel.voxelRayHitInfo.tag))
{
if (gameRandom.RandomFloat < MultiActionReversePatches.ProjectileGetValue(PassiveEffects.ProjectileStickChance, itemValueProjectile, 0.5f, firingEntity, null, itemProjectile.ItemTags | FastTags<TagGroup.Global>.Parse(Voxel.voxelRayHitInfo.fmcHit.blockValue.Block.blockMaterial.SurfaceCategory), true, false))
{
RestoreProjectileValue();
ProjectileID = ProjectileManager.AddProjectileItem(transform, -1, Voxel.voxelRayHitInfo.hit.pos, dir.normalized, itemValueProjectile.type);
SetState(State.Sticky);
}
else
{
gameManager.SpawnParticleEffectServer(new ParticleEffect("impact_metal_on_wood", Voxel.voxelRayHitInfo.hit.pos, Utils.BlockFaceToRotation(Voxel.voxelRayHitInfo.fmcHit.blockFace), 1f, Color.white, string.Format("{0}hit{1}", Voxel.voxelRayHitInfo.fmcHit.blockValue.Block.blockMaterial.SurfaceCategory, itemProjectile.MadeOfMaterial.SurfaceCategory), null), firingEntity.entityId, false, false);
SetState(State.Dead);
}
}
else if (gameRandom.RandomFloat < MultiActionReversePatches.ProjectileGetValue(PassiveEffects.ProjectileStickChance, itemValueProjectile, 0.5f, firingEntity, null, itemProjectile.ItemTags, true, false))
{
RestoreProjectileValue();
ProjectileID = ProjectileManager.AddProjectileItem(transform, -1, Voxel.voxelRayHitInfo.hit.pos, dir.normalized, itemValueProjectile.type);
Utils.SetLayerRecursively(ProjectileManager.GetProjectile(ProjectileID).gameObject, 14, null);
SetState(State.Sticky);
}
else
{
gameManager.SpawnParticleEffectServer(new ParticleEffect("impact_metal_on_wood", Voxel.voxelRayHitInfo.hit.pos, Utils.BlockFaceToRotation(Voxel.voxelRayHitInfo.fmcHit.blockFace), 1f, Color.white, "bullethitwood", null), firingEntity.entityId, false, false);
SetState(State.Dead);
}
}
else
{
SetState(State.Dead);
}
}
else
{
SetState(State.Dead);
}
if (state == State.Active)
{
SetState(State.Dead);
}
}
previousPosition = checkPos;
}
private void RestoreProjectileValue()
{
itemValueProjectile.Modifications = ItemValue.emptyItemValueArray;
itemValueProjectile.CosmeticMods = ItemValue.emptyItemValueArray;
itemValueProjectile.Quality = 0;
itemValueProjectile.UseTimes = 0;
itemValueProjectile.Meta = 0;
itemValueProjectile.SelectedAmmoTypeIndex = 0;
}
}
public static class MultiActionProjectileRewrites
{
public static void ProjectileHit(WorldRayHitInfo hitInfo, int _attackerEntityId, EnumDamageTypes _damageType, float _blockDamage,
float _entityDamage, float _staminaDamageMultiplier, float _weaponCondition, float _criticalHitChanceOLD,
float _dismemberChance, string _attackingDeviceMadeOf, DamageMultiplier _damageMultiplier,
List<string> _buffActions, ItemActionAttack.AttackHitInfo _attackDetails, int _flags = 1, int _actionExp = 0,
float _actionExpBonus = 0f, ItemActionAttack rangeCheckedAction = null,
Dictionary<string, ItemActionAttack.Bonuses> _toolBonuses = null,
ItemActionAttack.EnumAttackMode _attackMode = ItemActionAttack.EnumAttackMode.RealNoHarvesting,
Dictionary<string, string> _hitSoundOverrides = null, int ownedEntityId = -1, ItemValue projectileValue = null, ItemValue launcherValue = null)
{
if (_attackDetails != null)
{
_attackDetails.hitPosition = Vector3i.zero;
_attackDetails.bKilled = false;
}
if (hitInfo == null || hitInfo.tag == null)
{
return;
}
World world = GameManager.Instance.World;
bool canHarvest = true;
if (_attackMode == ItemActionAttack.EnumAttackMode.RealNoHarvestingOrEffects)
{
canHarvest = false;
_attackMode = ItemActionAttack.EnumAttackMode.RealNoHarvesting;
}
if (_attackDetails != null)
{
_attackDetails.itemsToDrop = null;
_attackDetails.bBlockHit = false;
_attackDetails.entityHit = null;
}
string blockFaceParticle = null;
string surfaceCategory = null;
float lightValueAtBlockPos = 1f;
Color blockFaceColor = Color.white;
bool isProtectionApplied = false;
EntityAlive attackerEntity = world.GetEntity(_attackerEntityId) as EntityAlive;
bool isHoldingDamageItem = false;
if (attackerEntity != null)
{
if (launcherValue == null)
{
launcherValue = attackerEntity.inventory.holdingItemItemValue;
}
isHoldingDamageItem = launcherValue.Equals(attackerEntity.inventory.holdingItemItemValue);
}
bool isHitTargetPlayer = true;
//if hits block or terrain
if (GameUtils.IsBlockOrTerrain(hitInfo.tag))
{
if (ItemAction.ShowDebugDisplayHit)
{
DebugLines.Create(null, attackerEntity.RootTransform, Camera.main.transform.position + Origin.position, hitInfo.hit.pos, new Color(1f, 0.5f, 1f), new Color(1f, 0f, 1f), ItemAction.DebugDisplayHitSize * 2f, ItemAction.DebugDisplayHitSize, ItemAction.DebugDisplayHitTime);
}
ChunkCluster hittedChunk = world.ChunkClusters[hitInfo.hit.clrIdx];
if (hittedChunk == null)
{
return;
}
Vector3i hitBlockPos = hitInfo.hit.blockPos;
BlockValue hitBlockValue = hittedChunk.GetBlock(hitBlockPos);
if (hitBlockValue.isair && hitInfo.hit.blockValue.Block.IsDistantDecoration && hitInfo.hit.blockValue.damage >= hitInfo.hit.blockValue.Block.MaxDamage - 1)
{
hitBlockValue = hitInfo.hit.blockValue;
world.SetBlockRPC(hitBlockPos, hitBlockValue);
}
Block hitBlock = hitBlockValue.Block;
if (hitBlock == null)
{
return;
}
if (hitBlockValue.ischild)
{
hitBlockPos = hitBlock.multiBlockPos.GetParentPos(hitBlockPos, hitBlockValue);
hitBlockValue = hittedChunk.GetBlock(hitBlockPos);
hitBlock = hitBlockValue.Block;
if (hitBlock == null)
{
return;
}
}
if (hitBlockValue.isair)
{
return;
}
float landProtectionModifier = 0;
if (!world.IsWithinTraderArea(hitInfo.hit.blockPos) && hitBlock.blockMaterial.id != "Mbedrock")
{
landProtectionModifier = world.GetLandProtectionHardnessModifier(hitInfo.hit.blockPos, attackerEntity, world.GetGameManager().GetPersistentLocalPlayer());
}
if (landProtectionModifier != 1f)
{
if (attackerEntity && _attackMode != ItemActionAttack.EnumAttackMode.Simulate && attackerEntity is EntityPlayer && !launcherValue.ItemClass.ignoreKeystoneSound && !launcherValue.ToBlockValue().Block.IgnoreKeystoneOverlay)
{
attackerEntity.PlayOneShot("keystone_impact_overlay", false);
}
if (landProtectionModifier < 1f)
{
isProtectionApplied = true;
}
}
if (hitBlockPos != _attackDetails.hitPosition || landProtectionModifier != _attackDetails.hardnessScale || hitBlockValue.type != _attackDetails.blockBeingDamaged.type || (isHoldingDamageItem && projectileValue.SelectedAmmoTypeIndex != _attackDetails.ammoIndex))
{
float finalHardness = Mathf.Max(hitBlock.GetHardness(), 0.1f) * landProtectionModifier;
float finalBlockDamage = _blockDamage * ((_damageMultiplier != null) ? _damageMultiplier.Get(hitBlock.blockMaterial.DamageCategory) : 1f);
if (attackerEntity)
{
finalBlockDamage *= attackerEntity.GetBlockDamageScale();
}
if (_toolBonuses != null && _toolBonuses.Count > 0)
{
finalBlockDamage *= calculateHarvestToolDamageBonus(_toolBonuses, hitBlock.itemsToDrop);
_attackDetails.bHarvestTool = true;
}
_attackDetails.damagePerHit = isProtectionApplied ? 0f : (finalBlockDamage / finalHardness);
_attackDetails.damage = 0f;
_attackDetails.hardnessScale = landProtectionModifier;
_attackDetails.hitPosition = hitBlockPos;
_attackDetails.blockBeingDamaged = hitBlockValue;
if (isHoldingDamageItem)
{
_attackDetails.ammoIndex = projectileValue.SelectedAmmoTypeIndex;
}
}
_attackDetails.raycastHitPosition = hitInfo.hit.blockPos;
Block fmcHitBlock = hitInfo.fmcHit.blockValue.Block;
lightValueAtBlockPos = world.GetLightBrightness(hitInfo.fmcHit.blockPos);
blockFaceColor = fmcHitBlock.GetColorForSide(hitInfo.fmcHit.blockValue, hitInfo.fmcHit.blockFace);
blockFaceParticle = fmcHitBlock.GetParticleForSide(hitInfo.fmcHit.blockValue, hitInfo.fmcHit.blockFace);
MaterialBlock materialForSide = fmcHitBlock.GetMaterialForSide(hitInfo.fmcHit.blockValue, hitInfo.fmcHit.blockFace);
surfaceCategory = materialForSide.SurfaceCategory;
float modifiedBlockDamage = _attackDetails.damagePerHit * _staminaDamageMultiplier;
if (attackerEntity)
{
string blockFaceDamageCategory = materialForSide.DamageCategory ?? string.Empty;
modifiedBlockDamage = (int)MultiActionReversePatches.ProjectileGetValue(PassiveEffects.DamageModifier, projectileValue, modifiedBlockDamage, attackerEntity, null, FastTags<TagGroup.Global>.Parse(blockFaceDamageCategory) | _attackDetails.WeaponTypeTag | hitInfo.fmcHit.blockValue.Block.Tags, true, false);
}
modifiedBlockDamage = ItemActionAttack.DegradationModifier(modifiedBlockDamage, _weaponCondition);
modifiedBlockDamage = isProtectionApplied ? 0f : Utils.FastMax(1f, modifiedBlockDamage);
_attackDetails.damage += modifiedBlockDamage;
_attackDetails.bKilled = false;
_attackDetails.damageTotalOfTarget = hitBlockValue.damage + _attackDetails.damage;
if (_attackDetails.damage > 0f)
{
BlockFace blockFaceFromHitInfo = GameUtils.GetBlockFaceFromHitInfo(hitBlockPos, hitBlockValue, hitInfo.hitCollider, hitInfo.hitTriangleIdx, out _, out _);
int blockFaceTexture = hittedChunk.GetBlockFaceTexture(hitBlockPos, blockFaceFromHitInfo);
int blockCurDamage = hitBlockValue.damage;
bool isBlockBroken = blockCurDamage >= hitBlock.MaxDamage;
int ownerAttackerID = ((ownedEntityId != -1 && ownedEntityId != -2) ? ownedEntityId : _attackerEntityId);
int blockNextDamage = (_attackMode != ItemActionAttack.EnumAttackMode.Simulate) ? hitBlock.DamageBlock(world, hittedChunk.ClusterIdx, hitBlockPos, hitBlockValue, (int)_attackDetails.damage, ownerAttackerID, _attackDetails, _attackDetails.bHarvestTool, false) : 0;
if (blockNextDamage == 0)
{
_attackDetails.damage = 0f;
}
else
{
_attackDetails.damage -= blockNextDamage - blockCurDamage;
}
if (_attackMode != ItemActionAttack.EnumAttackMode.Simulate && canHarvest && attackerEntity is EntityPlayerLocal && blockFaceTexture > 0 && hitBlock.MeshIndex == 0 && blockNextDamage >= hitBlock.MaxDamage * 1f)
{
ParticleEffect particleEffect = new ParticleEffect("paint_block", hitInfo.fmcHit.pos - Origin.position, Utils.BlockFaceToRotation(hitInfo.fmcHit.blockFace), lightValueAtBlockPos, blockFaceColor, null, null)
{
opqueTextureId = BlockTextureData.list[blockFaceTexture].TextureID
};
GameManager.Instance.SpawnParticleEffectClient(particleEffect, _attackerEntityId, false, false);
}
_attackDetails.damageGiven = ((!isBlockBroken) ? (blockNextDamage - blockCurDamage) : 0);
_attackDetails.damageMax = hitBlock.MaxDamage;
_attackDetails.bKilled = !isBlockBroken && blockNextDamage >= hitBlock.MaxDamage;
_attackDetails.itemsToDrop = hitBlock.itemsToDrop;
_attackDetails.bBlockHit = true;
_attackDetails.materialCategory = hitBlock.blockMaterial.SurfaceCategory;
if (attackerEntity != null && _attackMode != ItemActionAttack.EnumAttackMode.Simulate)
{
attackerEntity.MinEventContext.BlockValue = hitBlockValue;
attackerEntity.MinEventContext.Tags = hitBlock.Tags;
if (_attackDetails.bKilled)
{
attackerEntity.FireEvent(MinEventTypes.onSelfDestroyedBlock, isHoldingDamageItem);
attackerEntity.NotifyDestroyedBlock(_attackDetails);
}
else
{
attackerEntity.FireEvent(MinEventTypes.onSelfDamagedBlock, isHoldingDamageItem);
}
}
}
}
else if (hitInfo.tag.StartsWith("E_"))
{
Entity hitEntity = ItemActionAttack.FindHitEntityNoTagCheck(hitInfo, out string hitBodyPart);
if (hitEntity == null)
{
return;
}
if (hitEntity.entityId == _attackerEntityId)
{
return;
}
if (!hitEntity.CanDamageEntity(_attackerEntityId))
{
return;
}
EntityAlive hitEntityAlive = hitEntity as EntityAlive;
DamageSourceEntity damageSourceEntity = new DamageSourceEntity(EnumDamageSource.External, _damageType, _attackerEntityId, hitInfo.ray.direction, hitInfo.transform.name, hitInfo.hit.pos, Voxel.phyxRaycastHit.textureCoord);
damageSourceEntity.AttackingItem = projectileValue;
damageSourceEntity.DismemberChance = _dismemberChance;
damageSourceEntity.CreatorEntityId = ownedEntityId;
bool isCriticalHit = _attackDetails.isCriticalHit;
int finalEntityDamage = (int)_entityDamage;
if (attackerEntity != null && hitEntityAlive != null)
{
FastTags<TagGroup.Global> equipmentTags = FastTags<TagGroup.Global>.none;
if (hitEntityAlive.Health > 0)
{
equipmentTags = FastTags<TagGroup.Global>.Parse(damageSourceEntity.GetEntityDamageEquipmentSlotGroup(hitEntityAlive).ToStringCached());
equipmentTags |= DamagePatches.GetBodyPartTags(damageSourceEntity.GetEntityDamageBodyPart(hitEntityAlive));
}
finalEntityDamage = (int)MultiActionReversePatches.ProjectileGetValue(PassiveEffects.DamageModifier, projectileValue, finalEntityDamage, attackerEntity, null, equipmentTags | _attackDetails.WeaponTypeTag | hitEntityAlive.EntityClass.Tags, true, false);
finalEntityDamage = (int)MultiActionReversePatches.ProjectileGetValue(PassiveEffects.InternalDamageModifier, projectileValue, finalEntityDamage, hitEntityAlive, null, equipmentTags | projectileValue.ItemClass.ItemTags, true, false);
}
if (!hitEntityAlive || hitEntityAlive.Health > 0)
{
finalEntityDamage = Utils.FastMax(1, ItemActionAttack.difficultyModifier(finalEntityDamage, world.GetEntity(_attackerEntityId), hitEntity));
}
else if (_toolBonuses != null)
{
finalEntityDamage = (int)(finalEntityDamage * calculateHarvestToolDamageBonus(_toolBonuses, EntityClass.list[hitEntity.entityClass].itemsToDrop));
}
//Log.Out("Final entity damage: " + finalEntityDamage);
bool isAlreadyDead = hitEntity.IsDead();
int deathHealth = (hitEntityAlive != null) ? hitEntityAlive.DeathHealth : 0;
if (_attackMode != ItemActionAttack.EnumAttackMode.Simulate)
{
if (attackerEntity != null)
{
MinEventParams minEventContext = attackerEntity.MinEventContext;
minEventContext.Other = hitEntityAlive;
minEventContext.StartPosition = hitInfo.ray.origin;
}
if (SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer && (attackerEntity as EntityPlayer == null || !attackerEntity.isEntityRemote) && hitEntity.isEntityRemote && rangeCheckedAction != null)
{
EntityPlayer hitPlayer = hitEntity as EntityPlayer;
if (hitPlayer != null)
{
isHitTargetPlayer = false;
Ray lookRay = attackerEntity.GetLookRay();
lookRay.origin -= lookRay.direction * 0.15f;
float range = Utils.FastMax(rangeCheckedAction.Range, rangeCheckedAction.BlockRange) * ItemActionAttack.attackRangeMultiplier;
string hitTransformPath = null;
List<string> list_buffs = _buffActions;
if (list_buffs != null)
{
if (hitEntityAlive)
{
hitTransformPath = (hitBodyPart != null) ? GameUtils.GetChildTransformPath(hitEntity.transform, hitInfo.transform) : null;
}
else
{
list_buffs = null;
}
}
if (attackerEntity != null)
{
attackerEntity.FireEvent(MinEventTypes.onSelfAttackedOther, isHoldingDamageItem);
if (hitEntityAlive != null && hitEntityAlive.RecordedDamage.Strength > 0)
{
attackerEntity.FireEvent(MinEventTypes.onSelfDamagedOther, isHoldingDamageItem);
}
}
if (!isAlreadyDead && hitEntity.IsDead() && attackerEntity != null)
{
attackerEntity.FireEvent(MinEventTypes.onSelfKilledOther, isHoldingDamageItem);
}
if (hitEntityAlive && hitEntityAlive.RecordedDamage.ArmorDamage > hitEntityAlive.RecordedDamage.Strength)
{
surfaceCategory = "metal";
}
else
{
surfaceCategory = EntityClass.list[hitEntity.entityClass].Properties.Values["SurfaceCategory"];
}
blockFaceParticle = surfaceCategory;
lightValueAtBlockPos = hitEntity.GetLightBrightness();
string hitParticle = string.Format("impact_{0}_on_{1}", _attackingDeviceMadeOf, blockFaceParticle);
string hitSound = (surfaceCategory != null) ? string.Format("{0}hit{1}", _attackingDeviceMadeOf, surfaceCategory) : null;
if (_hitSoundOverrides != null && _hitSoundOverrides.ContainsKey(surfaceCategory))
{
hitSound = _hitSoundOverrides[surfaceCategory];
}
ParticleEffect particleEffect2 = new ParticleEffect(hitParticle, hitInfo.fmcHit.pos, Utils.BlockFaceToRotation(hitInfo.fmcHit.blockFace), lightValueAtBlockPos, blockFaceColor, hitSound, null);
hitPlayer.ServerNetSendRangeCheckedDamage(lookRay.origin, range, damageSourceEntity, finalEntityDamage, isCriticalHit, list_buffs, hitTransformPath, particleEffect2);
}
}
if (isHitTargetPlayer)
{
int damageDealt = hitEntity.DamageEntity(damageSourceEntity, finalEntityDamage, isCriticalHit, 1f);
if (damageDealt != -1 && attackerEntity)
{
MinEventParams attackerMinEventParams = attackerEntity.MinEventContext;
attackerMinEventParams.Other = hitEntityAlive;
attackerMinEventParams.StartPosition = hitInfo.ray.origin;
if (ownedEntityId != -1)
{
launcherValue.FireEvent(MinEventTypes.onSelfAttackedOther, attackerEntity.MinEventContext);
}
attackerEntity.FireEvent(MinEventTypes.onSelfAttackedOther, isHoldingDamageItem);
if (hitEntityAlive && hitEntityAlive.RecordedDamage.Strength > 0)
{
attackerEntity.FireEvent(MinEventTypes.onSelfDamagedOther, isHoldingDamageItem);
}
}
if (!isAlreadyDead && hitEntity.IsDead() && attackerEntity)
{
attackerEntity.FireEvent(MinEventTypes.onSelfKilledOther, isHoldingDamageItem);
}
if (damageDealt != -1 && hitEntityAlive && _buffActions != null && _buffActions.Count > 0)
{
for (int i = 0; i < _buffActions.Count; i++)
{
BuffClass buff = BuffManager.GetBuff(_buffActions[i]);
if (buff != null)
{
float bufProcChance = MultiActionReversePatches.ProjectileGetValue(PassiveEffects.BuffProcChance, null, 1f, attackerEntity, null, FastTags<TagGroup.Global>.Parse(buff.Name), true, false);
if (hitEntityAlive.rand.RandomFloat <= bufProcChance)
{
hitEntityAlive.Buffs.AddBuff(_buffActions[i], attackerEntity.entityId, true, false, -1f);
}
}
}
}
}
}
if (hitEntityAlive && hitEntityAlive.RecordedDamage.ArmorDamage > hitEntityAlive.RecordedDamage.Strength)
{
surfaceCategory = "metal";
}
else
{
surfaceCategory = EntityClass.list[hitEntity.entityClass].Properties.Values["SurfaceCategory"];
}
blockFaceParticle = surfaceCategory;
lightValueAtBlockPos = hitEntity.GetLightBrightness();
EntityPlayer attackerPlayer = attackerEntity as EntityPlayer;
if (attackerPlayer)
{
if (isAlreadyDead && hitEntity.IsDead() && hitEntityAlive && hitEntityAlive.DeathHealth + finalEntityDamage > -1 * EntityClass.list[hitEntity.entityClass].DeadBodyHitPoints)
{
_attackDetails.damageTotalOfTarget = (float)(-1 * hitEntityAlive.DeathHealth);
_attackDetails.damageGiven = deathHealth + Mathf.Min(EntityClass.list[hitEntity.entityClass].DeadBodyHitPoints, Mathf.Abs(hitEntityAlive.DeathHealth));
_attackDetails.damageMax = EntityClass.list[hitEntity.entityClass].DeadBodyHitPoints;
_attackDetails.bKilled = -1 * hitEntityAlive.DeathHealth >= EntityClass.list[hitEntity.entityClass].DeadBodyHitPoints;
_attackDetails.itemsToDrop = EntityClass.list[hitEntity.entityClass].itemsToDrop;
_attackDetails.entityHit = hitEntity;
_attackDetails.materialCategory = surfaceCategory;
}
if (!isAlreadyDead && (hitEntityAlive.IsDead() || hitEntityAlive.Health <= 0) && EntityClass.list.ContainsKey(hitEntity.entityClass))
{
if ((_flags & 2) > 0)
{
float trapXP = MultiActionReversePatches.ProjectileGetValue(PassiveEffects.ElectricalTrapXP, attackerPlayer.inventory.holdingItemItemValue, 0f, attackerPlayer, null, default, true, false);
if (trapXP > 0f)
{
attackerPlayer.AddKillXP(hitEntityAlive, trapXP);
}
}
else
{
attackerPlayer.AddKillXP(hitEntityAlive, 1f);
}
}
}
if (hitEntity is EntityDrone)
{
_attackDetails.entityHit = hitEntity;
}
}
if ((_flags & 8) > 0)
{
canHarvest = false;
}
if (isHitTargetPlayer && _attackMode != ItemActionAttack.EnumAttackMode.Simulate && canHarvest && blockFaceParticle != null && ((_attackDetails.bBlockHit && !_attackDetails.bKilled) || !_attackDetails.bBlockHit))
{
string hitParticle = string.Format("impact_{0}_on_{1}", _attackingDeviceMadeOf, blockFaceParticle);
if (_attackMode == ItemActionAttack.EnumAttackMode.RealAndHarvesting && (_flags & 4) > 0 && ParticleEffect.IsAvailable(hitParticle + "_harvest"))
{
hitParticle += "_harvest";
}
string hitSound = (surfaceCategory != null) ? string.Format("{0}hit{1}", _attackingDeviceMadeOf, surfaceCategory) : null;
if (_hitSoundOverrides != null && _hitSoundOverrides.ContainsKey(surfaceCategory))
{
hitSound = _hitSoundOverrides[surfaceCategory];
}
world.GetGameManager().SpawnParticleEffectServer(new ParticleEffect(hitParticle, hitInfo.fmcHit.pos, Utils.BlockFaceToRotation(hitInfo.fmcHit.blockFace), lightValueAtBlockPos, blockFaceColor, hitSound, null), _attackerEntityId, false, true);
}
if ((_flags & 1) > 0 && attackerEntity != null && attackerEntity.inventory != null)
{
attackerEntity.inventory.CallOnToolbeltChangedInternal();
}
}
private static float calculateHarvestToolDamageBonus(Dictionary<string, ItemActionAttack.Bonuses> _toolBonuses, Dictionary<EnumDropEvent, List<Block.SItemDropProb>> _harvestItems)
{
if (!_harvestItems.ContainsKey(EnumDropEvent.Harvest))
{
return 1f;
}
List<Block.SItemDropProb> list = _harvestItems[EnumDropEvent.Harvest];
for (int i = 0; i < list.Count; i++)
{
if (list[i].toolCategory != null && _toolBonuses.ContainsKey(list[i].toolCategory))
{
return _toolBonuses[list[i].toolCategory].Damage;
}
}
return 1f;
}
public static float GetProjectileDamageBlock(this ItemActionAttack self, ItemValue _itemValue, BlockValue _blockValue, EntityAlive _holdingEntity = null, int actionIndex = 0)
{
FastTags<TagGroup.Global> tmpTag = ((actionIndex != 1) ? ItemActionAttack.PrimaryTag : ItemActionAttack.SecondaryTag);
ItemClass launcherClass = ItemClass.GetForId(_itemValue.Meta);
tmpTag |= ((launcherClass == null) ? ItemActionAttack.MeleeTag : launcherClass.ItemTags);
if (_holdingEntity != null)
{
tmpTag |= _holdingEntity.CurrentStanceTag | _holdingEntity.CurrentMovementTag;
}
tmpTag |= _blockValue.Block.Tags;
float value = MultiActionReversePatches.ProjectileGetValue(PassiveEffects.BlockDamage, _itemValue, self.damageBlock, _holdingEntity, null, tmpTag, true, false)/* * GetProjectileBlockDamagePerc(_itemValue, _holdingEntity)*/;
//Log.Out($"block damage {value} base damage {self.GetBaseDamageBlock(null)} action index {actionIndex} launcher {launcherClass.Name} projectile {_itemValue.ItemClass.Name}");
return value;
}
public static float GetProjectileDamageEntity(this ItemActionAttack self, ItemValue _itemValue, EntityAlive _holdingEntity = null, int actionIndex = 0)
{
FastTags<TagGroup.Global> tmpTag = ((actionIndex != 1) ? ItemActionAttack.PrimaryTag : ItemActionAttack.SecondaryTag);
ItemClass launcherClass = ItemClass.GetForId(_itemValue.Meta);
tmpTag |= ((launcherClass == null) ? ItemActionAttack.MeleeTag : launcherClass.ItemTags);
if (_holdingEntity != null)
{
tmpTag |= _holdingEntity.CurrentStanceTag | _holdingEntity.CurrentMovementTag;
}
var res = MultiActionReversePatches.ProjectileGetValue(PassiveEffects.EntityDamage, _itemValue, self.damageEntity, _holdingEntity, null, tmpTag, true, false)/* * GetProjectileEntityDamagePerc(_itemValue, _holdingEntity)*/;
#if DEBUG
Log.Out($"get projectile damage entity for action index {actionIndex}, item {launcherClass.Name}, result {res}");
#endif
return res;
}
//public static float GetProjectileBlockDamagePerc(ItemValue _itemValue, EntityAlive _holdingEntity)
//{
// float value = MultiActionReversePatches.ProjectileGetValue(CustomEnums.ProjectileImpactDamagePercentBlock, _itemValue, 1, _holdingEntity, null);
// //Log.Out("Block damage perc: " + value);
// return value;
//}
//public static float GetProjectileEntityDamagePerc(ItemValue _itemValue, EntityAlive _holdingEntity)
//{
// float value = MultiActionReversePatches.ProjectileGetValue(CustomEnums.ProjectileImpactDamagePercentEntity, _itemValue, 1, _holdingEntity, null);
// //Log.Out("Entity damage perc: " + value);
// return value;
//}
public static void ProjectileValueModifyValue(this ItemValue _projectileItemValue, EntityAlive _entity, ItemValue _originalItemValue, PassiveEffects _passiveEffect, ref float _originalValue, ref float _perc_value, FastTags<TagGroup.Global> _tags, bool _useMods = true, bool _useDurability = false)
{
if (_originalItemValue != null)
{
Log.Warning($"original item value present: item {_originalItemValue.ItemClass.Name}");
return;
}
int seed = MinEventParams.CachedEventParam.Seed;
if (_entity != null)
{
seed = _entity.MinEventContext.Seed;
}
ItemClass launcherClass = ItemClass.GetForId(_projectileItemValue.Meta);
int actionIndex = _projectileItemValue.SelectedAmmoTypeIndex;
if (launcherClass != null)
{
if (launcherClass.Actions != null && launcherClass.Actions.Length != 0 && launcherClass.Actions[actionIndex] is ItemActionRanged)
{
ItemClass ammoClass = _projectileItemValue.ItemClass;
if (ammoClass != null && ammoClass.Effects != null)
{
ammoClass.Effects.ModifyValue(_entity, _passiveEffect, ref _originalValue, ref _perc_value, 0f, _tags);
}
}
if (launcherClass.Effects != null)
{
MinEventParams.CachedEventParam.Seed = (int)_projectileItemValue.Seed + (int)((_projectileItemValue.Seed != 0) ? _passiveEffect : PassiveEffects.None);
if (_entity != null)
{
_entity.MinEventContext.Seed = MinEventParams.CachedEventParam.Seed;
}
float prevOriginal = _originalValue;
launcherClass.Effects.ModifyValue(_entity, _passiveEffect, ref _originalValue, ref _perc_value, _projectileItemValue.Quality, _tags);
if (_useDurability)
{
float percentUsesLeft = _projectileItemValue.PercentUsesLeft;
switch (_passiveEffect)
{
case PassiveEffects.PhysicalDamageResist:
if (percentUsesLeft < 0.5f)
{
float diff = _originalValue - prevOriginal;
_originalValue = prevOriginal + diff * percentUsesLeft * 2f;
}
break;
case PassiveEffects.ElementalDamageResist:
if (percentUsesLeft < 0.5f)
{
float diff = _originalValue - prevOriginal;
_originalValue = prevOriginal + diff * percentUsesLeft * 2f;
}
break;
case PassiveEffects.BuffResistance:
if (percentUsesLeft < 0.5f)
{
float diff = _originalValue - prevOriginal;
_originalValue = prevOriginal + diff * percentUsesLeft * 2f;
}
break;
}
}
}
}
else
{
Log.Warning($"launcher class not found: item id{_projectileItemValue.Meta}");
}
if (_useMods)
{
for (int i = 0; i < _projectileItemValue.CosmeticMods.Length; i++)
{
if (_projectileItemValue.CosmeticMods[i] != null && _projectileItemValue.CosmeticMods[i].ItemClass is ItemClassModifier && !MultiActionManager.ShouldExcludePassive(_projectileItemValue.type, _projectileItemValue.CosmeticMods[i].type, actionIndex))
{
_projectileItemValue.CosmeticMods[i].ModifyValue(_entity, _projectileItemValue, _passiveEffect, ref _originalValue, ref _perc_value, _tags);
}
}
for (int i = 0; i < _projectileItemValue.Modifications.Length; i++)
{
if (_projectileItemValue.Modifications[i] != null && _projectileItemValue.Modifications[i].ItemClass is ItemClassModifier && !MultiActionManager.ShouldExcludePassive(_projectileItemValue.type, _projectileItemValue.Modifications[i].type, actionIndex))
{
_projectileItemValue.Modifications[i].ModifyValue(_entity, _projectileItemValue, _passiveEffect, ref _originalValue, ref _perc_value, _tags);
}
}
}
}
}
}

View File

@@ -0,0 +1,445 @@
using KFCommonUtilityLib.Scripts.StaticManagers;
using System;
using System.Collections.Generic;
using UnityEngine;
namespace KFCommonUtilityLib.Scripts.Utilities
{
public static class MultiActionUtils
{
public static readonly string[] ActionMetaNames = new string[]
{
"Meta0",
"Meta1",
"Meta2",
"Meta3",
"Meta4",
};
public static readonly string[] ActionSelectedAmmoNames = new string[]
{
"AmmoIndex0",
"AmmoIndex1",
"AmmoIndex2",
"AmmoIndex3",
"AmmoIndex4",
};
public static readonly int ExecutingActionIndexHash = Animator.StringToHash("ExecutingActionIndex");
public static void SetMinEventArrays()
{
MinEvent.Start = new[]
{
MinEventTypes.onSelfPrimaryActionStart,
MinEventTypes.onSelfSecondaryActionStart,
MinEventTypes.onSelfAction2Start,
MinEventTypes.onSelfPrimaryActionStart,
MinEventTypes.onSelfPrimaryActionStart,
};
MinEvent.Update = new[]
{
MinEventTypes.onSelfPrimaryActionUpdate,
MinEventTypes.onSelfSecondaryActionUpdate,
MinEventTypes.onSelfAction2Update,
MinEventTypes.onSelfPrimaryActionUpdate,
MinEventTypes.onSelfPrimaryActionUpdate,
};
MinEvent.End = new[]
{
MinEventTypes.onSelfPrimaryActionEnd,
MinEventTypes.onSelfSecondaryActionEnd,
MinEventTypes.onSelfAction2End,
MinEventTypes.onSelfPrimaryActionEnd,
MinEventTypes.onSelfPrimaryActionEnd,
};
}
//public static int GetActionIndexForItemValue(this ItemValue self)
//{
// object val = self.GetMetadata(MultiActionMapping.STR_MULTI_ACTION_INDEX);
// if (val is int index)
// return index;
// return 0;
//}
public static string GetPropertyName(int index, string prop)
{
return $"Action{index}.{prop}";
}
public static void FixedItemReloadServer(int entityId, int actionIndex)
{
if (GameManager.Instance.World == null)
{
return;
}
FixedItemReloadClient(entityId, actionIndex);
if (!SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer)
{
SingletonMonoBehaviour<ConnectionManager>.Instance.SendToServer(NetPackageManager.GetPackage<NetPackageFixedReload>().Setup(entityId, actionIndex), false);
return;
}
SingletonMonoBehaviour<ConnectionManager>.Instance.SendPackage(NetPackageManager.GetPackage<NetPackageFixedReload>().Setup(entityId, actionIndex), false, -1, entityId);
}
public static void FixedItemReloadClient(int entityId, int actionIndex)
{
if (GameManager.Instance.World == null)
{
return;
}
EntityAlive entityAlive = (EntityAlive)GameManager.Instance.World.GetEntity(entityId);
if (entityAlive != null && entityAlive.inventory.holdingItem.Actions[actionIndex] is ItemActionRanged actionRanged)
{
entityAlive.MinEventContext.ItemActionData = entityAlive.inventory.holdingItemData.actionData[actionIndex];
actionRanged.ReloadGun(entityAlive.inventory.holdingItemData.actionData[actionIndex]);
}
}
public static void CopyLauncherValueToProjectile(ItemValue launcherValue, ItemValue projectileValue, int index)
{
projectileValue.Meta = launcherValue.type;
projectileValue.SelectedAmmoTypeIndex = (byte)index;
projectileValue.UseTimes = launcherValue.UseTimes;
projectileValue.Quality = launcherValue.Quality;
projectileValue.Modifications = new ItemValue[launcherValue.Modifications.Length];
Array.Copy(launcherValue.Modifications, projectileValue.Modifications, launcherValue.Modifications.Length);
projectileValue.CosmeticMods = new ItemValue[launcherValue.CosmeticMods.Length];
Array.Copy(launcherValue.CosmeticMods, projectileValue.CosmeticMods, launcherValue.CosmeticMods.Length);
}
public static void SetMinEventParamsActionData(EntityAlive entity, int actionIndex)
{
if (actionIndex >= 0 && actionIndex < entity.inventory?.holdingItemData?.actionData?.Count)
entity.MinEventContext.ItemActionData = entity.inventory.holdingItemData.actionData[actionIndex];
}
public static string GetPropertyOverrideForAction(this ItemValue self, string _propertyName, string _originalValue, int actionIndex)
{
if (self.Modifications.Length == 0 && self.CosmeticMods.Length == 0)
{
return _originalValue;
}
string text = "";
ItemClass item = self.ItemClass;
for (int i = 0; i < self.Modifications.Length; i++)
{
ItemValue itemValue = self.Modifications[i];
if (itemValue != null)
{
if (itemValue.ItemClass is ItemClassModifier mod && GetPropertyOverrideForMod(mod, _propertyName, item, ref text, actionIndex))
{
return text;
}
}
}
text = "";
for (int j = 0; j < self.CosmeticMods.Length; j++)
{
ItemValue itemValue2 = self.CosmeticMods[j];
if (itemValue2 != null)
{
if (itemValue2.ItemClass is ItemClassModifier cos && GetPropertyOverrideForMod(cos, _propertyName, item, ref text, actionIndex))
{
return text;
}
}
}
return _originalValue;
}
public static IEnumerable<string> GetAllPropertyOverridesForAction(this ItemValue self, string _propertyName, int actionIndex)
{
if (self.Modifications.Length == 0 && self.CosmeticMods.Length == 0)
{
yield break;
}
string text = "";
ItemClass item = self.ItemClass;
for (int i = 0; i < self.Modifications.Length; i++)
{
ItemValue itemValue = self.Modifications[i];
if (itemValue != null)
{
if (itemValue.ItemClass is ItemClassModifier mod && GetPropertyOverrideForMod(mod, _propertyName, item, ref text, actionIndex))
{
yield return text;
}
}
}
text = "";
for (int j = 0; j < self.CosmeticMods.Length; j++)
{
ItemValue itemValue2 = self.CosmeticMods[j];
if (itemValue2 != null)
{
if (itemValue2.ItemClass is ItemClassModifier cos && GetPropertyOverrideForMod(cos, _propertyName, item, ref text, actionIndex))
{
yield return text;
}
}
}
}
public static bool GetPropertyOverrideForMod(ItemClassModifier mod, string _propertyName, ItemClass _item, ref string _value, int actionIndex)
{
//Log.Out($"Get property override for item {_item.Name} itemID{_item.Id} property {_propertyName} mod {mod.Name} modID {mod.Id} action {actionIndex} should exclude {MultiActionManager.ShouldExcludeMod(_item.Id, mod.Id, actionIndex)}");
if (MultiActionManager.ShouldExcludeProperty(_item.Id, mod.Id, actionIndex))
return false;
string _itemName = _item.GetItemName();
string itemNameWithActionIndex = $"{_itemName}_{actionIndex}";
if (mod.PropertyOverrides.ContainsKey(itemNameWithActionIndex) && mod.PropertyOverrides[itemNameWithActionIndex].Values.ContainsKey(_propertyName))
{
_value = mod.PropertyOverrides[itemNameWithActionIndex].Values[_propertyName];
return true;
}
if (mod.PropertyOverrides.ContainsKey(_itemName) && mod.PropertyOverrides[_itemName].Values.ContainsKey(_propertyName))
{
_value = mod.PropertyOverrides[_itemName].Values[_propertyName];
return true;
}
itemNameWithActionIndex = $"*_{actionIndex}";
if (mod.PropertyOverrides.ContainsKey(itemNameWithActionIndex) && mod.PropertyOverrides[itemNameWithActionIndex].Values.ContainsKey(_propertyName))
{
_value = mod.PropertyOverrides[itemNameWithActionIndex].Values[_propertyName];
return true;
}
if (mod.PropertyOverrides.ContainsKey("*") && mod.PropertyOverrides["*"].Values.ContainsKey(_propertyName))
{
_value = mod.PropertyOverrides["*"].Values[_propertyName];
return true;
}
return false;
}
public static int GetMode(this ItemValue self)
{
object mode = self.GetMetadata(MultiActionMapping.STR_MULTI_ACTION_INDEX);
if (mode is int v)
{
return v;
}
return 0;
}
public static int GetActionIndexByEntityEventParams(EntityAlive entity)
{
return GetActionIndexByEventParams(entity?.MinEventContext ?? MinEventParams.CachedEventParam);
}
public static int GetActionIndexByEventParams(MinEventParams pars)
{
if (pars?.ItemActionData == null)
return 0;
return pars.ItemActionData.indexInEntityOfAction;
}
public static int GetActionIndexByMetaData(this ItemValue self)
{
int mode = self.GetMode();
MultiActionIndice indice = MultiActionManager.GetActionIndiceForItemID(self.type);
return indice.GetActionIndexForMode(mode);
}
public static int GetSelectedAmmoIndexByMode(this ItemValue self, int mode)
{
MultiActionIndice indice = MultiActionManager.GetActionIndiceForItemID(self.type);
int metaIndex = indice.GetMetaIndexForMode(mode);
if (metaIndex >= 0)
{
object ammoIndex = self.GetMetadata(ActionSelectedAmmoNames[metaIndex]);
if (ammoIndex is int)
{
return (int)ammoIndex;
}
}
return self.SelectedAmmoTypeIndex;
}
public static int GetMetaByMode(this ItemValue self, int mode)
{
MultiActionIndice indice = MultiActionManager.GetActionIndiceForItemID(self.type);
int metaIndex = indice.GetMetaIndexForMode(mode);
if (metaIndex >= 0)
{
object meta = self.GetMetadata(ActionMetaNames[metaIndex]);
if (meta is int)
{
return (int)meta;
}
}
return self.Meta;
}
public static int GetSelectedAmmoIndexByActionIndex(this ItemValue self, int actionIndex)
{
MultiActionIndice indice = MultiActionManager.GetActionIndiceForItemID(self.type);
int mode = indice.GetModeForAction(actionIndex);
if (mode < 0)
return self.SelectedAmmoTypeIndex;
int metaIndex = indice.GetMetaIndexForMode(mode);
if (metaIndex >= 0)
{
object ammoIndex = self.GetMetadata(ActionSelectedAmmoNames[metaIndex]);
if (ammoIndex is int)
{
return (int)ammoIndex;
}
}
return self.SelectedAmmoTypeIndex;
}
public static int GetMetaByActionIndex(this ItemValue self, int actionIndex)
{
MultiActionIndice indice = MultiActionManager.GetActionIndiceForItemID(self.type);
int mode = indice.GetModeForAction(actionIndex);
if (mode < 0)
return self.Meta;
int metaIndex = indice.GetMetaIndexForMode(mode);
if (metaIndex >= 0)
{
object meta = self.GetMetadata(ActionMetaNames[metaIndex]);
if (meta is int)
{
//Log.Out($"GetMetaByActionIndex: mode: {mode}, action: {metaIndex}, meta: {(int)meta}\n{StackTraceUtility.ExtractStackTrace()}");
return (int)meta;
}
}
return self.Meta;
}
public static void SetMinEventParamsByEntityInventory(EntityAlive entity)
{
if (entity != null && entity.MinEventContext != null)
{
entity.MinEventContext.ItemActionData = entity.inventory?.holdingItemData?.actionData[MultiActionManager.GetActionIndexForEntity(entity)];
}
}
public static bool MultiActionRemoveAmmoFromItemStack(ItemStack stack, List<ItemStack> result)
{
ItemValue itemValue = stack.itemValue;
object mode = itemValue.GetMetadata(MultiActionMapping.STR_MULTI_ACTION_INDEX);
if (mode is false || mode is null)
{
return false;
}
MultiActionIndice indices = MultiActionManager.GetActionIndiceForItemID(itemValue.type);
ItemClass item = ItemClass.GetForId(itemValue.type);
for (int i = 0; i < MultiActionIndice.MAX_ACTION_COUNT; i++)
{
int metaIndex = indices.GetMetaIndexForMode(i);
if (metaIndex < 0)
{
break;
}
int actionIndex = indices.GetActionIndexForMode(i);
if (item.Actions[actionIndex] is ItemActionRanged ranged && !(ranged is ItemActionTextureBlock))
{
object meta = itemValue.GetMetadata(MultiActionUtils.ActionMetaNames[metaIndex]);
object ammoIndex = itemValue.GetMetadata(MultiActionUtils.ActionSelectedAmmoNames[metaIndex]);
if (meta is int && ammoIndex is int && (int)meta > 0)
{
itemValue.SetMetadata(MultiActionUtils.ActionMetaNames[metaIndex], 0, TypedMetadataValue.TypeTag.Integer);
ItemStack ammoStack = new ItemStack(ItemClass.GetItem(ranged.MagazineItemNames[(int)ammoIndex]), (int)meta);
result.Add(ammoStack);
Log.Out($"Remove ammo: metadata {MultiActionUtils.ActionMetaNames[metaIndex]}, meta {(int)meta}, left {itemValue.GetMetadata(MultiActionUtils.ActionMetaNames[metaIndex])}");
}
}
}
itemValue.Meta = 0;
return true;
}
public static readonly ItemActionData[] DummyActionDatas = new ItemActionData[]
{
new ItemActionData(null, 0),
new ItemActionData(null, 3),
new ItemActionData(null, 4)
};
public static int GetMultiActionInitialMetaData(this ItemClass itemClass, ItemValue itemValue)
{
MultiActionIndice indice = MultiActionManager.GetActionIndiceForItemID(itemClass.Id);
if (indice.modeCount <= 1)
{
return itemClass.GetInitialMetadata(itemValue);
}
var prevItemValue = MinEventParams.CachedEventParam.ItemValue;
var prevActionData = MinEventParams.CachedEventParam.ItemActionData;
MinEventParams.CachedEventParam.ItemValue = itemValue;
itemValue.SetMetadata(MultiActionMapping.STR_MULTI_ACTION_INDEX, 0, TypedMetadataValue.TypeTag.Integer);
int ret = 0;
for (int i = 0; i < indice.modeCount; i++)
{
MinEventParams.CachedEventParam.ItemActionData = DummyActionDatas[i];
ItemAction itemAction = itemClass.Actions[indice.GetActionIndexForMode(i)];
int meta = itemAction.GetInitialMeta(itemValue);
if (i == 0)
{
ret = meta;
}
else if (itemAction.Properties.Contains("ActionUnlocked") && !itemAction.Properties.GetBool("ActionUnlocked"))
{
meta = 0;
}
if (indice.GetMetaIndexForMode(i) == indice.GetActionIndexForMode(i))
itemValue.SetMetadata(MultiActionUtils.ActionMetaNames[indice.GetMetaIndexForMode(i)], meta, TypedMetadataValue.TypeTag.Integer);
}
MinEventParams.CachedEventParam.ItemValue = prevItemValue;
MinEventParams.CachedEventParam.ItemActionData = prevActionData;
return ret;
}
public static void SetCachedEventParamsDummyAction(ItemStack itemStack)
{
ItemClass itemClass = itemStack?.itemValue?.ItemClass;
if (itemClass != null)
{
MinEventParams.CachedEventParam.ItemActionData = MultiActionUtils.DummyActionDatas[itemStack.itemValue.GetMode()];
MinEventParams.CachedEventParam.ItemValue = itemStack.itemValue;
MinEventParams.CachedEventParam.Seed = itemStack.itemValue.Seed;
}
}
public static string GetDisplayTypeForAction(ItemStack itemStack)
{
return GetDisplayTypeForAction(itemStack?.itemValue);
}
public static string GetDisplayTypeForAction(ItemValue itemValue)
{
if (itemValue == null || itemValue.IsEmpty())
{
return "";
}
if (itemValue.ItemClass.Actions[itemValue.GetActionIndexByMetaData()] is IModuleContainerFor<ActionModuleMultiActionFix> module)
{
return module.Instance.GetDisplayType(itemValue);
}
return itemValue.ItemClass.DisplayType;
}
public static bool CanCompare(ItemValue itemValue1, ItemValue itemValue2)
{
if (itemValue1 == null || itemValue2 == null || itemValue1.IsEmpty() || itemValue2.IsEmpty())
{
return false;
}
string displayType1 = itemValue1.ItemClass.IsBlock() ? Block.list[itemValue1.ItemClass.Id].DisplayType : GetDisplayTypeForAction(itemValue1);
string displayType2 = itemValue2.ItemClass.IsBlock() ? Block.list[itemValue2.ItemClass.Id].DisplayType : GetDisplayTypeForAction(itemValue2);
ItemDisplayEntry displayStatsForTag = UIDisplayInfoManager.Current.GetDisplayStatsForTag(displayType1);
ItemDisplayEntry displayStatsForTag2 = UIDisplayInfoManager.Current.GetDisplayStatsForTag(displayType2);
return displayStatsForTag != null && displayStatsForTag2 != null && displayStatsForTag.DisplayGroup == displayStatsForTag2.DisplayGroup;
}
}
}

View File

@@ -0,0 +1,134 @@
using Unity.Collections;
using UnityEngine;
using UnityEngine.Rendering;
public class SaveTextureToFileUtility
{
public enum SaveTextureFileFormat
{
EXR, JPG, PNG, TGA
};
/// <summary>
/// Saves a Texture2D to disk with the specified filename and image format
/// </summary>
/// <param name="tex"></param>
/// <param name="filePath"></param>
/// <param name="fileFormat"></param>
/// <param name="jpgQuality"></param>
static public void SaveTexture2DToFile(Texture2D tex, string filePath, SaveTextureFileFormat fileFormat, int jpgQuality = 95)
{
switch (fileFormat)
{
case SaveTextureFileFormat.EXR:
System.IO.File.WriteAllBytes(filePath + ".exr", tex.EncodeToEXR());
break;
case SaveTextureFileFormat.JPG:
System.IO.File.WriteAllBytes(filePath + ".jpg", tex.EncodeToJPG(jpgQuality));
break;
case SaveTextureFileFormat.PNG:
System.IO.File.WriteAllBytes(filePath + ".png", tex.EncodeToPNG());
break;
case SaveTextureFileFormat.TGA:
System.IO.File.WriteAllBytes(filePath + ".tga", tex.EncodeToTGA());
break;
}
}
/// <summary>
/// Saves a RenderTexture to disk with the specified filename and image format
/// </summary>
/// <param name="renderTexture"></param>
/// <param name="filePath"></param>
/// <param name="fileFormat"></param>
/// <param name="jpgQuality"></param>
static public void SaveRenderTextureToFile(RenderTexture renderTexture, string filePath, SaveTextureFileFormat fileFormat = SaveTextureFileFormat.PNG, int jpgQuality = 95)
{
Texture2D tex;
if (fileFormat != SaveTextureFileFormat.EXR)
tex = new Texture2D(renderTexture.width, renderTexture.height, TextureFormat.ARGB32, false, false);
else
tex = new Texture2D(renderTexture.width, renderTexture.height, TextureFormat.RGBAFloat, false, true);
var oldRt = RenderTexture.active;
RenderTexture.active = renderTexture;
tex.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0);
tex.Apply();
RenderTexture.active = oldRt;
SaveTexture2DToFile(tex, filePath, fileFormat, jpgQuality);
if (Application.isPlaying)
Object.Destroy(tex);
else
Object.DestroyImmediate(tex);
}
static public void SaveTextureToFile(Texture source,
string filePath,
int width,
int height,
SaveTextureFileFormat fileFormat = SaveTextureFileFormat.PNG,
int jpgQuality = 95,
bool asynchronous = true,
System.Action<bool> done = null)
{
// check that the input we're getting is something we can handle:
if (!(source is Texture2D || source is RenderTexture))
{
done?.Invoke(false);
return;
}
// use the original texture size in case the input is negative:
if (width < 0 || height < 0)
{
width = source.width;
height = source.height;
}
// resize the original image:
var resizeRT = RenderTexture.GetTemporary(width, height, 0);
Graphics.Blit(source, resizeRT);
// create a native array to receive data from the GPU:
var narray = new NativeArray<byte>(width * height * 4, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
// request the texture data back from the GPU:
var request = AsyncGPUReadback.RequestIntoNativeArray(ref narray, resizeRT, 0, (AsyncGPUReadbackRequest req) =>
{
// if the readback was successful, encode and write the results to disk
if (!req.hasError)
{
NativeArray<byte> encoded;
switch (fileFormat)
{
case SaveTextureFileFormat.EXR:
encoded = ImageConversion.EncodeNativeArrayToEXR(narray, resizeRT.graphicsFormat, (uint)width, (uint)height);
break;
case SaveTextureFileFormat.JPG:
encoded = ImageConversion.EncodeNativeArrayToJPG(narray, resizeRT.graphicsFormat, (uint)width, (uint)height, 0, jpgQuality);
break;
case SaveTextureFileFormat.TGA:
encoded = ImageConversion.EncodeNativeArrayToTGA(narray, resizeRT.graphicsFormat, (uint)width, (uint)height);
break;
default:
encoded = ImageConversion.EncodeNativeArrayToPNG(narray, resizeRT.graphicsFormat, (uint)width, (uint)height);
break;
}
System.IO.File.WriteAllBytes(filePath, encoded.ToArray());
encoded.Dispose();
}
narray.Dispose();
// notify the user that the operation is done, and its outcome.
done?.Invoke(!req.hasError);
});
if (!asynchronous)
request.WaitForCompletion();
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace KFCommonUtilityLib
{
public class TemporaryMuzzleFlash : TemporaryObject
{
private void OnDisable()
{
StopAllCoroutines();
if (destroyMaterials)
{
Utils.CleanupMaterialsOfRenderers<Renderer[]>(transform.GetComponentsInChildren<Renderer>());
}
Destroy(gameObject);
}
}
}

View File

@@ -0,0 +1,5 @@
public class TypeBasedUID<T>
{
private static int uid = 0;
public static int UID { get => uid++; }
}