Upload from upload_mods.ps1
This commit is contained in:
18
Scripts/Attributes/ActionDataTargetAttribute.cs
Normal file
18
Scripts/Attributes/ActionDataTargetAttribute.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
45
Scripts/Attributes/MethodTargetAttribute.cs
Normal file
45
Scripts/Attributes/MethodTargetAttribute.cs
Normal 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())));
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Scripts/Attributes/PatchTargetAttribute.cs
Normal file
16
Scripts/Attributes/PatchTargetAttribute.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
14
Scripts/Attributes/TypeTargetExtensionAttribute.cs
Normal file
14
Scripts/Attributes/TypeTargetExtensionAttribute.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
175
Scripts/ConsoleCmd/ConsoleCmdCalibrateWeapon.cs
Normal file
175
Scripts/ConsoleCmd/ConsoleCmdCalibrateWeapon.cs
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
57
Scripts/ConsoleCmd/ConsoleCmdListParticleScripts.cs
Normal file
57
Scripts/ConsoleCmd/ConsoleCmdListParticleScripts.cs
Normal 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.";
|
||||
}
|
||||
}
|
||||
59
Scripts/ConsoleCmd/ConsoleCmdMultiActionItemValueDebug.cs
Normal file
59
Scripts/ConsoleCmd/ConsoleCmdMultiActionItemValueDebug.cs
Normal 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.";
|
||||
}
|
||||
}
|
||||
}
|
||||
59
Scripts/ConsoleCmd/ConsoleCmdPlayerDebugInfo.cs
Normal file
59
Scripts/ConsoleCmd/ConsoleCmdPlayerDebugInfo.cs
Normal 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.";
|
||||
}
|
||||
}
|
||||
}
|
||||
41
Scripts/ConsoleCmd/ConsoleCmdPrintLocalCache.cs
Normal file
41
Scripts/ConsoleCmd/ConsoleCmdPrintLocalCache.cs
Normal 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.";
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Scripts/ConsoleCmd/ConsoleCmdReloadLog.cs
Normal file
28
Scripts/ConsoleCmd/ConsoleCmdReloadLog.cs
Normal 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
753
Scripts/FbxExporter07.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Scripts/Global/CustomEnums.cs
Normal file
28
Scripts/Global/CustomEnums.cs
Normal 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
|
||||
}
|
||||
46
Scripts/Input/PlayerActionKFLib.cs
Normal file
46
Scripts/Input/PlayerActionKFLib.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
38
Scripts/Input/PlayerActionToggleFireMode.cs
Normal file
38
Scripts/Input/PlayerActionToggleFireMode.cs
Normal 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 });
|
||||
}
|
||||
}
|
||||
38
Scripts/Input/PlayerActionToggleMode.cs
Normal file
38
Scripts/Input/PlayerActionToggleMode.cs
Normal 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 });
|
||||
}
|
||||
}
|
||||
208
Scripts/Items/ItemActionAltMode.cs
Normal file
208
Scripts/Items/ItemActionAltMode.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
172
Scripts/Items/ItemActionHoldOpen.cs
Normal file
172
Scripts/Items/ItemActionHoldOpen.cs
Normal 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);
|
||||
// }
|
||||
// */
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
188
Scripts/Items/ItemActionRampUp.cs
Normal file
188
Scripts/Items/ItemActionRampUp.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
76
Scripts/Items/ItemActionRechargeable.cs
Normal file
76
Scripts/Items/ItemActionRechargeable.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
261
Scripts/Items/Modular/ActionModuleAlternative.cs
Normal file
261
Scripts/Items/Modular/ActionModuleAlternative.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
Scripts/Items/Modular/ActionModuleAnimationLocked.cs
Normal file
36
Scripts/Items/Modular/ActionModuleAnimationLocked.cs
Normal 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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
135
Scripts/Items/Modular/ActionModuleCustomAnimationDelay.cs
Normal file
135
Scripts/Items/Modular/ActionModuleCustomAnimationDelay.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
78
Scripts/Items/Modular/ActionModuleDisplayAsBuff.cs
Normal file
78
Scripts/Items/Modular/ActionModuleDisplayAsBuff.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
67
Scripts/Items/Modular/ActionModuleDynamicGraze.cs
Normal file
67
Scripts/Items/Modular/ActionModuleDynamicGraze.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
84
Scripts/Items/Modular/ActionModuleDynamicMuzzleFlash.cs
Normal file
84
Scripts/Items/Modular/ActionModuleDynamicMuzzleFlash.cs
Normal 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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
89
Scripts/Items/Modular/ActionModuleDynamicSensitivity.cs
Normal file
89
Scripts/Items/Modular/ActionModuleDynamicSensitivity.cs
Normal 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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
153
Scripts/Items/Modular/ActionModuleErgoAffected.cs
Normal file
153
Scripts/Items/Modular/ActionModuleErgoAffected.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
335
Scripts/Items/Modular/ActionModuleFireModeSelector.cs
Normal file
335
Scripts/Items/Modular/ActionModuleFireModeSelector.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
85
Scripts/Items/Modular/ActionModuleHoldOpen.cs
Normal file
85
Scripts/Items/Modular/ActionModuleHoldOpen.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
24
Scripts/Items/Modular/ActionModuleInspectable.cs
Normal file
24
Scripts/Items/Modular/ActionModuleInspectable.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
225
Scripts/Items/Modular/ActionModuleInterruptReload.cs
Normal file
225
Scripts/Items/Modular/ActionModuleInterruptReload.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
60
Scripts/Items/Modular/ActionModuleInvariableRPM.cs
Normal file
60
Scripts/Items/Modular/ActionModuleInvariableRPM.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
105
Scripts/Items/Modular/ActionModuleLocalPassiveCache.cs
Normal file
105
Scripts/Items/Modular/ActionModuleLocalPassiveCache.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
178
Scripts/Items/Modular/ActionModuleMetaConsumer.cs
Normal file
178
Scripts/Items/Modular/ActionModuleMetaConsumer.cs
Normal 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;
|
||||
//}
|
||||
}
|
||||
166
Scripts/Items/Modular/ActionModuleMetaRecharger.cs
Normal file
166
Scripts/Items/Modular/ActionModuleMetaRecharger.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
222
Scripts/Items/Modular/ActionModuleMultiActionFix.cs
Normal file
222
Scripts/Items/Modular/ActionModuleMultiActionFix.cs
Normal 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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
302
Scripts/Items/Modular/ActionModuleMultiBarrel.cs
Normal file
302
Scripts/Items/Modular/ActionModuleMultiBarrel.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
276
Scripts/Items/Modular/ActionModuleRampUp.cs
Normal file
276
Scripts/Items/Modular/ActionModuleRampUp.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Scripts/Items/Modular/ActionModuleTagged.cs
Normal file
30
Scripts/Items/Modular/ActionModuleTagged.cs
Normal 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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Scripts/Items/Modular/ActionModuleTranspilerTest.cs
Normal file
48
Scripts/Items/Modular/ActionModuleTranspilerTest.cs
Normal 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()}");
|
||||
}
|
||||
}
|
||||
73
Scripts/Items/Modular/ActionModuleVariableZoom.cs
Normal file
73
Scripts/Items/Modular/ActionModuleVariableZoom.cs
Normal 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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
261
Scripts/Items/ModularActions/ActionModuleAlternative.cs
Normal file
261
Scripts/Items/ModularActions/ActionModuleAlternative.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
Scripts/Items/ModularActions/ActionModuleAnimationLocked.cs
Normal file
36
Scripts/Items/ModularActions/ActionModuleAnimationLocked.cs
Normal 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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
135
Scripts/Items/ModularActions/ActionModuleCustomAnimationDelay.cs
Normal file
135
Scripts/Items/ModularActions/ActionModuleCustomAnimationDelay.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
78
Scripts/Items/ModularActions/ActionModuleDisplayAsBuff.cs
Normal file
78
Scripts/Items/ModularActions/ActionModuleDisplayAsBuff.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
67
Scripts/Items/ModularActions/ActionModuleDynamicGraze.cs
Normal file
67
Scripts/Items/ModularActions/ActionModuleDynamicGraze.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
157
Scripts/Items/ModularActions/ActionModuleErgoAffected.cs
Normal file
157
Scripts/Items/ModularActions/ActionModuleErgoAffected.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
336
Scripts/Items/ModularActions/ActionModuleFireModeSelector.cs
Normal file
336
Scripts/Items/ModularActions/ActionModuleFireModeSelector.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
85
Scripts/Items/ModularActions/ActionModuleHoldOpen.cs
Normal file
85
Scripts/Items/ModularActions/ActionModuleHoldOpen.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
24
Scripts/Items/ModularActions/ActionModuleInspectable.cs
Normal file
24
Scripts/Items/ModularActions/ActionModuleInspectable.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
225
Scripts/Items/ModularActions/ActionModuleInterruptReload.cs
Normal file
225
Scripts/Items/ModularActions/ActionModuleInterruptReload.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
60
Scripts/Items/ModularActions/ActionModuleInvariableRPM.cs
Normal file
60
Scripts/Items/ModularActions/ActionModuleInvariableRPM.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
105
Scripts/Items/ModularActions/ActionModuleLocalPassiveCache.cs
Normal file
105
Scripts/Items/ModularActions/ActionModuleLocalPassiveCache.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
178
Scripts/Items/ModularActions/ActionModuleMetaConsumer.cs
Normal file
178
Scripts/Items/ModularActions/ActionModuleMetaConsumer.cs
Normal 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;
|
||||
//}
|
||||
}
|
||||
166
Scripts/Items/ModularActions/ActionModuleMetaRecharger.cs
Normal file
166
Scripts/Items/ModularActions/ActionModuleMetaRecharger.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
222
Scripts/Items/ModularActions/ActionModuleMultiActionFix.cs
Normal file
222
Scripts/Items/ModularActions/ActionModuleMultiActionFix.cs
Normal 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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
302
Scripts/Items/ModularActions/ActionModuleMultiBarrel.cs
Normal file
302
Scripts/Items/ModularActions/ActionModuleMultiBarrel.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
271
Scripts/Items/ModularActions/ActionModuleProceduralAiming.cs
Normal file
271
Scripts/Items/ModularActions/ActionModuleProceduralAiming.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
276
Scripts/Items/ModularActions/ActionModuleRampUp.cs
Normal file
276
Scripts/Items/ModularActions/ActionModuleRampUp.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Scripts/Items/ModularActions/ActionModuleTagged.cs
Normal file
30
Scripts/Items/ModularActions/ActionModuleTagged.cs
Normal 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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Scripts/Items/ModularActions/ActionModuleTranspilerTest.cs
Normal file
48
Scripts/Items/ModularActions/ActionModuleTranspilerTest.cs
Normal 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()}");
|
||||
}
|
||||
}
|
||||
260
Scripts/Items/ModularActions/ActionModuleVariableZoom.cs
Normal file
260
Scripts/Items/ModularActions/ActionModuleVariableZoom.cs
Normal 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()}");
|
||||
//}
|
||||
}
|
||||
87
Scripts/Items/ModularClasses/ItemModuleSortable.cs
Normal file
87
Scripts/Items/ModularClasses/ItemModuleSortable.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
6
Scripts/Items/ModularClasses/ItemModuleVariableZoom.cs
Normal file
6
Scripts/Items/ModularClasses/ItemModuleVariableZoom.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
using KFCommonUtilityLib.Scripts.Attributes;
|
||||
|
||||
[TypeTarget(typeof(ItemClass))]
|
||||
public class ItemModuleVariableZoom
|
||||
{
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
21
Scripts/MinEventActions/MinEventActionAddItemToInventory.cs
Normal file
21
Scripts/MinEventActions/MinEventActionAddItemToInventory.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
28
Scripts/MinEventActions/MinEventActionAddRoundsToMagazine.cs
Normal file
28
Scripts/MinEventActions/MinEventActionAddRoundsToMagazine.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
56
Scripts/MinEventActions/MinEventActionAmmoAccessBase.cs
Normal file
56
Scripts/MinEventActions/MinEventActionAmmoAccessBase.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
248
Scripts/MinEventActions/MinEventActionCVarExpression.cs
Normal file
248
Scripts/MinEventActions/MinEventActionCVarExpression.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Scripts/MinEventActions/MinEventActionItemAccessBase.cs
Normal file
23
Scripts/MinEventActions/MinEventActionItemAccessBase.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
60
Scripts/MinEventActions/MinEventActionItemCountRandomBase.cs
Normal file
60
Scripts/MinEventActions/MinEventActionItemCountRandomBase.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
101
Scripts/MinEventActions/MinEventActionOverrideZoomFOV.cs
Normal file
101
Scripts/MinEventActions/MinEventActionOverrideZoomFOV.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
31
Scripts/MinEventActions/MinEventActionRemoteHoldingBase.cs
Normal file
31
Scripts/MinEventActions/MinEventActionRemoteHoldingBase.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
51
Scripts/MinEventActions/MinEventActionSetWeaponLabelColor.cs
Normal file
51
Scripts/MinEventActions/MinEventActionSetWeaponLabelColor.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
49
Scripts/MinEventActions/MinEventActionUpdateLocalCache.cs
Normal file
49
Scripts/MinEventActions/MinEventActionUpdateLocalCache.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
42
Scripts/NetPackages/NetPackageEntityActionIndex.cs
Normal file
42
Scripts/NetPackages/NetPackageEntityActionIndex.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
81
Scripts/NetPackages/NetPackageEntitySpawnWithCVar.cs
Normal file
81
Scripts/NetPackages/NetPackageEntitySpawnWithCVar.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
49
Scripts/NetPackages/NetPackageFixedReload.cs
Normal file
49
Scripts/NetPackages/NetPackageFixedReload.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
69
Scripts/NetPackages/NetPackageRemoteAttachPrefab.cs
Normal file
69
Scripts/NetPackages/NetPackageRemoteAttachPrefab.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
117
Scripts/NetPackages/NetPackageSyncWeaponLabelColor.cs
Normal file
117
Scripts/NetPackages/NetPackageSyncWeaponLabelColor.cs
Normal 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;
|
||||
}
|
||||
|
||||
82
Scripts/NetPackages/NetPackageSyncWeaponLabelText.cs
Normal file
82
Scripts/NetPackages/NetPackageSyncWeaponLabelText.cs
Normal 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;
|
||||
}
|
||||
|
||||
52
Scripts/Requirements/ActionHasTags.cs
Normal file
52
Scripts/Requirements/ActionHasTags.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
31
Scripts/Requirements/ActionIndexIs.cs
Normal file
31
Scripts/Requirements/ActionIndexIs.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
44
Scripts/Requirements/AmmoIndexIs.cs
Normal file
44
Scripts/Requirements/AmmoIndexIs.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
32
Scripts/Requirements/FireModeIs.cs
Normal file
32
Scripts/Requirements/FireModeIs.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
9
Scripts/Requirements/HoldingActionIndexIs.cs
Normal file
9
Scripts/Requirements/HoldingActionIndexIs.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
8
Scripts/Requirements/HoldingAmmoIndexIs.cs
Normal file
8
Scripts/Requirements/HoldingAmmoIndexIs.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
public class HoldingAmmoIndexIs : AmmoIndexIs
|
||||
{
|
||||
public override bool ParamsValid(MinEventParams _params)
|
||||
{
|
||||
itemValueCache = _params.Self?.inventory?.holdingItemItemValue;
|
||||
return itemValueCache != null;
|
||||
}
|
||||
}
|
||||
33
Scripts/Requirements/HoldingFireModeIs.cs
Normal file
33
Scripts/Requirements/HoldingFireModeIs.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
28
Scripts/Requirements/IsActionUnlocked.cs
Normal file
28
Scripts/Requirements/IsActionUnlocked.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
64
Scripts/Requirements/IsHoldingItemModificationActivated.cs
Normal file
64
Scripts/Requirements/IsHoldingItemModificationActivated.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
15
Scripts/Requirements/IsInJeep.cs
Normal file
15
Scripts/Requirements/IsInJeep.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
7
Scripts/Requirements/IsLocal.cs
Normal file
7
Scripts/Requirements/IsLocal.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
63
Scripts/Requirements/IsModificationActivated.cs
Normal file
63
Scripts/Requirements/IsModificationActivated.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
7
Scripts/Requirements/ItemActionIndexIs.cs
Normal file
7
Scripts/Requirements/ItemActionIndexIs.cs
Normal 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;
|
||||
// }
|
||||
//}
|
||||
33
Scripts/Requirements/ItemInInventory.cs
Normal file
33
Scripts/Requirements/ItemInInventory.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
17
Scripts/Requirements/PercentInHoldingItem.cs
Normal file
17
Scripts/Requirements/PercentInHoldingItem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
14
Scripts/Requirements/PercentInMagazine.cs
Normal file
14
Scripts/Requirements/PercentInMagazine.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
17
Scripts/Requirements/RoundsInHoldingItem.cs
Normal file
17
Scripts/Requirements/RoundsInHoldingItem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
32
Scripts/Requirements/RoundsInInventory.cs
Normal file
32
Scripts/Requirements/RoundsInInventory.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
21
Scripts/Requirements/RoundsInMagazineBase.cs
Normal file
21
Scripts/Requirements/RoundsInMagazineBase.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
437
Scripts/StaticManagers/AnimationRiggingManager.cs
Normal file
437
Scripts/StaticManagers/AnimationRiggingManager.cs
Normal 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}");
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
117
Scripts/StaticManagers/BackgroundInventoryUpdateManager.cs
Normal file
117
Scripts/StaticManagers/BackgroundInventoryUpdateManager.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
215
Scripts/StaticManagers/CustomEffectEnumManager.cs
Normal file
215
Scripts/StaticManagers/CustomEffectEnumManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
95
Scripts/StaticManagers/DelayLoadModuleManager.cs
Normal file
95
Scripts/StaticManagers/DelayLoadModuleManager.cs
Normal 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);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
937
Scripts/StaticManagers/ItemActionModuleManager.cs
Normal file
937
Scripts/StaticManagers/ItemActionModuleManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
257
Scripts/StaticManagers/LocalItemTagsManager.cs
Normal file
257
Scripts/StaticManagers/LocalItemTagsManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
600
Scripts/StaticManagers/MultiActionManager.cs
Normal file
600
Scripts/StaticManagers/MultiActionManager.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
370
Scripts/StaticManagers/RecoilManager.cs
Normal file
370
Scripts/StaticManagers/RecoilManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
125
Scripts/Utilities/EasingFunctions.cs
Normal file
125
Scripts/Utilities/EasingFunctions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
67
Scripts/Utilities/EntityInventoryExtension.cs
Normal file
67
Scripts/Utilities/EntityInventoryExtension.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
424
Scripts/Utilities/ExpressionParser/ExpressionParser.cs
Normal file
424
Scripts/Utilities/ExpressionParser/ExpressionParser.cs
Normal 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}")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Scripts/Utilities/ExpressionParser/FloatExpressionParser.cs
Normal file
38
Scripts/Utilities/ExpressionParser/FloatExpressionParser.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
124
Scripts/Utilities/ExpressionParser/Sprache/CommentParser.cs
Normal file
124
Scripts/Utilities/ExpressionParser/Sprache/CommentParser.cs
Normal 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 { }
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Scripts/Utilities/ExpressionParser/Sprache/IComment.cs
Normal file
43
Scripts/Utilities/ExpressionParser/Sprache/IComment.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
26
Scripts/Utilities/ExpressionParser/Sprache/ICommentedOfT.cs
Normal file
26
Scripts/Utilities/ExpressionParser/Sprache/ICommentedOfT.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
53
Scripts/Utilities/ExpressionParser/Sprache/IInput.cs
Normal file
53
Scripts/Utilities/ExpressionParser/Sprache/IInput.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
18
Scripts/Utilities/ExpressionParser/Sprache/IPositionAware.cs
Normal file
18
Scripts/Utilities/ExpressionParser/Sprache/IPositionAware.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
36
Scripts/Utilities/ExpressionParser/Sprache/IResultOfT.cs
Normal file
36
Scripts/Utilities/ExpressionParser/Sprache/IResultOfT.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
29
Scripts/Utilities/ExpressionParser/Sprache/ITextSpanOfT.cs
Normal file
29
Scripts/Utilities/ExpressionParser/Sprache/ITextSpanOfT.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
156
Scripts/Utilities/ExpressionParser/Sprache/Input.cs
Normal file
156
Scripts/Utilities/ExpressionParser/Sprache/Input.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
145
Scripts/Utilities/ExpressionParser/Sprache/Option.cs
Normal file
145
Scripts/Utilities/ExpressionParser/Sprache/Option.cs
Normal 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<T>"/>.
|
||||
/// </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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
126
Scripts/Utilities/ExpressionParser/Sprache/Parse.Commented.cs
Normal file
126
Scripts/Utilities/ExpressionParser/Sprache/Parse.Commented.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
80
Scripts/Utilities/ExpressionParser/Sprache/Parse.Optional.cs
Normal file
80
Scripts/Utilities/ExpressionParser/Sprache/Parse.Optional.cs
Normal 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);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
158
Scripts/Utilities/ExpressionParser/Sprache/Parse.Sequence.cs
Normal file
158
Scripts/Utilities/ExpressionParser/Sprache/Parse.Sequence.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
782
Scripts/Utilities/ExpressionParser/Sprache/Parse.cs
Normal file
782
Scripts/Utilities/ExpressionParser/Sprache/Parse.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
50
Scripts/Utilities/ExpressionParser/Sprache/ParseException.cs
Normal file
50
Scripts/Utilities/ExpressionParser/Sprache/ParseException.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Scripts/Utilities/ExpressionParser/Sprache/ParserOfT.cs
Normal file
54
Scripts/Utilities/ExpressionParser/Sprache/ParserOfT.cs
Normal 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<T>" />.
|
||||
/// </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));
|
||||
}
|
||||
}
|
||||
}
|
||||
136
Scripts/Utilities/ExpressionParser/Sprache/Position.cs
Normal file
136
Scripts/Utilities/ExpressionParser/Sprache/Position.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
111
Scripts/Utilities/ExpressionParser/Sprache/Result.cs
Normal file
111
Scripts/Utilities/ExpressionParser/Sprache/Result.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UniLinq;
|
||||
|
||||
namespace Sprache
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains helper functions to create <see cref="IResult<T>"/> 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<T>"/>.</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<T>"/>.</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);
|
||||
}
|
||||
}
|
||||
}
|
||||
26
Scripts/Utilities/ExpressionParser/Sprache/ResultHelper.cs
Normal file
26
Scripts/Utilities/ExpressionParser/Sprache/ResultHelper.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Scripts/Utilities/ExpressionParser/Sprache/licence.txt
Normal file
21
Scripts/Utilities/ExpressionParser/Sprache/licence.txt
Normal 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.
|
||||
19
Scripts/Utilities/Modular/IModuleProcessor.cs
Normal file
19
Scripts/Utilities/Modular/IModuleProcessor.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
80
Scripts/Utilities/Modular/ItemActionDataModuleProcessor.cs
Normal file
80
Scripts/Utilities/Modular/ItemActionDataModuleProcessor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
104
Scripts/Utilities/Modular/ItemActionModuleManager.cs
Normal file
104
Scripts/Utilities/Modular/ItemActionModuleManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
157
Scripts/Utilities/Modular/ItemActionModuleProcessor.cs
Normal file
157
Scripts/Utilities/Modular/ItemActionModuleProcessor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
104
Scripts/Utilities/Modular/ItemClassModuleManager.cs
Normal file
104
Scripts/Utilities/Modular/ItemClassModuleManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
Scripts/Utilities/Modular/ItemClassModuleProcessor.cs
Normal file
33
Scripts/Utilities/Modular/ItemClassModuleProcessor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
262
Scripts/Utilities/Modular/ModuleManagers.cs
Normal file
262
Scripts/Utilities/Modular/ModuleManagers.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
646
Scripts/Utilities/Modular/ModuleManipulator.cs
Normal file
646
Scripts/Utilities/Modular/ModuleManipulator.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
67
Scripts/Utilities/Modular/ModuleUtils.cs
Normal file
67
Scripts/Utilities/Modular/ModuleUtils.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
1685
Scripts/Utilities/MonoCecilExtensions.cs
Normal file
1685
Scripts/Utilities/MonoCecilExtensions.cs
Normal 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
|
||||
}
|
||||
738
Scripts/Utilities/MultiActionProjectileRewrites.cs
Normal file
738
Scripts/Utilities/MultiActionProjectileRewrites.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
445
Scripts/Utilities/MultiActionUtils.cs
Normal file
445
Scripts/Utilities/MultiActionUtils.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
134
Scripts/Utilities/SaveTextureToFileUtility.cs
Normal file
134
Scripts/Utilities/SaveTextureToFileUtility.cs
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
22
Scripts/Utilities/TemporaryMuzzleFlash.cs
Normal file
22
Scripts/Utilities/TemporaryMuzzleFlash.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
5
Scripts/Utilities/TypeBasedUID.cs
Normal file
5
Scripts/Utilities/TypeBasedUID.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
public class TypeBasedUID<T>
|
||||
{
|
||||
private static int uid = 0;
|
||||
public static int UID { get => uid++; }
|
||||
}
|
||||
Reference in New Issue
Block a user