commit 7345f42201a1a5bc02b8b38d55cf3e208fd2ad0a Author: Nathaniel Cosford Date: Wed Jun 4 16:13:32 2025 +0930 Upload from upload_mods.ps1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f6a2d21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,80 @@ +# Build and Object Folders +bin/ +obj/ + +# Nuget packages directory +packages/ + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Rr]elease/ +x64/ +*_i.c +*_p.c +*.ilk +*.obj +*.pch +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.log +*.vspscc +*.vssscc +.builds + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper* + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help +UpgradeLog*.XML + +# Lightswitch +_Pvt_Extensions +GeneratedArtifacts +*.xap +ModelManifest.xml + +#Backup file +*.bak + +#zzzili +v15/ +AssemblyOutput/ +AssemblyOutput/** diff --git a/Config/Localization.txt b/Config/Localization.txt new file mode 100644 index 0000000..d18bee6 --- /dev/null +++ b/Config/Localization.txt @@ -0,0 +1,31 @@ +Key,File,Type,UsedInMainMenu,english,schinese +inpGrpMultiActionName,UI,Controls Dialog,x,Multi Action,武器模式 +inpActToggleWeaponModeName,UI,Controls Dialog,x,Toggle Weapon Mode,切换武器模式 +inpActToggleWeaponModeDesc,UI,Controls Dialog,x,"For weapons with multiple modes, cycle between them with this key",使用有多种模式的武器时,在不同模式间循环切换 +inpGrpFireModeName,UI,Controls Dialog,x,Firing Mode,射击模式 +inpActToggleFireModeName,UI,Controls Dialog,x,Toggle Firing Mode,切换射击模式 +inpActToggleFireModeDesc,UI,Controls Dialog,x,"For weapons with multiple firing modes, cycle between them with this key",使用有多种射击模式的武器时,在不同模式间循环切换 +ttCurrentFiringMode,UI,Controls Dialog,x,"Firing Mode: {0}","射击模式:{0}" +ttFiringModeSemiAuto,UI,Controls Dialog,x,Single,半自动 +ttFiringModeBurst2,UI,Controls Dialog,x,2-round Burst,两连发 +ttFiringModeBurst3,UI,Controls Dialog,x,3-round Burst,三连发 +ttFiringModeFullAuto,UI,Controls Dialog,x,Full-Auto,全自动 +ttFiringModePumpAction,UI,Controls Dialog,x,Pump Action,泵动 +kflibTabRecoilSettingsName,UI,Menu,x,Recoil Settings,后坐力设置 +kflibSettingRecoilCSMName,UI,Menu,x,Sensitivity Multiplier,灵敏度倍率 +kflibSettingRecoilCSMDesc,UI,Menu,x,How much sensitivity compensation should be applied when dragging your mouse against the recoil while aiming. Easier recoil control for low zoom sensitivity users.,指示在瞄准状态下抵消后坐力时要应用多少灵敏度补正。帮助低瞄准灵敏度的用户更好地控制后坐力。 +kflibSettingEnablePreRecoilCompensationName,UI,Menu,x,Enable Pre-Recoil Compensation,启用后坐力提前补偿 +kflibSettingEnablePreRecoilCompensationDesc,UI,Menu,x,Allow mouse movement to negate incoming recoil before it takes effect.,允许在后坐力生效之前使用鼠标移动将其抵消。 +kflibCategoryRecoilCappingName,UI,Menu,x,Recoil Cap Settings,后坐力上限设置 +kflibSettingEnableCapName,UI,Menu,x,Enable Recoil Cap,启用后坐力上限 +kflibSettingEnableCapDesc,UI,Menu,x,Allow capping vertical recoil at certain angle threshold.,允许使用垂直后坐力角度上限。 +kflibSettingRecoilRemainName,UI,Menu,x,Recoil Multiplier,过量后坐力倍率 +kflibSettingRecoilRemainDesc,UI,Menu,x,Recoil multiplier after reaching the cap.,达到后坐力上限后的后坐力倍率。 +kflibSettingEnableSoftCapName,UI,Menu,x,Enable Soft Cap,启用平滑后坐力过渡 +kflibSettingEnableSoftCapDesc,UI,Menu,x,"Lerp towards recoil multiplier before reaching cap, resulting in a much smoother recoil pattern.",在达到上限之前就开始逐步降低后坐力,使后坐力表现更加平滑。 +kflibSettingMaxRecoilAngleName,UI,Menu,x,Max Recoil Angle,后坐力角度上限 +kflibSettingMaxRecoilAngleDesc,UI,Menu,x,A fixed max angle to trigger recoil cap. Only works when dynamic recoil is off.,设置固定角度的上限。仅在动态上限禁用时生效。 +kflibSettingEnableDynamicCapName,UI,Menu,x,Enable Dynamic Recoil Cap,启用动态上限 +kflibSettingEnableDynamicCapDesc,UI,Menu,x,Trigger recoil cap after certain shots.,设置基于射击次数的上限。 +kflibSettingMaxDynamicRecoilCapShotsName,UI,Menu,x,Max Shots Before Capping,最大射击次数 +kflibSettingMaxDynamicRecoilCapShotsDesc,UI,Menu,x,"Trigger recoil cap after certain shots. This is a rough value, and the actual cap angle is calculated by the max random kick angle per shot.",在射击指定次数后触发后坐力上限。上限角度是根据最大随机角度计算的,所以实际射击次数不一定与设置完全一致。 \ No newline at end of file diff --git a/Config/buffs.xml b/Config/buffs.xml new file mode 100644 index 0000000..de1765c --- /dev/null +++ b/Config/buffs.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CustomPlayerActionManager.dll b/CustomPlayerActionManager.dll new file mode 100644 index 0000000..3ac6ad7 Binary files /dev/null and b/CustomPlayerActionManager.dll differ diff --git a/CustomPlayerActionManager.pdb b/CustomPlayerActionManager.pdb new file mode 100644 index 0000000..91854ea Binary files /dev/null and b/CustomPlayerActionManager.pdb differ diff --git a/DOTween.Modules.dll b/DOTween.Modules.dll new file mode 100644 index 0000000..dbf31d2 Binary files /dev/null and b/DOTween.Modules.dll differ diff --git a/DOTween.Modules.pdb b/DOTween.Modules.pdb new file mode 100644 index 0000000..8cb1d77 Binary files /dev/null and b/DOTween.Modules.pdb differ diff --git a/DOTween.XML b/DOTween.XML new file mode 100644 index 0000000..ceb16e4 --- /dev/null +++ b/DOTween.XML @@ -0,0 +1,3077 @@ + + + + DOTween + + + + + Types of autoPlay behaviours + + + + No tween is automatically played + + + Only Sequences are automatically played + + + Only Tweeners are automatically played + + + All tweens are automatically played + + + + What axis to constrain in case of Vector tweens + + + + Called the first time the tween is set in a playing state, after any eventual delay + + + + Used in place of System.Func, which is not available in mscorlib. + + + + + Used in place of System.Action. + + + + + Public so it can be used by lose scripts related to DOTween (like DOTweenAnimation) + + + + + Used to separate DOTween class from the MonoBehaviour instance (in order to use static constructors on DOTween). + Contains all instance-based methods + + + + Used internally inside Unity Editor, as a trick to update DOTween's inspector at every frame + + + + Directly sets the current max capacity of Tweeners and Sequences + (meaning how many Tweeners and Sequences can be running at the same time), + so that DOTween doesn't need to automatically increase them in case the max is reached + (which might lead to hiccups when that happens). + Sequences capacity must be less or equal to Tweeners capacity + (if you pass a low Tweener capacity it will be automatically increased to match the Sequence's). + Beware: use this method only when there are no tweens running. + + Max Tweeners capacity. + Default: 200 + Max Sequences capacity. + Default: 50 + + + + This class contains a C# port of the easing equations created by Robert Penner (http://robertpenner.com/easing). + + + + + Easing equation function for a bounce (exponentially decaying parabolic bounce) easing in: accelerating from zero velocity. + + + Current time (in frames or seconds). + + + Expected easing duration (in frames or seconds). + + Unused: here to keep same delegate for all ease types. + Unused: here to keep same delegate for all ease types. + + The eased value. + + + + + Easing equation function for a bounce (exponentially decaying parabolic bounce) easing out: decelerating from zero velocity. + + + Current time (in frames or seconds). + + + Expected easing duration (in frames or seconds). + + Unused: here to keep same delegate for all ease types. + Unused: here to keep same delegate for all ease types. + + The eased value. + + + + + Easing equation function for a bounce (exponentially decaying parabolic bounce) easing in/out: acceleration until halfway, then deceleration. + + + Current time (in frames or seconds). + + + Expected easing duration (in frames or seconds). + + Unused: here to keep same delegate for all ease types. + Unused: here to keep same delegate for all ease types. + + The eased value. + + + + + Returns a value between 0 and 1 (inclusive) based on the elapsed time and ease selected + + + + + Returns a value between 0 and 1 (inclusive) based on the elapsed time and ease selected + + + + + Used to interpret AnimationCurves as eases. + Public so it can be used by external ease factories + + + + + Behaviour in case a tween nested inside a Sequence fails and is captured by safe mode + + + + If the Sequence contains other elements, kill the failed tween but preserve the rest + + + Kill the whole Sequence + + + + Log types thrown by errors captured and prevented by safe mode + + + + No logs. NOT RECOMMENDED + + + Throw a normal log + + + Throw a warning log (default) + + + Throw an error log + + + + Additional notices passed to plugins when updating. + Public so it can be used by custom plugins. Internally, only PathPlugin uses it + + + + + None + + + + + Lets the plugin know that we restarted or rewinded + + + + + OnRewind callback behaviour (can only be set via DOTween's Utility Panel) + + + + + When calling Rewind or PlayBackwards/SmoothRewind, OnRewind callbacks will be fired only if the tween isn't already rewinded + + + + + When calling Rewind, OnRewind callbacks will always be fired, even if the tween is already rewinded. + When calling PlayBackwards/SmoothRewind instead, OnRewind callbacks will be fired only if the tween isn't already rewinded + + + + + When calling Rewind or PlayBackwards/SmoothRewind, OnRewind callbacks will always be fired, even if the tween is already rewinded + + + + + Public only so custom shortcuts can access some of these methods + + + + + INTERNAL: used by DO shortcuts and Modules to set special startup mode + + + + + INTERNAL: used by DO shortcuts and Modules to set the tween as blendable + + + + + INTERNAL: used by DO shortcuts and Modules to prevent a tween from using a From setup even if passed + + + + + Used to dispatch commands that need to be captured externally, usually by Modules + + + + + Various utils + + + + + Returns a Vector3 with z = 0 + + + + + Returns the 2D angle between two vectors + + + + + Returns a point on a circle with the given center and radius, + using Unity's circle coordinates (0° points up and increases clockwise) + + + + + Uses approximate equality on each axis instead of Unity's Vector3 equality, + because the latter fails (in some cases) when assigning a Vector3 to a transform.position and then checking it. + + + + + Looks for the type within all possible project assembly names + + + + NO-GC METHOD: changes the start value of a tween and rewinds it (without pausing it). + Has no effect with tweens that are inside Sequences + The new start value + If bigger than 0 applies it as the new tween duration + + + NO-GC METHOD: changes the end value of a tween and rewinds it (without pausing it). + Has no effect with tweens that are inside Sequences + The new end value + If TRUE the start value will become the current target's value, otherwise it will stay the same + + + NO-GC METHOD: changes the end value of a tween and rewinds it (without pausing it). + Has no effect with tweens that are inside Sequences + The new end value + If bigger than 0 applies it as the new tween duration + If TRUE the start value will become the current target's value, otherwise it will stay the same + + + NO-GC METHOD: changes the start and end value of a tween and rewinds it (without pausing it). + Has no effect with tweens that are inside Sequences + The new start value + The new end value + If bigger than 0 applies it as the new tween duration + + + + Struct that stores two colors (used for LineRenderer tweens) + + + + + Used for tween callbacks + + + + + Used for tween callbacks + + + + + Used for custom and animationCurve-based ease functions. Must return a value between 0 and 1. + + + + + Straight Quaternion plugin. Instead of using Vector3 values accepts Quaternion values directly. + Beware: doesn't work with LoopType.Incremental (neither directly nor if inside a LoopType.Incremental Sequence). + To use it, call DOTween.To with the plugin parameter overload, passing it PureQuaternionPlugin.Plug() as first parameter + (do not use any of the other public PureQuaternionPlugin methods): + DOTween.To(PureQuaternionPlugin.Plug(), ()=> myQuaternionProperty, x=> myQuaternionProperty = x, myQuaternionEndValue, duration); + + + + + Plug this plugin inside a DOTween.To call. + Example: + DOTween.To(PureQuaternionPlugin.Plug(), ()=> myQuaternionProperty, x=> myQuaternionProperty = x, myQuaternionEndValue, duration); + + + + INTERNAL: do not use + + + INTERNAL: do not use + + + INTERNAL: do not use + + + INTERNAL: do not use + + + INTERNAL: do not use + + + INTERNAL: do not use + + + INTERNAL: do not use + + + INTERNAL: do not use + + + + Extra non-tweening-related curve methods + + + + + Cubic bezier curve methods + + + + + Calculates a point along the given Cubic Bezier segment-curve. + + Segment start point + Start point's control point/handle + Segment end point + End point's control point/handle + 0-1 percentage along which to retrieve point + + + + Returns an array containing a series of points along the given Cubic Bezier segment-curve. + + Start point + Start point's control point/handle + End point + End point's control point/handle + Cloud resolution (min: 2) + + + + Calculates a series of points along the given Cubic Bezier segment-curve and adds them to the given list. + + Start point + Start point's control point/handle + End point + End point's control point/handle + Cloud resolution (min: 2) + + + + Main DOTween class. Contains static methods to create and control tweens in a generic way + + + + DOTween's version + + + If TRUE (default) makes tweens slightly slower but safer, automatically taking care of a series of things + (like targets becoming null while a tween is playing). + Default: TRUE + + + Log type when safe mode reports capturing an error and preventing it + + + Behaviour in case a tween nested inside a Sequence fails (and is caught by safe mode). + Default: NestedTweenFailureBehaviour.TryToPreserveSequence + + + If TRUE you will get a DOTween report when exiting play mode (only in the Editor). + Useful to know how many max Tweeners and Sequences you reached and optimize your final project accordingly. + Beware, this will slightly slow down your tweens while inside Unity Editor. + Default: FALSE + + + Global DOTween global timeScale (default: 1). + The final timeScale of a non-timeScaleIndependent tween is: + Unity's Time.timeScale * DOTween.timeScale * tween.timeScale + while the final timeScale of a timeScaleIndependent tween is: + DOTween.unscaledTimeScale * DOTween.timeScale * tween.timeScale + + + DOTween timeScale applied only to timeScaleIndependent tweens (default: 1). + The final timeScale of a timeScaleIndependent tween is: + DOTween.unscaledTimeScale * DOTween.timeScale * tween.timeScale + + + If TRUE, DOTween will use Time.smoothDeltaTime instead of Time.deltaTime for UpdateType.Normal and UpdateType.Late tweens + (unless they're set as timeScaleIndependent, in which case a value between the last timestep + and will be used instead). + Setting this to TRUE will lead to smoother animations. + Default: FALSE + + + If is TRUE, this indicates the max timeStep that an independent update call can last. + Setting this to TRUE will lead to smoother animations. + Default: FALSE + + + DOTween's log behaviour. + Default: LogBehaviour.ErrorsOnly + + + Used to intercept DOTween's logs. If this method isn't NULL, DOTween will call it before writing a log via Unity's own Debug log methods. + Return TRUE if you want DOTween to proceed with the log, FALSE otherwise. + This method must return a bool and accept two parameters: + - LogType: the type of Unity log that DOTween is trying to log + - object: the log message that DOTween wants to log + + + If TRUE draws path gizmos in Unity Editor (if the gizmos button is active). + Deactivate this if you want to avoid gizmos overhead while in Unity Editor + + + If TRUE activates various debug options + + + Stores the target id so it can be used to give more info in case of safeMode error capturing. + Only active if both debugMode and useSafeMode are TRUE + + + Default updateType for new tweens. + Default: UpdateType.Normal + + + Sets whether Unity's timeScale should be taken into account by default or not. + Default: false + + + Default autoPlay behaviour for new tweens. + Default: AutoPlay.All + + + Default autoKillOnComplete behaviour for new tweens. + Default: TRUE + + + Default loopType applied to all new tweens. + Default: LoopType.Restart + + + If TRUE all newly created tweens are set as recyclable, otherwise not. + Default: FALSE + + + Default ease applied to all new Tweeners (not to Sequences which always have Ease.Linear as default). + Default: Ease.InOutQuad + + + Default overshoot/amplitude used for eases + Default: 1.70158f + + + Default period used for eases + Default: 0 + + + Used internally. Assigned/removed by DOTweenComponent.Create/DestroyInstance + + + + Must be called once, before the first ever DOTween call/reference, + otherwise it will be called automatically and will use default options. + Calling it a second time won't have any effect. + You can chain SetCapacity to this method, to directly set the max starting size of Tweeners and Sequences: + DOTween.Init(false, false, LogBehaviour.Default).SetCapacity(100, 20); + + If TRUE all new tweens will be set for recycling, meaning that when killed, + instead of being destroyed, they will be put in a pool and reused instead of creating new tweens. This option allows you to avoid + GC allocations by reusing tweens, but you will have to take care of tween references, since they might result active + even if they were killed (since they might have been respawned and are now being used for other tweens). + If you want to automatically set your tween references to NULL when a tween is killed + you can use the OnKill callback like this: + .OnKill(()=> myTweenReference = null) + You can change this setting at any time by changing the static property, + or you can set the recycling behaviour for each tween separately, using: + SetRecyclable(bool recyclable) + Default: FALSE + If TRUE makes tweens slightly slower but safer, automatically taking care of a series of things + (like targets becoming null while a tween is playing). + You can change this setting at any time by changing the static property. + Default: FALSE + Type of logging to use. + You can change this setting at any time by changing the static property. + Default: ErrorsOnly + + + + Directly sets the current max capacity of Tweeners and Sequences + (meaning how many Tweeners and Sequences can be running at the same time), + so that DOTween doesn't need to automatically increase them in case the max is reached + (which might lead to hiccups when that happens). + Sequences capacity must be less or equal to Tweeners capacity + (if you pass a low Tweener capacity it will be automatically increased to match the Sequence's). + Beware: use this method only when there are no tweens running. + + Max Tweeners capacity. + Default: 200 + Max Sequences capacity. + Default: 50 + + + + Kills all tweens, clears all cached tween pools and plugins and resets the max Tweeners/Sequences capacities to the default values. + + If TRUE also destroys DOTween's gameObject and resets its initializiation, default settings and everything else + (so that next time you use it it will need to be re-initialized) + + + + Clears all cached tween pools. + + + + + Checks all active tweens to find and remove eventually invalid ones (usually because their targets became NULL) + and returns the total number of invalid tweens found and removed. + IMPORTANT: this will cause an error on UWP platform, so don't use it there + BEWARE: this is a slightly expensive operation so use it with care + + + + + Updates all tweens that are set to . + + Manual deltaTime + Unscaled delta time (used with tweens set as timeScaleIndependent) + + + Tweens a property or field to the given value using default plugins + A getter for the field or property to tween. + Example usage with lambda:()=> myProperty + A setter for the field or property to tween + Example usage with lambda:x=> myProperty = x + The end value to reachThe tween's duration + + + Tweens a property or field to the given value using default plugins + A getter for the field or property to tween. + Example usage with lambda:()=> myProperty + A setter for the field or property to tween + Example usage with lambda:x=> myProperty = x + The end value to reachThe tween's duration + + + Tweens a property or field to the given value using default plugins + A getter for the field or property to tween. + Example usage with lambda:()=> myProperty + A setter for the field or property to tween + Example usage with lambda:x=> myProperty = x + The end value to reachThe tween's duration + + + Tweens a property or field to the given value using default plugins + A getter for the field or property to tween. + Example usage with lambda:()=> myProperty + A setter for the field or property to tween + Example usage with lambda:x=> myProperty = x + The end value to reachThe tween's duration + + + Tweens a property or field to the given value using default plugins + A getter for the field or property to tween. + Example usage with lambda:()=> myProperty + A setter for the field or property to tween + Example usage with lambda:x=> myProperty = x + The end value to reachThe tween's duration + + + Tweens a property or field to the given value using default plugins + A getter for the field or property to tween. + Example usage with lambda:()=> myProperty + A setter for the field or property to tween + Example usage with lambda:x=> myProperty = x + The end value to reachThe tween's duration + + + Tweens a property or field to the given value using default plugins + A getter for the field or property to tween. + Example usage with lambda:()=> myProperty + A setter for the field or property to tween + Example usage with lambda:x=> myProperty = x + The end value to reachThe tween's duration + + + Tweens a property or field to the given value using default plugins + A getter for the field or property to tween. + Example usage with lambda:()=> myProperty + A setter for the field or property to tween + Example usage with lambda:x=> myProperty = x + The end value to reachThe tween's duration + + + Tweens a property or field to the given value using default plugins + A getter for the field or property to tween. + Example usage with lambda:()=> myProperty + A setter for the field or property to tween + Example usage with lambda:x=> myProperty = x + The end value to reachThe tween's duration + + + Tweens a property or field to the given value using default plugins + A getter for the field or property to tween. + Example usage with lambda:()=> myProperty + A setter for the field or property to tween + Example usage with lambda:x=> myProperty = x + The end value to reachThe tween's duration + + + Tweens a property or field to the given value using default plugins + A getter for the field or property to tween. + Example usage with lambda:()=> myProperty + A setter for the field or property to tween + Example usage with lambda:x=> myProperty = x + The end value to reachThe tween's duration + + + Tweens a property or field to the given value using default plugins + A getter for the field or property to tween. + Example usage with lambda:()=> myProperty + A setter for the field or property to tween + Example usage with lambda:x=> myProperty = x + The end value to reachThe tween's duration + + + Tweens a property or field to the given value using default plugins + A getter for the field or property to tween. + Example usage with lambda:()=> myProperty + A setter for the field or property to tween + Example usage with lambda:x=> myProperty = x + The end value to reachThe tween's duration + + + Tweens a property or field to the given value using default plugins + A getter for the field or property to tween. + Example usage with lambda:()=> myProperty + A setter for the field or property to tween + Example usage with lambda:x=> myProperty = x + The end value to reachThe tween's duration + + + Tweens a property or field to the given value using a custom plugin + The plugin to use. Each custom plugin implements a static Get() method + you'll need to call to assign the correct plugin in the correct way, like this: + CustomPlugin.Get() + A getter for the field or property to tween. + Example usage with lambda:()=> myProperty + A setter for the field or property to tween + Example usage with lambda:x=> myProperty = x + The end value to reachThe tween's duration + + + Tweens only one axis of a Vector3 to the given value using default plugins. + A getter for the field or property to tween. + Example usage with lambda:()=> myProperty + A setter for the field or property to tween + Example usage with lambda:x=> myProperty = x + The end value to reachThe tween's duration + The axis to tween + + + Tweens only the alpha of a Color to the given value using default plugins + A getter for the field or property to tween. + Example usage with lambda:()=> myProperty + A setter for the field or property to tween + Example usage with lambda:x=> myProperty = x + The end value to reachThe tween's duration + + + Tweens a virtual property from the given start to the given end value + and implements a setter that allows to use that value with an external method or a lambda + Example: + To(MyMethod, 0, 12, 0.5f); + Where MyMethod is a function that accepts a float parameter (which will be the result of the virtual tween) + The action to perform with the tweened value + The value to start from + The end value to reach + The duration of the virtual tween + + + + Punches a Vector3 towards the given direction and then back to the starting one + as if it was connected to the starting position via an elastic. + This tween type generates some GC allocations at startup + A getter for the field or property to tween. + Example usage with lambda:()=> myProperty + A setter for the field or property to tween + Example usage with lambda:x=> myProperty = x + The direction and strength of the punch + The duration of the tween + Indicates how much will the punch vibrate + Represents how much (0 to 1) the vector will go beyond the starting position when bouncing backwards. + 1 creates a full oscillation between the direction and the opposite decaying direction, + while 0 oscillates only between the starting position and the decaying direction + + + Shakes a Vector3 with the given values. + A getter for the field or property to tween. + Example usage with lambda:()=> myProperty + A setter for the field or property to tween + Example usage with lambda:x=> myProperty = x + The duration of the tween + The shake strength + Indicates how much will the shake vibrate + Indicates how much the shake will be random (0 to 180 - values higher than 90 kind of suck, so beware). + Setting it to 0 will shake along a single direction and behave like a random punch. + If TRUE only shakes on the X Y axis (looks better with things like cameras). + If TRUE the shake will automatically fadeOut smoothly within the tween's duration, otherwise it will not + Randomness mode + + + Shakes a Vector3 with the given values. + A getter for the field or property to tween. + Example usage with lambda:()=> myProperty + A setter for the field or property to tween + Example usage with lambda:x=> myProperty = x + The duration of the tween + The shake strength on each axis + Indicates how much will the shake vibrate + Indicates how much the shake will be random (0 to 180 - values higher than 90 kind of suck, so beware). + Setting it to 0 will shake along a single direction and behave like a random punch. + If TRUE the shake will automatically fadeOut smoothly within the tween's duration, otherwise it will not + Randomness mode + + + Tweens a property or field to the given values using default plugins. + Ease is applied between each segment and not as a whole. + This tween type generates some GC allocations at startup + A getter for the field or property to tween. + Example usage with lambda:()=> myProperty + A setter for the field or property to tween + Example usage with lambda:x=> myProperty = x + The end values to reach for each segment. This array must have the same length as durations + The duration of each segment. This array must have the same length as endValues + + + + Returns a new to be used for tween groups. + Mind that Sequences don't have a target applied automatically like Tweener creation shortcuts, + so if you want to be able to kill this Sequence when calling DOTween.Kill(target) you'll have to add + the target manually; you can do that directly by using the overload instead of this one + + + + + Returns a new to be used for tween groups, and allows to set a target + (because Sequences don't have their target set automatically like Tweener creation shortcuts). + That way killing/controlling tweens by target will apply to this Sequence too. + + The target of the Sequence. Relevant only for static target-based methods like DOTween.Kill(target), + useless otherwise + + + Completes all tweens and returns the number of actual tweens completed + (meaning tweens that don't have infinite loops and were not already complete) + For Sequences only: if TRUE also internal Sequence callbacks will be fired, + otherwise they will be ignored + + + Completes all tweens with the given ID or target and returns the number of actual tweens completed + (meaning the tweens that don't have infinite loops and were not already complete) + For Sequences only: if TRUE internal Sequence callbacks will be fired, + otherwise they will be ignored + + + Flips all tweens (changing their direction to forward if it was backwards and viceversa), + then returns the number of actual tweens flipped + + + Flips the tweens with the given ID or target (changing their direction to forward if it was backwards and viceversa), + then returns the number of actual tweens flipped + + + Sends all tweens to the given position (calculating also eventual loop cycles) and returns the actual tweens involved + + + Sends all tweens with the given ID or target to the given position (calculating also eventual loop cycles) + and returns the actual tweens involved + + + Kills all tweens and returns the number of actual tweens killed + If TRUE completes the tweens before killing them + + + Kills all tweens and returns the number of actual tweens killed + If TRUE completes the tweens before killing them + Eventual IDs or targets to exclude from the killing + + + Kills all tweens with the given ID or target and returns the number of actual tweens killed + If TRUE completes the tweens before killing them + + + Kills all tweens with the given target and the given ID, and returns the number of actual tweens killed + If TRUE completes the tweens before killing them + + + Pauses all tweens and returns the number of actual tweens paused + + + Pauses all tweens with the given ID or target and returns the number of actual tweens paused + (meaning the tweens that were actually playing and have been paused) + + + Plays all tweens and returns the number of actual tweens played + (meaning tweens that were not already playing or complete) + + + Plays all tweens with the given ID or target and returns the number of actual tweens played + (meaning the tweens that were not already playing or complete) + + + Plays all tweens with the given target and the given ID, and returns the number of actual tweens played + (meaning the tweens that were not already playing or complete) + + + Plays backwards all tweens and returns the number of actual tweens played + (meaning tweens that were not already started, playing backwards or rewinded) + + + Plays backwards all tweens with the given ID or target and returns the number of actual tweens played + (meaning the tweens that were not already started, playing backwards or rewinded) + + + Plays backwards all tweens with the given target and ID and returns the number of actual tweens played + (meaning the tweens that were not already started, playing backwards or rewinded) + + + Plays forward all tweens and returns the number of actual tweens played + (meaning tweens that were not already playing forward or complete) + + + Plays forward all tweens with the given ID or target and returns the number of actual tweens played + (meaning the tweens that were not already playing forward or complete) + + + Plays forward all tweens with the given target and ID and returns the number of actual tweens played + (meaning the tweens that were not already started, playing backwards or rewinded) + + + Restarts all tweens, then returns the number of actual tweens restarted + + + Restarts all tweens with the given ID or target, then returns the number of actual tweens restarted + If TRUE includes the eventual tweens delays, otherwise skips them + If >= 0 changes the startup delay of all involved tweens to this value, otherwise doesn't touch it + + + Restarts all tweens with the given target and the given ID, and returns the number of actual tweens played + (meaning the tweens that were not already playing or complete) + If TRUE includes the eventual tweens delays, otherwise skips them + If >= 0 changes the startup delay of all involved tweens to this value, otherwise doesn't touch it + + + Rewinds and pauses all tweens, then returns the number of actual tweens rewinded + (meaning tweens that were not already rewinded) + + + Rewinds and pauses all tweens with the given ID or target, then returns the number of actual tweens rewinded + (meaning the tweens that were not already rewinded) + + + Smoothly rewinds all tweens (delays excluded), then returns the number of actual tweens rewinding/rewinded + (meaning tweens that were not already rewinded). + A "smooth rewind" animates the tween to its start position, + skipping all elapsed loops (except in case of LoopType.Incremental) while keeping the animation fluent. + Note that a tween that was smoothly rewinded will have its play direction flipped + + + Smoothly rewinds all tweens (delays excluded) with the given ID or target, then returns the number of actual tweens rewinding/rewinded + (meaning the tweens that were not already rewinded). + A "smooth rewind" animates the tween to its start position, + skipping all elapsed loops (except in case of LoopType.Incremental) while keeping the animation fluent. + Note that a tween that was smoothly rewinded will have its play direction flipped + + + Toggles the play state of all tweens and returns the number of actual tweens toggled + (meaning tweens that could be played or paused, depending on the toggle state) + + + Toggles the play state of all tweens with the given ID or target and returns the number of actual tweens toggled + (meaning the tweens that could be played or paused, depending on the toggle state) + + + + Returns TRUE if a tween with the given ID or target is active. + You can also use this to know if a shortcut tween is active for a given target. + Example: + transform.DOMoveX(45, 1); // transform is automatically added as the tween target + DOTween.IsTweening(transform); // Returns true + + The target or ID to look for + If FALSE (default) returns TRUE as long as a tween for the given target/ID is active, + otherwise also requires it to be playing + + + + Returns the total number of active tweens (so both Tweeners and Sequences). + A tween is considered active if it wasn't killed, regardless if it's playing or paused + + + + + Returns the total number of active Tweeners. + A Tweener is considered active if it wasn't killed, regardless if it's playing or paused + + + + + Returns the total number of active Sequences. + A Sequence is considered active if it wasn't killed, regardless if it's playing or paused + + + + + Returns the total number of active and playing tweens. + A tween is considered as playing even if its delay is actually playing + + + + + Returns a the total number of active tweens with the given id. + + If TRUE returns only the tweens with the given ID that are currently playing + + + + Returns a list of all active tweens in a playing state. + Returns NULL if there are no active playing tweens. + Beware: each time you call this method a new list is generated, so use it for debug only + + If NULL creates a new list, otherwise clears and fills this one (and thus saves allocations) + + + + Returns a list of all active tweens in a paused state. + Returns NULL if there are no active paused tweens. + Beware: each time you call this method a new list is generated, so use it for debug only + + If NULL creates a new list, otherwise clears and fills this one (and thus saves allocations) + + + + Returns a list of all active tweens with the given id. + Returns NULL if there are no active tweens with the given id. + Beware: each time you call this method a new list is generated + + If TRUE returns only the tweens with the given ID that are currently playing + If NULL creates a new list, otherwise clears and fills this one (and thus saves allocations) + + + + Returns a list of all active tweens with the given target. + Returns NULL if there are no active tweens with the given target. + Beware: each time you call this method a new list is generated + If TRUE returns only the tweens with the given target that are currently playing + If NULL creates a new list, otherwise clears and fills this one (and thus saves allocations) + + + + + Creates virtual tweens that can be used to change other elements via their OnUpdate calls + + + + + Tweens a virtual float. + You can add regular settings to the generated tween, + but do not use OnUpdate or you will overwrite the onVirtualUpdate parameter + + The value to start from + The value to tween to + The duration of the tween + A callback which must accept a parameter of type float, called at each update + + + + Tweens a virtual int. + You can add regular settings to the generated tween, + but do not use OnUpdate or you will overwrite the onVirtualUpdate parameter + + The value to start from + The value to tween to + The duration of the tween + A callback which must accept a parameter of type int, called at each update + + + + Tweens a virtual Vector2. + You can add regular settings to the generated tween, + but do not use OnUpdate or you will overwrite the onVirtualUpdate parameter + + The value to start from + The value to tween to + The duration of the tween + A callback which must accept a parameter of type Vector3, called at each update + + + + Tweens a virtual Vector3. + You can add regular settings to the generated tween, + but do not use OnUpdate or you will overwrite the onVirtualUpdate parameter + + The value to start from + The value to tween to + The duration of the tween + A callback which must accept a parameter of type Vector3, called at each update + + + + Tweens a virtual Color. + You can add regular settings to the generated tween, + but do not use OnUpdate or you will overwrite the onVirtualUpdate parameter + + The value to start from + The value to tween to + The duration of the tween + A callback which must accept a parameter of type Color, called at each update + + + Returns a value based on the given ease and lifetime percentage (0 to 1) + The value to start from when lifetimePercentage is 0 + The value to reach when lifetimePercentage is 1 + The time percentage (0 to 1) at which the value should be taken + The type of ease + + + Returns a value based on the given ease and lifetime percentage (0 to 1) + The value to start from when lifetimePercentage is 0 + The value to reach when lifetimePercentage is 1 + The time percentage (0 to 1) at which the value should be taken + The type of ease + Eventual overshoot to use with Back ease + + + Returns a value based on the given ease and lifetime percentage (0 to 1) + The value to start from when lifetimePercentage is 0 + The value to reach when lifetimePercentage is 1 + The time percentage (0 to 1) at which the value should be taken + The type of ease + Eventual amplitude to use with Elastic easeType + Eventual period to use with Elastic easeType + + + Returns a value based on the given ease and lifetime percentage (0 to 1) + The value to start from when lifetimePercentage is 0 + The value to reach when lifetimePercentage is 1 + The time percentage (0 to 1) at which the value should be taken + The AnimationCurve to use for ease + + + Returns a value based on the given ease and lifetime percentage (0 to 1) + The value to start from when lifetimePercentage is 0 + The value to reach when lifetimePercentage is 1 + The time percentage (0 to 1) at which the value should be taken + The type of ease + + + Returns a value based on the given ease and lifetime percentage (0 to 1) + The value to start from when lifetimePercentage is 0 + The value to reach when lifetimePercentage is 1 + The time percentage (0 to 1) at which the value should be taken + The type of ease + Eventual overshoot to use with Back ease + + + Returns a value based on the given ease and lifetime percentage (0 to 1) + The value to start from when lifetimePercentage is 0 + The value to reach when lifetimePercentage is 1 + The time percentage (0 to 1) at which the value should be taken + The type of ease + Eventual amplitude to use with Elastic easeType + Eventual period to use with Elastic easeType + + + Returns a value based on the given ease and lifetime percentage (0 to 1) + The value to start from when lifetimePercentage is 0 + The value to reach when lifetimePercentage is 1 + The time percentage (0 to 1) at which the value should be taken + The AnimationCurve to use for ease + + + Fires the given callback after the given time. + Callback delay + Callback to fire when the delay has expired + If TRUE (default) ignores Unity's timeScale + + + + Don't assign this! It's assigned automatically when creating 0 duration tweens + + + + + Don't assign this! It's assigned automatically when setting the ease to an AnimationCurve or to a custom ease function + + + + + Allows to wrap ease method in special ways, adding extra features + + + + + Converts the given ease so that it also creates a stop-motion effect, by playing the tween at the given FPS + + FPS at which the tween should be played + Ease type + + + + Converts the given ease so that it also creates a stop-motion effect, by playing the tween at the given FPS + + FPS at which the tween should be played + AnimationCurve to use for the ease + + + + Converts the given ease so that it also creates a stop-motion effect, by playing the tween at the given FPS + + FPS at which the tween should be played + Custom ease function to use + + + + Used to allow method chaining with DOTween.Init + + + + + Directly sets the current max capacity of Tweeners and Sequences + (meaning how many Tweeners and Sequences can be running at the same time), + so that DOTween doesn't need to automatically increase them in case the max is reached + (which might lead to hiccups when that happens). + Sequences capacity must be less or equal to Tweeners capacity + (if you pass a low Tweener capacity it will be automatically increased to match the Sequence's). + Beware: use this method only when there are no tweens running. + + Max Tweeners capacity. + Default: 200 + Max Sequences capacity. + Default: 50 + + + + Behaviour that can be assigned when chaining a SetLink to a tween + + + + Pauses the tween when the link target is disabled + + + Pauses the tween when the link target is disabled, plays it when it's enabled + + + Pauses the tween when the link target is disabled, restarts it when it's enabled + + + Plays the tween when the link target is enabled + + + Restarts the tween when the link target is enabled + + + Kills the tween when the link target is disabled + + + Kills the tween when the link target is destroyed (becomes NULL). This is always active even if another behaviour is chosen + + + Completes the tween when the link target is disabled + + + Completes and kills the tween when the link target is disabled + + + Rewinds the tween (delay excluded) when the link target is disabled + + + Rewinds and kills the tween when the link target is disabled + + + + Path mode (used to determine correct LookAt orientation) + + + + Ignores the path mode (and thus LookAt behaviour) + + + Regular 3D path + + + 2D top-down path + + + 2D side-scroller path + + + + Type of path to use with DOPath tweens + + + + Linear, composed of straight segments between each waypoint + + + Curved path (which uses Catmull-Rom curves) + + + EXPERIMENTAL: Curved path (which uses Cubic Bezier curves, where each point requires two extra control points) + + + + Tweens a Vector2 along a circle. + EndValue represents the center of the circle, start and end value degrees are inside options + ChangeValue x is changeValue°, y is unused + + + + + Path control point + + + + + Path waypoints (modified by PathPlugin when setting relative end/change value or by CubicBezierDecoder) and by DOTweenPathInspector + + + + + Minimum input points necessary to create the path (doesn't correspond to actual waypoints required) + + + + + Gets the point on the path at the given percentage (0 to 1) + + The percentage (0 to 1) at which to get the point + If TRUE constant speed is taken into account, otherwise not + + + + Base interface for all tween plugins options + + + + Resets the plugin + + + + This plugin generates some GC allocations at startup + + + + + Path plugin works exclusively with Transforms + + + + + Rotation mode used with DORotate methods + + + + + Fastest way that never rotates beyond 360° + + + + + Fastest way that rotates beyond 360° + + + + + Adds the given rotation to the transform using world axis and an advanced precision mode + (like when using transform.Rotate(Space.World)). + In this mode the end value is is always considered relative + + + + + Adds the given rotation to the transform's local axis + (like when rotating an object with the "local" switch enabled in Unity's editor or using transform.Rotate(Space.Self)). + In this mode the end value is is always considered relative + + + + + Type of scramble to apply to string tweens + + + + + No scrambling of characters + + + + + A-Z + a-z + 0-9 characters + + + + + A-Z characters + + + + + a-z characters + + + + + 0-9 characters + + + + + Custom characters + + + + + Type of randomness to apply to a shake tween + + + + Default, full randomness + + + Creates a more balanced randomness that looks more harmonic + + + + Methods that extend Tween objects and allow to control or get data from them + + + + Completes the tween + + + Completes the tween + For Sequences only: if TRUE also internal Sequence callbacks will be fired, + otherwise they will be ignored + + + Flips the direction of this tween (backwards if it was going forward or viceversa) + + + Forces the tween to initialize its settings immediately + + + Send the tween to the given position in time + Time position to reach + (if higher than the whole tween duration the tween will simply reach its end) + If TRUE will play the tween after reaching the given position, otherwise it will pause it + + + Send the tween to the given position in time while also executing any callback between the previous time position and the new one + Time position to reach + (if higher than the whole tween duration the tween will simply reach its end) + If TRUE will play the tween after reaching the given position, otherwise it will pause it + + + Kills the tween + If TRUE completes the tween before killing it + + + + Forces this tween to update manually, regardless of the set via SetUpdate. + Note that the tween will still be subject to normal tween rules, so if for example it's paused this method will do nothing. + Also note that if you only want to update this tween instance manually you'll have to set it to anyway, + so that it's not updated automatically. + + Manual deltaTime + Unscaled delta time (used with tweens set as timeScaleIndependent) + + + Pauses the tween + + + Plays the tween + + + Sets the tween in a backwards direction and plays it + + + Sets the tween in a forward direction and plays it + + + Restarts the tween from the beginning + Ignored in case of Sequences. If TRUE includes the eventual tween delay, otherwise skips it + Ignored in case of Sequences. If >= 0 changes the startup delay to this value, otherwise doesn't touch it + + + Rewinds and pauses the tween + Ignored in case of Sequences. If TRUE includes the eventual tween delay, otherwise skips it + + + Smoothly rewinds the tween (delays excluded). + A "smooth rewind" animates the tween to its start position, + skipping all elapsed loops (except in case of LoopType.Incremental) while keeping the animation fluent. + If called on a tween who is still waiting for its delay to happen, it will simply set the delay to 0 and pause the tween. + Note that a tween that was smoothly rewinded will have its play direction flipped + + + Plays the tween if it was paused, pauses it if it was playing + + + Send a path tween to the given waypoint. + Has no effect if this is not a path tween. + BEWARE, this is a special utility method: + it works only with Linear eases. Also, the lookAt direction might be wrong after calling this and might need to be set manually + (because it relies on a smooth path movement and doesn't work well with jumps that encompass dramatic direction changes) + Waypoint index to reach + (if higher than the max waypoint index the tween will simply go to the last one) + If TRUE will play the tween after reaching the given waypoint, otherwise it will pause it + + + + Creates a yield instruction that waits until the tween is killed or complete. + It can be used inside a coroutine as a yield. + Example usage:yield return myTween.WaitForCompletion(); + + + + + Creates a yield instruction that waits until the tween is killed or rewinded. + It can be used inside a coroutine as a yield. + Example usage:yield return myTween.WaitForRewind(); + + + + + Creates a yield instruction that waits until the tween is killed. + It can be used inside a coroutine as a yield. + Example usage:yield return myTween.WaitForKill(); + + + + + Creates a yield instruction that waits until the tween is killed or has gone through the given amount of loops. + It can be used inside a coroutine as a yield. + Example usage:yield return myTween.WaitForElapsedLoops(2); + + Elapsed loops to wait for + + + + Creates a yield instruction that waits until the tween is killed or has reached the given position (loops included, delays excluded). + It can be used inside a coroutine as a yield. + Example usage:yield return myTween.WaitForPosition(2.5f); + + Position (loops included, delays excluded) to wait for + + + + Creates a yield instruction that waits until the tween is killed or started + (meaning when the tween is set in a playing state the first time, after any eventual delay). + It can be used inside a coroutine as a yield. + Example usage:yield return myTween.WaitForStart(); + + + + Returns the total number of loops completed by this tween + + + Returns the eventual delay set for this tween + + + Returns the eventual elapsed delay set for this tween + + + Returns the duration of this tween (delays excluded). + NOTE: when using settings like SpeedBased, the duration will be recalculated when the tween starts + If TRUE returns the full duration loops included, + otherwise the duration of a single loop cycle + + + Returns the elapsed time for this tween (delays exluded) + If TRUE returns the elapsed time since startup loops included, + otherwise the elapsed time within the current loop cycle + + + Returns the elapsed percentage (0 to 1) of this tween (delays exluded) + If TRUE returns the elapsed percentage since startup loops included, + otherwise the elapsed percentage within the current loop cycle + + + Returns the elapsed percentage (0 to 1) of this tween (delays exluded), + based on a single loop, and calculating eventual backwards Yoyo loops as 1 to 0 instead of 0 to 1 + + + Returns FALSE if this tween has been killed or is NULL, TRUE otherwise. + BEWARE: if this tween is recyclable it might have been spawned again for another use and thus return TRUE anyway. + When working with recyclable tweens you should take care to know when a tween has been killed and manually set your references to NULL. + If you want to be sure your references are set to NULL when a tween is killed you can use the OnKill callback like this: + .OnKill(()=> myTweenReference = null) + + + Returns TRUE if this tween was reversed and is set to go backwards + + + NOTE: To check if a tween was simply set to go backwards see . + Returns TRUE if this tween is going backwards for any of these reasons: + - The tween was reversed and is going backwards on a straight loop + - The tween was reversed and is going backwards on an odd Yoyo loop + - The tween is going forward but on an even Yoyo loop + IMPORTANT: if used inside a tween's callback, this will return a result concerning the exact frame when it's asked, + so for example in a callback at the end of a Yoyo loop step this method will never return FALSE + because the frame will never end exactly there and the tween will already be going backwards when the callback is fired + + + Returns TRUE if the tween is complete + (silently fails and returns FALSE if the tween has been killed) + + + Returns TRUE if this tween has been initialized + + + Returns TRUE if this tween is playing + + + Returns the total number of loops set for this tween + (returns -1 if the loops are infinite) + + + + Returns a point on a path based on the given path percentage. + Returns Vector3.zero if this is not a path tween, if the tween is invalid, or if the path is not yet initialized. + A path is initialized after its tween starts, or immediately if the tween was created with the Path Editor (DOTween Pro feature). + You can force a path to be initialized by calling myTween.ForceInit(). + + Percentage of the path (0 to 1) on which to get the point + + + + Returns an array of points that can be used to draw the path. + Note that this method generates allocations, because it creates a new array. + Returns NULL if this is not a path tween, if the tween is invalid, or if the path is not yet initialized. + A path is initialized after its tween starts, or immediately if the tween was created with the Path Editor (DOTween Pro feature). + You can force a path to be initialized by calling myTween.ForceInit(). + + How many points to create for each path segment (waypoint to waypoint). + Only used in case of non-Linear paths + + + + Returns the length of a path. + Returns -1 if this is not a path tween, if the tween is invalid, or if the path is not yet initialized. + A path is initialized after its tween starts, or immediately if the tween was created with the Path Editor (DOTween Pro feature). + You can force a path to be initialized by calling myTween.ForceInit(). + + + + + Types of loop + + + + Each loop cycle restarts from the beginning + + + The tween moves forward and backwards at alternate cycles + + + Continuously increments the tween at the end of each loop cycle (A to B, B to B+(A-B), and so on), thus always moving "onward". + In case of String tweens works only if the tween is set as relative + + + + Controls other tweens as a group + + + + + Methods that extend known Unity objects and allow to directly create and control tweens from their instances + + + + Tweens a Camera's aspect to the given value. + Also stores the camera as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + + + Tweens a Camera's backgroundColor to the given value. + Also stores the camera as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + + + Tweens a Camera's farClipPlane to the given value. + Also stores the camera as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + + + Tweens a Camera's fieldOfView to the given value. + Also stores the camera as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + + + Tweens a Camera's nearClipPlane to the given value. + Also stores the camera as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + + + Tweens a Camera's orthographicSize to the given value. + Also stores the camera as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + + + Tweens a Camera's pixelRect to the given value. + Also stores the camera as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + + + Tweens a Camera's rect to the given value. + Also stores the camera as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + + + Shakes a Camera's localPosition along its relative X Y axes with the given values. + Also stores the camera as the tween's target so it can be used for filtered operations + The duration of the tween + The shake strength + Indicates how much will the shake vibrate + Indicates how much the shake will be random (0 to 180 - values higher than 90 kind of suck, so beware). + Setting it to 0 will shake along a single direction. + If TRUE the shake will automatically fadeOut smoothly within the tween's duration, otherwise it will not + Randomness mode + + + Shakes a Camera's localPosition along its relative X Y axes with the given values. + Also stores the camera as the tween's target so it can be used for filtered operations + The duration of the tween + The shake strength on each axis + Indicates how much will the shake vibrate + Indicates how much the shake will be random (0 to 180 - values higher than 90 kind of suck, so beware). + Setting it to 0 will shake along a single direction. + If TRUE the shake will automatically fadeOut smoothly within the tween's duration, otherwise it will not + Randomness mode + + + Shakes a Camera's localRotation. + Also stores the camera as the tween's target so it can be used for filtered operations + The duration of the tween + The shake strength + Indicates how much will the shake vibrate + Indicates how much the shake will be random (0 to 180 - values higher than 90 kind of suck, so beware). + Setting it to 0 will shake along a single direction. + If TRUE the shake will automatically fadeOut smoothly within the tween's duration, otherwise it will not + Randomness mode + + + Shakes a Camera's localRotation. + Also stores the camera as the tween's target so it can be used for filtered operations + The duration of the tween + The shake strength on each axis + Indicates how much will the shake vibrate + Indicates how much the shake will be random (0 to 180 - values higher than 90 kind of suck, so beware). + Setting it to 0 will shake along a single direction. + If TRUE the shake will automatically fadeOut smoothly within the tween's duration, otherwise it will not + Randomness mode + + + Tweens a Light's color to the given value. + Also stores the light as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + + + Tweens a Light's intensity to the given value. + Also stores the light as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + + + Tweens a Light's shadowStrength to the given value. + Also stores the light as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + + + Tweens a LineRenderer's color to the given value. + Also stores the LineRenderer as the tween's target so it can be used for filtered operations. + Note that this method requires to also insert the start colors for the tween, + since LineRenderers have no way to get them. + The start value to tween from + The end value to reachThe duration of the tween + + + Tweens a Material's color to the given value. + Also stores the material as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + + + Tweens a Material's named color property to the given value. + Also stores the material as the tween's target so it can be used for filtered operations + The end value to reach + The name of the material property to tween (like _Tint or _SpecColor) + The duration of the tween + + + Tweens a Material's named color property with the given ID to the given value. + Also stores the material as the tween's target so it can be used for filtered operations + The end value to reach + The ID of the material property to tween (also called nameID in Unity's manual) + The duration of the tween + + + Tweens a Material's alpha color to the given value + (will have no effect unless your material supports transparency). + Also stores the material as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + + + Tweens a Material's alpha color to the given value + (will have no effect unless your material supports transparency). + Also stores the material as the tween's target so it can be used for filtered operations + The end value to reach + The name of the material property to tween (like _Tint or _SpecColor) + The duration of the tween + + + Tweens a Material's alpha color with the given ID to the given value + (will have no effect unless your material supports transparency). + Also stores the material as the tween's target so it can be used for filtered operations + The end value to reach + The ID of the material property to tween (also called nameID in Unity's manual) + The duration of the tween + + + Tweens a Material's named float property to the given value. + Also stores the material as the tween's target so it can be used for filtered operations + The end value to reach + The name of the material property to tween + The duration of the tween + + + Tweens a Material's named float property with the given ID to the given value. + Also stores the material as the tween's target so it can be used for filtered operations + The end value to reach + The ID of the material property to tween (also called nameID in Unity's manual) + The duration of the tween + + + Tweens a Material's texture offset to the given value. + Also stores the material as the tween's target so it can be used for filtered operations + The end value to reach + The duration of the tween + + + Tweens a Material's named texture offset property to the given value. + Also stores the material as the tween's target so it can be used for filtered operations + The end value to reach + The name of the material property to tween + The duration of the tween + + + Tweens a Material's texture scale to the given value. + Also stores the material as the tween's target so it can be used for filtered operations + The end value to reach + The duration of the tween + + + Tweens a Material's named texture scale property to the given value. + Also stores the material as the tween's target so it can be used for filtered operations + The end value to reach + The name of the material property to tween + The duration of the tween + + + Tweens a Material's named Vector property to the given value. + Also stores the material as the tween's target so it can be used for filtered operations + The end value to reach + The name of the material property to tween + The duration of the tween + + + Tweens a Material's named Vector property with the given ID to the given value. + Also stores the material as the tween's target so it can be used for filtered operations + The end value to reach + The ID of the material property to tween (also called nameID in Unity's manual) + The duration of the tween + + + Tweens a TrailRenderer's startWidth/endWidth to the given value. + Also stores the TrailRenderer as the tween's target so it can be used for filtered operations + The end startWidth to reachThe end endWidth to reach + The duration of the tween + + + Tweens a TrailRenderer's time to the given value. + Also stores the TrailRenderer as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + + + Tweens a Transform's position to the given value. + Also stores the transform as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + If TRUE the tween will smoothly snap all values to integers + + + Tweens a Transform's X position to the given value. + Also stores the transform as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + If TRUE the tween will smoothly snap all values to integers + + + Tweens a Transform's Y position to the given value. + Also stores the transform as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + If TRUE the tween will smoothly snap all values to integers + + + Tweens a Transform's Z position to the given value. + Also stores the transform as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + If TRUE the tween will smoothly snap all values to integers + + + Tweens a Transform's localPosition to the given value. + Also stores the transform as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + If TRUE the tween will smoothly snap all values to integers + + + Tweens a Transform's X localPosition to the given value. + Also stores the transform as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + If TRUE the tween will smoothly snap all values to integers + + + Tweens a Transform's Y localPosition to the given value. + Also stores the transform as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + If TRUE the tween will smoothly snap all values to integers + + + Tweens a Transform's Z localPosition to the given value. + Also stores the transform as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + If TRUE the tween will smoothly snap all values to integers + + + Tweens a Transform's rotation to the given value. + Also stores the transform as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + Rotation mode + + + Tweens a Transform's rotation to the given value using pure quaternion values. + Also stores the transform as the tween's target so it can be used for filtered operations. + PLEASE NOTE: DORotate, which takes Vector3 values, is the preferred rotation method. + This method was implemented for very special cases, and doesn't support LoopType.Incremental loops + (neither for itself nor if placed inside a LoopType.Incremental Sequence) + + The end value to reachThe duration of the tween + + + Tweens a Transform's localRotation to the given value. + Also stores the transform as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + Rotation mode + + + Tweens a Transform's rotation to the given value using pure quaternion values. + Also stores the transform as the tween's target so it can be used for filtered operations. + PLEASE NOTE: DOLocalRotate, which takes Vector3 values, is the preferred rotation method. + This method was implemented for very special cases, and doesn't support LoopType.Incremental loops + (neither for itself nor if placed inside a LoopType.Incremental Sequence) + + The end value to reachThe duration of the tween + + + Tweens a Transform's localScale to the given value. + Also stores the transform as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + + + Tweens a Transform's localScale uniformly to the given value. + Also stores the transform as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + + + Tweens a Transform's X localScale to the given value. + Also stores the transform as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + + + Tweens a Transform's Y localScale to the given value. + Also stores the transform as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + + + Tweens a Transform's Z localScale to the given value. + Also stores the transform as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + + + Tweens a Transform's rotation so that it will look towards the given world position. + Also stores the transform as the tween's target so it can be used for filtered operations + The position to look atThe duration of the tween + Eventual axis constraint for the rotation + The vector that defines in which direction up is (default: Vector3.up) + + + EXPERIMENTAL Tweens a Transform's rotation so that it will look towards the given world position, + while also updating the lookAt position every frame + (contrary to which calculates the lookAt rotation only once, when the tween starts). + Also stores the transform as the tween's target so it can be used for filtered operations + The position to look atThe duration of the tween + Eventual axis constraint for the rotation + The vector that defines in which direction up is (default: Vector3.up) + + + Punches a Transform's localPosition towards the given direction and then back to the starting one + as if it was connected to the starting position via an elastic. + The direction and strength of the punch (added to the Transform's current position) + The duration of the tween + Indicates how much will the punch vibrate + Represents how much (0 to 1) the vector will go beyond the starting position when bouncing backwards. + 1 creates a full oscillation between the punch direction and the opposite direction, + while 0 oscillates only between the punch and the start position + If TRUE the tween will smoothly snap all values to integers + + + Punches a Transform's localScale towards the given size and then back to the starting one + as if it was connected to the starting scale via an elastic. + The punch strength (added to the Transform's current scale) + The duration of the tween + Indicates how much will the punch vibrate + Represents how much (0 to 1) the vector will go beyond the starting size when bouncing backwards. + 1 creates a full oscillation between the punch scale and the opposite scale, + while 0 oscillates only between the punch scale and the start scale + + + Punches a Transform's localRotation towards the given size and then back to the starting one + as if it was connected to the starting rotation via an elastic. + The punch strength (added to the Transform's current rotation) + The duration of the tween + Indicates how much will the punch vibrate + Represents how much (0 to 1) the vector will go beyond the starting rotation when bouncing backwards. + 1 creates a full oscillation between the punch rotation and the opposite rotation, + while 0 oscillates only between the punch and the start rotation + + + Shakes a Transform's localPosition with the given values. + The duration of the tween + The shake strength + Indicates how much will the shake vibrate + Indicates how much the shake will be random (0 to 180 - values higher than 90 kind of suck, so beware). + Setting it to 0 will shake along a single direction. + If TRUE the tween will smoothly snap all values to integers + If TRUE the shake will automatically fadeOut smoothly within the tween's duration, otherwise it will not + Randomness mode + + + Shakes a Transform's localPosition with the given values. + The duration of the tween + The shake strength on each axis + Indicates how much will the shake vibrate + Indicates how much the shake will be random (0 to 180 - values higher than 90 kind of suck, so beware). + Setting it to 0 will shake along a single direction. + If TRUE the tween will smoothly snap all values to integers + If TRUE the shake will automatically fadeOut smoothly within the tween's duration, otherwise it will not + Randomness mode + + + Shakes a Transform's localRotation. + The duration of the tween + The shake strength + Indicates how much will the shake vibrate + Indicates how much the shake will be random (0 to 180 - values higher than 90 kind of suck, so beware). + Setting it to 0 will shake along a single direction. + If TRUE the shake will automatically fadeOut smoothly within the tween's duration, otherwise it will not + Randomness mode + + + Shakes a Transform's localRotation. + The duration of the tween + The shake strength on each axis + Indicates how much will the shake vibrate + Indicates how much the shake will be random (0 to 180 - values higher than 90 kind of suck, so beware). + Setting it to 0 will shake along a single direction. + If TRUE the shake will automatically fadeOut smoothly within the tween's duration, otherwise it will not + Randomness mode + + + Shakes a Transform's localScale. + The duration of the tween + The shake strength + Indicates how much will the shake vibrate + Indicates how much the shake will be random (0 to 180 - values higher than 90 kind of suck, so beware). + Setting it to 0 will shake along a single direction. + If TRUE the shake will automatically fadeOut smoothly within the tween's duration, otherwise it will not + Randomness mode + + + Shakes a Transform's localScale. + The duration of the tween + The shake strength on each axis + Indicates how much will the shake vibrate + Indicates how much the shake will be random (0 to 180 - values higher than 90 kind of suck, so beware). + Setting it to 0 will shake along a single direction. + If TRUE the shake will automatically fadeOut smoothly within the tween's duration, otherwise it will not + Randomness mode + + + Tweens a Transform's position to the given value, while also applying a jump effect along the Y axis. + Returns a Sequence instead of a Tweener. + Also stores the transform as the tween's target so it can be used for filtered operations + The end value to reach + Power of the jump (the max height of the jump is represented by this plus the final Y offset) + Total number of jumps + The duration of the tween + If TRUE the tween will smoothly snap all values to integers + + + Tweens a Transform's localPosition to the given value, while also applying a jump effect along the Y axis. + Returns a Sequence instead of a Tweener. + Also stores the transform as the tween's target so it can be used for filtered operations + The end value to reach + Power of the jump (the max height of the jump is represented by this plus the final Y offset) + Total number of jumps + The duration of the tween + If TRUE the tween will smoothly snap all values to integers + + + Tweens a Transform's position through the given path waypoints, using the chosen path algorithm. + Also stores the transform as the tween's target so it can be used for filtered operations + The waypoints to go through + The duration of the tween + The type of path: Linear (straight path), CatmullRom (curved CatmullRom path) or CubicBezier (curved with control points) + The path mode: 3D, side-scroller 2D, top-down 2D + The resolution of the path (useless in case of Linear paths): higher resolutions make for more detailed curved paths but are more expensive. + Defaults to 10, but a value of 5 is usually enough if you don't have dramatic long curves between waypoints + The color of the path (shown when gizmos are active in the Play panel and the tween is running) + + + Tweens a Transform's localPosition through the given path waypoints, using the chosen path algorithm. + Also stores the transform as the tween's target so it can be used for filtered operations + The waypoint to go through + The duration of the tween + The type of path: Linear (straight path), CatmullRom (curved CatmullRom path) or CubicBezier (curved with control points) + The path mode: 3D, side-scroller 2D, top-down 2D + The resolution of the path: higher resolutions make for more detailed curved paths but are more expensive. + Defaults to 10, but a value of 5 is usually enough if you don't have dramatic long curves between waypoints + The color of the path (shown when gizmos are active in the Play panel and the tween is running) + + + IMPORTANT: Unless you really know what you're doing, you should use the overload that accepts a Vector3 array instead. + Tweens a Transform's position via the given path. + Also stores the transform as the tween's target so it can be used for filtered operations + The path to use + The duration of the tween + The path mode: 3D, side-scroller 2D, top-down 2D + + + IMPORTANT: Unless you really know what you're doing, you should use the overload that accepts a Vector3 array instead. + Tweens a Transform's localPosition via the given path. + Also stores the transform as the tween's target so it can be used for filtered operations + The path to use + The duration of the tween + The path mode: 3D, side-scroller 2D, top-down 2D + + + Tweens a Tween's timeScale to the given value. + Also stores the Tween as the tween's target so it can be used for filtered operations + The end value to reachThe duration of the tween + + + Tweens a Light's color to the given value, + in a way that allows other DOBlendableColor tweens to work together on the same target, + instead than fight each other as multiple DOColor would do. + Also stores the Light as the tween's target so it can be used for filtered operations + The value to tween toThe duration of the tween + + + Tweens a Material's color to the given value, + in a way that allows other DOBlendableColor tweens to work together on the same target, + instead than fight each other as multiple DOColor would do. + Also stores the Material as the tween's target so it can be used for filtered operations + The value to tween toThe duration of the tween + + + Tweens a Material's named color property to the given value, + in a way that allows other DOBlendableColor tweens to work together on the same target, + instead than fight each other as multiple DOColor would do. + Also stores the Material as the tween's target so it can be used for filtered operations + The value to tween to + The name of the material property to tween (like _Tint or _SpecColor) + The duration of the tween + + + Tweens a Material's named color property with the given ID to the given value, + in a way that allows other DOBlendableColor tweens to work together on the same target, + instead than fight each other as multiple DOColor would do. + Also stores the Material as the tween's target so it can be used for filtered operations + The value to tween to + The ID of the material property to tween (also called nameID in Unity's manual) + The duration of the tween + + + Tweens a Transform's position BY the given value (as if you chained a SetRelative), + in a way that allows other DOBlendableMove tweens to work together on the same target, + instead than fight each other as multiple DOMove would do. + Also stores the transform as the tween's target so it can be used for filtered operations + The value to tween byThe duration of the tween + If TRUE the tween will smoothly snap all values to integers + + + Tweens a Transform's localPosition BY the given value (as if you chained a SetRelative), + in a way that allows other DOBlendableMove tweens to work together on the same target, + instead than fight each other as multiple DOMove would do. + Also stores the transform as the tween's target so it can be used for filtered operations + The value to tween byThe duration of the tween + If TRUE the tween will smoothly snap all values to integers + + + EXPERIMENTAL METHOD - Tweens a Transform's rotation BY the given value (as if you chained a SetRelative), + in a way that allows other DOBlendableRotate tweens to work together on the same target, + instead than fight each other as multiple DORotate would do. + Also stores the transform as the tween's target so it can be used for filtered operations + The value to tween byThe duration of the tween + Rotation mode + + + EXPERIMENTAL METHOD - Tweens a Transform's lcoalRotation BY the given value (as if you chained a SetRelative), + in a way that allows other DOBlendableRotate tweens to work together on the same target, + instead than fight each other as multiple DORotate would do. + Also stores the transform as the tween's target so it can be used for filtered operations + The value to tween byThe duration of the tween + Rotation mode + + + Punches a Transform's localRotation BY the given value and then back to the starting one + as if it was connected to the starting rotation via an elastic. Does it in a way that allows other + DOBlendableRotate tweens to work together on the same target + The punch strength (added to the Transform's current rotation) + The duration of the tween + Indicates how much will the punch vibrate + Represents how much (0 to 1) the vector will go beyond the starting rotation when bouncing backwards. + 1 creates a full oscillation between the punch rotation and the opposite rotation, + while 0 oscillates only between the punch and the start rotation + + + Tweens a Transform's localScale BY the given value (as if you chained a SetRelative), + in a way that allows other DOBlendableScale tweens to work together on the same target, + instead than fight each other as multiple DOScale would do. + Also stores the transform as the tween's target so it can be used for filtered operations + The value to tween byThe duration of the tween + + + + Completes all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens completed + (meaning the tweens that don't have infinite loops and were not already complete) + + For Sequences only: if TRUE also internal Sequence callbacks will be fired, + otherwise they will be ignored + + + + Completes all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens completed + (meaning the tweens that don't have infinite loops and were not already complete) + + For Sequences only: if TRUE also internal Sequence callbacks will be fired, + otherwise they will be ignored + + + + Kills all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens killed. + + If TRUE completes the tween before killing it + + + + Kills all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens killed. + + If TRUE completes the tween before killing it + + + + Flips the direction (backwards if it was going forward or viceversa) of all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens flipped. + + + + + Flips the direction (backwards if it was going forward or viceversa) of all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens flipped. + + + + + Sends to the given position all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens involved. + + Time position to reach + (if higher than the whole tween duration the tween will simply reach its end) + If TRUE will play the tween after reaching the given position, otherwise it will pause it + + + + Sends to the given position all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens involved. + + Time position to reach + (if higher than the whole tween duration the tween will simply reach its end) + If TRUE will play the tween after reaching the given position, otherwise it will pause it + + + + Pauses all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens paused. + + + + + Pauses all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens paused. + + + + + Plays all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens played. + + + + + Plays all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens played. + + + + + Plays backwards all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens played. + + + + + Plays backwards all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens played. + + + + + Plays forward all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens played. + + + + + Plays forward all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens played. + + + + + Restarts all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens restarted. + + + + + Restarts all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens restarted. + + + + + Rewinds all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens rewinded. + + + + + Rewinds all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens rewinded. + + + + + Smoothly rewinds all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens rewinded. + + + + + Smoothly rewinds all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens rewinded. + + + + + Toggles the paused state (plays if it was paused, pauses if it was playing) of all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens involved. + + + + + Toggles the paused state (plays if it was paused, pauses if it was playing) of all tweens that have this target as a reference + (meaning tweens that were started from this target, or that had this target added as an Id) + and returns the total number of tweens involved. + + + + + This class serves only as a utility class to store tween settings to apply on multiple tweens. + It is in no way needed otherwise, since you can directly apply tween settings to a tween via chaining + + + + A variable you can eventually Clear and reuse when needed, + to avoid instantiating TweenParams objects + + + Creates a new TweenParams object, which you can use to store tween settings + to pass to multiple tweens via myTween.SetAs(myTweenParms) + + + Clears and resets this TweenParams instance using default values, + so it can be reused without instantiating another one + + + Sets the autoKill behaviour of the tween. + Has no effect if the tween has already started + If TRUE the tween will be automatically killed when complete + + + Sets an ID for the tween, which can then be used as a filter with DOTween's static methods. + The ID to assign to this tween. Can be an int, a string, an object or anything else. + + + Sets an ID for the tween, which can then be used as a filter with DOTween's static methods. + The ID to assign to this tween. Can be an int, a string, an object or anything else. + + + Sets an ID for the tween, which can then be used as a filter with DOTween's static methods. + The ID to assign to this tween. Can be an int, a string, an object or anything else. + + + Sets the target for the tween, which can then be used as a filter with DOTween's static methods. + IMPORTANT: use it with caution. If you just want to set an ID for the tween use SetId instead. + When using shorcuts the shortcut target is already assigned as the tween's target, + so using this method will overwrite it and prevent shortcut-operations like myTarget.DOPause from working correctly. + The target to assign to this tween. Can be an int, a string, an object or anything else. + + + Sets the looping options for the tween. + Has no effect if the tween has already started + Number of cycles to play (-1 for infinite - will be converted to 1 in case the tween is nested in a Sequence) + Loop behaviour type (default: LoopType.Restart) + + + Sets the ease of the tween. + If applied to Sequences eases the whole sequence animation + Eventual overshoot or amplitude to use with Back or Elastic easeType (default is 1.70158) + Eventual period to use with Elastic easeType (default is 0) + + + Sets the ease of the tween using an AnimationCurve. + If applied to Sequences eases the whole sequence animation + + + Sets the ease of the tween using a custom ease function. + If applied to Sequences eases the whole sequence animation + + + Sets the recycling behaviour for the tween. + If TRUE the tween will be recycled after being killed, otherwise it will be destroyed. + + + Sets the update type to the one defined in DOTween.defaultUpdateType (UpdateType.Normal unless changed) + and lets you choose if it should be independent from Unity's Time.timeScale + If TRUE the tween will ignore Unity's Time.timeScale + + + Sets the type of update (default or independent) for the tween + The type of update (default: UpdateType.Normal) + If TRUE the tween will ignore Unity's Time.timeScale + + + Sets the onStart callback for the tween. + Called the first time the tween is set in a playing state, after any eventual delay + + + Sets the onPlay callback for the tween. + Called when the tween is set in a playing state, after any eventual delay. + Also called each time the tween resumes playing from a paused state + + + Sets the onRewind callback for the tween. + Called when the tween is rewinded, + either by calling Rewind or by reaching the start position while playing backwards. + Rewinding a tween that is already rewinded will not fire this callback + + + Sets the onUpdate callback for the tween. + Called each time the tween updates + + + Sets the onStepComplete callback for the tween. + Called the moment the tween completes one loop cycle, even when going backwards + + + Sets the onComplete callback for the tween. + Called the moment the tween reaches its final forward position, loops included + + + Sets the onKill callback for the tween. + Called the moment the tween is killed + + + Sets the onWaypointChange callback for the tween. + Called when a path tween reaches a new waypoint + + + Sets a delayed startup for the tween. + Has no effect on Sequences or if the tween has already started + + + If isRelative is TRUE sets the tween as relative + (the endValue will be calculated as startValue + endValue instead than being used directly). + Has no effect on Sequences or if the tween has already started + + + If isSpeedBased is TRUE sets the tween as speed based + (the duration will represent the number of units the tween moves x second). + Has no effect on Sequences, nested tweens, or if the tween has already started + + + + Methods that extend Tween objects and allow to set their parameters + + + + Sets the autoKill behaviour of the tween to TRUE. + Has no effect if the tween has already started or if it's added to a Sequence + + + Sets the autoKill behaviour of the tween. + Has no effect if the tween has already started or if it's added to a Sequence + If TRUE the tween will be automatically killed when complete + + + Sets an ID for the tween (), which can then be used as a filter with DOTween's static methods. + The ID to assign to this tween. Can be an int, a string, an object or anything else. + + + Sets a string ID for the tween (), which can then be used as a filter with DOTween's static methods. + Filtering via string is 2X faster than using an object as an ID (using the alternate obejct overload) + The string ID to assign to this tween. + + + Sets an int ID for the tween (), which can then be used as a filter with DOTween's static methods. + Filtering via int is 4X faster than via object, 2X faster than via string (using the alternate object/string overloads) + The int ID to assign to this tween. + + + Allows to link this tween to a GameObject + so that it will be automatically killed when the GameObject is destroyed. + Has no effect if the tween is added to a Sequence + The link target (unrelated to the target set via SetTarget) + + + Allows to link this tween to a GameObject and assign a behaviour depending on it. + This will also automatically kill the tween when the GameObject is destroyed. + Has no effect if the tween is added to a Sequence + The link target (unrelated to the target set via SetTarget) + The behaviour to use ( is always evaluated even if you choose another one) + + + Sets the target for the tween, which can then be used as a filter with DOTween's static methods. + IMPORTANT: use it with caution. If you just want to set an ID for the tween use SetId instead. + When using shorcuts the shortcut target is already assigned as the tween's target, + so using this method will overwrite it and prevent shortcut-operations like myTarget.DOPause from working correctly. + The target to assign to this tween. Can be an int, a string, an object or anything else. + + + Sets the looping options for the tween. + Has no effect if the tween has already started + Number of cycles to play (-1 for infinite - will be converted to 1 in case the tween is nested in a Sequence) + + + Sets the looping options for the tween. + Has no effect if the tween has already started + Number of cycles to play (-1 for infinite - will be converted to 1 in case the tween is nested in a Sequence) + Loop behaviour type (default: LoopType.Restart) + + + Sets the ease of the tween. + If applied to Sequences eases the whole sequence animation + + + Sets the ease of the tween. + If applied to Sequences eases the whole sequence animation + + Eventual overshoot to use with Back or Flash ease (default is 1.70158 - 1 for Flash). + In case of Flash ease it must be an intenger and sets the total number of flashes that will happen. + Using an even number will complete the tween on the starting value, while an odd one will complete it on the end value. + + + + Sets the ease of the tween. + If applied to Sequences eases the whole sequence animation + Eventual amplitude to use with Elastic easeType or overshoot to use with Flash easeType (default is 1.70158 - 1 for Flash). + In case of Flash ease it must be an integer and sets the total number of flashes that will happen. + Using an even number will complete the tween on the starting value, while an odd one will complete it on the end value. + + Eventual period to use with Elastic or Flash easeType (default is 0). + In case of Flash ease it indicates the power in time of the ease, and must be between -1 and 1. + 0 is balanced, 1 weakens the ease with time, -1 starts the ease weakened and gives it power towards the end. + + + + Sets the ease of the tween using an AnimationCurve. + If applied to Sequences eases the whole sequence animation + + + Sets the ease of the tween using a custom ease function (which must return a value between 0 and 1). + If applied to Sequences eases the whole sequence animation + + + Allows the tween to be recycled after being killed. + + + Sets the recycling behaviour for the tween. + If TRUE the tween will be recycled after being killed, otherwise it will be destroyed. + + + Sets the update type to UpdateType.Normal and lets you choose if it should be independent from Unity's Time.timeScale + If TRUE the tween will ignore Unity's Time.timeScale + + + Sets the type of update for the tween + The type of update (defalt: UpdateType.Normal) + + + Sets the type of update for the tween and lets you choose if it should be independent from Unity's Time.timeScale + The type of update + If TRUE the tween will ignore Unity's Time.timeScale + + + EXPERIMENTAL: inverts this tween, so that it will play from the end to the beginning + (playing it backwards will actually play it from the beginning to the end). + Has no effect if the tween has already started or if it's added to a Sequence + + + EXPERIMENTAL: inverts this tween, so that it will play from the end to the beginning + (playing it backwards will actually play it from the beginning to the end). + Has no effect if the tween has already started or if it's added to a Sequence + If TRUE the tween will be inverted, otherwise it won't + + + Sets the onStart callback for the tween, clearing any previous onStart callback that was set. + Called the first time the tween is set in a playing state, after any eventual delay + + + Sets the onPlay callback for the tween, clearing any previous onPlay callback that was set. + Called when the tween is set in a playing state, after any eventual delay. + Also called each time the tween resumes playing from a paused state + + + Sets the onPause callback for the tween, clearing any previous onPause callback that was set. + Called when the tween state changes from playing to paused. + If the tween has autoKill set to FALSE, this is called also when the tween reaches completion. + + + Sets the onRewind callback for the tween, clearing any previous onRewind callback that was set. + Called when the tween is rewinded, + either by calling Rewind or by reaching the start position while playing backwards. + Rewinding a tween that is already rewinded will not fire this callback + + + Sets the onUpdate callback for the tween, clearing any previous onUpdate callback that was set. + Called each time the tween updates + + + Sets the onStepComplete callback for the tween, clearing any previous onStepComplete callback that was set. + Called the moment the tween completes one loop cycle, even when going backwards + + + Sets the onComplete callback for the tween, clearing any previous onComplete callback that was set. + Called the moment the tween reaches its final forward position, loops included + + + Sets the onKill callback for the tween, clearing any previous onKill callback that was set. + Called the moment the tween is killed + + + Sets the onWaypointChange callback for the tween, clearing any previous onWaypointChange callback that was set. + Called when a path tween's current waypoint changes + + + Sets the parameters of the tween (id, ease, loops, delay, timeScale, callbacks, etc) as the parameters of the given one. + Doesn't copy specific SetOptions settings: those will need to be applied manually each time. + Has no effect if the tween has already started. + NOTE: the tween's target will not be changed + Tween from which to copy the parameters + + + Sets the parameters of the tween (id, ease, loops, delay, timeScale, callbacks, etc) as the parameters of the given TweenParams. + Has no effect if the tween has already started. + TweenParams from which to copy the parameters + + + Adds the given tween to the end of the Sequence. + Has no effect if the Sequence has already started + The tween to append + + + Adds the given tween to the beginning of the Sequence, pushing forward the other nested content. + Has no effect if the Sequence has already started + The tween to prepend + + + Inserts the given tween at the same time position of the last tween, callback or intervale added to the Sequence. + Note that, in case of a Join after an interval, the insertion time will be the time where the interval starts, not where it finishes. + Has no effect if the Sequence has already started + + + Inserts the given tween at the given time position in the Sequence, + automatically adding an interval if needed. + Has no effect if the Sequence has already started + The time position where the tween will be placed + The tween to insert + + + Adds the given interval to the end of the Sequence. + Has no effect if the Sequence has already started + The interval duration + + + Adds the given interval to the beginning of the Sequence, pushing forward the other nested content. + Has no effect if the Sequence has already started + The interval duration + + + Adds the given callback to the end of the Sequence. + Has no effect if the Sequence has already started + The callback to append + + + Adds the given callback to the beginning of the Sequence, pushing forward the other nested content. + Has no effect if the Sequence has already started + The callback to prepend + + + Inserts the given callback at the given time position in the Sequence, + automatically adding an interval if needed. + Has no effect if the Sequence has already started + The time position where the callback will be placed + The callback to insert + + + Changes a TO tween into a FROM tween: sets the current target's position as the tween's endValue + then immediately sends the target to the previously set endValue. + + + Changes a TO tween into a FROM tween: sets the current target's position as the tween's endValue + then immediately sends the target to the previously set endValue. + If TRUE the FROM value will be calculated as relative to the current one + + + Changes a TO tween into a FROM tween: sets the current value of the target as the endValue, + and the previously passed endValue as the actual startValue. + If TRUE sets the target to from value immediately, otherwise waits for the tween to start + If TRUE the FROM value will be calculated as relative to the current one + + + Changes a TO tween into a FROM tween: sets the tween's starting value to the given one + and eventually sets the tween's target to that value immediately. + Value to start from + If TRUE sets the target to from value immediately, otherwise waits for the tween to start + If TRUE the FROM/TO values will be calculated as relative to the current ones + + + Changes a TO tween into a FROM tween: sets the tween's starting value to the given one + and eventually sets the tween's target to that value immediately. + Alpha value to start from (in case of Fade tweens) + If TRUE sets the target to from value immediately, otherwise waits for the tween to start + If TRUE the FROM/TO values will be calculated as relative to the current ones + + + Changes a TO tween into a FROM tween: sets the tween's starting value to the given one + and eventually sets the tween's target to that value immediately. + Value to start from (in case of Vector tweens that act on a single coordinate or scale tweens) + If TRUE sets the target to from value immediately, otherwise waits for the tween to start + If TRUE the FROM/TO values will be calculated as relative to the current ones + + + Changes a TO tween into a FROM tween: sets the tween's starting value to the given one + and eventually sets the tween's target to that value immediately. + Value to start from (in case of Vector tweens that act on a single coordinate or scale tweens) + If TRUE sets the target to from value immediately, otherwise waits for the tween to start + If TRUE the FROM/TO values will be calculated as relative to the current ones + + + Sets a delayed startup for the tween. + In case of Sequences behaves the same as , + which means the delay will repeat in case of loops (while with tweens it's ignored after the first loop cycle). + Has no effect if the tween has already started + + + EXPERIMENTAL: implemented in v1.2.340. + Sets a delayed startup for the tween with options to choose how the delay is applied in case of Sequences. + Has no effect if the tween has already started + Only used by types: If FALSE sets the delay as a one-time occurrence + (defaults to this for types), + otherwise as a Sequence interval which will repeat at the beginning of every loop cycle + + + Sets the tween as relative + (the endValue will be calculated as startValue + endValue instead than being used directly). + Has no effect on Sequences or if the tween has already started + + + If isRelative is TRUE sets the tween as relative + (the endValue will be calculated as startValue + endValue instead than being used directly). + Has no effect on Sequences or if the tween has already started + + + If isSpeedBased is TRUE sets the tween as speed based + (the duration will represent the number of units the tween moves x second). + Has no effect on Sequences, nested tweens, or if the tween has already started + + + If isSpeedBased is TRUE sets the tween as speed based + (the duration will represent the number of units the tween moves x second). + Has no effect on Sequences, nested tweens, or if the tween has already started + + + Options for float tweens + If TRUE the tween will smoothly snap all values to integers + + + Options for Vector2 tweens + If TRUE the tween will smoothly snap all values to integers + + + Options for Vector2 tweens + Selecting an axis will tween the vector only on that axis, leaving the others untouched + If TRUE the tween will smoothly snap all values to integers + + + Options for Vector3 tweens + If TRUE the tween will smoothly snap all values to integers + + + Options for Vector3 tweens + Selecting an axis will tween the vector only on that axis, leaving the others untouched + If TRUE the tween will smoothly snap all values to integers + + + Options for Vector4 tweens + If TRUE the tween will smoothly snap all values to integers + + + Options for Vector4 tweens + Selecting an axis will tween the vector only on that axis, leaving the others untouched + If TRUE the tween will smoothly snap all values to integers + + + Options for Quaternion tweens + If TRUE (default) the rotation will take the shortest route, and will not rotate more than 360°. + If FALSE the rotation will be fully accounted. Is always FALSE if the tween is set as relative + + + Options for Color tweens + If TRUE only the alpha value of the color will be tweened + + + Options for Vector4 tweens + If TRUE the tween will smoothly snap all values to integers + + + Options for Vector4 tweens + If TRUE, rich text will be interpreted correctly while animated, + otherwise all tags will be considered as normal text + The type of scramble to use, if any + A string containing the characters to use for scrambling. + Use as many characters as possible (minimum 10) because DOTween uses a fast scramble mode which gives better results with more characters. + Leave it to NULL to use default ones + + + Options for Vector3Array tweens + If TRUE the tween will smoothly snap all values to integers + + + Options for Vector3Array tweens + If TRUE the tween will smoothly snap all values to integers + + + Options for ShapeCircle tweens + If TRUE the center you set in the DOTween.To method will be considered as relative + to the starting position of the target + If TRUE the tween will smoothly snap all values to integers + + + Options for Path tweens (created via the DOPath shortcut) + The eventual movement axis to lock. You can input multiple axis if you separate them like this: + AxisConstrain.X | AxisConstraint.Y + The eventual rotation axis to lock. You can input multiple axis if you separate them like this: + AxisConstrain.X | AxisConstraint.Y + + + Options for Path tweens (created via the DOPath shortcut) + If TRUE the path will be automatically closed + The eventual movement axis to lock. You can input multiple axis if you separate them like this: + AxisConstrain.X | AxisConstraint.Y + The eventual rotation axis to lock. You can input multiple axis if you separate them like this: + AxisConstrain.X | AxisConstraint.Y + + + Additional LookAt options for Path tweens (created via the DOPath shortcut). + Orients the target towards the given position. + Must be chained directly to the tween creation method or to a SetOptions + The position to look at + The eventual direction to consider as "forward". + If left to NULL defaults to the regular forward side of the transform + The vector that defines in which direction up is (default: Vector3.up) + + + Additional LookAt options for Path tweens (created via the DOPath shortcut). + Orients the target towards the given position with options to keep the Z rotation stable. + Must be chained directly to the tween creation method or to a SetOptions + The position to look at + If TRUE doesn't rotate the target along the Z axis + + + Additional LookAt options for Path tweens (created via the DOPath shortcut). + Orients the target towards another transform. + Must be chained directly to the tween creation method or to a SetOptions + The transform to look at + The eventual direction to consider as "forward". + If left to NULL defaults to the regular forward side of the transform + The vector that defines in which direction up is (default: Vector3.up) + + + Additional LookAt options for Path tweens (created via the DOPath shortcut). + Orients the target towards another transform with options to keep the Z rotation stable. + Must be chained directly to the tween creation method or to a SetOptions + The transform to look at + If TRUE doesn't rotate the target along the Z axis + + + Additional LookAt options for Path tweens (created via the DOPath shortcut). + Orients the target to the path, with the given lookAhead. + Must be chained directly to the tween creation method or to a SetOptions + The percentage of lookAhead to use (0 to 1) + The eventual direction to consider as "forward". + If left to NULL defaults to the regular forward side of the transform + The vector that defines in which direction up is (default: Vector3.up) + + + Additional LookAt options for Path tweens (created via the DOPath shortcut). + Orients the path with options to keep the Z rotation stable. + Must be chained directly to the tween creation method or to a SetOptions + The percentage of lookAhead to use (0 to 1) + If TRUE doesn't rotate the target along the Z axis + + + + Types of log behaviours + + + + Log only warnings and errors + + + Log warnings, errors and additional infos + + + Log only errors + + + + Indicates either a Tweener or a Sequence + + + + TimeScale for the tween + + + If TRUE the tween will play backwards + + + If TRUE the tween is completely inverted but without playing it backwards + (play backwards will actually play the tween in the original direction) + + + Object ID (usable for filtering with DOTween static methods). Can be anything except a string or an int + (use or for those) + + + String ID (usable for filtering with DOTween static methods). 2X faster than using an object id + + + Int ID (usable for filtering with DOTween static methods). 4X faster than using an object id, 2X faster than using a string id. + Default is -999 so avoid using an ID like that or it will capture all unset intIds + + + Tween target (usable for filtering with DOTween static methods). Automatically set by tween creation shortcuts + + + Called when the tween is set in a playing state, after any eventual delay. + Also called each time the tween resumes playing from a paused state + + + Called when the tween state changes from playing to paused. + If the tween has autoKill set to FALSE, this is called also when the tween reaches completion. + + + Called when the tween is rewinded, + either by calling Rewind or by reaching the start position while playing backwards. + Rewinding a tween that is already rewinded will not fire this callback + + + Called each time the tween updates + + + Called the moment the tween completes one loop cycle + + + Called the moment the tween reaches completion (loops included) + + + Called the moment the tween is killed + + + Called when a path tween's current waypoint changes + + + Tweeners-only (ignored by Sequences), returns TRUE if the tween was set as relative + + + + Set by SetTarget if DOTween's Debug Mode is on (see DOTween Utility Panel -> "Store GameObject's ID" debug option + + + + FALSE when tween is (or should be) despawned - set only by TweenManager + + + Gets and sets the time position (loops included, delays excluded) of the tween + + + Returns TRUE if the tween is set to loop (either a set number of times or infinitely) + + + TRUE after the tween was set in a play state at least once, AFTER any delay is elapsed + + + Time position within a single loop cycle + + + + Animates a single value + + + + Changes the start value of a tween and rewinds it (without pausing it). + Has no effect with tweens that are inside Sequences + The new start value + If bigger than 0 applies it as the new tween duration + + + Changes the end value of a tween and rewinds it (without pausing it). + Has no effect with tweens that are inside Sequences + The new end value + If bigger than 0 applies it as the new tween duration + If TRUE the start value will become the current target's value, otherwise it will stay the same + + + Changes the end value of a tween and rewinds it (without pausing it). + Has no effect with tweens that are inside Sequences + The new end value + If TRUE the start value will become the current target's value, otherwise it will stay the same + + + Changes the start and end value of a tween and rewinds it (without pausing it). + Has no effect with tweens that are inside Sequences + The new start value + The new end value + If bigger than 0 applies it as the new tween duration + + + + Used internally + + + + + Update type + + + + Updates every frame during Update calls + + + Updates every frame during LateUpdate calls + + + Updates using FixedUpdate calls + + + Updates using manual update calls + + + diff --git a/DOTween.dll b/DOTween.dll new file mode 100644 index 0000000..7287521 Binary files /dev/null and b/DOTween.dll differ diff --git a/DOTween.dll.mdb b/DOTween.dll.mdb new file mode 100644 index 0000000..c9f680b Binary files /dev/null and b/DOTween.dll.mdb differ diff --git a/DelayLoad/FullautoLauncherAnimationRiggingCompatibilityPatch.dll b/DelayLoad/FullautoLauncherAnimationRiggingCompatibilityPatch.dll new file mode 100644 index 0000000..ee4788c Binary files /dev/null and b/DelayLoad/FullautoLauncherAnimationRiggingCompatibilityPatch.dll differ diff --git a/DelayLoad/FullautoLauncherAnimationRiggingCompatibilityPatch.pdb b/DelayLoad/FullautoLauncherAnimationRiggingCompatibilityPatch.pdb new file mode 100644 index 0000000..f462162 Binary files /dev/null and b/DelayLoad/FullautoLauncherAnimationRiggingCompatibilityPatch.pdb differ diff --git a/DelayLoad/SMXMultiActionCompatibilityPatch.dll b/DelayLoad/SMXMultiActionCompatibilityPatch.dll new file mode 100644 index 0000000..d0bd087 Binary files /dev/null and b/DelayLoad/SMXMultiActionCompatibilityPatch.dll differ diff --git a/DelayLoad/SMXMultiActionCompatibilityPatch.pdb b/DelayLoad/SMXMultiActionCompatibilityPatch.pdb new file mode 100644 index 0000000..29d01ac Binary files /dev/null and b/DelayLoad/SMXMultiActionCompatibilityPatch.pdb differ diff --git a/GearsAPI.dll b/GearsAPI.dll new file mode 100644 index 0000000..d01ef8f Binary files /dev/null and b/GearsAPI.dll differ diff --git a/Harmony/AnimationRiggingPatches.cs b/Harmony/AnimationRiggingPatches.cs new file mode 100644 index 0000000..166f0f5 --- /dev/null +++ b/Harmony/AnimationRiggingPatches.cs @@ -0,0 +1,1731 @@ +using HarmonyLib; +using KFCommonUtilityLib; +using KFCommonUtilityLib.Scripts.StaticManagers; +using KFCommonUtilityLib.Scripts.Utilities; +using System; +using System.Collections.Generic; +using System.Reflection.Emit; +using System.Xml.Linq; +using UniLinq; +using UnityEngine; +using UnityEngine.Animations; +using UnityEngine.Animations.Rigging; + +[HarmonyPatch] +static class AnimationRiggingPatches +{ + [HarmonyPatch(typeof(SDCSUtils), nameof(SDCSUtils.setupEquipmentCommon))] + [HarmonyPrefix] + private static bool Prefix_setupEquipmentCommon_SDCSUtils(GameObject _rigObj, out bool __state) + { + __state = false; + if (_rigObj.TryGetComponent(out var animator)) + { + __state = true; + animator.UnbindAllStreamHandles(); + animator.UnbindAllSceneHandles(); + } + return true; + } + + [HarmonyPatch(typeof(SDCSUtils), nameof(SDCSUtils.setupEquipmentCommon))] + [HarmonyPostfix] + private static void Postfix_setupEquipmentCommon_SDCSUtils(GameObject _rigObj, bool __state) + { + if (__state && _rigObj.TryGetComponent(out var animator)) + { + animator.Rebind(); + } + } + + [HarmonyPatch(typeof(SDCSUtils), nameof(SDCSUtils.setupRig))] + [HarmonyPrefix] + private static bool Prefix_setupRig_SDCSUtils(ref RuntimeAnimatorController animController, ref GameObject _rigObj) + { + if (_rigObj && _rigObj.TryGetComponent(out var builder) && builder.HasWeaponOverride) + { + animController = null; + } + return true; + } + + //[HarmonyPatch(typeof(UMACharacterBodyAnimator), nameof(UMACharacterBodyAnimator.assignLayerWeights))] + //[HarmonyPrefix] + //private static bool Prefix_assignLayerWeights_UMACharacterBodyAnimator(UMACharacterBodyAnimator __instance) + //{ + // if (__instance.Animator && __instance.Animator.TryGetComponent(out var builder) && builder.HasWeaponOverride) + // { + // return false; + // } + // return true; + //} + + //[HarmonyPatch(typeof(AvatarSDCSController), nameof(AvatarSDCSController.setLayerWeights))] + //[HarmonyPrefix] + //private static bool Prefix_setLayerWeights_AvatarSDCSController(AvatarSDCSController __instance) + //{ + // if (__instance.anim && __instance.anim.TryGetComponent(out var builder) && builder.HasWeaponOverride) + // { + // return false; + // } + // return true; + //} + + [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.OnModificationsChanged))] + [HarmonyPostfix] + private static void Postfix_OnModificationChanged_ItemActionRanged(ItemActionData _data) + { + ItemActionRanged.ItemActionDataRanged rangedData = (ItemActionRanged.ItemActionDataRanged)_data; + if (rangedData.IsDoubleBarrel) + { + rangedData.muzzle = AnimationRiggingManager.GetTransformOverrideByName(rangedData.invData.model, "Muzzle_L"); + rangedData.muzzle2 = AnimationRiggingManager.GetTransformOverrideByName(rangedData.invData.model, "Muzzle_R"); + } + else + { + rangedData.muzzle = AnimationRiggingManager.GetTransformOverrideByName(rangedData.invData.model, "Muzzle"); + } + rangedData.Laser = AnimationRiggingManager.GetTransformOverrideByName(rangedData.invData.model, "laser"); + + ItemActionLauncher.ItemActionDataLauncher launcherData = _data as ItemActionLauncher.ItemActionDataLauncher; + if (launcherData != null) + { + launcherData.projectileJoint = AnimationRiggingManager.GetTransformOverrideByName(launcherData.invData.model, "ProjectileJoint"); + } + } + + + /// + /// attachment path patch, only apply to MinEventActionSetTransformActive! + /// + /// + /// + [HarmonyPatch(typeof(MinEventActionSetTransformActive), nameof(MinEventActionSetTransformActive.Execute))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_Execute_MinEventActionSetTransformActive(IEnumerable instructions) + { + var codes = instructions.ToList(); + var mtd_find = AccessTools.Method(typeof(GameUtils), nameof(GameUtils.FindDeepChild)); + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_find)) + { + codes.RemoveAt(i); + codes.InsertRange(i, new[] + { + new CodeInstruction(OpCodes.Ldarg_1), + CodeInstruction.LoadField(typeof(MinEventParams), nameof(MinEventParams.Self)), + CodeInstruction.Call(typeof(AnimationRiggingManager), nameof(AnimationRiggingManager.GetAttachmentReferenceOverrideTransform)) + }); + break; + } + } + + return codes; + } + + [HarmonyPatch(typeof(MinEventActionSetTransformChildrenActive), nameof(MinEventActionSetTransformChildrenActive.Execute))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_Execute_MinEventActionSetTransformChildrenActive(IEnumerable instructions) + { + var codes = instructions.ToList(); + var mtd_find = AccessTools.Method(typeof(GameUtils), nameof(GameUtils.FindDeepChildActive)); + var fld_trans = AccessTools.Field(typeof(MinEventActionSetTransformChildrenActive), nameof(MinEventActionSetTransformChildrenActive.transformPath)); + for (int i = 1; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_find) && codes[i - 1].LoadsField(fld_trans)) + { + codes.RemoveAt(i); + codes.InsertRange(i, new[] + { + new CodeInstruction(OpCodes.Ldarg_1), + CodeInstruction.LoadField(typeof(MinEventParams), nameof(MinEventParams.Self)), + CodeInstruction.Call(typeof(AnimationRiggingManager), nameof(AnimationRiggingManager.GetAttachmentReferenceOverrideTransformActive)) + }); + break; + } + } + return codes; + } + + [HarmonyPatch(typeof(MinEventActionAddPart), nameof(MinEventActionAddPart.Execute))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_Execute_MinEventActionAddPart(IEnumerable instructions) + { + var codes = instructions.ToList(); + var mtd_idx = AccessTools.PropertyGetter(typeof(Inventory), nameof(Inventory.holdingItemIdx)); + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_idx)) + { + codes.InsertRange(i + 2, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(MinEventActionAddPart),nameof(MinEventActionAddPart.partName)), + new CodeInstruction(OpCodes.Ldc_I4_1), + CodeInstruction.Call(typeof(AnimationRiggingManager), nameof(AnimationRiggingManager.GetAddPartTransformOverride)) + }); + break; + } + } + return codes; + } + + [HarmonyPatch(typeof(MinEventActionAttachPrefabToHeldItem), nameof(MinEventActionAttachPrefabToHeldItem.Execute))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_Execute_MinEventActionAttachPrefabToHeldItem(IEnumerable instructions, ILGenerator generator) + { + var codes = instructions.ToList(); + var mtd_find = AccessTools.Method(typeof(GameUtils), nameof(GameUtils.FindDeepChild)); + var fld_trans = AccessTools.Field(typeof(MinEventParams), nameof(MinEventParams.Transform)); + var mtd_layer = AccessTools.Method(typeof(Utils), nameof(Utils.SetLayerRecursively)); + + var lbd_targets = generator.DeclareLocal(typeof(AnimationTargetsAbs)); + + for (int i = 1; i < codes.Count; i++) + { + if (codes[i].opcode == OpCodes.Stloc_0) + { + if (codes[i - 1].Calls(mtd_find)) + { + codes.InsertRange(i, new[] + { + new CodeInstruction(OpCodes.Ldc_I4_0), + CodeInstruction.Call(typeof(AnimationRiggingManager), nameof(AnimationRiggingManager.GetAddPartTransformOverride)) + }); + codes.RemoveAt(i - 1); + i += 1; + } + else if (codes[i - 1].LoadsField(fld_trans)) + { + codes.InsertRange(i, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(MinEventActionAttachPrefabToHeldItem), nameof(MinEventActionAttachPrefabToHeldItem.parent_transform)), + new CodeInstruction(OpCodes.Ldc_I4_0), + CodeInstruction.Call(typeof(AnimationRiggingManager), nameof(AnimationRiggingManager.GetAddPartTransformOverride)) + }); + i += 4; + } + } + else if (codes[i].opcode == OpCodes.Stloc_2) + { + var lbl = generator.DefineLabel(); + var lbls = codes[i + 1].ExtractLabels(); + codes[i + 1].WithLabels(lbl); + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_1).WithLabels(lbls), + CodeInstruction.LoadField(typeof(MinEventParams), nameof(MinEventParams.Transform)), + new CodeInstruction(OpCodes.Ldnull), + new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(UnityEngine.Object), "op_Inequality")), + new CodeInstruction(OpCodes.Brfalse_S, lbl), + new CodeInstruction(OpCodes.Ldarg_1), + CodeInstruction.LoadField(typeof(MinEventParams), nameof(MinEventParams.Transform)), + CodeInstruction.Call(typeof(Transform), nameof(Transform.GetComponent), new Type[0], new Type[]{ typeof(AnimationTargetsAbs)}), + new CodeInstruction(OpCodes.Stloc_S, lbd_targets) + }); + i += 9; + } + else if (codes[i].opcode == OpCodes.Stloc_S && ((LocalBuilder)codes[i].operand).LocalIndex == 4) + { + codes.RemoveAt(i - 1); + codes.InsertRange(i - 1, new[] + { + new CodeInstruction(OpCodes.Ldloc_S, lbd_targets), + new CodeInstruction(OpCodes.Ldloc_2), + CodeInstruction.Call(typeof(AnimationRiggingPatches), nameof(CreateOrMoveAttachment)) + }); + i += 2; + } + else if (codes[i].Calls(mtd_layer)) + { + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldloc_S, lbd_targets), + new CodeInstruction(OpCodes.Ldloc_S, 4), + CodeInstruction.Call(typeof(AnimationRiggingPatches), nameof(CheckAttachmentRefMerge)) + }); + i += 3; + } + else if (codes[i].opcode == OpCodes.Stloc_S && ((LocalBuilder)codes[i].operand).LocalIndex == 5) + { + var lbl = generator.DefineLabel(); + var lbls = codes[i + 1].ExtractLabels(); + codes[i + 1].WithLabels(lbl); + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldloc_3).WithLabels(lbls), + CodeInstruction.Call(typeof(Transform), nameof(Transform.GetComponent), new Type[0], new Type[]{ typeof(IgnoreTint)}), + new CodeInstruction(OpCodes.Ldnull), + new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(UnityEngine.Object), "op_Inequality")), + new CodeInstruction(OpCodes.Brfalse_S, lbl), + new CodeInstruction(OpCodes.Ret) + }); + i += 6; + } + } + codes.InsertRange(0, new[] + { + new CodeInstruction(OpCodes.Ldnull), + new CodeInstruction(OpCodes.Stloc_S, lbd_targets) + }); + return codes; + } + + private static GameObject CreateOrMoveAttachment(GameObject go, AnimationTargetsAbs targets, string name) + { + GameObject res = null; + if (targets) + { + res = targets.GetPrefab(name); + } + if (!res) + { + res = GameObject.Instantiate(go); + } + return res; + } + + private static void CheckAttachmentRefMerge(AnimationTargetsAbs targets, GameObject attachmentReference) + { + if (targets) + { + targets.AttachPrefab(attachmentReference); + } + } + + /// + /// reload logging patch + /// + /// + [HarmonyPatch(typeof(AnimatorRangedReloadState), nameof(AnimatorRangedReloadState.OnStateEnter))] + [HarmonyPostfix] + private static void Postfix_OnStateEnter_AnimatorRangedReloadState(AnimatorStateInfo stateInfo) + { + if (ConsoleCmdReloadLog.LogInfo) + { + Log.Out(string.Format("ANIMATION LENGTH: length {0} speed {1} speedMultiplier {2} original length {3}", stateInfo.length, stateInfo.speed, stateInfo.speedMultiplier, stateInfo.length * stateInfo.speedMultiplier)); + } + } + + [HarmonyPatch(typeof(EntityAlive), nameof(EntityAlive.OnHoldingItemChanged))] + [HarmonyPostfix] + private static void Postfix_OnHoldingItemChanged_EntityAlive(EntityAlive __instance) + { + AnimationRiggingManager.OnHoldingItemIndexChanged(__instance as EntityPlayer); + } + + [HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.OnHoldingItemChanged))] + [HarmonyPostfix] + private static void Postfix_OnHoldingItemChanged_EntityPlayerLocal(EntityPlayerLocal __instance) + { + AnimationRiggingManager.OnHoldingItemIndexChanged(__instance); + } + + [HarmonyPatch(typeof(GameManager), nameof(GameManager.SaveAndCleanupWorld))] + [HarmonyPostfix] + private static void Postfix_SaveAndCleanupWorld_GameManager() + { + AnimationRiggingManager.Clear(); + } + + private static void ParseTakeOverReloadTime(XElement _node) + { + string itemName = _node.GetAttribute("name"); + if (string.IsNullOrEmpty(itemName)) + { + return; + } + ItemClass item = ItemClass.GetItemClass(itemName); + if (item.Properties.GetBool("TakeOverReloadTime")) + { + AnimationRiggingManager.AddReloadTimeTakeOverItem(item.Name); + //Log.Out($"take over reload time: {item.Name} {item.Id}"); + } + } + + [HarmonyPatch(typeof(ItemClassesFromXml), nameof(ItemClassesFromXml.parseItem))] + [HarmonyPostfix] + private static void Postfix_parseItem_ItemClassesFromXml(XElement _node) + { + ParseTakeOverReloadTime(_node); + } + + //[HarmonyPatch(typeof(ItemClass), nameof(ItemClass.StopHolding))] + //[HarmonyPostfix] + //private static void Postfix_StopHolding_ItemClass(Transform _modelTransform) + //{ + // if (_modelTransform != null && _modelTransform.TryGetComponent(out var targets) && !targets.Destroyed) + // { + // targets.SetEnabled(false); + // } + //} + + [HarmonyPatch(typeof(Inventory), nameof(Inventory.createHeldItem))] + [HarmonyPostfix] + private static void Postfix_createHeldItem_Inventory(Inventory __instance, Transform __result) + { + if (__result && __result.TryGetComponent(out var targets) && !targets.Destroyed) + { + if (GameManager.IsDedicatedServer || !(__instance.entity is EntityPlayer player)) + { + targets.Destroy(); + } + else + { + if (player is EntityPlayerLocal localPlayer) + { + targets.Init(localPlayer.emodel.avatarController.GetActiveModelRoot(), localPlayer.bFirstPersonView); + } + else + { + targets.DestroyFpv(); + targets.Init(player.emodel.avatarController.GetActiveModelRoot(), false); + } + } + } + } + + [HarmonyPatch(typeof(Inventory), nameof(Inventory.ForceHoldingItemUpdate))] + [HarmonyPrefix] + private static bool Prefix_ForceHoldingItemUpdate(Inventory __instance) + { + if (__instance.entity is EntityPlayer) + AnimationRiggingManager.OnClearInventorySlot(__instance, __instance.holdingItemIdx); + return true; + } + + [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.ItemActionEffects))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_ItemActionEffects_ItemActionRanged(IEnumerable instructions, ILGenerator generator) + { + var codes = instructions.ToList(); + + var fld_fpv = AccessTools.Field(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.bFirstPersonView)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].LoadsField(fld_fpv)) + { + codes.InsertRange(i + 4, new[] + { + new CodeInstruction(OpCodes.Ldloc_S, codes[i + 3].operand), + new CodeInstruction(OpCodes.Ldarg_2), + new CodeInstruction(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(ItemActionAttack), nameof(ItemActionAttack.particlesMuzzleFire))), + new CodeInstruction(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(ItemActionAttack), nameof(ItemActionAttack.particlesMuzzleFireFpv))), + new CodeInstruction(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(ItemActionAttack), nameof(ItemActionAttack.particlesMuzzleSmoke))), + new CodeInstruction(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(ItemActionAttack), nameof(ItemActionAttack.particlesMuzzleSmokeFpv))), + new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(AnimationRiggingManager), nameof(AnimationRiggingManager.SpawnFpvParticles))), + new CodeInstruction(OpCodes.Brtrue_S, codes[i - 5].operand) + }); + break; + } + } + //FieldInfo fld_muzzle = AccessTools.Field(typeof(ItemActionRanged.ItemActionDataRanged), nameof(ItemActionRanged.ItemActionDataRanged.muzzle)); + //FieldInfo fld_muzzle2 = AccessTools.Field(typeof(ItemActionRanged.ItemActionDataRanged), nameof(ItemActionRanged.ItemActionDataRanged.muzzle2)); + //MethodInfo mtd_getmuzzle = AccessTools.Method(typeof(AnimationRiggingManager), nameof(AnimationRiggingManager.GetMuzzleOverrideFPV)); + //MethodInfo mtd_getmuzzle2 = AccessTools.Method(typeof(AnimationRiggingManager), nameof(AnimationRiggingManager.GetMuzzle2OverrideFPV)); + //for (int i = 0; i < codes.Count; i++) + //{ + // if (codes[i].LoadsField(fld_muzzle)) + // { + // codes.InsertRange(i + 1, new[] + // { + // new CodeInstruction(OpCodes.Ldloc_S, 4), + // new CodeInstruction(OpCodes.Call, mtd_getmuzzle) + // }); + // } + // else if (codes[i].LoadsField(fld_muzzle2)) + // { + // codes.InsertRange(i + 1, new[] + // { + // new CodeInstruction(OpCodes.Ldloc_S, 4), + // new CodeInstruction(OpCodes.Call, mtd_getmuzzle2) + // }); + // } + //} + + return codes; + } + + [HarmonyPatch(typeof(Inventory), nameof(Inventory.clearSlotByIndex))] + [HarmonyPrefix] + private static bool Prefix_clearSlotByIndex(Inventory __instance, int _idx) + { + if (__instance.entity is EntityPlayer) + AnimationRiggingManager.OnClearInventorySlot(__instance, _idx); + return true; + } + + [HarmonyPatch(typeof(AvatarMultiBodyController), nameof(AvatarMultiBodyController.Update))] + [HarmonyPostfix] + private static void Postfix_Update_AvatarMultiBodyController(AvatarMultiBodyController __instance) + { + AnimationRiggingManager.UpdatePlayerAvatar(__instance); + if (__instance is AvatarLocalPlayerController avatarLocalPlayer) + { + //if ((avatarLocalPlayer.entity as EntityPlayerLocal).bFirstPersonView && !avatarLocalPlayer.entity.inventory.GetIsFinishedSwitchingHeldItem()) + //{ + // avatarLocalPlayer.UpdateInt(AvatarController.weaponHoldTypeHash, -1, false); + // avatarLocalPlayer.UpdateBool("Holstered", false, false); + // avatarLocalPlayer.FPSArms.Animator.Play("idle", 0, 0f); + //} + var mapping = MultiActionManager.GetMappingForEntity(__instance.entity.entityId); + if (mapping != null) + { + avatarLocalPlayer.UpdateInt(MultiActionUtils.ExecutingActionIndexHash, mapping.CurActionIndex, true); + } + + if (__instance.entity.inventory?.holdingItemData?.actionData != null) + { + foreach (var actionData in __instance.entity.inventory.holdingItemData.actionData) + { + if (actionData is IModuleContainerFor data) + { + avatarLocalPlayer.UpdateInt(ActionModuleFireModeSelector.FireModeParamHashes[actionData.indexInEntityOfAction], data.Instance.currentFireMode, true); + } + } + } + } + if (__instance.entity.AttachedToEntity) + { + __instance.SetVehicleAnimation(AvatarController.vehiclePoseHash, __instance.entity.vehiclePoseMode); + } + } + + [HarmonyPatch(typeof(LegacyAvatarController), nameof(LegacyAvatarController.Update))] + [HarmonyPostfix] + private static void Postfix_Update_LegacyAvatarController(LegacyAvatarController __instance) + { + AnimationRiggingManager.UpdatePlayerAvatar(__instance); + if (__instance.entity && __instance.entity.AttachedToEntity) + { + __instance.SetVehicleAnimation(AvatarController.vehiclePoseHash, __instance.entity.vehiclePoseMode); + } + } + + //[HarmonyPatch(typeof(AvatarLocalPlayerController), nameof(AvatarLocalPlayerController.LateUpdate))] + //[HarmonyPostfix] + //private static void Postfix_LateUpdate_AvatarLocalPlayerController(AvatarLocalPlayerController __instance) + //{ + // var targets = AnimationRiggingManager.GetRigTargetsFromPlayer(__instance.entity as EntityPlayer); + // if (targets && !targets.Destroyed) + // { + // targets.UpdateTpvSpineRotation(__instance.entity as EntityPlayer); + // } + //} + + //[HarmonyPatch(typeof(AvatarSDCSController), nameof(AvatarSDCSController.LateUpdate))] + //[HarmonyPostfix] + //private static void Postfix_LateUpdate_AvatarSDCSController(AvatarSDCSController __instance) + //{ + // var targets = AnimationRiggingManager.GetRigTargetsFromPlayer(__instance.entity as EntityPlayer); + // if (targets && !targets.Destroyed) + // { + // targets.UpdateTpvSpineRotation(__instance.entity as EntityPlayer); + // } + //} + + [HarmonyPatch(typeof(AvatarMultiBodyController), nameof(AvatarMultiBodyController.StartAnimationReloading))] + [HarmonyPrefix] + private static bool Prefix_StartAnimationReloding_AvatarController(AvatarMultiBodyController __instance) + { + __instance.Entity?.FireEvent(CustomEnums.onReloadAboutToStart); + return true; + } + + [HarmonyPatch(typeof(AvatarMultiBodyController), nameof(AvatarMultiBodyController.StartAnimationReloading))] + [HarmonyPostfix] + private static void Postfix_StartAnimationReloading_AvatarMultibodyController(AvatarMultiBodyController __instance) + { + if (__instance.HeldItemTransform != null && __instance.HeldItemTransform.TryGetComponent(out var targets) && !targets.Destroyed) + { + EntityAlive entity = __instance.Entity; + ItemValue holdingItemItemValue = entity.inventory.holdingItemItemValue; +//#if DEBUG +// float x = 1, y = 1; +// var tags = entity.inventory.holdingItem.ItemTags; +// var tags_prev = tags; +// MultiActionManager.ModifyItemTags(entity.inventory.holdingItemItemValue, entity.MinEventContext.ItemActionData, ref tags); +// entity.Progression.ModifyValue(PassiveEffects.ReloadSpeedMultiplier, ref x, ref y, tags); +// Log.Out($"item {entity.inventory.holdingItem.Name} action index {entity.MinEventContext.ItemActionData.indexInEntityOfAction} progression base {x} perc {y} has tag {tags.Test_AnySet(FastTags.Parse("perkMachineGunner"))} \ntags prev {tags_prev} \ntags after {tags}"); +//#endif + float reloadSpeed = EffectManager.GetValue(PassiveEffects.ReloadSpeedMultiplier, holdingItemItemValue, 1f, entity); + float reloadSpeedRatio = EffectManager.GetValue(CustomEnums.ReloadSpeedRatioFPV2TPV, holdingItemItemValue, 1f, entity); + + float partialReloadMultiplier = EffectManager.GetValue(CustomEnums.PartialReloadCount, holdingItemItemValue, 0, entity); + float partialReloadRatio = 1f; + if (partialReloadMultiplier <= 0) + { + partialReloadMultiplier = 1; + } + else + { + int magSize = (int)EffectManager.GetValue(PassiveEffects.MagazineSize, holdingItemItemValue, ((ItemActionRanged)entity.inventory.holdingItem.Actions[MultiActionManager.GetActionIndexForEntity(entity)]).BulletsPerMagazine, entity); + //how many partial reload is required to fill an empty mag + partialReloadRatio = Mathf.Ceil(magSize / partialReloadMultiplier); + //how many partial reload is required to finish this reload + partialReloadMultiplier = Mathf.Ceil((magSize - holdingItemItemValue.Meta) / partialReloadMultiplier); + //reload time percentage of this reload + partialReloadRatio = partialReloadMultiplier / partialReloadRatio; + } + + float localMultiplier, remoteMultiplier; + bool isFPV = entity as EntityPlayerLocal != null && (entity as EntityPlayerLocal).emodel.IsFPV; + bool takeOverReloadTime = AnimationRiggingManager.IsReloadTimeTakeOverItem(holdingItemItemValue.type); + + if (isFPV && !takeOverReloadTime) + { + localMultiplier = reloadSpeed / reloadSpeedRatio; + } + else if (!isFPV && takeOverReloadTime) + { + localMultiplier = reloadSpeed * reloadSpeedRatio / partialReloadMultiplier; + } + else if(isFPV && takeOverReloadTime) + { + localMultiplier = reloadSpeed; + } + else + { + localMultiplier = reloadSpeed * partialReloadRatio; + } + + if (takeOverReloadTime) + { + remoteMultiplier = reloadSpeed * reloadSpeedRatio / partialReloadMultiplier; + } + else + { + remoteMultiplier = reloadSpeed * partialReloadRatio; + } + + if (ConsoleCmdReloadLog.LogInfo) + Log.Out($"Set reload multiplier: isFPV {isFPV}, reloadSpeed {reloadSpeed}, reloadSpeedRatio {reloadSpeedRatio}, finalMultiplier {localMultiplier}, remoteMultiplier {remoteMultiplier}, partialMultiplier {partialReloadMultiplier}, partialRatio {partialReloadRatio}"); + + __instance.UpdateFloat(AvatarController.reloadSpeedHash, localMultiplier, false); + SetDataFloat(__instance, (AvatarController.DataTypes)AvatarController.reloadSpeedHash, remoteMultiplier, true); + } + } + + /// + /// sets float only on remote clients but not on local client. + /// + /// + /// + /// + /// + [HarmonyPatch(typeof(AvatarController), nameof(AvatarController.SetDataFloat))] + [HarmonyReversePatch(HarmonyReversePatchType.Original)] + private static void SetDataFloat(AvatarController __instance, AvatarController.DataTypes _type, float _value, bool _netsync = true) + { + IEnumerable Transpiler(IEnumerable instructions) + { + if (instructions == null) + return null; + + var codes = instructions.ToList(); + codes.RemoveRange(0, 5); + codes[0].labels.Clear(); + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].LoadsConstant(AnimParamData.ValueTypes.DataFloat)) + { + codes[i].opcode = OpCodes.Ldc_I4; + codes[i].operand = (int)AnimParamData.ValueTypes.Float; + break; + } + } + return codes; + } + _ = Transpiler(null); + } + + //[HarmonyPatch(typeof(AvatarMultiBodyController), nameof(AvatarMultiBodyController.StartAnimationAttack))] + //[HarmonyPostfix] + //private static void Postfix_StartAnimationAttack_AvatarMultiBodyController(AvatarMultiBodyController __instance) + //{ + // if (__instance is AvatarLocalPlayerController) + // AnimationRiggingManager.FpvWeaponFire(); + //} + + [HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.SetFirstPersonView))] + [HarmonyPrefix] + private static bool Prefix_SetFirstPersonView_EntityPlayerLocal(EntityPlayerLocal __instance, bool _bFirstPersonView) + { + var targets = AnimationRiggingManager.GetRigTargetsFromPlayer(__instance); + if (_bFirstPersonView != __instance.bFirstPersonView && targets && !targets.Destroyed && targets.IsAnimationSet) + { + //targets.SetEnabled(false); + //targets.GraphBuilder.SetCurrentTarget(null); + Log.Out($"Switch view destroy slot {__instance.inventory.holdingItemIdx}"); + targets.Destroy(); + } + return true; + } + + [HarmonyPatch(typeof(AvatarLocalPlayerController), nameof(AvatarLocalPlayerController.SwitchModelAndView))] + [HarmonyPostfix] + private static void Postfix_SwitchModelAndView_AvatarLocalPlayerController(AvatarLocalPlayerController __instance, bool _bFPV) + { + if (_bFPV) + { + __instance.hasTurnRate = false; + } + } + + [HarmonyPatch(typeof(AvatarLocalPlayerController), nameof(AvatarLocalPlayerController.SetInRightHand))] + [HarmonyPostfix] + private static void Postfix_SetInRightHand_AvatarLocalPlayerController(Transform _transform, AvatarLocalPlayerController __instance) + { + if (_transform != null && _transform.TryGetComponent(out var targets) && !targets.Destroyed && targets.ItemCurrent) + { + //targets.SetEnabled(true); + targets.GraphBuilder.SetCurrentTarget(targets); + } + else if (__instance.PrimaryBody?.Animator && __instance.PrimaryBody.Animator.TryGetComponent(out var builder)) + { + builder.SetCurrentTarget(null); + } + } + + [HarmonyPatch(typeof(LegacyAvatarController), nameof(LegacyAvatarController.SetInRightHand))] + [HarmonyPostfix] + private static void Postfix_SetInRightHand_LegacyAvatarController(Transform _transform, LegacyAvatarController __instance) + { + if (_transform != null && _transform.TryGetComponent(out var targets) && !targets.Destroyed && targets.ItemCurrent) + { + //targets.SetEnabled(true); + targets.GraphBuilder.SetCurrentTarget(targets); + } + else if (__instance.anim && __instance.anim.TryGetComponent(out var builder)) + { + builder.SetCurrentTarget(null); + } + } + + //[HarmonyPatch(typeof(Inventory), nameof(Inventory.setHoldingItemTransform))] + //[HarmonyPrefix] + //private static bool Prefix_setHoldingItemTransform_Inventory(Inventory __instance) + //{ + // if (__instance.lastdrawnHoldingItemTransform && __instance.lastdrawnHoldingItemTransform.TryGetComponent(out var targets) && !targets.Destroyed) + // { + // targets.SetEnabled(false); + // } + // return true; + //} + + [HarmonyPatch(typeof(vp_FPWeapon), nameof(vp_FPWeapon.Start))] + [HarmonyPostfix] + private static void Postfix_Start_vp_FPWeapon(vp_FPWeapon __instance) + { + var player = __instance.GetComponentInParent(); + if (player && player.inventory != null) + { + for (int i = 0; i < player.inventory.models.Length; i++) + { + Transform model = player.inventory.models[i]; + if (model != null && model.TryGetComponent(out var targets) && !targets.Destroyed) + { + if (i == player.inventory.holdingItemIdx) + { + player.inventory.ForceHoldingItemUpdate(); + } + else + { + targets.Init(__instance.transform, true); + } + } + } + } + } + + #region temporary fix for arm glitch on switching weapon + [HarmonyPatch(typeof(Inventory), nameof(Inventory.updateHoldingItem))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_updateHoldingItem_Inventory(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var mtd_setparent = AccessTools.Method(typeof(Transform), nameof(Transform.SetParent), new[] { typeof(Transform), typeof(bool) }); + var mtd_startholding = AccessTools.Method(typeof(ItemClass), nameof(ItemClass.StartHolding)); + var mtd_showrighthand = AccessTools.Method(typeof(Inventory), nameof(Inventory.ShowRightHand)); + var mtd_holdingchanged = AccessTools.Method(typeof(EntityAlive), nameof(EntityAlive.OnHoldingItemChanged)); + var prop_holdingitem = AccessTools.PropertyGetter(typeof(Inventory), nameof(Inventory.holdingItem)); + var fld_transform = AccessTools.Field(typeof(MinEventParams), nameof(MinEventParams.Transform)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_setparent)) + { + codes[i - 1].opcode = OpCodes.Ldc_I4_1; + } + else if (codes[i].Calls(mtd_startholding)) + { + for (int j = i - 1; j >= 0; j--) + { + if (codes[j].Calls(prop_holdingitem)) + { + for (int k = i + 1; k < codes.Count; k++) + { + if (codes[k].StoresField(fld_transform)) + { + codes.InsertRange(k + 1, new[] + { + new CodeInstruction(OpCodes.Ldloc_0).WithLabels(codes[k + 1].ExtractLabels()), + CodeInstruction.LoadField(typeof(CustomEnums), nameof(CustomEnums.onSelfHoldingItemAssemble)), + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(Inventory), nameof(Inventory.entity)), + CodeInstruction.LoadField(typeof(EntityAlive), nameof(EntityAlive.MinEventContext)), + CodeInstruction.Call(typeof(ItemValue), nameof(ItemValue.FireEvent)), + }); + k += 6; + } + else if (codes[k].Calls(mtd_showrighthand)) + { + codes.InsertRange(k + 1, codes.GetRange(j - 1, i - j + 2)); + codes[i + 1].WithLabels(codes[j - 1].ExtractLabels()); + codes.RemoveRange(j - 1, i - j + 2); + break; + } + } + break; + } + } + i += 6; + } + else if (codes[i].Calls(mtd_holdingchanged)) + { + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_0).WithLabels(codes[i + 1].ExtractLabels()), + CodeInstruction.Call(typeof(Inventory), nameof(Inventory.syncHeldItem)) + }); + break; + } + } + return codes; + } + + [HarmonyPatch(typeof(Inventory), nameof(Inventory.setHoldingItemTransform))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_setHoldingItemTransform_Inventory(IEnumerable instructions) + { + var codes = instructions.ToList(); + var mtd_sync = AccessTools.Method(typeof(Inventory), nameof(Inventory.syncHeldItem)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_sync)) + { + codes[i + 1].WithLabels(codes[i - 1].ExtractLabels()); + codes.RemoveRange(i - 1, 2); + break; + } + } + return codes; + } + + + //private static Coroutine delayShowWeaponCo; + //private static IEnumerator DelayShowWeapon(Camera camera) + //{ + // Log.Out($"Delay show weapon!"); + // camera.cullingMask &= ~(1 << 10); + // yield return new WaitForSeconds(0.5f); + // if (camera) + // { + // camera.cullingMask |= 1 << 10; + // } + // delayShowWeaponCo = null; + // Log.Out($"Show weapon!"); + // yield break; + //} + + //[HarmonyPatch(typeof(Inventory), nameof(Inventory.setHeldItemByIndex))] + //[HarmonyPrefix] + //private static bool Prefix_setHeldItemByIndex_Inventory(Inventory __instance, out bool __state) + //{ + // __state = __instance.holdingItemData?.model && __instance.holdingItemData.model.GetComponent(); + + // return true; + //} + + //[HarmonyPatch(typeof(Inventory), nameof(Inventory.setHeldItemByIndex))] + //[HarmonyPostfix] + //private static void Postfix_setHeldItemByIndex_Inventory(Inventory __instance, bool __state) + //{ + // if (__state && __instance.entity is EntityPlayerLocal player && player.bFirstPersonView && (!__instance.holdingItemData?.model || !__instance.holdingItemData.model.GetComponent())) + // { + // if (delayShowWeaponCo != null) + // { + // ThreadManager.StopCoroutine(delayShowWeaponCo); + // } + + // if (__instance.holdingItemIdx == __instance.DUMMY_SLOT_IDX) + // { + // player.ShowHoldingItem(true); + // } + // else + // { + // delayShowWeaponCo = ThreadManager.StartCoroutine(DelayShowWeapon(player.playerCamera)); + // } + // } + //} + + //[HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.ShowHoldingItem))] + //[HarmonyPrefix] + //private static bool Prefix_ShowHoldingItem_EntityPlayerLocal(bool show) + //{ + // if (delayShowWeaponCo != null) + // { + // if (show) + // { + // return false; + // } + // ThreadManager.StopCoroutine(delayShowWeaponCo); + // } + // return true; + //} + /* + [HarmonyPatch(typeof(Inventory), nameof(Inventory.setHoldingItemTransform))] + [HarmonyPostfix] + private static void Postfix_setHoldingItemTransform_Inventory(Transform _t, Inventory __instance) + { + if (_t != null && _t.TryGetComponent(out var targets) && !targets.Destroyed) + { + targets.SetEnabled(__instance.entity.emodel.IsFPV); + } + } + + [HarmonyPatch(typeof(Inventory), nameof(Inventory.SetItem), new[] {typeof(int), typeof(ItemValue), typeof(int), typeof(bool)})] + [HarmonyTranspiler] + private static IEnumerable Transpiler_SetItem_Inventory(IEnumerable instructions) + { + MethodInfo mtd_update = AccessTools.Method(typeof(Inventory), nameof(Inventory.updateHoldingItem)); + foreach (var code in instructions) + { + yield return code; + if (code.Calls(mtd_update)) + { + yield return new CodeInstruction(OpCodes.Ldarg_0); + yield return new CodeInstruction(OpCodes.Ldc_I4_1); + yield return new CodeInstruction(OpCodes.Ldc_R4, 0f); + yield return CodeInstruction.Call(typeof(Inventory), nameof(Inventory.ShowHeldItem)); + } + } + } + + [HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.ShowWeaponCamera))] + [HarmonyPostfix] + private static void Postfix_ShowWeaponCamera_EntityPlayerLocal(EntityPlayerLocal __instance, bool show) + { + if (__instance.bFirstPersonView) + { + __instance.weaponCamera.cullingMask &= ~(1 << 10); + if (delayShowWeaponCo != null) + { + ThreadManager.StopCoroutine(delayShowWeaponCo); + } + if (show) + { + delayShowWeaponCo = ThreadManager.StartCoroutine(DelayShowWeapon(__instance.weaponCamera)); + } + } + } + + */ + #endregion + + [HarmonyPatch(typeof(World), nameof(World.SpawnEntityInWorld))] + [HarmonyPrefix] + private static bool Prefix_SpawnEntityInWorld_World(Entity _entity) + { + if (_entity is EntityItem _entityItem) + { + var targets = _entityItem.GetComponentInChildren(true); + if (targets && !targets.Destroyed) + { + targets.Destroy(); + } + } + return true; + } + + [HarmonyPatch(typeof(World), nameof(World.SpawnEntityInWorld))] + [HarmonyPostfix] + private static void Postfix_SpawnEntityInWorld_World(Entity _entity) + { + if (_entity is EntityPlayer player && !(_entity is EntityPlayerLocal) && player.inventory != null) + { + foreach (var model in player.inventory.models) + { + if (model && model.TryGetComponent(out var targets) && !targets.Destroyed) + { + targets.DestroyFpv(); + targets.Init(player.emodel.avatarController.GetActiveModelRoot(), false); + } + } + } + } + + [HarmonyPatch(typeof(EntityItem), nameof(EntityItem.createMesh))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_createMesh_EntityItem(IEnumerable instructions) + { + var codes = instructions.ToList(); + codes[codes.Count - 1].WithLabels(codes[codes.Count - 11].labels); + codes.RemoveRange(codes.Count - 11, 10); + return codes; + } + + [HarmonyPatch(typeof(EntityItem), nameof(EntityItem.createMesh))] + [HarmonyPostfix] + private static void Postfix_createMesh_EntityItem(EntityItem __instance) + { + if (__instance.itemTransform) + { + __instance.itemTransform.tag = "Item"; + if (__instance.itemTransform.TryGetComponent(out var targets) && !targets.Destroyed) + { + targets.Destroy(); + } + } + __instance.meshRenderers = __instance.itemTransform.GetComponentsInChildren(true); + __instance.VisiblityCheck(0, false); + } + + [HarmonyPatch(typeof(EModelBase), nameof(EModelBase.SwitchModelAndView))] + [HarmonyPostfix] + private static void Postfix_SwitchModelAndView_EModelBase(EModelBase __instance) + { + if (__instance.entity is EntityPlayerLocal player && player.inventory != null) + { + foreach (var model in player.inventory.models) + { + if (model && model.TryGetComponent(out var targets) && !targets.Destroyed) + { + targets.Init(player.emodel.avatarController.GetActiveModelRoot(), player.bFirstPersonView); + } + } + } + } + + [HarmonyPatch(typeof(EntityAlive), nameof(EntityAlive.Detach))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_Detach_EntityAlive(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var fld_inv = AccessTools.Field(typeof(EntityAlive), nameof(EntityAlive.inventory)); + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].StoresField(fld_inv)) + { + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.Call(typeof(AnimationRiggingPatches), nameof(AnimationRiggingPatches.DetachInitInventory)) + }); + break; + } + } + return codes; + } + + private static void DetachInitInventory(EntityAlive __instance) + { + if (!(__instance is EntityPlayer player)) + { + return; + } + if (__instance.inventory != null) + { + foreach (var model in __instance.inventory.models) + { + if (model && model.TryGetComponent(out var targets) && !targets.Destroyed) + { + targets.Init(__instance.emodel.avatarController.GetActiveModelRoot(), player is EntityPlayerLocal localPlayer ? localPlayer.bFirstPersonView : false); + } + } + } + } + + [HarmonyPatch(typeof(SDCSUtils), nameof(SDCSUtils.cleanupEquipment))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_cleanupEquipment_SDCSUtils(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var mtd_removeat = AccessTools.Method(typeof(List), nameof(List.RemoveAt)); + var mtd_destroy = AccessTools.Method(typeof(GameUtils), nameof(GameUtils.DestroyAllChildrenBut), new[] {typeof(Transform), typeof(List)}); + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_removeat)) + { + codes.InsertRange(i - 2, new[] + { + new CodeInstruction(OpCodes.Ldloc_2), + new CodeInstruction(OpCodes.Ldloc_3), + CodeInstruction.Call(typeof(List), "get_Item"), + CodeInstruction.Call(typeof(RigLayer), "get_name"), + CodeInstruction.Call(typeof(AnimationRiggingManager), nameof(AnimationRiggingManager.ShouldExcludeRig)), + new CodeInstruction(OpCodes.Brtrue_S, codes[i - 3].operand) + }); + i += 6; + } + else if (codes[i].Calls(mtd_destroy)) + { + codes.InsertRange(i, new[] + { + new CodeInstruction(OpCodes.Dup), + CodeInstruction.Call(typeof(AnimationRiggingManager), nameof(AnimationRiggingManager.GetExcludeRigs)), + CodeInstruction.Call(typeof(List), nameof(List.AddRange)), + }); + i += 3; + } + } + + return codes; + } + + [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.SwapSelectedAmmo))] + [HarmonyPrefix] + private static bool Prefix_SwapSelectedAmmo_ItemActionRanged(ItemActionRanged __instance, EntityAlive _entity, int _ammoIndex) + { + if (_ammoIndex == (int)_entity.inventory.holdingItemItemValue.SelectedAmmoTypeIndex && __instance is IModuleContainerFor inspectable && _entity is EntityPlayerLocal player) + { + ItemActionRanged.ItemActionDataRanged _actionData = _entity.inventory.holdingItemData.actionData[__instance.ActionIndex] as ItemActionRanged.ItemActionDataRanged; + if (!_entity.MovementRunning && !_entity.AimingGun && !player.bLerpCameraFlag && _actionData != null && !_entity.inventory.holdingItem.IsActionRunning(_entity.inventory.holdingItemData) && !__instance.CanReload(_actionData) && (_entity.inventory.holdingItemItemValue.Meta > 0 || inspectable.Instance.allowEmptyInspect)) + { + _entity.emodel.avatarController._setTrigger("weaponInspect", true); + return false; + } + } + return true; + } + + [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.ExecuteAction))] + [HarmonyPostfix] + private static void Postfix_ExecuteAction_ItemActionRanged(ItemActionRanged __instance, ItemActionData _actionData) + { + if (_actionData is ItemActionRanged.ItemActionDataRanged rangedData) + { + int burstCount = __instance.GetBurstCount(_actionData); + _actionData.invData.holdingEntity.emodel.avatarController._setBool("TriggerPulled", rangedData.bPressed && rangedData.curBurstCount < burstCount, true); + } + } + + [HarmonyPatch(typeof(ItemClass), nameof(ItemClass.LateInitAll))] + [HarmonyPostfix] + private static void Postfix_LateInitAll_ItemClass() + { + AnimationRiggingManager.ParseItemIDs(); + } + + [HarmonyPatch(typeof(Animator), nameof(Animator.Rebind), new Type[0])] + [HarmonyReversePatch(HarmonyReversePatchType.Original)] + public static void RebindNoDefault(this Animator __instance) + { + IEnumerable Transpiler(IEnumerable instructions) + { + if (instructions == null) + { + yield break; + } + foreach (var ins in instructions) + { + if (ins.opcode == OpCodes.Ldc_I4_1) + { + yield return new CodeInstruction(OpCodes.Ldc_I4_0); + } + else + { + yield return ins; + } + } + } + _ = Transpiler(null); + } + + //[HarmonyPatch(typeof(ItemActionDynamic), nameof(ItemActionDynamic.GetExecuteActionGrazeTarget))] + //[HarmonyPostfix] + //private static void Postfix_Test2(WorldRayHitInfo[] __result) + //{ + // Log.Out($"World ray info count: {__result.Length}"); + //} + + //[HarmonyPatch(typeof(ItemActionDynamic), nameof(ItemActionDynamic.hitTarget))] + //[HarmonyPostfix] + //private static void Postfix_hittest(ItemActionData _actionData, WorldRayHitInfo hitInfo, bool _isGrazingHit = false) + //{ + // Log.Out($"HIT TARGET! IsGrazing: {_isGrazingHit}\n{StackTraceUtility.ExtractStackTrace()}"); + //} + + //[HarmonyPatch(typeof(AvatarLocalPlayerController), nameof(AvatarLocalPlayerController._setTrigger))] + //[HarmonyPostfix] + //private static void Postfix_AvatarLocalPlayerController_SetTrigger(int _pid, AvatarLocalPlayerController __instance) + //{ + // AnimationRiggingManager.SetTrigger(_pid, __instance.entity as EntityPlayer); + //} + + //[HarmonyPatch(typeof(AvatarLocalPlayerController), nameof(AvatarLocalPlayerController._resetTrigger))] + //[HarmonyPostfix] + //private static void Postfix_AvatarLocalPlayerController_ResetTrigger(int _pid, AvatarLocalPlayerController __instance) + //{ + // AnimationRiggingManager.ResetTrigger(_pid, __instance.entity as EntityPlayer); + //} + + //[HarmonyPatch(typeof(AvatarLocalPlayerController), nameof(AvatarLocalPlayerController._setFloat))] + //[HarmonyPostfix] + //private static void Postfix_AvatarLocalPlayerController_SetFloat(int _pid, float _value, AvatarLocalPlayerController __instance) + //{ + // AnimationRiggingManager.SetFloat(_pid, _value, __instance.entity as EntityPlayer); + //} + + //[HarmonyPatch(typeof(AvatarLocalPlayerController), nameof(AvatarLocalPlayerController._setBool))] + //[HarmonyPostfix] + //private static void Postfix_AvatarLocalPlayerController_SetBool(int _pid, bool _value, AvatarLocalPlayerController __instance) + //{ + // AnimationRiggingManager.SetBool(_pid, _value, __instance.entity as EntityPlayer); + //} + + //[HarmonyPatch(typeof(AvatarLocalPlayerController), nameof(AvatarLocalPlayerController._setInt))] + //[HarmonyPostfix] + //private static void Postfix_AvatarLocalPlayerController_SetInt(int _pid, int _value, AvatarLocalPlayerController __instance) + //{ + // AnimationRiggingManager.SetInt(_pid, _value, __instance.entity as EntityPlayer); + //} + + //[HarmonyPatch(typeof(LegacyAvatarController), nameof(LegacyAvatarController._setTrigger))] + //[HarmonyPostfix] + //private static void Postfix_LegacyAvatarController_SetTrigger(int _propertyHash, LegacyAvatarController __instance) + //{ + // AnimationRiggingManager.SetTrigger(_propertyHash, __instance.entity as EntityPlayer); + //} + + //[HarmonyPatch(typeof(LegacyAvatarController), nameof(LegacyAvatarController._resetTrigger))] + //[HarmonyPostfix] + //private static void Postfix_LegacyAvatarController_ResetTrigger(int _propertyHash, LegacyAvatarController __instance) + //{ + // AnimationRiggingManager.ResetTrigger(_propertyHash, __instance.entity as EntityPlayer); + //} + + //[HarmonyPatch(typeof(LegacyAvatarController), nameof(LegacyAvatarController._setFloat))] + //[HarmonyPostfix] + //private static void Postfix_LegacyAvatarController_SetFloat(int _propertyHash, float _value, LegacyAvatarController __instance) + //{ + // AnimationRiggingManager.SetFloat(_propertyHash, _value, __instance.entity as EntityPlayer); + //} + + //[HarmonyPatch(typeof(LegacyAvatarController), nameof(LegacyAvatarController._setBool))] + //[HarmonyPostfix] + //private static void Postfix_LegacyAvatarController_SetBool(int _propertyHash, bool _value, LegacyAvatarController __instance) + //{ + // AnimationRiggingManager.SetBool(_propertyHash, _value, __instance.entity as EntityPlayer); + //} + + //[HarmonyPatch(typeof(LegacyAvatarController), nameof(LegacyAvatarController._setInt))] + //[HarmonyPostfix] + //private static void Postfix_LegacyAvatarController_SetInt(int _propertyHash, int _value, LegacyAvatarController __instance) + //{ + // AnimationRiggingManager.SetInt(_propertyHash, _value, __instance.entity as EntityPlayer); + //} + + [HarmonyPatch(typeof(AvatarLocalPlayerController), nameof(AvatarLocalPlayerController._resetTrigger), typeof(int), typeof(bool))] + [HarmonyReversePatch(HarmonyReversePatchType.Original)] + public static void VanillaResetTrigger(AvatarLocalPlayerController __instance, int _pid, bool _netsync = true) + { + + } + + [HarmonyPatch(typeof(AvatarController), nameof(AvatarController.TryGetTrigger), new[] { typeof(int), typeof(bool) }, new[] { ArgumentType.Normal, ArgumentType.Out })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_TryGetTrigger_AvatarController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetBool), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedBool))); + } + + [HarmonyPatch(typeof(AvatarController), nameof(AvatarController.TryGetBool), new[] { typeof(int), typeof(bool) }, new[] { ArgumentType.Normal, ArgumentType.Out })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_TryGetBool_AvatarController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetBool), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedBool))); + } + + [HarmonyPatch(typeof(AvatarController), nameof(AvatarController.TryGetInt), new[] { typeof(int), typeof(int) }, new[] { ArgumentType.Normal, ArgumentType.Out })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_TryGetInt_AvatarController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetInteger), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedInt))); + } + + [HarmonyPatch(typeof(AvatarController), nameof(AvatarController.TryGetFloat), new[] { typeof(int), typeof(float) }, new[] { ArgumentType.Normal, ArgumentType.Out })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_TryGetFloat_AvatarController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetFloat), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedFloat))); + } + + [HarmonyPatch(typeof(AvatarMultiBodyController), nameof(AvatarMultiBodyController.TryGetTrigger), new[] { typeof(int), typeof(bool) }, new[] { ArgumentType.Normal, ArgumentType.Out })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_TryGetTrigger_AvatarMultiBodyController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetBool), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedBool))); + } + + [HarmonyPatch(typeof(AvatarMultiBodyController), nameof(AvatarMultiBodyController.TryGetBool), new[] { typeof(int), typeof(bool) }, new[] { ArgumentType.Normal, ArgumentType.Out })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_TryGetBool_AvatarMultiBodyController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetBool), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedBool))); + } + + [HarmonyPatch(typeof(AvatarMultiBodyController), nameof(AvatarMultiBodyController.TryGetInt), new[] { typeof(int), typeof(int) }, new[] { ArgumentType.Normal, ArgumentType.Out })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_TryGetInt_AvatarMultiBodyController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetInteger), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedInt))); + } + + [HarmonyPatch(typeof(AvatarMultiBodyController), nameof(AvatarMultiBodyController.TryGetFloat), new[] { typeof(int), typeof(float) }, new[] { ArgumentType.Normal, ArgumentType.Out })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_TryGetFloat_AvatarMultiBodyController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetFloat), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedFloat))); + } + + [HarmonyPatch(typeof(AvatarController), nameof(AvatarController._setBool), new[] { typeof(int), typeof(bool), typeof(bool) })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_setBool_AvatarController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetBool), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedBool))) + .MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.SetBool), new[] { typeof(int), typeof(bool) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.SetWrappedBool))); + } + + [HarmonyPatch(typeof(AvatarController), nameof(AvatarController._setTrigger), new[] { typeof(int), typeof(bool) })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_setTrigger_AvatarController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetBool), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedBool))) + .MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.SetTrigger), new[] { typeof(int)}), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.SetWrappedTrigger))); + } + + [HarmonyPatch(typeof(AvatarController), nameof(AvatarController._resetTrigger), new[] { typeof(int), typeof(bool) })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_resetTrigger_AvatarController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetBool), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedBool))) + .MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.ResetTrigger), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.ResetWrappedTrigger))); + } + + [HarmonyPatch(typeof(AvatarController), nameof(AvatarController._setInt), new[] { typeof(int), typeof(int), typeof(bool) })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_setInt_AvatarController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetInteger), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedInt))) + .MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.SetInteger), new[] { typeof(int), typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.SetWrappedInt))); + } + + [HarmonyPatch(typeof(AvatarController), nameof(AvatarController._setFloat), new[] { typeof(int), typeof(float), typeof(bool) })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_setFloat_AvatarController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetFloat), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedFloat))) + .MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.SetFloat), new[] { typeof(int), typeof(float) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.SetWrappedFloat))); + } + + [HarmonyPatch(typeof(AvatarCharacterController), nameof(AvatarCharacterController._setBool), new[] { typeof(int), typeof(bool), typeof(bool) })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_setBool_AvatarCharacterController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.SetBool), new[] { typeof(int), typeof(bool) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.SetWrappedBool))); + } + + [HarmonyPatch(typeof(AvatarCharacterController), nameof(AvatarCharacterController._setTrigger), new[] { typeof(int), typeof(bool) })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_setTrigger_AvatarCharacterController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.SetTrigger), new[] { typeof(int)}), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.SetWrappedTrigger))); + } + + [HarmonyPatch(typeof(AvatarCharacterController), nameof(AvatarCharacterController._resetTrigger), new[] { typeof(int), typeof(bool) })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_resetTrigger_AvatarCharacterController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.ResetTrigger), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.ResetWrappedTrigger))); + } + + [HarmonyPatch(typeof(AvatarCharacterController), nameof(AvatarCharacterController._setInt), new[] { typeof(int), typeof(int), typeof(bool) })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_setInt_AvatarCharacterController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.SetInteger), new[] { typeof(int), typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.SetWrappedInt))); + } + + [HarmonyPatch(typeof(AvatarCharacterController), nameof(AvatarCharacterController._setFloat), new[] { typeof(int), typeof(float), typeof(bool) })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_setFloat_AvatarCharacterController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.SetFloat), new[] { typeof(int), typeof(float) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.SetWrappedFloat))); + } + + [HarmonyPatch(typeof(AvatarMultiBodyController), nameof(AvatarMultiBodyController._setBool), new[] { typeof(int), typeof(bool), typeof(bool) })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_setBool_AvatarMultiBodyController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetBool), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedBool))) + .MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.SetBool), new[] { typeof(int), typeof(bool) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.SetWrappedBool))); + } + + [HarmonyPatch(typeof(AvatarMultiBodyController), nameof(AvatarMultiBodyController._setTrigger), new[] { typeof(int), typeof(bool) })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_setTrigger_AvatarMultiBodyController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetBool), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedBool))) + .MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.SetTrigger), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.SetWrappedTrigger))); + } + + [HarmonyPatch(typeof(AvatarMultiBodyController), nameof(AvatarMultiBodyController._resetTrigger), new[] { typeof(int), typeof(bool) })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_resetTrigger_AvatarMultiBodyController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetBool), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedBool))) + .MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.ResetTrigger), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.ResetWrappedTrigger))); + } + + [HarmonyPatch(typeof(AvatarMultiBodyController), nameof(AvatarMultiBodyController._setInt), new[] { typeof(int), typeof(int), typeof(bool) })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_setInt_AvatarMultiBodyController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetInteger), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedInt))) + .MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.SetInteger), new[] { typeof(int), typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.SetWrappedInt))); + } + + [HarmonyPatch(typeof(AvatarMultiBodyController), nameof(AvatarMultiBodyController._setFloat), new[] { typeof(int), typeof(float), typeof(bool) })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_setFloat_AvatarMultiBodyController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetFloat), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedFloat))) + .MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.SetFloat), new[] { typeof(int), typeof(float) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.SetWrappedFloat))); + } + + [HarmonyPatch(typeof(AvatarController), nameof(AvatarController.GetParameterName))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_GetParameterName_AvatarController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.PropertyGetter(typeof(Animator), nameof(Animator.parameters)), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedParameters))); + } + + [HarmonyPatch(typeof(AvatarController), nameof(AvatarController.SyncAnimParameters))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_SyncAnimParameters_AvatarController(IEnumerable instructions, ILGenerator generator) + { + var codes = instructions.MethodReplacer( + AccessTools.PropertyGetter(typeof(Animator), nameof(Animator.parameters)), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedParameters))) + .MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetBool), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedBool))) + .MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetInteger), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedInt))) + .MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetFloat), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedFloat))) + .ToList(); + + //var lbd_wrapper = generator.DeclareLocal(typeof(IAnimatorWrapper)); + + //var fld_anim = AccessTools.Field(typeof(AvatarController), nameof(AvatarController.anim)); + + //for (int i = 1; i < codes.Count; i++) + //{ + // if (codes[i].opcode == OpCodes.Stloc_0) + // { + // codes.InsertRange(i + 1, new[] + // { + // new CodeInstruction(OpCodes.Ldarg_0), + // CodeInstruction.LoadField(typeof(AvatarController), nameof(AvatarController.anim)), + // CodeInstruction.Call(typeof(KFExtensions), nameof(KFExtensions.GetAnimatorWrapper)), + // new CodeInstruction(OpCodes.Stloc_S, lbd_wrapper) + // }); + // i += 4; + // } + // else if (codes[i].opcode == OpCodes.Ldloc_3 && codes[i - 1].LoadsField(fld_anim)) + // { + // codes.Insert(i - 2, new CodeInstruction(OpCodes.Ldloc_S, lbd_wrapper).WithLabels(codes[i - 2].ExtractLabels())); + // codes.RemoveRange(i - 1, 2); + // i--; + // } + //} + + return codes; + } + + [HarmonyPatch(typeof(AvatarSDCSController), nameof(AvatarSDCSController.LateUpdate))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_LateUpdate_AvatarSDCSController(IEnumerable instructions, ILGenerator generator) + { + var mtd_getbool = AccessTools.Method(typeof(Animator), nameof(Animator.GetBool), new[] { typeof(int) }); + var mtd_getint = AccessTools.Method(typeof(Animator), nameof(Animator.GetInteger), new[] { typeof(int) }); + var mtd_getfloat = AccessTools.Method(typeof(Animator), nameof(Animator.GetFloat), new[] { typeof(int) }); + var mtd_istransition = AccessTools.Method(typeof(Animator),nameof(Animator.IsInTransition), new[] { typeof(int) }); + var mtd_updatespine = AccessTools.Method(typeof(LegacyAvatarController), nameof(LegacyAvatarController.updateSpineRotation)); + var fld_reload = AccessTools.Field(typeof(AvatarController), nameof(AvatarController.reloadHash)); + var mtd_getvanillabool = AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedBool)); + var mtd_getvanillaint = AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedInt)); + var mtd_getvanillafloat = AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedFloat)); + var mtd_isvanillatransition = AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.IsVanillaInTransition)); + var codes = instructions.Manipulator(ins => ins.opcode == OpCodes.Ldstr, ins => + { + switch (ins.operand) + { + case "Reload": + ins.opcode = OpCodes.Ldsfld; + ins.operand = fld_reload; + break; + } + }).MethodReplacer(AccessTools.Method(typeof(Animator), nameof(Animator.GetBool), new[] { typeof(string) }), mtd_getbool) + .MethodReplacer(AccessTools.Method(typeof(Animator), nameof(Animator.GetInteger), new[] { typeof(string) }), mtd_getint) + .MethodReplacer(AccessTools.Method(typeof(Animator), nameof(Animator.GetFloat), new[] { typeof(string) }), mtd_getfloat) + .MethodReplacer(AccessTools.Method(typeof(Animator), nameof(Animator.SetBool), new[] { typeof(string), typeof(bool) }), AccessTools.Method(typeof(Animator), nameof(Animator.SetBool), new[] { typeof(int), typeof(bool) })) + .MethodReplacer(mtd_getbool, mtd_getvanillabool) + .MethodReplacer(mtd_getint, mtd_getvanillaint) + .MethodReplacer(mtd_getfloat, mtd_getvanillafloat) + .MethodReplacer(mtd_istransition, mtd_isvanillatransition) + .ToList(); + + //var lbd_wrapper = generator.DeclareLocal(typeof(IAnimatorWrapper)); + + //for (var i = 0; i < codes.Count; i++) + //{ + // if (codes[i].Calls(mtd_updatespine)) + // { + // codes.InsertRange(i + 1, new[] + // { + // new CodeInstruction(OpCodes.Ldarg_0), + // CodeInstruction.LoadField(typeof(AvatarController), nameof(AvatarController.anim)), + // CodeInstruction.Call(typeof(KFExtensions), nameof(KFExtensions.GetItemAnimatorWrapper)), + // new CodeInstruction(OpCodes.Stloc_S, lbd_wrapper) + // }); + // i += 4; + // } + // else if (codes[i].Calls(mtd_getvanillabool) || codes[i].Calls(mtd_getvanillafloat) || codes[i].Calls(mtd_getvanillaint) || codes[i].Calls(mtd_isvanillatransition)) + // { + // codes.Insert(i - 3, new CodeInstruction(OpCodes.Ldloc_S, lbd_wrapper).WithLabels(codes[i - 3].ExtractLabels())); + // codes.RemoveRange(i - 2, 2); + // i--; + // } + //} + //foreach (var code in codes) + //{ + // Log.Out(code.ToString()); + //} + return codes; + } + + [HarmonyPatch(typeof(AvatarSDCSController), nameof(AvatarSDCSController.updateLayerStateInfo))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_updateLayerStateInfo_AvatarSDCSController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetCurrentAnimatorStateInfo)), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetCurrentVanillaStateInfo))); + } + + [HarmonyPatch(typeof(AvatarUMAController), nameof(AvatarUMAController.updateLayerStateInfo))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_updateLayerStateInfo_AvatarUMAController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetCurrentAnimatorStateInfo)), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetCurrentVanillaStateInfo))); + } + + [HarmonyPatch(typeof(LegacyAvatarController), nameof(LegacyAvatarController.updateLayerStateInfo))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_updateLayerStateInfo_LegacyAvatarController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetCurrentAnimatorStateInfo)), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetCurrentVanillaStateInfo))); + } + + [HarmonyPatch(typeof(AvatarSDCSController), nameof(AvatarSDCSController.setLayerWeights))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_setLayerWeights_AvatarSDCSController(IEnumerable instructions) + { + int id = Animator.StringToHash("MinibikeIdle"); + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.SetLayerWeight)), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.SetVanillaLayerWeight))) + .MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.IsInTransition)), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.IsVanillaInTransition))) + .MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetBool), new[] { typeof(string) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedBool))) + .Manipulator(ins => ins.opcode == OpCodes.Ldstr, ins => { ins.opcode = OpCodes.Ldc_I4; ins.operand = id; }); + } + + [HarmonyPatch(typeof(LegacyAvatarController), nameof(LegacyAvatarController.setLayerWeights))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_setLayerWeights_LegacyAvatarController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.SetLayerWeight)), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.SetVanillaLayerWeight))); + } + + [HarmonyPatch(typeof(AvatarUMAController), nameof(AvatarUMAController.setLayerWeights))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_setLayerWeights_AvatarUMAController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.SetLayerWeight)), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.SetVanillaLayerWeight))) + .MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetInteger), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedInt))) + .MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.IsInTransition)), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.IsVanillaInTransition))); + } + + [HarmonyPatch(typeof(UMACharacterBodyAnimator), nameof(UMACharacterBodyAnimator.assignLayerWeights))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_assignLayerWeights_UMACharacterBodyAnimator(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.SetLayerWeight)), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.SetVanillaLayerWeight))) + .MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.GetInteger), new[] { typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.GetWrappedInt))) + .MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.IsInTransition)), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.IsVanillaInTransition))); + } + + [HarmonyPatch(typeof(AvatarController), nameof(AvatarController.Update))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_Update_AvatarController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.SetLayerWeight)), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.SetVanillaLayerWeight))); + } + + [HarmonyPatch(typeof(AvatarController), nameof(AvatarController.InitHitDuration))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_InitHitDuration_AvatarController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.SetLayerWeight)), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.SetVanillaLayerWeight))); + } + + private static int drunkHash = Animator.StringToHash("drunk"); + [HarmonyPatch(typeof(FirstPersonAnimator), nameof(FirstPersonAnimator.SetDrunk))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_SetDrunk_FirstPersonAnimator(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.SetFloat), new[] { typeof(string), typeof(float) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.SetWrappedFloat))) + .Manipulator(ins => ins.LoadsConstant("drunk"), + ins => + { + ins.opcode = OpCodes.Ldsfld; + ins.operand = AccessTools.Field(typeof(AnimationRiggingPatches), nameof(AnimationRiggingPatches.drunkHash)); + }); + } + + [HarmonyPatch(typeof(AvatarMultiBodyController), nameof(AvatarMultiBodyController.SetVehicleAnimation))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_SetVehicleAnimation_AvatarMultiBodyController(IEnumerable instructions) + { + return instructions.MethodReplacer( + AccessTools.Method(typeof(Animator), nameof(Animator.SetInteger), new[] { typeof(string), typeof(int) }), + AccessTools.Method(typeof(KFExtensions), nameof(KFExtensions.SetWrappedInt))); + } + //BodyAnimator.LateUpdate + //UMACharacterBodyAnimator.LateUpdate + //BodyAnimator.cacheLayerStateInfo + //UMACharacterBodyAnimator.cacheLayerStateInfo + //not used +} diff --git a/Harmony/BackgroundInventoryUpdatePatch.cs b/Harmony/BackgroundInventoryUpdatePatch.cs new file mode 100644 index 0000000..4e8eb4b --- /dev/null +++ b/Harmony/BackgroundInventoryUpdatePatch.cs @@ -0,0 +1,51 @@ +using HarmonyLib; +using KFCommonUtilityLib.Scripts.StaticManagers; + +namespace KFCommonUtilityLib.Harmony +{ + [HarmonyPatch] + public class BackgroundInventoryUpdatePatch + { + [HarmonyPatch(typeof(Inventory), nameof(Inventory.clearSlotByIndex))] + [HarmonyPostfix] + private static void Postfix_clearSlotByIndex_Inventory(Inventory __instance, int _idx) + { + BackgroundInventoryUpdateManager.UnregisterUpdater(__instance.entity, _idx); + } + + [HarmonyPatch(typeof(EntityAlive), nameof(EntityAlive.OnEntityDeath))] + [HarmonyPostfix] + private static void Postfix_OnEntityDeath_EntityAlive(EntityAlive __instance) + { + BackgroundInventoryUpdateManager.UnregisterUpdater(__instance); + } + + [HarmonyPatch(typeof(Inventory), nameof(Inventory.OnUpdate))] + [HarmonyPostfix] + private static void Postfix_OnUpdate_Inventory(Inventory __instance) + { + BackgroundInventoryUpdateManager.Update(__instance.entity); + } + + [HarmonyPatch(typeof(GameManager), nameof(GameManager.SaveAndCleanupWorld))] + [HarmonyPostfix] + private static void Postfix_SaveAndCleanupWorld_GameManager() + { + BackgroundInventoryUpdateManager.Cleanup(); + } + + [HarmonyPatch(typeof(EntityAlive), nameof(EntityAlive.AttachToEntity))] + [HarmonyPostfix] + private static void Postfix_AttachToEntity_EntityAlive(EntityAlive __instance) + { + BackgroundInventoryUpdateManager.DisableUpdater(__instance); + } + + [HarmonyPatch(typeof(EntityAlive), nameof(EntityAlive.Detach))] + [HarmonyPostfix] + private static void Postfix_Detach_EntityAlive(EntityAlive __instance) + { + BackgroundInventoryUpdateManager.EnableUpdater(__instance); + } + } +} diff --git a/Harmony/DamagePatches.cs b/Harmony/DamagePatches.cs new file mode 100644 index 0000000..12f71cc --- /dev/null +++ b/Harmony/DamagePatches.cs @@ -0,0 +1,174 @@ +using HarmonyLib; +using System.Collections.Generic; +using System.Reflection.Emit; +using UniLinq; +using UnityEngine; + +[HarmonyPatch] +public static class DamagePatches +{ + [HarmonyPatch(typeof(EntityAlive), nameof(EntityAlive.damageEntityLocal))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_damageEntityLocal_EntityAlive(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var mtd_calc = AccessTools.Method(typeof(Equipment), nameof(Equipment.CalcDamage)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_calc)) + { + codes[i] = CodeInstruction.Call(typeof(DamagePatches), nameof(DamagePatches.CalcEquipmentDamage)); + codes.RemoveRange(i - 12, 12); + codes.InsertRange(i - 12, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(EntityAlive), nameof(EntityAlive.MinEventContext)), + CodeInstruction.LoadField(typeof(MinEventParams), nameof(MinEventParams.Other)) + }); + break; + } + } + + return codes; + } + + [HarmonyPatch(typeof(ItemActionAttack), nameof(ItemActionAttack.Hit))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_Hit_ItemActionAttack(IEnumerable instructions) + { + var codes = instructions.ToList(); + var mtd_getdamagegroup = AccessTools.Method(typeof(DamageSource), nameof(DamageSource.GetEntityDamageEquipmentSlotGroup)); + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_getdamagegroup)) + { + codes.InsertRange(i + 3, new[] + { + new CodeInstruction(OpCodes.Ldloc_S, codes[i - 2].operand), + new CodeInstruction(OpCodes.Ldloc_S, codes[i - 1].operand), + CodeInstruction.Call(typeof(DamageSource), nameof(DamageSource.GetEntityDamageBodyPart)), + CodeInstruction.Call(typeof(DamagePatches), nameof(DamagePatches.GetBodyPartTags)), + CodeInstruction.Call(typeof(FastTags), "op_BitwiseOr") + }); + break; + } + } + return codes; + } + + private static void CalcEquipmentDamage(Equipment equipment, ref DamageResponse damageResponse, EntityAlive attacker) + { + damageResponse.ArmorDamage = damageResponse.Strength; + if (damageResponse.Source.DamageTypeTag.Test_AnySet(Equipment.physicalDamageTypes)) + { + if (damageResponse.Strength > 0) + { + float totalPhysicalArmorResistPercent = GetTotalPhysicalArmorResistPercent(equipment, in damageResponse, attacker) * .01f; + damageResponse.ArmorDamage = Utils.FastMax((totalPhysicalArmorResistPercent > 0f) ? 1 : 0, Mathf.RoundToInt((float)damageResponse.Strength * totalPhysicalArmorResistPercent)); + damageResponse.Strength -= damageResponse.ArmorDamage; + return; + } + } + else + { + damageResponse.Strength = Mathf.RoundToInt(Utils.FastMax(0f, (float)damageResponse.Strength * (1f - EffectManager.GetValue(PassiveEffects.ElementalDamageResist, null, 0f, equipment.m_entity, null, damageResponse.Source.DamageTypeTag) * .01f))); + damageResponse.ArmorDamage = Mathf.RoundToInt((float)Utils.FastMax(0, damageResponse.ArmorDamage - damageResponse.Strength)); + } + } + + private static float GetTotalPhysicalArmorResistPercent(Equipment equipment, in DamageResponse damageResponse, EntityAlive attacker) + { + if (!equipment?.m_entity) + return 0f; + FastTags bodyPartTags = GetBodyPartTags(damageResponse.HitBodyPart); + float resist = EffectManager.GetValue(PassiveEffects.PhysicalDamageResist, null, 0f, equipment.m_entity, null, Equipment.coreDamageResist | bodyPartTags); + if (attacker) + { + attacker.MinEventContext.Other = equipment.m_entity; + attacker.MinEventContext.ItemValue = damageResponse.Source.AttackingItem ?? ItemValue.None; + if (damageResponse.Source.AttackingItem != null) + { + if (damageResponse.Source.AttackingItem.ItemClass.Actions[1] is ItemActionProjectile) + { + return MultiActionReversePatches.ProjectileGetValue(PassiveEffects.TargetArmor, damageResponse.Source.AttackingItem, resist, attacker, null, damageResponse.Source.AttackingItem.ItemClass.ItemTags | bodyPartTags); + } + return EffectManager.GetValue(PassiveEffects.TargetArmor, damageResponse.Source.AttackingItem, resist, attacker, null, damageResponse.Source.AttackingItem.ItemClass.ItemTags | bodyPartTags); + } + return EffectManager.GetValue(PassiveEffects.TargetArmor, null, resist, attacker, null, bodyPartTags); + } + return resist; + } + + public static FastTags GetBodyPartTags(EnumBodyPartHit bodyparts) + { + if ((bodyparts & EnumBodyPartHit.LeftUpperArm) > EnumBodyPartHit.None) + { + return LeftUpperArmTags; + } + if ((bodyparts & EnumBodyPartHit.LeftLowerArm) > EnumBodyPartHit.None) + { + return LeftLowerArmTags; + } + if ((bodyparts & EnumBodyPartHit.RightUpperArm) > EnumBodyPartHit.None) + { + return RightUpperArmTags; + } + if ((bodyparts & EnumBodyPartHit.RightLowerArm) > EnumBodyPartHit.None) + { + return RightLowerArmTags; + } + if ((bodyparts & EnumBodyPartHit.LeftUpperLeg) > EnumBodyPartHit.None) + { + return LeftUpperLegTags; + } + if ((bodyparts & EnumBodyPartHit.LeftLowerLeg) > EnumBodyPartHit.None) + { + return LeftLowerLegTags; + } + if ((bodyparts & EnumBodyPartHit.RightUpperLeg) > EnumBodyPartHit.None) + { + return RightUpperLegTags; + } + if ((bodyparts & EnumBodyPartHit.RightLowerLeg) > EnumBodyPartHit.None) + { + return RightLowerLegTags; + } + if ((bodyparts & EnumBodyPartHit.Head) > EnumBodyPartHit.None) + { + return HeadTags; + } + if ((bodyparts & EnumBodyPartHit.Torso) > EnumBodyPartHit.None) + { + return TorsoTags; + } + if ((bodyparts & EnumBodyPartHit.Special) > EnumBodyPartHit.None) + { + return SpecialTags; + } + return FastTags.none; + } + + private static FastTags TorsoTags = FastTags.Parse("torso"); + private static FastTags HeadTags = FastTags.Parse("head"); + private static FastTags SpecialTags = FastTags.Parse("special"); + private static FastTags ArmsTags = FastTags.Parse("arms"); + private static FastTags UpperArmsTags = FastTags.Parse("upperArms"); + private static FastTags LowerArmsTags = FastTags.Parse("lowerArms"); + private static FastTags LeftArmTags = FastTags.Parse("leftArms"); + private static FastTags RightArmTags = FastTags.Parse("rightArms"); + private static FastTags LeftUpperArmTags = FastTags.Parse("leftUpperArms") | LeftArmTags | UpperArmsTags | ArmsTags; + private static FastTags LeftLowerArmTags = FastTags.Parse("leftLowerArms") | LeftArmTags | LowerArmsTags | ArmsTags; + private static FastTags RightUpperArmTags = FastTags.Parse("rightUpperArms") | RightArmTags | UpperArmsTags | ArmsTags; + private static FastTags RightLowerArmTags = FastTags.Parse("rightLowerArms") | RightArmTags | LowerArmsTags | ArmsTags; + private static FastTags LegsTags = FastTags.Parse("legs"); + private static FastTags UpperLegsTags = FastTags.Parse("upperLegs"); + private static FastTags LowerLegsTags = FastTags.Parse("lowerLegs"); + private static FastTags LeftLegTags = FastTags.Parse("leftLegs"); + private static FastTags RightLegTags = FastTags.Parse("rightLegs"); + private static FastTags LeftUpperLegTags = FastTags.Parse("leftUpperLegs") | LeftLegTags | UpperLegsTags | LegsTags; + private static FastTags LeftLowerLegTags = FastTags.Parse("leftLowerLegs") | LeftLegTags | LowerLegsTags | LegsTags; + private static FastTags RightUpperLegTags = FastTags.Parse("rightUpperLegs") | RightLegTags | UpperLegsTags | LegsTags; + private static FastTags RightLowerLegTags = FastTags.Parse("rightLowerLegs") | RightLegTags | LowerLegsTags | LegsTags; +} \ No newline at end of file diff --git a/Harmony/DisplayMetaAsBuffPatch.cs b/Harmony/DisplayMetaAsBuffPatch.cs new file mode 100644 index 0000000..13638ec --- /dev/null +++ b/Harmony/DisplayMetaAsBuffPatch.cs @@ -0,0 +1,21 @@ +using HarmonyLib; + +namespace KFCommonUtilityLib.Harmony +{ + [HarmonyPatch] + public static class DisplayMetaAsBuffPatch + { + [HarmonyPatch(typeof(XUiM_PlayerBuffs), nameof(XUiM_PlayerBuffs.GetBuffDisplayInfo))] + [HarmonyPrefix] + private static bool Prefix_GetBuffDisplayInfo_XUiM_PlayerBuffs(EntityUINotification notification, ref string __result) + { + if (notification is DisplayAsBuffEntityUINotification && notification.Buff != null) + { + __result = notification.CurrentValue.ToString(); + return false; + } + + return true; + } + } +} diff --git a/Harmony/FPVLegPatches.cs b/Harmony/FPVLegPatches.cs new file mode 100644 index 0000000..d3d65a9 --- /dev/null +++ b/Harmony/FPVLegPatches.cs @@ -0,0 +1,34 @@ +using HarmonyLib; +using System.Collections.Generic; +using UniLinq; +using System.Reflection.Emit; + +namespace KFCommonUtilityLib.Harmony +{ + //[HarmonyPatch] + public static class FPVLegPatches + { + [HarmonyPatch(typeof(SDCSUtils), nameof(SDCSUtils.CreateVizTP))] + [HarmonyPrefix] + private static void Prefix_SDCSUtils_CreateTP(EntityAlive entity, ref bool isFPV, out bool __state) + { + __state = isFPV; + if (entity is EntityPlayerLocal) + { + entity.emodel.IsFPV = false; + isFPV = false; + } + } + + [HarmonyPatch(typeof(SDCSUtils), nameof(SDCSUtils.CreateVizTP))] + [HarmonyPostfix] + private static void Postfix_SDCSUtils_CreateTP(EntityAlive entity, ref bool isFPV, bool __state) + { + if (entity is EntityPlayerLocal) + { + entity.emodel.IsFPV = __state; + isFPV = __state; + } + } + } +} diff --git a/Harmony/HideMarkerOnAimPatch.cs b/Harmony/HideMarkerOnAimPatch.cs new file mode 100644 index 0000000..3408306 --- /dev/null +++ b/Harmony/HideMarkerOnAimPatch.cs @@ -0,0 +1,45 @@ +using HarmonyLib; +using UnityEngine; + +[HarmonyPatch] +public class HideMarkerOnAimPatch +{ + [HarmonyPatch(typeof(XUiC_OnScreenIcons), nameof(XUiC_OnScreenIcons.Init))] + [HarmonyPostfix] + private static void Postfix_Init_XUiC_OnScreenIcons(XUiC_OnScreenIcons __instance) + { + GameObject iconParent = new GameObject("AllIconParent"); + iconParent.transform.SetParent(__instance.ViewComponent.UiTransform); + iconParent.transform.localScale = Vector3.one; + } + + [HarmonyPatch(typeof(XUiC_OnScreenIcons), nameof(XUiC_OnScreenIcons.CreateIcon))] + [HarmonyPostfix] + private static void Postfix_CreateIcon_XUiC_OnScreenIcons(XUiC_OnScreenIcons __instance) + { + if (__instance.ViewComponent?.UiTransform) + { + __instance.screenIconList[__instance.screenIconList.Count - 1].Transform.SetParent(__instance.ViewComponent.UiTransform.Find("AllIconParent")); + } + } + + [HarmonyPatch(typeof(XUiC_OnScreenIcons), nameof(XUiC_OnScreenIcons.Update))] + [HarmonyPrefix] + private static bool Prefix_Update_XUiC_OnScreenIcons(XUiC_OnScreenIcons __instance) + { + GameObject iconParent = __instance.ViewComponent.UiTransform.Find("AllIconParent").gameObject; + if (!iconParent) + { + return true; + } + if (__instance.xui.playerUI.entityPlayer.bAimingGun) + { + iconParent.SetActive(false); + } + else + { + iconParent.SetActive(true); + } + return true; + } +} diff --git a/Harmony/Init.cs b/Harmony/Init.cs new file mode 100644 index 0000000..5eaa75b --- /dev/null +++ b/Harmony/Init.cs @@ -0,0 +1,96 @@ +using GearsAPI.Settings; +using GearsAPI.Settings.Global; +using GearsAPI.Settings.World; +using HarmonyLib; +using KFCommonUtilityLib; +using KFCommonUtilityLib.Scripts.StaticManagers; +using KFCommonUtilityLib.Scripts.Utilities; +using System.Reflection; +using System.Runtime.InteropServices; + +public class CommonUtilityLibInit : IModApi +{ + private static bool inited = false; + internal static Harmony HarmonyInstance { get; private set; } + public void InitMod(Mod _modInstance) + { + if (inited) + return; + inited = true; + Log.Out(" Loading Patch: " + GetType()); + unsafe + { + Log.Out($"size of MultiActionIndice: {sizeof(MultiActionIndice)} marshal size: {Marshal.SizeOf()}"); + Log.Out($"{AccessTools.Method(typeof(ItemActionRanged), nameof(ItemActionRanged.StartHolding)).FullDescription()}"); + } + //QualitySettings.streamingMipmapsMemoryBudget = 4096; + DelayLoadModuleManager.RegisterDelayloadDll("FullautoLauncher", "FullautoLauncherAnimationRiggingCompatibilityPatch"); + //DelayLoadModuleManager.RegisterDelayloadDll("SMXcore", "SMXMultiActionCompatibilityPatch"); + //DelayLoadModuleManager.RegisterDelayloadDll("SCore", "SCoreEntityHitCompatibilityPatch"); + CustomEffectEnumManager.RegisterEnumType(); + CustomEffectEnumManager.RegisterEnumType(); + + ModuleManagers.ClearOutputFolder(); + ItemClassModuleManager.Init(); + ItemActionModuleManager.Init(); + + ModEvents.GameAwake.RegisterHandler(CommonUtilityPatch.InitShotStates); + ModEvents.GameAwake.RegisterHandler(CustomEffectEnumManager.InitDefault); + ModEvents.GameAwake.RegisterHandler(DelayLoadModuleManager.DelayLoad); + //ModEvents.GameAwake.RegisterHandler(AssemblyLocator.Init); + ModEvents.GameAwake.RegisterHandler(MultiActionUtils.SetMinEventArrays); + ModEvents.GameStartDone.RegisterHandler(RegisterKFEnums); + //DOES NOT WORK ON MULTIPLAYER? Patch to ItemClass.LateInitAll + //ModEvents.GameStartDone.RegisterHandler(AnimationRiggingManager.ParseItemIDs); + ModEvents.GameStartDone.RegisterHandler(MultiActionManager.PostloadCleanup); + //ModEvents.GameStartDone.RegisterHandler(CustomEffectEnumManager.PrintResults); + //ModEvents.GameUpdate.RegisterHandler(CommonUtilityPatch.ForceUpdateGC); + HarmonyInstance = new Harmony(GetType().ToString()); + HarmonyInstance.PatchAll(Assembly.GetExecutingAssembly()); + } + + private static void RegisterKFEnums() + { + CustomEnums.onSelfMagzineDeplete = CustomEffectEnumManager.RegisterOrGetEnum("onSelfMagzineDeplete"); + CustomEnums.onReloadAboutToStart = CustomEffectEnumManager.RegisterOrGetEnum("onReloadAboutToStart"); + CustomEnums.onRechargeValueUpdate = CustomEffectEnumManager.RegisterOrGetEnum("onRechargeValueUpdate"); + CustomEnums.onSelfItemSwitchMode = CustomEffectEnumManager.RegisterOrGetEnum("onSelfItemSwitchMode"); + CustomEnums.onSelfBurstModeChanged = CustomEffectEnumManager.RegisterOrGetEnum("onSelfBurstModeChanged"); + CustomEnums.onSelfFirstCVarSync = CustomEffectEnumManager.RegisterOrGetEnum("onSelfFirstCVarSync"); + CustomEnums.onSelfHoldingItemAssemble = CustomEffectEnumManager.RegisterOrGetEnum("onSelfHoldingItemAssemble"); + + CustomEnums.ReloadSpeedRatioFPV2TPV = CustomEffectEnumManager.RegisterOrGetEnum("ReloadSpeedRatioFPV2TPV"); + CustomEnums.RecoilSnappiness = CustomEffectEnumManager.RegisterOrGetEnum("RecoilSnappiness"); + CustomEnums.RecoilReturnSpeed = CustomEffectEnumManager.RegisterOrGetEnum("RecoilReturnSpeed"); + //CustomEnums.ProjectileImpactDamagePercentBlock = CustomEffectEnumManager.RegisterOrGetEnum("ProjectileImpactDamagePercentBlock"); + //CustomEnums.ProjectileImpactDamagePercentEntity = CustomEffectEnumManager.RegisterOrGetEnum("ProjectileImpactDamagePercentEntity"); + CustomEnums.PartialReloadCount = CustomEffectEnumManager.RegisterOrGetEnum("PartialReloadCount"); + + CustomEnums.CustomTaggedEffect = CustomEffectEnumManager.RegisterOrGetEnum("CustomTaggedEffect"); + CustomEnums.KickDegreeHorizontalModifier = CustomEffectEnumManager.RegisterOrGetEnum("KickDegreeHorizontalModifier"); + CustomEnums.KickDegreeVerticalModifier = CustomEffectEnumManager.RegisterOrGetEnum("KickDegreeVerticalModifier"); + CustomEnums.WeaponErgonomics = CustomEffectEnumManager.RegisterOrGetEnum("WeaponErgonomics"); + CustomEnums.RecoilCameraShakeStrength = CustomEffectEnumManager.RegisterOrGetEnum("RecoilCameraShakeStrength"); + CustomEnums.BurstShotInterval = CustomEffectEnumManager.RegisterOrGetEnum("BurstShotInterval"); + CustomEnums.MaxWeaponSpread = CustomEffectEnumManager.RegisterOrGetEnum("MaxWeaponSpread"); + } +} + +public class GearsImpl : IGearsModApi +{ + public void InitMod(IGearsMod modInstance) + { + + } + + public void OnGlobalSettingsLoaded(IModGlobalSettings modSettings) + { + RecoilManager.InitRecoilSettings(modSettings); + } + + public void OnWorldSettingsLoaded(IModWorldSettings worldSettings) + { + + } +} + diff --git a/Harmony/InvariableRPMPatches.cs b/Harmony/InvariableRPMPatches.cs new file mode 100644 index 0000000..8cc131c --- /dev/null +++ b/Harmony/InvariableRPMPatches.cs @@ -0,0 +1,73 @@ +using HarmonyLib; +using KFCommonUtilityLib.Scripts.StaticManagers; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Emit; + +namespace KFCommonUtilityLib.Harmony +{ + [HarmonyPatch] + public static class InvariableRPMPatches + { + //added as a transpiler so that it's applied before all post processing + [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.OnHoldingUpdate))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_OnHoldingUpdate_ItemActionRanged(IEnumerable 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(InvariableRPMPatches), nameof(InvariableRPMPatches.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 res; + if (rangedAction is IModuleContainerFor actionModule) + { + 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); + res = rpm * perc; + //Log.Out($"fixed RPM {res}"); + } + else + { + res = EffectManager.GetValue(PassiveEffects.RoundsPerMinute, rangedData.invData.itemValue, 60f / rangedData.OriginalDelay, rangedData.invData.holdingEntity); + } + res = 60f / res; + return res; + } + } +} diff --git a/Harmony/ItemActionModulePatch.cs b/Harmony/ItemActionModulePatch.cs new file mode 100644 index 0000000..f25f2f0 --- /dev/null +++ b/Harmony/ItemActionModulePatch.cs @@ -0,0 +1,41 @@ +using HarmonyLib; +using HarmonyLib.Public.Patching; +using KFCommonUtilityLib.Scripts.StaticManagers; +using System.Reflection; + +namespace KFCommonUtilityLib.Harmony +{ + [HarmonyPatch] + public static class ItemActionModulePatch + { + [HarmonyPatch(typeof(GameManager), nameof(GameManager.StartGame))] + [HarmonyPrefix] + private static bool Prefix_StartGame_GameManager() + { + ItemActionModuleManager.InitNew(); + return true; + } + + [HarmonyPatch(typeof(ItemClass), nameof(ItemClass.Init))] + [HarmonyPostfix] + private static void Postfix_Init_ItemClass(ItemClass __instance) + { + ItemActionModuleManager.CheckItem(__instance); + } + + [HarmonyPatch(typeof(ItemClass), nameof(ItemClass.LateInitAll))] + [HarmonyPrefix] + private static bool Prefix_LateInitAll_ItemClass() + { + ItemActionModuleManager.FinishAndLoad(); + return true; + } + + [HarmonyPatch(typeof(PatchManager), "GetRealMethod")] + [HarmonyReversePatch] + public static MethodBase GetRealMethod(MethodInfo method, bool useReplacement) + { + return null; + } + } +} diff --git a/Harmony/ModularPatches.cs b/Harmony/ModularPatches.cs new file mode 100644 index 0000000..7e9931f --- /dev/null +++ b/Harmony/ModularPatches.cs @@ -0,0 +1,343 @@ +using HarmonyLib; +using HarmonyLib.Public.Patching; +using System.Collections.Generic; +using UniLinq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; +using System; + +namespace KFCommonUtilityLib.Harmony +{ + [HarmonyPatch] + public static class ModularPatches + { + [HarmonyPatch(typeof(GameManager), nameof(GameManager.StartGame))] + [HarmonyPrefix] + private static bool Prefix_StartGame_GameManager() + { + ModuleManagers.InitNew(); + return true; + } + + [HarmonyPatch(typeof(ItemClass), nameof(ItemClass.Init))] + [HarmonyPostfix] + private static void Postfix_Init_ItemClass(ItemClass __instance) + { + ItemClassModuleManager.CheckItem(__instance); + ItemActionModuleManager.CheckItem(__instance); + } + + [HarmonyPatch(typeof(GameManager), nameof(GameManager.worldInfoCo), MethodType.Enumerator)] + [HarmonyTranspiler] + private static IEnumerable Transpiler_worldInfoCo_GameManager(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var mtd_all = AccessTools.Method(typeof(WorldStaticData), nameof(WorldStaticData.AllConfigsReceivedAndLoaded)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_all)) + { + codes.Insert(i + 2, CodeInstruction.Call(typeof(ModuleManagers), nameof(ModuleManagers.FinishAndLoad)).WithLabels(codes[i + 2].ExtractLabels())); + break; + } + } + return codes; + } + + [HarmonyPatch(typeof(ConnectionManager), nameof(ConnectionManager.ServerReady))] + [HarmonyPrefix] + private static void Prefix_ServerReady_ConnectionManager() + { + ModuleManagers.FinishAndLoad(); + } + + [HarmonyPatch(typeof(WorldStaticData), nameof(WorldStaticData.ReloadAllXmlsSync))] + [HarmonyPrefix] + private static void Prefix_ReloadAllXmlsSync_WorldStaticData() + { + ModuleManagers.InitNew(); + } + + [HarmonyPatch(typeof(WorldStaticData), nameof(WorldStaticData.ReloadAllXmlsSync))] + [HarmonyPostfix] + private static void Postfix_ReloadAllXmlsSync_WorldStaticData() + { + ModuleManagers.FinishAndLoad(); + } + + [HarmonyPatch(typeof(GameManager), nameof(GameManager.Disconnect))] + [HarmonyPostfix] + private static void Postfix_Disconnect_GameManager() + { + ModuleManagers.Cleanup(); + } + + [HarmonyPatch(typeof(PatchManager), "GetRealMethod")] + [HarmonyReversePatch] + public static MethodBase GetRealMethod(MethodInfo method, bool useReplacement) + { + IEnumerable Transpiler(IEnumerable instructions) + { + if (instructions == null) + { + return null; + } + return new CodeInstruction[] + { + CodeInstruction.LoadField(typeof(PatchManager), "ReplacementToOriginals"), + CodeInstruction.LoadField(typeof(PatchManager), "ReplacementToOriginalsMono"), + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.Call(typeof(ModularPatches), nameof(ModularPatches.GetPatched)), + new CodeInstruction(OpCodes.Ret) + }; + } + _ = Transpiler(null); + return null; + } + + private static MethodBase GetPatched(ConditionalWeakTable ReplacementToOriginals, Dictionary ReplacementToOriginalsMono, MethodInfo method) + { + MethodInfo methodInfo = method.Identifiable(); + ConditionalWeakTable replacementToOriginals = ReplacementToOriginals; + lock (replacementToOriginals) + { + foreach (var pair in replacementToOriginals) + { + if (pair.Value == method) + { + Log.Out($"Found method replacement {pair.Key.FullDescription()} for method {method.FullDescription()}"); + return pair.Key; + } + } + } + if (AccessTools.IsMonoRuntime) + { + long num = (long)method.MethodHandle.GetFunctionPointer(); + Dictionary replacementToOriginalsMono = ReplacementToOriginalsMono; + lock (replacementToOriginalsMono) + { + foreach (var pair in replacementToOriginalsMono) + { + if (pair.Value[0] == method) + { + Log.Out($"Found MONO method replacement {pair.Value[1].FullDescription()} for method {method.FullDescription()}"); + return pair.Value[1]; + } + } + } + } + return method; + } + + [HarmonyPatch(typeof(PatchManager), nameof(PatchManager.ToPatchInfo))] + [HarmonyReversePatch] + public static PatchInfo ToPatchInfoDontAdd(this MethodBase methodBase) + { + IEnumerable Transpiler(IEnumerable instructions) + { + if (instructions == null) + { + return null; + } + + var codes = instructions.ToList(); + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].opcode == OpCodes.Dup) + { + codes[i - 1].WithLabels(codes[i - 3].ExtractLabels()); + codes.RemoveAt(i + 2); + codes.RemoveAt(i); + codes.RemoveRange(i - 3, 2); + break; + } + } + return codes; + } + _ = Transpiler(null); + return null; + } + + [HarmonyPatch(typeof(PatchInfo), "AddTranspilers")] + [HarmonyReversePatch] + public static void AddTranspilers(this PatchInfo self, string owner, params HarmonyMethod[] methods) + { + } + + public static PatchInfo Copy(this PatchInfo self) + { + var res = new PatchInfo(); + res.prefixes = new Patch[self.prefixes.Length]; + res.postfixes = new Patch[self.postfixes.Length]; + res.transpilers = new Patch[self.transpilers.Length]; + res.finalizers = new Patch[self.finalizers.Length]; + res.ilmanipulators = new Patch[self.ilmanipulators.Length]; + Array.Copy(self.prefixes, res.prefixes, res.prefixes.Length); + Array.Copy(self.postfixes, res.postfixes, res.postfixes.Length); + Array.Copy(self.transpilers, res.transpilers, res.transpilers.Length); + Array.Copy(self.finalizers, res.finalizers, res.finalizers.Length); + Array.Copy(self.ilmanipulators, res.ilmanipulators, res.ilmanipulators.Length); + return res; + } + + //public static MethodBase GetOriginalMethod(this HarmonyMethod attr) + //{ + // try + // { + // MethodType? methodType = attr.methodType; + // if (methodType != null) + // { + // switch (methodType.GetValueOrDefault()) + // { + // case MethodType.Normal: + // if (attr.methodName == null) + // { + // return null; + // } + // return AccessTools.DeclaredMethod(attr.declaringType, attr.methodName, attr.argumentTypes, null); + // case MethodType.Getter: + // { + // if (attr.methodName == null) + // { + // PropertyInfo propertyInfo = AccessTools.DeclaredIndexer(attr.declaringType, attr.argumentTypes); + // return (propertyInfo != null) ? propertyInfo.GetGetMethod(true) : null; + // } + // PropertyInfo propertyInfo2 = AccessTools.DeclaredProperty(attr.declaringType, attr.methodName); + // return (propertyInfo2 != null) ? propertyInfo2.GetGetMethod(true) : null; + // } + // case MethodType.Setter: + // { + // if (attr.methodName == null) + // { + // PropertyInfo propertyInfo3 = AccessTools.DeclaredIndexer(attr.declaringType, attr.argumentTypes); + // return (propertyInfo3 != null) ? propertyInfo3.GetSetMethod(true) : null; + // } + // PropertyInfo propertyInfo4 = AccessTools.DeclaredProperty(attr.declaringType, attr.methodName); + // return (propertyInfo4 != null) ? propertyInfo4.GetSetMethod(true) : null; + // } + // case MethodType.Constructor: + // return AccessTools.DeclaredConstructor(attr.declaringType, attr.argumentTypes, false); + // case MethodType.StaticConstructor: + // return AccessTools.GetDeclaredConstructors(attr.declaringType, null).FirstOrDefault((ConstructorInfo c) => c.IsStatic); + // case MethodType.Enumerator: + // if (attr.methodName == null) + // { + // return null; + // } + // return AccessTools.EnumeratorMoveNext(AccessTools.DeclaredMethod(attr.declaringType, attr.methodName, attr.argumentTypes, null)); + // case MethodType.Async: + // if (attr.methodName == null) + // { + // return null; + // } + // return AccessTools.AsyncMoveNext(AccessTools.DeclaredMethod(attr.declaringType, attr.methodName, attr.argumentTypes, null)); + // } + // } + // } + // catch (AmbiguousMatchException ex) + // { + // throw new Exception("Ambiguous match for HarmonyMethod[" + attr.ToString() + "]", ex.InnerException ?? ex); + // } + // return null; + //} + + //public static MethodBase GetBaseMethod(this HarmonyMethod attr) + //{ + // try + // { + // MethodType? methodType = attr.methodType; + // if (methodType != null) + // { + // switch (methodType.GetValueOrDefault()) + // { + // case MethodType.Normal: + // if (attr.methodName == null) + // { + // return null; + // } + // return AccessTools.Method(attr.declaringType, attr.methodName, attr.argumentTypes, null); + // case MethodType.Getter: + // { + // if (attr.methodName == null) + // { + // PropertyInfo propertyInfo = AccessTools.Indexer(attr.declaringType, attr.argumentTypes); + // return (propertyInfo != null) ? propertyInfo.GetGetMethod(true) : null; + // } + // PropertyInfo propertyInfo2 = AccessTools.Property(attr.declaringType, attr.methodName); + // return (propertyInfo2 != null) ? propertyInfo2.GetGetMethod(true) : null; + // } + // case MethodType.Setter: + // { + // if (attr.methodName == null) + // { + // PropertyInfo propertyInfo3 = AccessTools.Indexer(attr.declaringType, attr.argumentTypes); + // return (propertyInfo3 != null) ? propertyInfo3.GetSetMethod(true) : null; + // } + // PropertyInfo propertyInfo4 = AccessTools.Property(attr.declaringType, attr.methodName); + // return (propertyInfo4 != null) ? propertyInfo4.GetSetMethod(true) : null; + // } + // case MethodType.Constructor: + // return AccessTools.Constructor(attr.declaringType, attr.argumentTypes, false); + // case MethodType.StaticConstructor: + // return AccessTools.GetDeclaredConstructors(attr.declaringType, null).FirstOrDefault((ConstructorInfo c) => c.IsStatic); + // case MethodType.Enumerator: + // if (attr.methodName == null) + // { + // return null; + // } + // return AccessTools.EnumeratorMoveNext(AccessTools.Method(attr.declaringType, attr.methodName, attr.argumentTypes, null)); + // case MethodType.Async: + // if (attr.methodName == null) + // { + // return null; + // } + // return AccessTools.AsyncMoveNext(AccessTools.Method(attr.declaringType, attr.methodName, attr.argumentTypes, null)); + // } + // } + // } + // catch (AmbiguousMatchException ex) + // { + // throw new Exception("Ambiguous match for HarmonyMethod[" + attr.ToString() + "]", ex.InnerException ?? ex); + // } + // return null; + //} + } + + [HarmonyPatch] + public static class PatchToolsPatches + { + public static MethodBase TargetMethod() + { + return AccessTools.DeclaredMethod(AccessTools.TypeByName("HarmonyLib.PatchTools"), "GetOriginalMethod"); + } + + [HarmonyReversePatch] + public static MethodBase GetOriginalMethod(this HarmonyMethod attr) + { + return null; + } + + [HarmonyReversePatch] + public static MethodBase GetBaseMethod(this HarmonyMethod attr) + { + IEnumerable Transpiler(IEnumerable instructions) + { + if (instructions == null) + return null; + return instructions.MethodReplacer(AccessTools.Method(typeof(AccessTools), nameof(AccessTools.DeclaredMethod), new[] { typeof(Type), typeof(string), typeof(Type[]), typeof(Type[]) }), + AccessTools.Method(typeof(AccessTools), nameof(AccessTools.Method), new[] { typeof(Type), typeof(string), typeof(Type[]), typeof(Type[]) })) + .MethodReplacer(AccessTools.Method(typeof(AccessTools), nameof(AccessTools.DeclaredIndexer)), + AccessTools.Method(typeof(AccessTools), nameof(AccessTools.Indexer))) + .MethodReplacer(AccessTools.Method(typeof(AccessTools), nameof(AccessTools.DeclaredProperty), new[] { typeof(Type), typeof(string) }), + AccessTools.Method(typeof(AccessTools), nameof(AccessTools.Property), new[] { typeof(Type), typeof(string) })) + .MethodReplacer(AccessTools.Method(typeof(AccessTools), nameof(AccessTools.DeclaredConstructor)), + AccessTools.Method(typeof(AccessTools), nameof(AccessTools.Constructor))); + } + _ = Transpiler(null); + return null; + } + } +} diff --git a/Harmony/MultiActionPatches.cs b/Harmony/MultiActionPatches.cs new file mode 100644 index 0000000..e34b85b --- /dev/null +++ b/Harmony/MultiActionPatches.cs @@ -0,0 +1,2909 @@ +using GameEvent.SequenceActions; +using HarmonyLib; +using KFCommonUtilityLib.Scripts.NetPackages; +using KFCommonUtilityLib.Scripts.StaticManagers; +using KFCommonUtilityLib.Scripts.Utilities; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using UniLinq; +using UnityEngine; + +namespace KFCommonUtilityLib.Harmony +{ + //done?: patch all accesses to ItemClass.Actions so that they process all actions + //done?: patch ItemClass.ExecuteAction, MinEvent triggers + //done: replace GameManager.ItemReload* + //done: patch ItemActionRanged.ConsumeAmmo + //todo: patch passive effect handling and trigger effect firing, in ItemValue.ModifyValue set action index from tags + //todo: patch trigger action index enum/ or just let it use secondary and tag check? + //todo: handle ItemActionAttack.GetDamageEntity/GetDamageBlock and call sites actionIndex + //todo: sell, assemble, scrap remove ammo + + //nope, not gonna work + //try old style action toggle, + //replace meta and ammo index when toggling, => solves ammo issue + //provide requirement for checking current mode, => solves effect group condition issue + //replace hardcoded action index with current mode => bulk patch is enough? + //ItemClass subclass? maybe not + //is inventory update on remote client really needed? put off + + //todo: figure out when is meta and ammo index used, how to set their value in minimum patches + //ExecuteAction, Reload, what's more? + //safe to work within ItemAction scope + //even if meta and ammo index is set accordingly, better keep checking them in reload script + [HarmonyPatch] + public static class MultiActionPatches + { + #region Run Correct ItemAction + + #region Ranged Reload + //Replace reload action index with animator item action index parameter + //set MinEventParams.ItemActionData before getting passive value + [HarmonyPatch(typeof(AnimatorRangedReloadState), nameof(AnimatorRangedReloadState.OnStateEnter))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_OnStateEnter_AnimatorRangedReloadState(IEnumerable instructions, ILGenerator generator) + { + var codes = instructions.ToList(); + + LocalBuilder lbd_index = generator.DeclareLocal(typeof(int)); + + FieldInfo fld_action = AccessTools.Field(typeof(ItemClass), nameof(ItemClass.Actions)); + FieldInfo fld_actionData = AccessTools.Field(typeof(ItemInventoryData), nameof(ItemInventoryData.actionData)); + FieldInfo fld_meta = AccessTools.Field(typeof(ItemValue), nameof(ItemValue.Meta)); + FieldInfo fld_ammoindex = AccessTools.Field(typeof(ItemValue), nameof(ItemValue.SelectedAmmoTypeIndex)); + MethodInfo mtd_getvalue = AccessTools.Method(typeof(EffectManager), nameof(EffectManager.GetValue)); + bool firstRet = true; + + for (int i = 0; i < codes.Count; i++) + { + var code = codes[i]; + if ((code.LoadsField(fld_action) || code.LoadsField(fld_actionData)) && codes[i + 1].opcode == OpCodes.Ldc_I4_0) + { + //get correct ItemAction and data + codes[i + 1].opcode = OpCodes.Ldloc_S; + codes[i + 1].operand = lbd_index; + } + else if (code.Calls(mtd_getvalue)) + { + codes.InsertRange(i, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(AnimatorRangedReloadState), "actionData"), + CodeInstruction.LoadField(typeof(ItemActionData), nameof(ItemActionData.invData)), + CodeInstruction.LoadField(typeof(ItemInventoryData), nameof(ItemInventoryData.holdingEntity)), + CodeInstruction.LoadField(typeof(EntityAlive), nameof(EntityAlive.MinEventContext)), + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(AnimatorRangedReloadState), "actionData"), + CodeInstruction.StoreField(typeof(MinEventParams), nameof(MinEventParams.ItemActionData)) + }); + break; + } + else if (code.opcode == OpCodes.Ret && firstRet) + { + firstRet = false; + var insert = new[] + { + new CodeInstruction(OpCodes.Ldloc_0), + CodeInstruction.Call(typeof(MultiActionManager), nameof(MultiActionManager.GetActionIndexForEntity)), + new CodeInstruction(OpCodes.Stloc_S, lbd_index) + }; + insert[0].MoveLabelsFrom(codes[i + 1]); + codes.InsertRange(i + 1, insert); + } + } + + return codes; + } + + [HarmonyPatch(typeof(AnimatorRangedReloadState), nameof(AnimatorRangedReloadState.OnStateExit))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_OnStateExit_AnimatorRangedReloadState(IEnumerable instructions) + { + var codes = instructions.ToList(); + + MethodInfo mtd_getvalue = AccessTools.Method(typeof(EffectManager), nameof(EffectManager.GetValue)); + + for (int i = 0; i < codes.Count; i++) + { + var code = codes[i]; + if (code.Calls(mtd_getvalue)) + { + codes.InsertRange(i, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(AnimatorRangedReloadState), nameof(AnimatorRangedReloadState.actionData)), + CodeInstruction.LoadField(typeof(ItemActionData), nameof(ItemActionData.invData)), + CodeInstruction.LoadField(typeof(ItemInventoryData), nameof(ItemInventoryData.holdingEntity)), + CodeInstruction.LoadField(typeof(EntityAlive), nameof(EntityAlive.MinEventContext)), + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(AnimatorRangedReloadState), nameof(AnimatorRangedReloadState.actionData)), + CodeInstruction.StoreField(typeof(MinEventParams), nameof(MinEventParams.ItemActionData)) + }); + break; + } + } + + return codes; + } + #endregion + + //KEEP + #region Aiming + [HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.IsAimingGunPossible))] + [HarmonyPrefix] + private static bool Prefix_IsAimingGunPossible_EntityPlayerLocal(ref bool __result, EntityPlayerLocal __instance) + { + __result = true; + for (int i = 0; i < __instance.inventory.holdingItem.Actions.Length; i++) + { + ItemAction action = __instance.inventory.holdingItem.Actions[i]; + ItemActionData actionData = __instance.inventory.holdingItemData.actionData[i]; + __result &= (action == null || action.IsAimingGunPossible(actionData)); + } + return false; + } + #endregion + + //KEEP + #region Cancel bow draw + [HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.TryCancelBowDraw))] + [HarmonyPrefix] + private static bool Prefix_TryCancelBowDraw(EntityPlayerLocal __instance) + { + for (int i = 0; i < __instance.inventory.holdingItem.Actions.Length; i++) + { + ItemAction action = __instance.inventory.holdingItem.Actions[i]; + ItemActionData actionData = __instance.inventory.holdingItemData.actionData[i]; + if (action is ItemActionCatapult catapult) + { + action.CancelAction(actionData); + actionData.HasExecuted = false; + } + } + return false; + } + #endregion + + //KEEP + #region Consume wheel scroll + [HarmonyPatch(typeof(ItemClass), nameof(ItemClass.ConsumeScrollWheel))] + [HarmonyPrefix] + private static bool Prefix_ConsumeScrollWheel_ItemClass(ItemClass __instance, ItemInventoryData _data, float _scrollWheelInput, PlayerActionsLocal _playerInput, ref bool __result) + { + __result = false; + for (int i = 0; i < __instance.Actions.Length; i++) + { + ItemAction action = __instance.Actions[i]; + if (action != null && action.ConsumeScrollWheel(_data.actionData[i], _scrollWheelInput, _playerInput)) + { + __result = true; + break; + } + + } + return false; + } + #endregion + + //KEEP + #region Create modifier data for more actions + [HarmonyPatch(typeof(ItemClass), nameof(ItemClass.CreateInventoryData))] + [HarmonyPostfix] + private static void Postfix_CreateInventoryData_ItemClass(ItemInventoryData __result, ItemClass __instance) + { + int prevCount = __result.actionData.Count; + while (__result.actionData.Count < __instance.Actions.Length) + { + __result.actionData.Add(null); + } + for (; prevCount < __instance.Actions.Length; prevCount++) + { + if (__instance.Actions[prevCount] != null) + __result.actionData[prevCount] = __instance.Actions[prevCount].CreateModifierData(__result, prevCount); + } + } + #endregion + + //todo: should I patch ItemClass.ExecuteAction for melee actions? + + //KEEP + #region IsFocusBlockInside? + [HarmonyPatch(typeof(ItemClass), nameof(ItemClass.IsFocusBlockInside))] + [HarmonyPrefix] + private static bool Prefix_IsFocusBlockInside_ItemClass(ItemClass __instance, ref bool __result) + { + __result = __instance.Actions.All(action => action != null && action.IsFocusBlockInside()); + return false; + } + #endregion + + //KEEP + #region IsHUDDisabled + [HarmonyPatch(typeof(ItemClass), nameof(ItemClass.IsHUDDisabled))] + [HarmonyPrefix] + private static bool Prefix_IsHUDDisabled_ItemClass(ItemClass __instance, ref bool __result, ItemInventoryData _data) + { + __result = false; + for (int i = 0; i < __instance.Actions.Length; i++) + { + __result |= __instance.Actions[i] != null && __instance.Actions[i].IsHUDDisabled(_data.actionData[i]); + } + return false; + } + #endregion + + //KEEP + #region OnHUD + [HarmonyPatch(typeof(ItemClass), nameof(ItemClass.OnHUD))] + [HarmonyPrefix] + private static bool Prefix_OnHUD_ItemClass(ItemInventoryData _data, int _x, int _y, ItemClass __instance) + { + for (int i = 0; i < __instance.Actions.Length; i++) + { + __instance.Actions[i]?.OnHUD(_data.actionData[i], _x, _y); + } + return false; + } + + [HarmonyPatch(typeof(ItemClass), nameof(ItemClass.GetCrosshairType))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_GetCrosshairType_ItemClass(IEnumerable instructions, ILGenerator generator) + { + var codes = instructions.ToList(); + + LocalBuilder lbd_index = generator.DeclareLocal(typeof(int)); + + FieldInfo fld_action = AccessTools.Field(typeof(ItemClass), nameof(ItemClass.Actions)); + for (int i = 0; i < codes.Count; i++) + { + var code = codes[i]; + if (code.LoadsField(fld_action) && codes[i + 1].opcode == OpCodes.Ldc_I4_0) + { + codes[i + 1].opcode = OpCodes.Ldloc_S; + codes[i + 1].operand = lbd_index; + } + } + + codes.InsertRange(0, new[] + { + new CodeInstruction(OpCodes.Ldarg_1), + CodeInstruction.LoadField(typeof(ItemInventoryData), nameof(ItemInventoryData.holdingEntity)), + CodeInstruction.Call(typeof(MultiActionManager), nameof(MultiActionManager.GetActionIndexForEntity)), + new CodeInstruction(OpCodes.Stloc_S, lbd_index) + }); + + return codes; + } + #endregion + + //KEEP + #region OnScreenOverlay + [HarmonyPatch(typeof(ItemClass), nameof(ItemClass.OnScreenOverlay))] + [HarmonyPrefix] + private static bool Prefix_OnScreenOverlay_ItemClass(ItemInventoryData _data, ItemClass __instance) + { + for (int i = 0; i < __instance.Actions.Length; i++) + { + __instance.Actions[i]?.OnScreenOverlay(_data.actionData[i]); + } + return false; + } + #endregion + + #region inventory related + [HarmonyPatch(typeof(Inventory), nameof(Inventory.GetHoldingGun))] + [HarmonyPrefix] + private static bool Prefix_GetHoldingGun_Inventory(Inventory __instance, ref ItemActionAttack __result) + { + __result = __instance.holdingItem.Actions[MultiActionManager.GetActionIndexForEntity(__instance.entity)] as ItemActionAttack ?? __instance.holdingItem.Actions[0] as ItemActionAttack; + return false; + } + + [HarmonyPatch(typeof(Inventory), nameof(Inventory.GetHoldingDynamicMelee))] + [HarmonyPrefix] + private static bool Prefix_GetHoldingDynamicMelee_Inventory(Inventory __instance, ref ItemActionDynamic __result) + { + __result = __instance.holdingItem.Actions[MultiActionManager.GetActionIndexForEntity(__instance.entity)] as ItemActionDynamic ?? __instance.holdingItem.Actions[0] as ItemActionDynamic; + return false; + } + + //[HarmonyPatch(typeof(Inventory), "clearSlotByIndex")] + //[HarmonyPrefix] + //private static bool Prefix_clearSlotByIndex_Inventory(int _idx, Inventory __instance, EntityAlive ___entity) + //{ + // if (__instance.holdingItemIdx == _idx && ___entity != null && !___entity.isEntityRemote) + // { + // var mapping = MultiActionManager.GetMappingForEntity(___entity.entityId); + // mapping?.SaveMeta(); + // } + // return true; + //} + #endregion + + #region GameManager.ItemReload* + [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.SetAmmoType))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_SetAmmoType_ItemActionRanged(IEnumerable instructions) + { + var codes = instructions.ToList(); + + MethodInfo mtd_reloadserver = AccessTools.Method(typeof(GameManager), nameof(GameManager.ItemReloadServer)); + + for (int i = 0; i < codes.Count; i++) + { + var code = codes[i]; + if (code.Calls(mtd_reloadserver)) + { + codes.RemoveAt(i); + codes.InsertRange(i, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(ItemAction), nameof(ItemAction.ActionIndex)), + CodeInstruction.Call(typeof(MultiActionUtils), nameof(MultiActionUtils.FixedItemReloadServer)) + }); + codes.RemoveAt(i - 3); + break; + } + } + + return codes; + } + + [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.SwapAmmoType))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_SwapAmmoType_ItemActionRanged(IEnumerable instructions) + { + var codes = instructions.ToList(); + + MethodInfo mtd_reloadserver = AccessTools.Method(typeof(GameManager), nameof(GameManager.ItemReloadServer)); + FieldInfo fld_actiondata = AccessTools.Field(typeof(ItemInventoryData), nameof(ItemInventoryData.actionData)); + + for (int i = 0; i < codes.Count; i++) + { + var code = codes[i]; + if (code.Calls(mtd_reloadserver)) + { + codes.RemoveAt(i); + codes.InsertRange(i, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(ItemAction), nameof(ItemAction.ActionIndex)), + CodeInstruction.Call(typeof(MultiActionUtils), nameof(MultiActionUtils.FixedItemReloadServer)) + }); + codes[i - 4].MoveLabelsFrom(codes[i - 5]); + codes.RemoveAt(i - 5); + break; + } + else if (code.LoadsField(fld_actiondata)) + { + codes.RemoveAt(i + 1); + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(ItemAction), nameof(ItemAction.ActionIndex)) + }); + i += 1; + } + } + + return codes; + } + + [HarmonyPatch(typeof(ItemActionLauncher), nameof(ItemActionLauncher.SwapAmmoType))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_SwapAmmoType_ItemActionLauncher(IEnumerable instructions) + { + var codes = instructions.ToList(); + + FieldInfo fld_actiondata = AccessTools.Field(typeof(ItemInventoryData), nameof(ItemInventoryData.actionData)); + + for (int i = 0; i < codes.Count; i++) + { + var code = codes[i]; + if (code.LoadsField(fld_actiondata)) + { + codes.RemoveAt(i + 1); + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(ItemAction), nameof(ItemAction.ActionIndex)) + }); + break; + } + } + + return codes; + } + + [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.SwapSelectedAmmo))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_SwapSelectedAmmo_ItemActionRanged(IEnumerable instructions) + { + var codes = instructions.ToList(); + + MethodInfo mtd_reloadserver = AccessTools.Method(typeof(GameManager), nameof(GameManager.ItemReloadServer)); + MethodInfo mtd_canreload = AccessTools.Method(typeof(ItemActionAttack), nameof(ItemActionAttack.CanReload)); + + for (int i = 0; i < codes.Count; i++) + { + var code = codes[i]; + if (code.Calls(mtd_reloadserver)) + { + codes.RemoveAt(i); + codes.InsertRange(i, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(ItemAction), nameof(ItemAction.ActionIndex)), + CodeInstruction.Call(typeof(MultiActionUtils), nameof(MultiActionUtils.FixedItemReloadServer)) + }); + codes.RemoveAt(i - 3); + break; + } + else if (code.Calls(mtd_canreload)) + { + codes.RemoveAt(i - 2); + codes.InsertRange(i - 2, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(ItemAction), nameof(ItemAction.ActionIndex)) + }); + codes.RemoveRange(i - 9, 3); + codes.Insert(i - 9, new CodeInstruction(OpCodes.Ldarg_0)); + i--; + } + } + + return codes; + } + + [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.loadNewAmmunition))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_loadNewAmmunition_ItemActionRanged(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var fld_actiondata = AccessTools.Field(typeof(ItemInventoryData), nameof(ItemInventoryData.actionData)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].LoadsField(fld_actiondata)) + { + codes.RemoveAt(i + 1); + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_3), + CodeInstruction.Call(typeof(MultiActionManager), nameof(MultiActionManager.GetActionIndexForEntity)) + }); + break; + } + } + return codes; + } + #endregion + + //KEEP + #region Launcher logic + #region Launcher projectile meta and action index + [HarmonyPatch(typeof(ItemActionLauncher), nameof(ItemActionLauncher.StartHolding))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_StartHolding_ItemActionLauncher(IEnumerable instructions, ILGenerator generator) + { + var codes = instructions.ToList(); + + var fld_meta = AccessTools.Field(typeof(ItemValue), nameof(ItemValue.Meta)); + var mtd_delete = AccessTools.Method(typeof(ItemActionLauncher), nameof(ItemActionLauncher.DeleteProjectiles)); + var prop_itemvalue = AccessTools.PropertyGetter(typeof(ItemInventoryData), nameof(ItemInventoryData.itemValue)); + var lbd_meta = generator.DeclareLocal(typeof(int)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_delete)) + { + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_1), + CodeInstruction.LoadField(typeof(ItemActionData), nameof(ItemActionData.invData)), + new CodeInstruction(OpCodes.Callvirt, prop_itemvalue), + new CodeInstruction(OpCodes.Ldarg_1), + CodeInstruction.LoadField(typeof(ItemActionData), nameof(ItemActionData.indexInEntityOfAction)), + CodeInstruction.Call(typeof(MultiActionUtils), nameof(MultiActionUtils.GetMetaByActionIndex)), + new CodeInstruction(OpCodes.Stloc_S, lbd_meta) + }); + i += 7; + } + else if (codes[i].LoadsField(fld_meta)) + { + codes.Insert(i + 1, new CodeInstruction(OpCodes.Ldloc_S, lbd_meta)); + codes.RemoveRange(i - 3, 4); + i -= 3; + } + } + + return codes; + } + + [HarmonyPatch(typeof(ItemActionLauncher), nameof(ItemActionLauncher.instantiateProjectile))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_instantiateProjectile_ItemActionLauncher_MetaIndex(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var fld_ammoindex = AccessTools.Field(typeof(ItemValue), nameof(ItemValue.SelectedAmmoTypeIndex)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].LoadsField(fld_ammoindex)) + { + codes[i].opcode = OpCodes.Call; + codes[i].operand = AccessTools.Method(typeof(MultiActionUtils), nameof(MultiActionUtils.GetSelectedAmmoIndexByActionIndex)); + codes.InsertRange(i, new[] + { + new CodeInstruction(OpCodes.Ldarg_1), + CodeInstruction.LoadField(typeof(ItemActionData), nameof(ItemActionData.indexInEntityOfAction)) + }); + break; + } + } + + return codes; + } + #endregion + + [HarmonyPatch(typeof(ItemActionLauncher), nameof(ItemActionLauncher.instantiateProjectile))] + [HarmonyTranspiler] + //use custom script + private static IEnumerable Transpiler_instantiateProjectile_ItemActionLauncher_ReplaceScript(IEnumerable instructions) + { + MethodInfo mtd_addcomponent = AccessTools.Method(typeof(GameObject), nameof(GameObject.AddComponent), Array.Empty()); + MethodInfo mtd_addcomponentprev = mtd_addcomponent.MakeGenericMethod(typeof(ProjectileMoveScript)); + MethodInfo mtd_addcomponentnew = mtd_addcomponent.MakeGenericMethod(typeof(CustomProjectileMoveScript)); + foreach (var code in instructions) + { + if (code.Calls(mtd_addcomponentprev)) + { + Log.Out("replacing launcher projectile script..."); + code.operand = mtd_addcomponentnew; + } + yield return code; + } + } + + //copy ItemValue to projectile + [HarmonyPatch(typeof(ItemActionLauncher), nameof(ItemActionLauncher.instantiateProjectile))] + [HarmonyPostfix] + private static void Postfix_instantiateProjectile_ItemActionLauncher(Transform __result) + { + var script = __result.GetComponent(); + var projectileValue = script.itemValueProjectile; + var launcherValue = script.itemValueLauncher; + projectileValue.Activated = (byte)(Mathf.Clamp01(script.actionData.strainPercent) * byte.MaxValue); + MultiActionUtils.CopyLauncherValueToProjectile(launcherValue, projectileValue, script.actionData.indexInEntityOfAction); + } + + // + [HarmonyPatch(typeof(ProjectileMoveScript), nameof(ProjectileMoveScript.Fire))] + [HarmonyPrefix] + private static bool Prefix_Fire_ProjectileMoveScript(ProjectileMoveScript __instance, Vector3 _idealStartPosition, Vector3 _flyDirection, Entity _firingEntity, int _hmOverride, float _radius) + { + if (_firingEntity is EntityAlive entityAlive) + entityAlive.MinEventContext.ItemActionData = __instance.actionData; + if (__instance is CustomProjectileMoveScript) + { + __instance.ProjectileFire(_idealStartPosition, _flyDirection, _firingEntity, _hmOverride, _radius); + return false; + } + + return true; + } + #endregion + + #endregion + + #region FireEvent patches, set params + //KEEP + #region Ranged ExecuteAction FireEvent params + [HarmonyPatch(typeof(ItemClass), nameof(ItemClass.ExecuteAction))] + [HarmonyPrefix] + private static bool Prefix_ExecuteAction_ItemClass(ref int _actionIdx, ItemInventoryData _data, PlayerActionsLocal _playerActions) + { + + if (_actionIdx != 0 || _playerActions == null || !(_data.holdingEntity is EntityPlayerLocal player)) + { + return true; + } + _actionIdx = MultiActionManager.GetActionIndexForEntity(player); + player.MinEventContext.ItemActionData = _data.actionData[_actionIdx]; + return true; + } + + //why? ask TFP the fuck they are doing + [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.ExecuteAction))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_ExecuteAction_ItemActionRanged(IEnumerable instructions, ILGenerator generator) + { + var codes = instructions.ToList(); + + FieldInfo fld_itemactiondata = AccessTools.Field(typeof(MinEventParams), nameof(MinEventParams.ItemActionData)); + MethodInfo mtd_reloadserver = AccessTools.Method(typeof(IGameManager), nameof(IGameManager.ItemReloadServer)); + FieldInfo fld_gamemanager = AccessTools.Field(typeof(ItemInventoryData), nameof(ItemInventoryData.gameManager)); + MethodInfo mtd_getkickback = AccessTools.Method(typeof(ItemActionAttack), nameof(ItemActionAttack.GetKickbackForce)); + MethodInfo mtd_getmaxammo = AccessTools.Method(typeof(ItemActionRanged), nameof(ItemActionRanged.GetMaxAmmoCount)); + for (int i = 0; i < codes.Count; i++) + { + var code = codes[i]; + if (code.StoresField(fld_itemactiondata)) + { + codes.Insert(i, new CodeInstruction(OpCodes.Ldarg_1)); + codes.RemoveRange(i - 5, 5); + i -= 4; + } + else if (code.Calls(mtd_reloadserver)) + { + int j = i; + while (!codes[j].LoadsField(fld_gamemanager)) + { + j--; + } + codes.RemoveAt(i); + codes.InsertRange(i, new[] + { + new CodeInstruction(OpCodes.Ldloc_0), + CodeInstruction.LoadField(typeof(ItemActionData), nameof(ItemActionData.indexInEntityOfAction)), + CodeInstruction.Call(typeof(MultiActionUtils), nameof(MultiActionUtils.FixedItemReloadServer)) + }); + codes.RemoveRange(j - 2, 3); + i--; + } + //else if (code.Calls(mtd_getmaxammo)) + //{ + // int j = i + 1; + // for (; j < codes.Count; j++) + // { + // if (codes[j].Calls(mtd_getkickback)) + // { + // break; + // } + // } + // if (j < codes.Count) + // { + // var jumpto = codes[j - 2]; + // var label = generator.DefineLabel(); + // jumpto.labels.Add(label); + // codes.Insert(i - 2, new CodeInstruction(OpCodes.Br_S, label)); + // codes[i - 2].MoveLabelsFrom(codes[i - 1]); + // i++; + // } + //} + } + + return codes; + } + #endregion + + #region ItemAction.CancelAction + [HarmonyPatch(typeof(ItemActionCatapult), nameof(ItemActionCatapult.CancelAction))] + [HarmonyPrefix] + private static bool Prefix_CancelAction_ItemActionCatapult(ItemActionData _actionData) + { + _actionData.invData.holdingEntity.MinEventContext.ItemActionData = _actionData; + return true; + } + #endregion + + #region ItemActionDynamicMelee.Raycast + [HarmonyPatch(typeof(ItemActionDynamicMelee), nameof(ItemActionDynamicMelee.Raycast))] + [HarmonyPrefix] + private static bool Prefix_Raycast_ItemActionDynamicMelee(ItemActionDynamic.ItemActionDynamicData _actionData) + { + _actionData.invData.holdingEntity.MinEventContext.ItemActionData = _actionData; + return true; + } + #endregion + + #region Inventory.FireEvent, set current action + [HarmonyPatch(typeof(Inventory), nameof(Inventory.FireEvent))] + [HarmonyPrefix] + private static bool Prefix_FireEvent_Inventory(Inventory __instance) + { + MultiActionUtils.SetMinEventParamsByEntityInventory(__instance.entity); + return true; + } + #endregion + + #region Inventory.syncHeldItem, set current action + [HarmonyPatch(typeof(Inventory), nameof(Inventory.syncHeldItem))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_syncHeldItem_Inventory(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var mtd_test = AccessTools.Method(typeof(FastTags), nameof(FastTags.Test_AnySet)); + var fld_itemvalue = AccessTools.Field(typeof(MinEventParams), nameof(MinEventParams.ItemValue)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].StoresField(fld_itemvalue) && codes[i - 7].Calls(mtd_test)) + { + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(Inventory), nameof(Inventory.entity)), + CodeInstruction.Call(typeof(MultiActionUtils), nameof(MultiActionUtils.SetMinEventParamsByEntityInventory)) + }); + break; + } + } + + return codes; + } + #endregion + + #region ItemValue.FireEvent, read current action + [HarmonyPatch(typeof(ItemValue), nameof(ItemValue.FireEvent))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_FireEvent_ItemValue(IEnumerable instructions, ILGenerator generator) + { + var codes = instructions.ToList(); + + LocalBuilder lbd_index = generator.DeclareLocal(typeof(int)); + + FieldInfo fld_action = AccessTools.Field(typeof(ItemClass), nameof(ItemClass.Actions)); + FieldInfo fld_ammoindex = AccessTools.Field(typeof(ItemValue), nameof(ItemValue.SelectedAmmoTypeIndex)); + MethodInfo mtd_fireevent = AccessTools.Method(typeof(ItemValue), nameof(ItemValue.FireEvent)); + for (int i = 0; i < codes.Count; i++) + { + var code = codes[i]; + if (code.LoadsField(fld_action) && codes[i + 1].opcode == OpCodes.Ldc_I4_0) + { + codes[i + 1].opcode = OpCodes.Ldloc_S; + codes[i + 1].operand = lbd_index; + if (codes[i - 9].opcode == OpCodes.Ret) + { + codes.InsertRange(i - 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_2), + CodeInstruction.Call(typeof(MultiActionUtils), nameof(MultiActionUtils.GetActionIndexByEventParams)), + new CodeInstruction(OpCodes.Stloc_S, lbd_index) + }); + i += 3; + } + } + else if (code.LoadsField(fld_ammoindex)) + { + code.opcode = OpCodes.Call; + code.operand = AccessTools.Method(typeof(MultiActionUtils), nameof(MultiActionUtils.GetSelectedAmmoIndexByActionIndex)); + codes.Insert(i, new CodeInstruction(OpCodes.Ldloc_S, lbd_index)); + i++; + } + //action exclude mods + else if (code.Calls(mtd_fireevent) && codes[i + 1].opcode != OpCodes.Ldloc_0) + { + for (int j = i; j >= 0; j--) + { + if (codes[j].opcode == OpCodes.Brfalse_S || codes[j].opcode == OpCodes.Brfalse) + { + var label = codes[j].operand; + codes.InsertRange(j + 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(ItemValue), nameof(ItemValue.type)), + new CodeInstruction(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Ldfld, codes[j + 2].operand), + new CodeInstruction(codes[j + 3].opcode, codes[j + 3].operand), + new CodeInstruction(OpCodes.Ldelem_Ref), + CodeInstruction.LoadField(typeof(ItemValue), nameof(ItemValue.type)), + new CodeInstruction(OpCodes.Ldloc_S, lbd_index), + CodeInstruction.Call(typeof(MultiActionManager), nameof(MultiActionManager.ShouldExcludeTrigger)), + new CodeInstruction(OpCodes.Brtrue_S, label) + }); + i += 10; + break; + } + } + } + } + + return codes; + } + #endregion + + //onSelfEquipStop, onSelfHoldingItemCreated, onSelfEquipStart are not available for individual action, + + //some are already set in update or execute action + + #endregion + + #region EffectManager.GetValue patches, set params + //set correct action index for ItemValue + [HarmonyPatch(typeof(ItemValue), nameof(ItemValue.ModifyValue))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_ModifyValue_ItemValue(IEnumerable instructions, ILGenerator generator) + { + var codes = instructions.ToList(); + + LocalBuilder lbd_index = generator.DeclareLocal(typeof(int)); + + FieldInfo fld_action = AccessTools.Field(typeof(ItemClass), nameof(ItemClass.Actions)); + FieldInfo fld_ammoindex = AccessTools.Field(typeof(ItemValue), nameof(ItemValue.SelectedAmmoTypeIndex)); + FieldInfo fld_mods = AccessTools.Field(typeof(ItemValue), nameof(ItemValue.Modifications)); + FieldInfo fld_cos = AccessTools.Field(typeof(ItemValue), nameof(ItemValue.CosmeticMods)); + MethodInfo mtd_modify = AccessTools.Method(typeof(ItemValue), nameof(ItemValue.ModifyValue)); + for (int i = 0; i < codes.Count; i++) + { + var code = codes[i]; + if (code.opcode == OpCodes.Stloc_1) + { + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_1), + CodeInstruction.Call(typeof(MultiActionUtils), nameof(MultiActionUtils.GetActionIndexByEntityEventParams)), + new CodeInstruction(OpCodes.Stloc_S, lbd_index) + }); + } + else if (code.LoadsField(fld_action) && codes[i + 1].opcode == OpCodes.Ldc_I4_0) + { + codes[i + 1].opcode = OpCodes.Ldloc_S; + codes[i + 1].operand = lbd_index; + } + else if (code.LoadsField(fld_ammoindex)) + { + code.opcode = OpCodes.Call; + code.operand = AccessTools.Method(typeof(MultiActionUtils), nameof(MultiActionUtils.GetSelectedAmmoIndexByActionIndex)); + codes.Insert(i, new CodeInstruction(OpCodes.Ldloc_S, lbd_index)); + i++; + } + else if (code.Calls(mtd_modify)) + { + for (int j = i; j >= 0; j--) + { + if (codes[j].opcode == OpCodes.Brfalse_S || codes[j].opcode == OpCodes.Brfalse) + { + var label = codes[j].operand; + codes.InsertRange(j + 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(ItemValue), nameof(ItemValue.type)), + new CodeInstruction(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Ldfld, codes[j + 2].operand), + new CodeInstruction(codes[j + 3].opcode, codes[j + 3].operand), + new CodeInstruction(OpCodes.Ldelem_Ref), + CodeInstruction.LoadField(typeof(ItemValue), nameof(ItemValue.type)), + new CodeInstruction(OpCodes.Ldloc_S, lbd_index), + CodeInstruction.Call(typeof(MultiActionManager), nameof(MultiActionManager.ShouldExcludePassive)), + new CodeInstruction(OpCodes.Brtrue_S, label) + }); + i += 10; + break; + } + } + } + } + + return codes; + } + + //only current mode should execute OnHUD + [HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.OnHUD))] + [HarmonyPrefix] + private static bool Prefix_OnHUD_EntityPlayerLocal(EntityPlayerLocal __instance, out ItemActionData __state) + { + __state = __instance.MinEventContext.ItemActionData; + MultiActionUtils.SetMinEventParamsByEntityInventory(__instance); + return true; + } + + [HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.OnHUD))] + [HarmonyPostfix] + private static void Postfix_OnHUD_EntityPlayerLocal(EntityPlayerLocal __instance, ItemActionData __state) + { + __instance.MinEventContext.ItemActionData = __state; + } + + //for passive value calc + [HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.guiDrawCrosshair))] + [HarmonyPrefix] + private static bool Prefix_guiDrawCrosshair_EntityPlayerLocal(EntityPlayerLocal __instance) + { + MultiActionUtils.SetMinEventParamsByEntityInventory(__instance); + return true; + } + + //draw crosshair for current action + [HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.guiDrawCrosshair))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_guiDrawCrosshair_EntityPlayerLocal(IEnumerable instructions, ILGenerator generator) + { + var codes = instructions.ToList(); + + LocalBuilder lbd_index = generator.DeclareLocal(typeof(int)); + + FieldInfo fld_actiondata = AccessTools.Field(typeof(ItemInventoryData), nameof(ItemInventoryData.actionData)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].opcode == OpCodes.Stloc_1) + { + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.Call(typeof(MultiActionManager), nameof(MultiActionManager.GetActionIndexForEntity)), + new CodeInstruction(OpCodes.Stloc_S, lbd_index) + }); + i += 3; + } + else if (codes[i].LoadsField(fld_actiondata) && codes[i + 1].opcode == OpCodes.Ldc_I4_0) + { + codes[i + 1].opcode = OpCodes.Ldloc_S; + codes[i + 1].operand = lbd_index; + } + } + + return codes; + } + + [HarmonyPatch(typeof(ItemAction), nameof(ItemAction.ExecuteBuffActions))] + [HarmonyPrefix] + private static bool Prefix_ExecuteBuffActions_ItemAction(int instigatorId, out (EntityAlive entity, ItemActionData actionData) __state) + { + __state = default; + EntityAlive entity = GameManager.Instance.World.GetEntity(instigatorId) as EntityAlive; + if (entity != null) + { + __state.entity = entity; + __state.actionData = entity.MinEventContext.ItemActionData; + MultiActionUtils.SetMinEventParamsByEntityInventory(entity); + } + else + return false; + return true; + } + + [HarmonyPatch(typeof(ItemAction), nameof(ItemAction.ExecuteBuffActions))] + [HarmonyPostfix] + private static void Postfix_ExecuteBuffActions_ItemAction((EntityAlive entity, ItemActionData actionData) __state, bool __runOriginal) + { + if (__runOriginal && __state.entity != null) + __state.entity.MinEventContext.ItemActionData = __state.actionData; + } + + //ItemAction.GetDismemberChance already set + //ItemActionDynamic.GetExecuteActionTarget not needed + + [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.ItemActionEffects))] + [HarmonyPrefix] + private static bool Prefix_ItemActionEffects_ItemActionLauncher(ItemActionData _actionData, out ItemActionData __state) + { + __state = _actionData.invData.holdingEntity.MinEventContext.ItemActionData; + _actionData.invData.holdingEntity.MinEventContext.ItemActionData = _actionData; + return true; + } + + [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.ItemActionEffects))] + [HarmonyPostfix] + private static void Postfix_ItemActionEffects_ItemActionLauncher(ItemActionData _actionData, ItemActionData __state) + { + _actionData.invData.holdingEntity.MinEventContext.ItemActionData = __state; + } + + [HarmonyPatch(typeof(ItemActionLauncher), nameof(ItemActionLauncher.ClampAmmoCount))] + [HarmonyPrefix] + private static bool Prefix_ClampAmmoCount_ItemActionLauncher(ItemActionLauncher.ItemActionDataLauncher actionData, out ItemActionData __state) + { + __state = actionData.invData.holdingEntity.MinEventContext.ItemActionData; + actionData.invData.holdingEntity.MinEventContext.ItemActionData = actionData; + return true; + } + + [HarmonyPatch(typeof(ItemActionLauncher), nameof(ItemActionLauncher.ClampAmmoCount))] + [HarmonyPostfix] + private static void Postfix_ClampAmmoCount_ItemActionLauncher(ItemActionLauncher.ItemActionDataLauncher actionData, ItemActionData __state) + { + actionData.invData.holdingEntity.MinEventContext.ItemActionData = __state; + } + #endregion + + #region EffectManager.GetValuesAndSources patches, set params + + [HarmonyPatch(typeof(EntityStats), nameof(EntityStats.Update))] + [HarmonyPrefix] + private static bool Prefix_Update_EntityStats(EntityStats __instance) + { + MultiActionUtils.SetMinEventParamsByEntityInventory(__instance.m_entity); + return true; + } + + //set correct action index for ItemValue + [HarmonyPatch(typeof(ItemValue), nameof(ItemValue.GetModifiedValueData))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_GetModifiedValueData_ItemValue(IEnumerable instructions, ILGenerator generator) + { + var codes = instructions.ToList(); + + LocalBuilder lbd_index = generator.DeclareLocal(typeof(int)); + + FieldInfo fld_action = AccessTools.Field(typeof(ItemClass), nameof(ItemClass.Actions)); + FieldInfo fld_ammoindex = AccessTools.Field(typeof(ItemValue), nameof(ItemValue.SelectedAmmoTypeIndex)); + FieldInfo fld_mods = AccessTools.Field(typeof(ItemValue), nameof(ItemValue.Modifications)); + FieldInfo fld_cos = AccessTools.Field(typeof(ItemValue), nameof(ItemValue.CosmeticMods)); + MethodInfo mtd_getvalue = AccessTools.Method(typeof(ItemValue), nameof(ItemValue.GetModifiedValueData)); + for (int i = 0; i < codes.Count; i++) + { + var code = codes[i]; + if (code.opcode == OpCodes.Stloc_0) + { + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_3), + CodeInstruction.Call(typeof(MultiActionUtils), nameof(MultiActionUtils.GetActionIndexByEntityEventParams)), + new CodeInstruction(OpCodes.Stloc_S, lbd_index) + }); + } + else if (code.LoadsField(fld_action) && codes[i + 1].opcode == OpCodes.Ldc_I4_0) + { + codes[i + 1].opcode = OpCodes.Ldloc_S; + codes[i + 1].operand = lbd_index; + } + else if (code.LoadsField(fld_ammoindex)) + { + code.opcode = OpCodes.Call; + code.operand = AccessTools.Method(typeof(MultiActionUtils), nameof(MultiActionUtils.GetSelectedAmmoIndexByActionIndex)); + codes.Insert(i, new CodeInstruction(OpCodes.Ldloc_S, lbd_index)); + i++; + } + else if (code.Calls(mtd_getvalue) && codes[i + 1].opcode != OpCodes.Ldloc_0) + { + for (int j = i; j >= 0; j--) + { + if (codes[j].opcode == OpCodes.Brfalse_S || codes[j].opcode == OpCodes.Brfalse) + { + var label = codes[j].operand; + codes.InsertRange(j + 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(ItemValue), nameof(ItemValue.type)), + new CodeInstruction(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Ldfld, codes[j + 2].operand), + new CodeInstruction(codes[j + 3].opcode, codes[j + 3].operand), + new CodeInstruction(OpCodes.Ldelem_Ref), + CodeInstruction.LoadField(typeof(ItemValue), nameof(ItemValue.type)), + new CodeInstruction(OpCodes.Ldloc_S, lbd_index), + CodeInstruction.Call(typeof(MultiActionManager), nameof(MultiActionManager.ShouldExcludePassive)), + new CodeInstruction(OpCodes.Brtrue_S, label) + }); + i += 10; + break; + } + } + } + } + + return codes; + } + #endregion + + //KEEP + #region Misc + //load correct property for melee + [HarmonyPatch(typeof(AnimatorMeleeAttackState), nameof(AnimatorMeleeAttackState.OnStateEnter))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_OnStateEnter_AnimatorMeleeAttackState(IEnumerable instructions) + { + var codes = instructions.ToList(); + var fld_actionindex = AccessTools.Field(typeof(AnimatorMeleeAttackState), nameof(AnimatorMeleeAttackState.actionIndex)); + MethodInfo mtd_getvalue = AccessTools.Method(typeof(EffectManager), nameof(EffectManager.GetValue)); + for (int i = 0; i < codes.Count; i++) + { + var code = codes[i]; + if (code.LoadsField(fld_actionindex) && codes[i + 2].opcode == OpCodes.Ldstr) + { + string property = codes[i + 2].operand.ToString(); + property = property.Split('.')[1]; + codes.RemoveRange(i + 1, 4); + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldstr, property), + CodeInstruction.Call(typeof(MultiActionUtils), nameof(MultiActionUtils.GetPropertyName)) + }); + i -= 2; + } + else if (code.Calls(mtd_getvalue)) + { + codes.InsertRange(i, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(AnimatorMeleeAttackState), "entity"), + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(AnimatorMeleeAttackState), "actionIndex"), + CodeInstruction.Call(typeof(MultiActionUtils), nameof(MultiActionUtils.SetMinEventParamsActionData)) + }); + i += 5; + } + } + + return codes; + } + + //make sure it's set to current action after for loop + [HarmonyPatch(typeof(ItemClass), nameof(ItemClass.OnHoldingUpdate))] + [HarmonyPostfix] + private static void Postfix_OnHoldingUpdate_ItemClass(ItemInventoryData _data) + { + MultiActionUtils.SetMinEventParamsByEntityInventory(_data.holdingEntity); + } + + [HarmonyPatch(typeof(ItemClass), nameof(ItemClass.StopHolding))] + [HarmonyPostfix] + private static void Postfix_StopHolding_ItemClass(ItemInventoryData _data) + { + if (_data.holdingEntity != null) + { + MultiActionUtils.SetMinEventParamsByEntityInventory(_data.holdingEntity); + MultiActionManager.SetMappingForEntity(_data.holdingEntity.entityId, null); + } + } + + [HarmonyPatch(typeof(ItemClass), nameof(ItemClass.StartHolding))] + [HarmonyPostfix] + private static void Postfix_StartHolding_ItemClass(ItemInventoryData _data) + { + MultiActionUtils.SetMinEventParamsByEntityInventory(_data.holdingEntity); + } + + //should be fixed in Harmony 2.12.0.0 + //[HarmonyPatch(typeof(ItemClassesFromXml), nameof(ItemClassesFromXml.parseItem))] + //[HarmonyPrefix] + //private static bool Prefix_parseItem_ItemClassesFromXml(XElement _node) + //{ + // DynamicProperties dynamicProperties = new DynamicProperties(); + // string attribute = _node.GetAttribute("name"); + // if (attribute.Length == 0) + // { + // throw new Exception("Attribute 'name' missing on item"); + // } + // //here + // List[] array = new List[ItemClass.cMaxActionNames]; + // for (int i = 0; i < array.Length; i++) + // { + // array[i] = new List(); + // } + + // foreach (XElement item in _node.Elements("property")) + // { + // dynamicProperties.Add(item); + // string attribute2 = item.GetAttribute("class"); + // if (attribute2.StartsWith("Action")) + // { + // int num = attribute2[attribute2.Length - 1] - '0'; + // array[num].AddRange(RequirementBase.ParseRequirements(item)); + // } + // } + + // if (dynamicProperties.Values.ContainsKey("Extends")) + // { + // string text = dynamicProperties.Values["Extends"]; + // ItemClass itemClass = ItemClass.GetItemClass(text); + // if (itemClass == null) + // { + // throw new Exception($"Extends item {text} is not specified for item {attribute}'"); + // } + + // HashSet hashSet = new HashSet { Block.PropCreativeMode }; + // if (dynamicProperties.Params1.ContainsKey("Extends")) + // { + // string[] array2 = dynamicProperties.Params1["Extends"].Split(new[] { ',' }, StringSplitOptions.None); + // foreach (string text2 in array2) + // { + // hashSet.Add(text2.Trim()); + // } + // } + + // DynamicProperties dynamicProperties2 = new DynamicProperties(); + // dynamicProperties2.CopyFrom(itemClass.Properties, hashSet); + // dynamicProperties2.CopyFrom(dynamicProperties); + // dynamicProperties = dynamicProperties2; + // } + + // ItemClass itemClass2; + // if (dynamicProperties.Values.ContainsKey("Class")) + // { + // string text3 = dynamicProperties.Values["Class"]; + // if (!text3.Contains(",")) + // { + // text3 += ",Assembly-CSharp"; + // } + // try + // { + // itemClass2 = (ItemClass)Activator.CreateInstance(Type.GetType(text3)); + // } + // catch (Exception) + // { + // throw new Exception("No item class '" + text3 + " found!"); + // } + // } + // else + // { + // itemClass2 = new ItemClass(); + // } + + // itemClass2.Properties = dynamicProperties; + // if (dynamicProperties.Params1.ContainsKey("Extends")) + // { + // string text4 = dynamicProperties.Values["Extends"]; + // if (ItemClass.GetItemClass(text4) == null) + // { + // throw new Exception($"Extends item {text4} is not specified for item {attribute}'"); + // } + // } + + // itemClass2.Effects = MinEffectController.ParseXml(_node, null, MinEffectController.SourceParentType.ItemClass, itemClass2.Id); + // itemClass2.SetName(attribute); + // itemClass2.setLocalizedItemName(Localization.Get(attribute)); + // if (dynamicProperties.Values.ContainsKey("Stacknumber")) + // { + // itemClass2.Stacknumber = new DataItem(int.Parse(dynamicProperties.Values["Stacknumber"])); + // } + // else + // { + // itemClass2.Stacknumber = new DataItem(500); + // } + + // if (dynamicProperties.Values.ContainsKey("Canhold")) + // { + // itemClass2.SetCanHold(StringParsers.ParseBool(dynamicProperties.Values["Canhold"])); + // } + + // if (dynamicProperties.Values.ContainsKey("Candrop")) + // { + // itemClass2.SetCanDrop(StringParsers.ParseBool(dynamicProperties.Values["Candrop"])); + // } + + // if (!dynamicProperties.Values.ContainsKey("Material")) + // { + // throw new Exception("Attribute 'material' missing on item '" + attribute + "'"); + // } + + // itemClass2.MadeOfMaterial = MaterialBlock.fromString(dynamicProperties.Values["Material"]); + // if (itemClass2.MadeOfMaterial == null) + // { + // throw new Exception("Attribute 'material' '" + dynamicProperties.Values["Material"] + "' refers to not existing material in item '" + attribute + "'"); + // } + + // if (!dynamicProperties.Values.ContainsKey("Meshfile") && itemClass2.CanHold()) + // { + // throw new Exception("Attribute 'Meshfile' missing on item '" + attribute + "'"); + // } + + // itemClass2.MeshFile = dynamicProperties.Values["Meshfile"]; + // DataLoader.PreloadBundle(itemClass2.MeshFile); + // StringParsers.TryParseFloat(dynamicProperties.Values["StickyOffset"], out itemClass2.StickyOffset); + // StringParsers.TryParseFloat(dynamicProperties.Values["StickyColliderRadius"], out itemClass2.StickyColliderRadius); + // StringParsers.TryParseSInt32(dynamicProperties.Values["StickyColliderUp"], out itemClass2.StickyColliderUp); + // StringParsers.TryParseFloat(dynamicProperties.Values["StickyColliderLength"], out itemClass2.StickyColliderLength); + // itemClass2.StickyMaterial = dynamicProperties.Values["StickyMaterial"]; + // if (dynamicProperties.Values.ContainsKey("ImageEffectOnActive")) + // { + // itemClass2.ImageEffectOnActive = new DataItem(dynamicProperties.Values["ImageEffectOnActive"]); + // } + + // if (dynamicProperties.Values.ContainsKey("Active")) + // { + // itemClass2.Active = new DataItem(_startValue: false); + // } + + // if (dynamicProperties.Values.ContainsKey(ItemClass.PropIsSticky)) + // { + // itemClass2.IsSticky = StringParsers.ParseBool(dynamicProperties.Values[ItemClass.PropIsSticky]); + // } + + // if (dynamicProperties.Values.ContainsKey("DropMeshfile") && itemClass2.CanHold()) + // { + // itemClass2.DropMeshFile = dynamicProperties.Values["DropMeshfile"]; + // DataLoader.PreloadBundle(itemClass2.DropMeshFile); + // } + + // if (dynamicProperties.Values.ContainsKey("HandMeshfile") && itemClass2.CanHold()) + // { + // itemClass2.HandMeshFile = dynamicProperties.Values["HandMeshfile"]; + // DataLoader.PreloadBundle(itemClass2.HandMeshFile); + // } + + // if (dynamicProperties.Values.ContainsKey("HoldType")) + // { + // string s = dynamicProperties.Values["HoldType"]; + // int result = 0; + // if (!int.TryParse(s, out result)) + // { + // throw new Exception("Cannot parse attribute hold_type for item '" + attribute + "'"); + // } + + // itemClass2.HoldType = new DataItem(result); + // } + + // if (dynamicProperties.Values.ContainsKey("RepairTools")) + // { + // string[] array3 = dynamicProperties.Values["RepairTools"].Replace(" ", "").Split(new[] { ',' }, StringSplitOptions.None); + // DataItem[] array4 = new DataItem[array3.Length]; + // for (int k = 0; k < array3.Length; k++) + // { + // array4[k] = new DataItem(array3[k]); + // } + + // itemClass2.RepairTools = new ItemData.DataItemArrayRepairTools(array4); + // } + + // if (dynamicProperties.Values.ContainsKey("RepairAmount")) + // { + // int result2 = 0; + // int.TryParse(dynamicProperties.Values["RepairAmount"], out result2); + // itemClass2.RepairAmount = new DataItem(result2); + // } + + // if (dynamicProperties.Values.ContainsKey("RepairTime")) + // { + // float _result = 0f; + // StringParsers.TryParseFloat(dynamicProperties.Values["RepairTime"], out _result); + // itemClass2.RepairTime = new DataItem(_result); + // } + // else if (itemClass2.RepairAmount != null) + // { + // itemClass2.RepairTime = new DataItem(1f); + // } + + // if (dynamicProperties.Values.ContainsKey("Degradation")) + // { + // itemClass2.MaxUseTimes = new DataItem(int.Parse(dynamicProperties.Values["Degradation"])); + // } + // else + // { + // itemClass2.MaxUseTimes = new DataItem(0); + // itemClass2.MaxUseTimesBreaksAfter = new DataItem(_startValue: false); + // } + + // if (dynamicProperties.Values.ContainsKey("DegradationBreaksAfter")) + // { + // itemClass2.MaxUseTimesBreaksAfter = new DataItem(StringParsers.ParseBool(dynamicProperties.Values["DegradationBreaksAfter"])); + // } + // else if (dynamicProperties.Values.ContainsKey("Degradation")) + // { + // itemClass2.MaxUseTimesBreaksAfter = new DataItem(_startValue: true); + // } + + // if (dynamicProperties.Values.ContainsKey("EconomicValue")) + // { + // itemClass2.EconomicValue = StringParsers.ParseFloat(dynamicProperties.Values["EconomicValue"]); + // } + + // if (dynamicProperties.Classes.ContainsKey("Preview")) + // { + // DynamicProperties dynamicProperties3 = dynamicProperties.Classes["Preview"]; + // itemClass2.Preview = new PreviewData(); + // if (dynamicProperties3.Values.ContainsKey("Zoom")) + // { + // itemClass2.Preview.Zoom = new DataItem(int.Parse(dynamicProperties3.Values["Zoom"])); + // } + + // if (dynamicProperties3.Values.ContainsKey("Pos")) + // { + // itemClass2.Preview.Pos = new DataItem(StringParsers.ParseVector2(dynamicProperties3.Values["Pos"])); + // } + // else + // { + // itemClass2.Preview.Pos = new DataItem(Vector2.zero); + // } + + // if (dynamicProperties3.Values.ContainsKey("Rot")) + // { + // itemClass2.Preview.Rot = new DataItem(StringParsers.ParseVector3(dynamicProperties3.Values["Rot"])); + // } + // else + // { + // itemClass2.Preview.Rot = new DataItem(Vector3.zero); + // } + // } + + // for (int l = 0; l < itemClass2.Actions.Length; l++) + // { + // string text5 = ItemClass.itemActionNames[l]; + // if (dynamicProperties.Classes.ContainsKey(text5)) + // { + // if (!dynamicProperties.Values.ContainsKey(text5 + ".Class")) + // { + // throw new Exception("No class attribute found on " + text5 + " in item with '" + attribute + "'"); + // } + + // string text6 = dynamicProperties.Values[text5 + ".Class"]; + // ItemAction itemAction; + // try + // { + // itemAction = (ItemAction)Activator.CreateInstance(ReflectionHelpers.GetTypeWithPrefix("ItemAction", text6)); + // } + // catch (Exception) + // { + // throw new Exception("ItemAction class '" + text6 + " could not be instantiated"); + // } + + // itemAction.item = itemClass2; + // itemAction.ActionIndex = l; + // itemAction.ReadFrom(dynamicProperties.Classes[text5]); + // if (array[l].Count > 0) + // { + // itemAction.ExecutionRequirements = array[l]; + // } + + // itemClass2.Actions[l] = itemAction; + // } + // } + + // itemClass2.Init(); + // return false; + //} + + /// + /// fix requirement array count + /// + /// + /// + [HarmonyPatch(typeof(ItemClassesFromXml), nameof(ItemClassesFromXml.parseItem))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_parseItem_ItemClassesFromXml(IEnumerable instructions) + { + bool isLastInsThrow = false; + foreach (var code in instructions) + { + if (isLastInsThrow) + { + isLastInsThrow = false; + if (code.opcode == OpCodes.Ldc_I4_3) + { + code.opcode = OpCodes.Ldc_I4_5; + } + } + if (code.opcode == OpCodes.Throw) + { + isLastInsThrow = true; + } + yield return code; + } + } + + [HarmonyPatch(typeof(ItemModificationsFromXml), nameof(ItemModificationsFromXml.parseItem))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_parseItem_ItemModificationsFromXml(IEnumerable instructions) + { + var codes = instructions.ToList(); + + for (int i = 0; i < codes.Count - 1; i++) + { + if (codes[i].opcode == OpCodes.Ldc_I4_3 && codes[i + 1].opcode == OpCodes.Newarr) + { + codes[i].opcode = OpCodes.Ldc_I4_5; + break; + } + } + return codes; + } + + /* + [HarmonyPatch(typeof(ItemClassesFromXml), nameof(ItemClassesFromXml.parseItem))] + [HarmonyPrefix] + private static bool Prefix_parseItem_ItemClassesFromXml(XElement _node) + { + //throw new Exception("Exception thrown from here!"); + DynamicProperties dynamicProperties = new DynamicProperties(); + string attribute = _node.GetAttribute("name"); + if (attribute.Length == 0) + { + throw new Exception("Attribute 'name' missing on item"); + } + //Log.Out($"Parsing item {attribute}..."); + List[] array = new List[5]; + for (int i = 0; i < array.Length; i++) + { + array[i] = new List(); + } + + foreach (XElement item in _node.Elements("property")) + { + dynamicProperties.Add(item); + string attribute2 = item.GetAttribute("class"); + if (attribute2.StartsWith("Action")) + { + int num = attribute2[attribute2.Length - 1] - 48; + array[num].AddRange(RequirementBase.ParseRequirements(item)); + } + } + + if (dynamicProperties.Values.ContainsKey("Extends")) + { + string text = dynamicProperties.Values["Extends"]; + ItemClass itemClass = ItemClass.GetItemClass(text); + if (itemClass == null) + { + throw new Exception($"Extends item {text} is not specified for item {attribute}'"); + } + + HashSet hashSet = new HashSet { Block.PropCreativeMode }; + if (dynamicProperties.Params1.ContainsKey("Extends")) + { + string[] array2 = dynamicProperties.Params1["Extends"].Split(','); + foreach (string text2 in array2) + { + hashSet.Add(text2.Trim()); + } + } + + DynamicProperties dynamicProperties2 = new DynamicProperties(); + dynamicProperties2.CopyFrom(itemClass.Properties, hashSet); + dynamicProperties2.CopyFrom(dynamicProperties); + dynamicProperties = dynamicProperties2; + } + + ItemClass itemClass2; + if (dynamicProperties.Values.ContainsKey("Class")) + { + string text3 = dynamicProperties.Values["Class"]; + if (!text3.Contains(",")) + { + text3 += ",Assembly-CSharp"; + } + try + { + itemClass2 = (ItemClass)Activator.CreateInstance(Type.GetType(text3)); + } + catch (Exception) + { + throw new Exception("No item class '" + text3 + "' found!"); + } + } + else + { + itemClass2 = new ItemClass(); + } + + itemClass2.Properties = dynamicProperties; + if (dynamicProperties.Params1.ContainsKey("Extends")) + { + string text4 = dynamicProperties.Values["Extends"]; + if (ItemClass.GetItemClass(text4) == null) + { + throw new Exception($"Extends item {text4} is not specified for item {attribute}'"); + } + } + + itemClass2.Effects = MinEffectController.ParseXml(_node, null, MinEffectController.SourceParentType.ItemClass, itemClass2.Id); + itemClass2.SetName(attribute); + itemClass2.setLocalizedItemName(Localization.Get(attribute)); + if (dynamicProperties.Values.ContainsKey("Stacknumber")) + { + itemClass2.Stacknumber = new DataItem(int.Parse(dynamicProperties.Values["Stacknumber"])); + } + else + { + itemClass2.Stacknumber = new DataItem(500); + } + + if (dynamicProperties.Values.ContainsKey("Canhold")) + { + itemClass2.SetCanHold(StringParsers.ParseBool(dynamicProperties.Values["Canhold"])); + } + + if (dynamicProperties.Values.ContainsKey("Candrop")) + { + itemClass2.SetCanDrop(StringParsers.ParseBool(dynamicProperties.Values["Candrop"])); + } + + if (!dynamicProperties.Values.ContainsKey("Material")) + { + throw new Exception("Attribute 'material' missing on item '" + attribute + "'"); + } + + itemClass2.MadeOfMaterial = MaterialBlock.fromString(dynamicProperties.Values["Material"]); + if (itemClass2.MadeOfMaterial == null) + { + throw new Exception("Attribute 'material' '" + dynamicProperties.Values["Material"] + "' refers to not existing material in item '" + attribute + "'"); + } + + if (!dynamicProperties.Values.ContainsKey("Meshfile") && itemClass2.CanHold()) + { + throw new Exception("Attribute 'Meshfile' missing on item '" + attribute + "'"); + } + + itemClass2.MeshFile = dynamicProperties.Values["Meshfile"]; + DataLoader.PreloadBundle(itemClass2.MeshFile); + StringParsers.TryParseFloat(dynamicProperties.Values["StickyOffset"], out itemClass2.StickyOffset); + StringParsers.TryParseFloat(dynamicProperties.Values["StickyColliderRadius"], out itemClass2.StickyColliderRadius); + StringParsers.TryParseSInt32(dynamicProperties.Values["StickyColliderUp"], out itemClass2.StickyColliderUp); + StringParsers.TryParseFloat(dynamicProperties.Values["StickyColliderLength"], out itemClass2.StickyColliderLength); + itemClass2.StickyMaterial = dynamicProperties.Values["StickyMaterial"]; + if (dynamicProperties.Values.ContainsKey("ImageEffectOnActive")) + { + itemClass2.ImageEffectOnActive = new DataItem(dynamicProperties.Values["ImageEffectOnActive"]); + } + + if (dynamicProperties.Values.ContainsKey("Active")) + { + itemClass2.Active = new DataItem(_startValue: false); + } + + if (dynamicProperties.Values.ContainsKey(ItemClass.PropIsSticky)) + { + itemClass2.IsSticky = StringParsers.ParseBool(dynamicProperties.Values[ItemClass.PropIsSticky]); + } + + if (dynamicProperties.Values.ContainsKey("DropMeshfile") && itemClass2.CanHold()) + { + itemClass2.DropMeshFile = dynamicProperties.Values["DropMeshfile"]; + DataLoader.PreloadBundle(itemClass2.DropMeshFile); + } + + if (dynamicProperties.Values.ContainsKey("HandMeshfile") && itemClass2.CanHold()) + { + itemClass2.HandMeshFile = dynamicProperties.Values["HandMeshfile"]; + DataLoader.PreloadBundle(itemClass2.HandMeshFile); + } + + if (dynamicProperties.Values.ContainsKey("HoldType")) + { + string s = dynamicProperties.Values["HoldType"]; + int result = 0; + if (!int.TryParse(s, out result)) + { + throw new Exception("Cannot parse attribute hold_type for item '" + attribute + "'"); + } + + itemClass2.HoldType = new DataItem(result); + } + + if (dynamicProperties.Values.ContainsKey("RepairTools")) + { + string[] array3 = dynamicProperties.Values["RepairTools"].Replace(" ", "").Split(','); + DataItem[] array4 = new DataItem[array3.Length]; + for (int k = 0; k < array3.Length; k++) + { + array4[k] = new DataItem(array3[k]); + } + + itemClass2.RepairTools = new ItemData.DataItemArrayRepairTools(array4); + } + + if (dynamicProperties.Values.ContainsKey("RepairAmount")) + { + int result2 = 0; + int.TryParse(dynamicProperties.Values["RepairAmount"], out result2); + itemClass2.RepairAmount = new DataItem(result2); + } + + if (dynamicProperties.Values.ContainsKey("RepairTime")) + { + float _result = 0f; + StringParsers.TryParseFloat(dynamicProperties.Values["RepairTime"], out _result); + itemClass2.RepairTime = new DataItem(_result); + } + else if (itemClass2.RepairAmount != null) + { + itemClass2.RepairTime = new DataItem(1f); + } + + if (dynamicProperties.Values.ContainsKey("Degradation")) + { + itemClass2.MaxUseTimes = new DataItem(int.Parse(dynamicProperties.Values["Degradation"])); + } + else + { + itemClass2.MaxUseTimes = new DataItem(0); + itemClass2.MaxUseTimesBreaksAfter = new DataItem(_startValue: false); + } + + if (dynamicProperties.Values.ContainsKey("DegradationBreaksAfter")) + { + itemClass2.MaxUseTimesBreaksAfter = new DataItem(StringParsers.ParseBool(dynamicProperties.Values["DegradationBreaksAfter"])); + } + else if (dynamicProperties.Values.ContainsKey("Degradation")) + { + itemClass2.MaxUseTimesBreaksAfter = new DataItem(_startValue: true); + } + + if (dynamicProperties.Values.ContainsKey("EconomicValue")) + { + itemClass2.EconomicValue = StringParsers.ParseFloat(dynamicProperties.Values["EconomicValue"]); + } + + if (dynamicProperties.Classes.ContainsKey("Preview")) + { + DynamicProperties dynamicProperties3 = dynamicProperties.Classes["Preview"]; + itemClass2.Preview = new PreviewData(); + if (dynamicProperties3.Values.ContainsKey("Zoom")) + { + itemClass2.Preview.Zoom = new DataItem(int.Parse(dynamicProperties3.Values["Zoom"])); + } + + if (dynamicProperties3.Values.ContainsKey("Pos")) + { + itemClass2.Preview.Pos = new DataItem(StringParsers.ParseVector2(dynamicProperties3.Values["Pos"])); + } + else + { + itemClass2.Preview.Pos = new DataItem(Vector2.zero); + } + + if (dynamicProperties3.Values.ContainsKey("Rot")) + { + itemClass2.Preview.Rot = new DataItem(StringParsers.ParseVector3(dynamicProperties3.Values["Rot"])); + } + else + { + itemClass2.Preview.Rot = new DataItem(Vector3.zero); + } + } + + for (int l = 0; l < itemClass2.Actions.Length; l++) + { + string text5 = ItemClass.itemActionNames[l]; + if (dynamicProperties.Classes.ContainsKey(text5)) + { + if (!dynamicProperties.Values.ContainsKey(text5 + ".Class")) + { + throw new Exception("No class attribute found on " + text5 + " in item with '" + attribute + "'"); + } + + string text6 = dynamicProperties.Values[text5 + ".Class"]; + ItemAction itemAction; + try + { + itemAction = (ItemAction)Activator.CreateInstance(ReflectionHelpers.GetTypeWithPrefix("ItemAction", text6)); + } + catch (Exception) + { + throw new Exception("ItemAction class '" + text6 + " could not be instantiated"); + } + + itemAction.item = itemClass2; + itemAction.ActionIndex = l; + itemAction.ReadFrom(dynamicProperties.Classes[text5]); + if (array[l].Count > 0) + { + itemAction.ExecutionRequirements = array[l]; + } + + itemClass2.Actions[l] = itemAction; + } + } + + itemClass2.Init(); + return false; + } + */ + + [HarmonyPatch(typeof(Inventory), nameof(Inventory.onInventoryChanged))] + [HarmonyPrefix] + private static bool Prefix_onInventoryChanged_Inventory(Inventory __instance) + { + if (__instance.entity != null) + MultiActionManager.UpdateLocalMetaSave(__instance.entity.entityId); + return true; + } + #endregion + + //KEEP + #region Action mode handling + [HarmonyPatch(typeof(NetPackagePlayerStats), nameof(NetPackagePlayerStats.ProcessPackage))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_ProcessPackage_NetPackagePlayerStats(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var fld_entityid = AccessTools.Field(typeof(NetPackagePlayerStats), nameof(NetPackagePlayerStats.entityId)); + var fld_itemstack = AccessTools.Field(typeof(NetPackagePlayerStats), nameof(NetPackagePlayerStats.holdingItemStack)); + codes.InsertRange(codes.Count - 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Ldfld, fld_entityid), + new CodeInstruction(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Ldfld, fld_itemstack), + CodeInstruction.Call(typeof(MultiActionPatches), nameof(CheckItemValueMode)) + }); + + return codes; + } + [HarmonyPatch(typeof(NetPackageHoldingItem), nameof(NetPackageHoldingItem.ProcessPackage))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_ProcessPackage_NetPackageHoldingItem(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var fld_entityid = AccessTools.Field(typeof(NetPackagePlayerStats), nameof(NetPackageHoldingItem.entityId)); + var fld_itemstack = AccessTools.Field(typeof(NetPackagePlayerStats), nameof(NetPackageHoldingItem.holdingItemStack)); + codes.InsertRange(codes.Count - 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Ldfld, fld_entityid), + new CodeInstruction(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Ldfld, fld_itemstack), + CodeInstruction.Call(typeof(MultiActionPatches), nameof(CheckItemValueMode)) + }); + + return codes; + } + + private static void CheckItemValueMode(int entityId, ItemStack holdingItemStack) + { + ItemValue itemValue = holdingItemStack.itemValue; + if (itemValue.HasMetadata(MultiActionMapping.STR_MULTI_ACTION_INDEX)) + { + int mode = (int)itemValue.GetMetadata(MultiActionMapping.STR_MULTI_ACTION_INDEX); + if (MultiActionManager.SetModeForEntity(entityId, mode) && ConnectionManager.Instance.IsServer) + { + ConnectionManager.Instance.SendPackage(NetPackageManager.GetPackage().Setup(entityId, mode), false, -1, entityId); + } + } + } + + [HarmonyPatch(typeof(GameManager), nameof(GameManager.UpdateTick))] + [HarmonyPostfix] + private static void Postfix_UpdateTick_GameManager(GameManager __instance) + { + if (MultiActionManager.LocalModeChanged && __instance.m_World != null) + { + MultiActionManager.LocalModeChanged = false; + int playerID = __instance.m_World.GetPrimaryPlayerId(); + if (ConnectionManager.Instance.IsClient) + { + ConnectionManager.Instance.SendToServer(NetPackageManager.GetPackage().Setup(playerID, MultiActionManager.GetModeForEntity(playerID))); + } + else + { + ConnectionManager.Instance.SendPackage(NetPackageManager.GetPackage().Setup(playerID, MultiActionManager.GetModeForEntity(playerID)), false, -1, playerID); + } + } + } + #endregion + + #region Input Handling + [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(); + + MultiActionManager.UpdateLocalInput(__instance.entityPlayerLocal, __instance.playerInput, isUIOpen, Time.deltaTime); + + return true; + } + #endregion + + #region HUD display + /// + /// redirect check to alternative action module + /// + /// + /// + [HarmonyPatch(typeof(XUiC_HUDStatBar), nameof(XUiC_HUDStatBar.HasChanged))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_HasChanged_XUiC_HUDStatBar(IEnumerable instructions) + { + var codes = instructions.ToList(); + + MethodInfo mtd_edittool = AccessTools.Method(typeof(ItemAction), nameof(ItemAction.IsEditingTool)); + for (int i = 0; i < codes.Count; i++) + { + var code = codes[i]; + if (code.Calls(mtd_edittool)) + { + codes.RemoveRange(i - 1, 3); + break; + } + } + + return codes; + } + + [HarmonyPatch(typeof(XUiC_HUDStatBar), nameof(XUiC_HUDStatBar.SetupActiveItemEntry))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_SetupActiveItemEntry_XUiC_HUDStatBar(IEnumerable instructions, ILGenerator generator) + { + var codes = instructions.ToList(); + + var lbd_index = generator.DeclareLocal(typeof(int)); + FieldInfo fld_action = AccessTools.Field(typeof(ItemClass), nameof(ItemClass.Actions)); + FieldInfo fld_ammoindex = AccessTools.Field(typeof(ItemValue), nameof(ItemValue.SelectedAmmoTypeIndex)); + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].LoadsField(fld_action)) + { + codes.RemoveAt(i + 1); + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldloc_2), + CodeInstruction.Call(typeof(MultiActionUtils), nameof(MultiActionUtils.GetActionIndexByMetaData)), + new CodeInstruction(OpCodes.Dup), + new CodeInstruction(OpCodes.Stloc_S, lbd_index) + }); + i += 3; + } + else if (codes[i].LoadsField(fld_ammoindex)) + { + codes[i].opcode = OpCodes.Call; + codes[i].operand = AccessTools.Method(typeof(MultiActionUtils), nameof(MultiActionUtils.GetSelectedAmmoIndexByActionIndex)); + codes.Insert(i, new CodeInstruction(OpCodes.Ldloc_S, lbd_index)); + break; + } + } + return codes; + } + + [HarmonyPatch(typeof(XUiC_Radial), nameof(XUiC_Radial.handleActivatableItemCommand))] + [HarmonyPrefix] + private static bool Prefix_handleActivatableItemCommand_XUiC_Radial(XUiC_Radial _sender) + { + EntityPlayerLocal entityPlayer = _sender.xui.playerUI.entityPlayer; + MultiActionUtils.SetMinEventParamsByEntityInventory(entityPlayer); + return true; + } + + [HarmonyPatch(typeof(XUiC_ItemInfoWindow), nameof(XUiC_ItemInfoWindow.GetBindingValue))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_GetBindingValue_XUiC_ItemInfoWindow(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var fld_actions = AccessTools.Field(typeof(ItemClass), nameof(ItemClass.Actions)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].LoadsField(fld_actions) && codes[i + 1].opcode == OpCodes.Ldc_I4_0) + { + codes.RemoveAt(i + 1); + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(XUiC_ItemInfoWindow), nameof(XUiC_ItemInfoWindow.itemStack)), + CodeInstruction.LoadField(typeof(ItemStack), nameof(ItemStack.itemValue)), + CodeInstruction.Call(typeof(MultiActionUtils), nameof(MultiActionUtils.GetActionIndexByMetaData)) + }); + i += 3; + } + } + + return codes; + } + #endregion + + #region Cancel reload on switching item + //redirect these calls to action 0 and handle them in alternative module + //may change in the future + [HarmonyPatch(typeof(PlayerMoveController), nameof(PlayerMoveController.Update))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_Update_PlayerMoveController(IEnumerable instructions) + { + var codes = instructions.ToList(); + + MethodInfo mtd_getgun = AccessTools.Method(typeof(Inventory), nameof(Inventory.GetHoldingGun)); + MethodInfo mtd_getprimary = AccessTools.Method(typeof(Inventory), nameof(Inventory.GetHoldingPrimary)); + FieldInfo fld_reload = AccessTools.Field(typeof(PlayerActionsPermanent), nameof(PlayerActionsPermanent.Reload)); + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_getgun)) + { + codes[i].operand = mtd_getprimary; + //codes.RemoveAt(i - 2); + //codes.InsertRange(i - 2, new[] + //{ + // new CodeInstruction(OpCodes.Ldarg_0), + // CodeInstruction.LoadField(typeof(PlayerMoveController), "entityPlayerLocal"), + // CodeInstruction.Call(typeof(MultiActionManager), nameof(MultiActionManager.GetActionIndexForEntity)) + //}); + //i += 2; + } + else if (codes[i].LoadsField(fld_reload)) + { + var label = codes[i + 6].operand; + codes.InsertRange(i + 7, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(PlayerMoveController), nameof(PlayerMoveController.entityPlayerLocal)), + CodeInstruction.LoadField(typeof(EntityAlive), nameof(EntityAlive.inventory)), + CodeInstruction.Call(typeof(Inventory), nameof(Inventory.GetIsFinishedSwitchingHeldItem)), + new CodeInstruction(OpCodes.Brfalse, label) + }); + i += 5; + } + } + + return codes; + } + #endregion + + #region Underwater check + //skip underwater check if action is not current action + [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.OnHoldingUpdate))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_OnHoldingUpdate_ItemActionRanged(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var fld_ammonames = AccessTools.Field(typeof(ItemActionAttack), nameof(ItemActionAttack.MagazineItemNames)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].LoadsField(fld_ammonames) && (codes[i + 1].opcode == OpCodes.Brfalse_S || codes[i + 1].opcode == OpCodes.Brfalse)) + { + var jumpto = codes[i + 1].operand; + codes.InsertRange(i - 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_1).WithLabels(codes[i - 1].ExtractLabels()), + CodeInstruction.LoadField(typeof(ItemActionData), nameof(ItemActionData.indexInEntityOfAction)), + new CodeInstruction(OpCodes.Ldarg_1), + CodeInstruction.LoadField(typeof(ItemActionData), nameof(ItemActionData.invData)), + CodeInstruction.LoadField(typeof(ItemInventoryData), nameof(ItemInventoryData.holdingEntity)), + CodeInstruction.Call(typeof(MultiActionManager), nameof(MultiActionManager.GetActionIndexForEntity)), + new CodeInstruction(OpCodes.Bne_Un_S, jumpto) + }); + break; + } + } + + return codes; + } + #endregion + + #region GameEvent + [HarmonyPatch(typeof(ActionUnloadItems), nameof(ActionUnloadItems.HandleItemStackChange))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_HandleItemStackChange_ActionUnloadItems(IEnumerable instructions, ILGenerator generator) + { + var codes = instructions.ToList(); + + var fld_actions = AccessTools.Field(typeof(ItemClass), nameof(ItemClass.Actions)); + + for (var i = 0; i < codes.Count; i++) + { + if (codes[i].LoadsField(fld_actions)) + { + var label = generator.DefineLabel(); + codes[i - 1].WithLabels(label); + codes.InsertRange(i - 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_1), + new CodeInstruction(OpCodes.Ldind_Ref), + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(ActionUnloadItems), nameof(ActionUnloadItems.ItemStacks)), + CodeInstruction.Call(typeof(MultiActionUtils), nameof(MultiActionUtils.MultiActionRemoveAmmoFromItemStack)), + new CodeInstruction(OpCodes.Brfalse_S, label), + new CodeInstruction(OpCodes.Ldc_I4_1), + new CodeInstruction(OpCodes.Ret) + }); + break; + } + } + + return codes; + } + #endregion + + #region fast toolbelt item switching issue fix + private static Coroutine switchHoldingItemCo; + [HarmonyPatch(typeof(Inventory), nameof(Inventory.ShowHeldItem))] + [HarmonyPrefix] + private static bool Prefix_ShowHeldItem_Inventory(bool show, Inventory __instance) + { + //Log.Out($"ShowHeldItem {show} on entity {__instance.entity.entityName}\n{StackTraceUtility.ExtractStackTrace()}"); + if (show && __instance.entity is EntityPlayerLocal && switchHoldingItemCo != null) + { + GameManager.Instance.StopCoroutine(switchHoldingItemCo); + switchHoldingItemCo = null; + __instance.SetIsFinishedSwitchingHeldItem(); + } + return true; + } + + [HarmonyPatch(typeof(Inventory), nameof(Inventory.ShowHeldItem))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_ShowHeldItem_Inventory(IEnumerable instructions, ILGenerator generator) + { + var codes = instructions.ToList(); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].opcode == OpCodes.Pop) + { + var label = generator.DefineLabel(); + codes[i].WithLabels(label); + codes.InsertRange(i, new[] + { + new CodeInstruction(OpCodes.Ldarg_1), + new CodeInstruction(OpCodes.Brfalse_S, label), + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(Inventory), nameof(Inventory.entity)), + new CodeInstruction(OpCodes.Isinst, typeof(EntityPlayerLocal)), + new CodeInstruction(OpCodes.Brfalse_S, label), + CodeInstruction.StoreField(typeof(MultiActionPatches), nameof(switchHoldingItemCo)), + new CodeInstruction(OpCodes.Ret) + }); + break; + } + } + return codes; + } + #endregion + + #region item info display fix + [HarmonyPatch(typeof(XUiM_ItemStack), nameof(XUiM_ItemStack.GetStatItemValueTextWithModInfo))] + [HarmonyPrefix] + private static bool Prefix_GetStatItemValueTextWithModInfo_XUiM_ItemStack(ItemStack itemStack) + { + MultiActionUtils.SetCachedEventParamsDummyAction(itemStack); + return true; + } + + [HarmonyPatch(typeof(XUiM_ItemStack), nameof(XUiM_ItemStack.GetStatItemValueTextWithModColoring))] + [HarmonyPrefix] + private static bool Prefix_GetStatItemValueTextWithModColoring_XUiM_ItemStack(ItemStack itemStack) + { + MultiActionUtils.SetCachedEventParamsDummyAction(itemStack); + return true; + } + + [HarmonyPatch(typeof(XUiM_ItemStack), nameof(XUiM_ItemStack.GetStatItemValueTextWithCompareInfo))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_GetStatItemValueTextWithCompareInfo_XUiM_ItemStack(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var mtd_getvalue = AccessTools.Method(typeof(EffectManager), nameof(EffectManager.GetValue)); + var fld_seed = AccessTools.Field(typeof(MinEventParams), nameof(MinEventParams.Seed)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_getvalue)) + { + codes.InsertRange(i + 2, new[] + { + new CodeInstruction(OpCodes.Ldarg_1), + CodeInstruction.Call(typeof(MultiActionUtils), nameof(MultiActionUtils.SetCachedEventParamsDummyAction)), + }); + for (int j = i; j >= 0; j--) + { + if (codes[j].StoresField(fld_seed)) + { + codes.InsertRange(j + 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.Call(typeof(MultiActionUtils), nameof(MultiActionUtils.SetCachedEventParamsDummyAction)) + }); + codes.RemoveRange(j - 8, 9); + break; + } + } + break; + } + } + + return codes.Manipulator(static ins => ins.IsLdarg(2), static ins => ins.opcode = OpCodes.Ldnull); + } + + [HarmonyPatch(typeof(XUiC_ItemStack), nameof(XUiC_ItemStack.GetBindingValue))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_GetBindingValue_XUiC_ItemStack(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var prop_perc = AccessTools.PropertyGetter(typeof(ItemValue), nameof(ItemValue.PercentUsesLeft)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(prop_perc)) + { + codes.InsertRange(i - 3, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(XUiC_ItemStack), nameof(XUiC_ItemStack.itemStack)), + CodeInstruction.Call(typeof(MultiActionUtils), nameof(MultiActionUtils.SetCachedEventParamsDummyAction)) + }); + break; + } + } + + return codes; + } + #endregion + + #region ItemAction exclude tags + [HarmonyPatch(typeof(GameManager), nameof(GameManager.StartGame))] + [HarmonyPrefix] + private static bool Prefix_StartGame_GameManager() + { + MultiActionManager.PreloadCleanup(); + return true; + } + + [HarmonyPatch(typeof(ItemClass), nameof(ItemClass.LateInit))] + [HarmonyPostfix] + private static void Postfix_LateInit_ItemClass(ItemClass __instance) + { + MultiActionManager.ParseItemActionExcludeTagsAndModifiers(__instance); + } + + /// + /// + /// + /// + /// + [HarmonyPatch(typeof(EffectManager), nameof(EffectManager.GetValue))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_GetValue_EffectManager(IEnumerable instructions) + { + var codes = instructions.ToList(); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].IsStarg(5)) + { + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_1).WithLabels(codes[i + 1].ExtractLabels()), + CodeInstruction.LoadField(typeof(MinEventParams), nameof(MinEventParams.CachedEventParam)), + CodeInstruction.LoadField(typeof(MinEventParams), nameof(MinEventParams.ItemActionData)), + new CodeInstruction(OpCodes.Ldarga_S, 5), + CodeInstruction.Call(typeof(MultiActionManager), nameof(MultiActionManager.ModifyItemTags)) + }); + break; + } + } + + return codes; + } + + [HarmonyPatch(typeof(EffectManager), nameof(EffectManager.GetValuesAndSources))] + [HarmonyPrefix] + private static bool Prefix_GetValuesAndSources_EffectManager(ItemValue _originalItemValue, EntityAlive _entity, ref FastTags tags) + { + MultiActionManager.ModifyItemTags(_originalItemValue, _entity?.MinEventContext?.ItemActionData, ref tags); + return true; + } + #endregion + + #region ItemAction exclude modifiers + //see Transpiler_ModifyValue_ItemValue + //see Transpiler_GetModifiedValueData_ItemValue + //see MultiActionProjectileRewrites.ProjectileValueModifyValue + //see MultiActionUtils.GetPropertyOverrideForAction + //see MultiActionManager.ParseItemActionExcludeTagsAndModifiers + #endregion + + #region requirement tags exclude + //[HarmonyPatch(typeof(TriggerHasTags), nameof(TriggerHasTags.IsValid))] + //[HarmonyTranspiler] + //private static IEnumerable Transpiler_IsValid_TriggerHasTags(IEnumerable instructions, ILGenerator generator) + //{ + // var codes = instructions.ToList(); + + // var lbd_tags = generator.DeclareLocal(typeof(FastTags)); + // FieldInfo fld_tags = AccessTools.Field(typeof(MinEventParams), nameof(MinEventParams.Tags)); + // bool firstRet = true; + + // for (int i = 0; i < codes.Count; i++) + // { + // if (codes[i].opcode == OpCodes.Ret && firstRet) + // { + // firstRet = false; + // codes.InsertRange(i + 1, new[] + // { + // new CodeInstruction(OpCodes.Ldarg_1), + // new CodeInstruction(OpCodes.Ldfld, fld_tags), + // new CodeInstruction(OpCodes.Stloc_S, lbd_tags), + // new CodeInstruction(OpCodes.Ldarg_1), + // CodeInstruction.LoadField(typeof(MinEventParams), nameof(MinEventParams.ItemValue)), + // new CodeInstruction(OpCodes.Ldarg_1), + // CodeInstruction.LoadField(typeof(MinEventParams), nameof(MinEventParams.ItemActionData)), + // new CodeInstruction(OpCodes.Ldloca_S, lbd_tags), + // CodeInstruction.Call(typeof(MultiActionManager), nameof(MultiActionManager.ModifyItemTags)) + // }); + // i += 9; + // } + // else if (codes[i].LoadsField(fld_tags)) + // { + // codes[i].opcode = OpCodes.Ldloca_S; + // codes[i].operand = lbd_tags; + // codes[i].WithLabels(codes[i - 1].ExtractLabels()); + // codes.RemoveAt(i - 1); + // i--; + // } + // } + + // return codes; + //} + + [HarmonyPatch(typeof(ItemHasTags), nameof(ItemHasTags.IsValid))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_IsValid_ItemHasTags(IEnumerable instructions, ILGenerator generator) + { + var codes = instructions.ToList(); + + var lbd_tags = generator.DeclareLocal(typeof(FastTags)); + FieldInfo fld_itemvalue = AccessTools.Field(typeof(MinEventParams), nameof(MinEventParams.ItemValue)); + FieldInfo fld_hasalltags = AccessTools.Field(typeof(ItemHasTags), nameof(ItemHasTags.hasAllTags)); + MethodInfo prop_itemclass = AccessTools.PropertyGetter(typeof(ItemValue), nameof(ItemValue.ItemClass)); + MethodInfo mtd_hasanytags = AccessTools.Method(typeof(ItemClass), nameof(ItemClass.HasAnyTags)); + MethodInfo mtd_hasalltags = AccessTools.Method(typeof(ItemClass), nameof(ItemClass.HasAllTags)); + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].LoadsField(fld_hasalltags)) + { + codes.InsertRange(i - 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_1), + new CodeInstruction(OpCodes.Ldfld, fld_itemvalue), + new CodeInstruction(OpCodes.Callvirt, prop_itemclass), + CodeInstruction.LoadField(typeof(ItemClass), nameof(ItemClass.ItemTags)), + new CodeInstruction(OpCodes.Stloc_S, lbd_tags), + new CodeInstruction(OpCodes.Ldarg_1), + new CodeInstruction(OpCodes.Ldfld, fld_itemvalue), + new CodeInstruction(OpCodes.Ldarg_1), + CodeInstruction.LoadField(typeof(MinEventParams), nameof(MinEventParams.ItemActionData)), + new CodeInstruction(OpCodes.Ldloca_S, lbd_tags), + CodeInstruction.Call(typeof(MultiActionManager), nameof(MultiActionManager.ModifyItemTags)) + }); + i += 11; + } + else if (codes[i].Calls(mtd_hasanytags)) + { + codes[i].opcode = OpCodes.Call; + codes[i].operand = AccessTools.Method(typeof(FastTags), nameof(FastTags.Test_AnySet)); + var labels = codes[i - 5].ExtractLabels(); + codes.RemoveRange(i - 5, 3); + codes.Insert(i - 5, new CodeInstruction(OpCodes.Ldloca_S, lbd_tags).WithLabels(labels)); + i -= 2; + } + else if (codes[i].Calls(mtd_hasalltags)) + { + codes[i].opcode = OpCodes.Call; + codes[i].operand = AccessTools.Method(typeof(FastTags), nameof(FastTags.Test_AllSet)); + var labels = codes[i - 5].ExtractLabels(); + codes.RemoveRange(i - 5, 3); + codes.Insert(i - 5, new CodeInstruction(OpCodes.Ldloca_S, lbd_tags).WithLabels(labels)); + i -= 2; + } + } + return codes; + } + + + [HarmonyPatch(typeof(HoldingItemHasTags), nameof(ItemHasTags.IsValid))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_IsValid_HoldingItemHasTags(IEnumerable instructions, ILGenerator generator) + { + var codes = instructions.ToList(); + + var lbd_tags = generator.DeclareLocal(typeof(FastTags)); + FieldInfo fld_itemvalue = AccessTools.Field(typeof(MinEventParams), nameof(MinEventParams.ItemValue)); + FieldInfo fld_hasalltags = AccessTools.Field(typeof(HoldingItemHasTags), nameof(HoldingItemHasTags.hasAllTags)); + MethodInfo prop_itemclass = AccessTools.PropertyGetter(typeof(ItemValue), nameof(ItemValue.ItemClass)); + MethodInfo prop_itemvalue = AccessTools.PropertyGetter(typeof(Inventory), nameof(Inventory.holdingItemItemValue)); + MethodInfo mtd_hasanytags = AccessTools.Method(typeof(ItemClass), nameof(ItemClass.HasAnyTags)); + MethodInfo mtd_hasalltags = AccessTools.Method(typeof(ItemClass), nameof(ItemClass.HasAllTags)); + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].LoadsField(fld_hasalltags)) + { + codes.InsertRange(i - 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(HoldingItemHasTags), nameof(HoldingItemHasTags.target)), + CodeInstruction.LoadField(typeof(EntityAlive), nameof(EntityAlive.inventory)), + new CodeInstruction(OpCodes.Callvirt, prop_itemvalue), + new CodeInstruction(OpCodes.Callvirt, prop_itemclass), + CodeInstruction.LoadField(typeof(ItemClass), nameof(ItemClass.ItemTags)), + new CodeInstruction(OpCodes.Stloc_S, lbd_tags), + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(HoldingItemHasTags), nameof(HoldingItemHasTags.target)), + CodeInstruction.LoadField(typeof(EntityAlive), nameof(EntityAlive.inventory)), + new CodeInstruction(OpCodes.Callvirt, prop_itemvalue), + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(HoldingItemHasTags), nameof(HoldingItemHasTags.target)), + CodeInstruction.LoadField(typeof(EntityAlive), nameof(EntityAlive.MinEventContext)), + CodeInstruction.LoadField(typeof(MinEventParams), nameof(MinEventParams.ItemActionData)), + new CodeInstruction(OpCodes.Ldloca_S, lbd_tags), + CodeInstruction.Call(typeof(MultiActionManager), nameof(MultiActionManager.ModifyItemTags)) + }); + i += 17; + } + else if (codes[i].Calls(mtd_hasanytags)) + { + codes[i].opcode = OpCodes.Call; + codes[i].operand = AccessTools.Method(typeof(FastTags), nameof(FastTags.Test_AnySet)); + var labels = codes[i - 6].ExtractLabels(); + codes.RemoveRange(i - 6, 4); + codes.Insert(i - 6, new CodeInstruction(OpCodes.Ldloca_S, lbd_tags).WithLabels(labels)); + i -= 3; + } + else if (codes[i].Calls(mtd_hasalltags)) + { + codes[i].opcode = OpCodes.Call; + codes[i].operand = AccessTools.Method(typeof(FastTags), nameof(FastTags.Test_AllSet)); + var labels = codes[i - 6].ExtractLabels(); + codes.RemoveRange(i - 6, 4); + codes.Insert(i - 6, new CodeInstruction(OpCodes.Ldloca_S, lbd_tags).WithLabels(labels)); + i -= 3; + } + } + return codes; + } + #endregion + + #region Inventory make ItemValue valid on creating inventory data + [HarmonyPatch(typeof(Inventory), nameof(Inventory.SetItem), new[] { typeof(int), typeof(ItemValue), typeof(int), typeof(bool) })] + [HarmonyTranspiler] + private static IEnumerable Transpiler_SetItem_Inventory(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var mtd_clone = AccessTools.Method(typeof(ItemValue), nameof(ItemValue.Clone)); + var mtd_create = AccessTools.Method(typeof(Inventory), nameof(Inventory.createHeldItem)); + var mtd_invdata = AccessTools.Method(typeof(Inventory), nameof(Inventory.createInventoryData)); + for (int i = 0; i < codes.Count; i++) + { + //if (codes[i].opcode == OpCodes.Ldarg_3 && (codes[i + 1].opcode == OpCodes.Brtrue_S || codes[i + 1].opcode == OpCodes.Brtrue)) + //{ + // var label = codes[i + 4].ExtractLabels(); + // codes.InsertRange(i + 4, new[] + // { + // new CodeInstruction(OpCodes.Ldarg_2).WithLabels(label), + // new CodeInstruction(OpCodes.Callvirt, mtd_clone), + // new CodeInstruction(OpCodes.Starg_S, 2) + // }); + // i += 7; + //} + //else + if (codes[i].Calls(mtd_create)) + { + codes.InsertRange(i - 12, new[] + { + new CodeInstruction(OpCodes.Ldarg_2), + CodeInstruction.StoreField(typeof(ActionModuleAlternative), nameof(ActionModuleAlternative.InventorySetItemTemp)) + }); + i += 4; + for (int j = i; j < codes.Count; j++) + { + if (codes[j].Calls(mtd_invdata)) + { + codes.InsertRange(j + 2, new[] + { + new CodeInstruction(OpCodes.Ldnull), + CodeInstruction.StoreField(typeof(ActionModuleAlternative), nameof(ActionModuleAlternative.InventorySetItemTemp)) + }); + i = j + 4; + break; + } + } + break; + } + //else if (codes[i].Calls(mtd_clone)) + //{ + // codes.RemoveAt(i); + // break; + //} + } + + return codes; + } + + [HarmonyPatch(typeof(Inventory), nameof(Inventory.ForceHoldingItemUpdate))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_ForceHoldingItemUpdate_Inventory(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var mtd_invdata = AccessTools.Method(typeof(Inventory), nameof(Inventory.createInventoryData)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_invdata)) + { + codes.InsertRange(i + 2, new[] + { + new CodeInstruction(OpCodes.Ldnull), + CodeInstruction.StoreField(typeof(ActionModuleAlternative), nameof(ActionModuleAlternative.InventorySetItemTemp)) + }); + codes.InsertRange(i - 8, new[] + { + new CodeInstruction(OpCodes.Ldloc_0).WithLabels(codes[i - 8].ExtractLabels()), + CodeInstruction.StoreField(typeof(ActionModuleAlternative), nameof(ActionModuleAlternative.InventorySetItemTemp)) + }); + break; + } + } + + return codes; + } + #endregion + + #region Temporaty fix for hud ammo mismatch + [HarmonyPatch(typeof(XUiC_HUDStatBar), nameof(XUiC_HUDStatBar.Update))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_Update_XUiC_HUDStatBar(IEnumerable instructions) + { + MethodInfo mtd_getfocus = AccessTools.Method(typeof(Inventory), nameof(Inventory.GetFocusedItemIdx)); + MethodInfo mtd_getholding = AccessTools.PropertyGetter(typeof(Inventory), nameof(Inventory.holdingItemIdx)); + + foreach (var ins in instructions) + { + if (ins.Calls(mtd_getfocus)) + { + ins.operand = mtd_getholding; + } + yield return ins; + } + } + #endregion + } + + //Moved to MultiActionFix + //#region Ranged Reload + //[HarmonyPatch] + //public static class RangedReloadPatches + //{ + // private static IEnumerable TargetMethods() + // { + // return new MethodInfo[] + // { + // AccessTools.Method(typeof(ItemActionAttack), nameof(ItemActionAttack.ReloadGun)), + // AccessTools.Method(typeof(ItemActionRanged), nameof(ItemActionRanged.ReloadGun)), + // AccessTools.Method(typeof(ItemActionLauncher), nameof(ItemActionLauncher.ReloadGun)) + // }; + // } + + // //Why? Ask TFP why they don't just call base.ReloadGun() + // [HarmonyPrefix] + // private static bool Prefix_ReloadGun(ItemActionData _actionData) + // { + // int reloadAnimationIndex = MultiActionManager.GetMetaIndexForActionIndex(_actionData.invData.holdingEntity.entityId, _actionData.indexInEntityOfAction); + // _actionData.invData.holdingEntity.emodel?.avatarController?.UpdateInt(AvatarController.itemActionIndexHash, reloadAnimationIndex, false); + // _actionData.invData.holdingEntity.MinEventContext.ItemActionData = _actionData; + // return true; + // } + //} + //#endregion + + //KEEP + #region Melee action tags + [HarmonyPatch] + public static class ActionTagPatches1 + { + private static IEnumerable TargetMethods() + { + return new MethodInfo[] + { + AccessTools.Method(typeof(AnimatorMeleeAttackState), nameof(AnimatorMeleeAttackState.OnStateEnter), new[] {typeof(Animator), typeof(AnimatorStateInfo), typeof(int)}), + AccessTools.Method(typeof(ItemActionAttack), nameof(ItemActionAttack.GetDamageBlock)), + AccessTools.Method(typeof(ItemActionAttack), nameof(ItemActionAttack.GetDamageEntity)), + AccessTools.Method(typeof(ItemActionDynamic), nameof(ItemActionDynamic.GetDamageBlock)), + AccessTools.Method(typeof(ItemActionDynamic), nameof(ItemActionDynamic.GetDamageEntity)), + AccessTools.Method(typeof(ItemActionThrownWeapon), nameof(ItemActionThrownWeapon.GetDamageBlock)), + AccessTools.Method(typeof(ItemActionThrownWeapon), nameof(ItemActionThrownWeapon.GetDamageEntity)) + }; + } + + //set correct tag for action index above 2 + //only action 1 uses secondary tag, others still use primary + [HarmonyTranspiler] + private static IEnumerable Transpiler(IEnumerable instructions) + { + var codes = instructions.ToList(); + + FieldInfo fld_tag = AccessTools.Field(typeof(ItemActionAttack), nameof(ItemActionAttack.SecondaryTag)); + for (int i = 0; i < codes.Count; i++) + { + var code = codes[i]; + if (code.LoadsField(fld_tag)) + { + codes.InsertRange(i - 1, new[] + { + new CodeInstruction(OpCodes.Ldc_I4_1), + new CodeInstruction(OpCodes.Ceq) + }); + i += 2; + } + } + + return codes; + } + } + + [HarmonyPatch] + public static class ActionTagPatches2 + { + private static IEnumerable TargetMethods() + { + return new MethodInfo[] + { + AccessTools.Method(typeof(ItemActionDynamicMelee), nameof(ItemActionDynamicMelee.Raycast)), + AccessTools.Method(typeof(ItemActionDynamic), nameof(ItemActionDynamic.GetExecuteActionGrazeTarget)), + AccessTools.Method(typeof(ItemActionDynamic), nameof(ItemActionDynamic.hitTarget)), + AccessTools.Method(typeof(ItemActionDynamicMelee), nameof(ItemActionDynamicMelee.canStartAttack)), + AccessTools.Method(typeof(ItemActionDynamicMelee), nameof(ItemActionDynamicMelee.OnHoldingUpdate)), + AccessTools.Method(typeof(ItemActionDynamicMelee), nameof(ItemActionDynamicMelee.SetAttackFinished)), + AccessTools.Method(typeof(ItemActionMelee), nameof(ItemActionMelee.OnHoldingUpdate)), + AccessTools.Method(typeof(ItemActionRanged), nameof(ItemActionRanged.ExecuteAction)), + AccessTools.Method(typeof(ItemActionRanged), nameof(ItemActionRanged.fireShot)), + AccessTools.Method(typeof(ItemActionThrownWeapon), nameof(ItemActionThrownWeapon.throwAway)), + AccessTools.Method(typeof(ItemActionUseOther), nameof(ItemActionUseOther.ExecuteAction)), + + }; + } + + [HarmonyTranspiler] + private static IEnumerable Transpiler(IEnumerable instructions) + { + var codes = instructions.ToList(); + + FieldInfo fld_index = AccessTools.Field(typeof(ItemActionData), nameof(ItemActionData.indexInEntityOfAction)); + for (int i = 0; i < codes.Count - 1; i++) + { + var code = codes[i]; + if (code.LoadsField(fld_index) && (codes[i + 1].opcode == OpCodes.Brfalse_S || codes[i + 1].opcode == OpCodes.Brfalse)) + { + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldc_I4_1), + new CodeInstruction(OpCodes.Ceq) + }); + i += 2; + } + } + + return codes; + } + } + #endregion + + //KEEP + #region 3 + [HarmonyPatch] + public static class ThreePatches + { + private static IEnumerable TargetMethods() + { + return new MethodInfo[] + { + AccessTools.Method(typeof(ItemClass), nameof(ItemClass.OnHoldingUpdate)), + AccessTools.Method(typeof(ItemClass), nameof(ItemClass.CleanupHoldingActions)), + AccessTools.Method(typeof(ItemClass), nameof(ItemClass.StartHolding)), + AccessTools.Method(typeof(ItemClass), nameof(ItemClass.StopHolding)), + AccessTools.Method(typeof(ItemClass), nameof(ItemClass.IsActionRunning)) + }; + } + + [HarmonyTranspiler] + private static IEnumerable Transpiler_Three(IEnumerable instructions) + { + foreach (var instruction in instructions) + { + if (instruction.opcode == OpCodes.Ldc_I4_3) + instruction.opcode = OpCodes.Ldc_I4_5; + yield return instruction; + } + } + } + #endregion + + #region ItemAction property override + [HarmonyPatch] + public static class ItemActionPropertyOverridePatches + { + private static IEnumerable TargetMethods() + { + return AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(a => + { + try + { + return a.GetTypes(); + } + catch + { + return new Type[0]; + } + }) + .Where(t => t.IsSubclassOf(typeof(ItemAction))) + .Select(t => AccessTools.Method(t, nameof(ItemAction.OnModificationsChanged))) + .Where(m => m.IsDeclaredMember()); + } + + [HarmonyTranspiler] + private static IEnumerable Transpiler(IEnumerable instructions, MethodBase __originalMethod) + { + Log.Out($"Patching property override method {__originalMethod.DeclaringType.Name}.{__originalMethod.Name}"); + var codes = instructions.ToList(); + var mtd_override = AccessTools.Method(typeof(ItemValue), nameof(ItemValue.GetPropertyOverride)); + var mtd_newoverride = AccessTools.Method(typeof(MultiActionUtils), nameof(MultiActionUtils.GetPropertyOverrideForAction)); + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_override)) + { + codes[i].opcode = OpCodes.Call; + codes[i].operand = mtd_newoverride; + codes.InsertRange(i, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(ItemAction), nameof(ItemAction.ActionIndex)) + }); + i += 2; + } + } + return codes; + } + } + + #endregion + + #region Remove ammo + [HarmonyPatch] + public static class RemoveAmmoPatches + { + private static IEnumerable TargetMethods() + { + return new[] + { + AccessTools.Method(typeof(ItemActionEntryAssemble), nameof(ItemActionEntryAssemble.HandleRemoveAmmo)), + AccessTools.Method(typeof(ItemActionEntryScrap), nameof(ItemActionEntryScrap.HandleRemoveAmmo)), + AccessTools.Method(typeof(ItemActionEntrySell), nameof(ItemActionEntrySell.HandleRemoveAmmo)), + }; + } + + [HarmonyPrefix] + private static bool Prefix(BaseItemActionEntry __instance, ItemStack stack, ref ItemStack __result) + { + List list_ammo_stack = new List(); + if (!MultiActionUtils.MultiActionRemoveAmmoFromItemStack(stack, list_ammo_stack)) + return true; + + foreach (var ammoStack in list_ammo_stack) + { + if (!__instance.ItemController.xui.PlayerInventory.AddItem(ammoStack)) + { + __instance.ItemController.xui.PlayerInventory.DropItem(ammoStack); + } + } + __result = stack; + return false; + } + } + #endregion + + #region Generate initial meta + [HarmonyPatch] + public static class InitialMetaPatches + { + private static IEnumerable TargetMethods() + { + return new MethodInfo[] + { + AccessTools.Method(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.SetupStartingItems)), + AccessTools.Method(typeof(ItemClass), nameof(ItemClass.CreateItemStacks)) + }; + } + + [HarmonyTranspiler] + private static IEnumerable Transpiler(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var mtd_initial = AccessTools.Method(typeof(ItemClass), nameof(ItemClass.GetInitialMetadata)); + var mtd_initialnew = AccessTools.Method(typeof(MultiActionUtils), nameof(MultiActionUtils.GetMultiActionInitialMetaData)); + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_initial)) + { + codes[i].opcode = OpCodes.Call; + codes[i].operand = mtd_initialnew; + break; + } + } + + return codes; + } + } + #endregion + + #region Item Info DisplayType + [HarmonyPatch] + public static class DisplayTypePatches + { + [HarmonyPatch(typeof(XUiC_AssembleWindow), nameof(XUiC_AssembleWindow.ItemStack), MethodType.Setter)] + [HarmonyTranspiler] + private static IEnumerable Transpiler_ItemStack_XUiC_AssembleWindow(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var fld_displaytype = AccessTools.Field(typeof(ItemClass), nameof(ItemClass.DisplayType)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].LoadsField(fld_displaytype)) + { + codes.RemoveRange(i - 1, 2); + codes.InsertRange(i - 1, new[] + { + CodeInstruction.LoadField(typeof(XUiC_AssembleWindow), nameof(XUiC_AssembleWindow.itemStack)), + CodeInstruction.Call(typeof(MultiActionUtils), nameof(MultiActionUtils.GetDisplayTypeForAction), new []{ typeof(ItemStack) }) + }); + break; + } + } + return codes; + } + + [HarmonyPatch(typeof(XUiC_ItemInfoWindow), nameof(XUiC_ItemInfoWindow.SetInfo))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_SetInfo_XUiC_ItemInfoWindow(IEnumerable instructions, MethodBase originalMethod) + { + var codes = instructions.ToList(); + + var fld_displaytype = AccessTools.Field(typeof(ItemClass), nameof(ItemClass.DisplayType)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].LoadsField(fld_displaytype)) + { + codes.RemoveRange(i - 1, 2); + codes.InsertRange(i - 1, new[] + { + CodeInstruction.LoadField(typeof(XUiC_ItemInfoWindow), nameof(XUiC_ItemInfoWindow.itemStack)), + CodeInstruction.Call(typeof(MultiActionUtils), nameof(MultiActionUtils.GetDisplayTypeForAction), new []{ typeof(ItemStack) }) + }); + break; + } + } + return codes; + } + + [HarmonyPatch(typeof(XUiM_ItemStack), nameof(XUiM_ItemStack.HasItemStats))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_HasItemStats_XUiM_ItemStack(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var fld_displaytype = AccessTools.Field(typeof(ItemClass), nameof(ItemClass.DisplayType)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].LoadsField(fld_displaytype)) + { + codes.RemoveRange(i - 1, 2); + codes.Insert(i - 1, CodeInstruction.Call(typeof(MultiActionUtils), nameof(MultiActionUtils.GetDisplayTypeForAction), new []{ typeof(ItemValue) })); + break; + } + } + + return codes; + } + + [HarmonyPatch(typeof(XUiC_ItemInfoWindow), nameof(XUiC_ItemInfoWindow.HoverEntry), MethodType.Setter)] + [HarmonyTranspiler] + private static IEnumerable Transpiler_HoverEntry_XUiC_ItemInfoWindow(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var mtd_cancompare = AccessTools.Method(typeof(XUiM_ItemStack), nameof(XUiM_ItemStack.CanCompare)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_cancompare)) + { + codes[i] = CodeInstruction.Call(typeof(MultiActionUtils), nameof(MultiActionUtils.CanCompare)); + codes[i - 1] = CodeInstruction.LoadField(typeof(XUiC_ItemInfoWindow), nameof(XUiC_ItemInfoWindow.itemStack)); + codes.Insert(i, CodeInstruction.LoadField(typeof(ItemStack), nameof(ItemStack.itemValue))); + codes.RemoveAt(i - 3); + break; + } + } + return codes; + } + } + #endregion +} diff --git a/Harmony/MultiActionReversePatches.cs b/Harmony/MultiActionReversePatches.cs new file mode 100644 index 0000000..622ec55 --- /dev/null +++ b/Harmony/MultiActionReversePatches.cs @@ -0,0 +1,68 @@ +using HarmonyLib; +using KFCommonUtilityLib.Scripts.Utilities; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using UnityEngine; + +[HarmonyPatch] +public static class MultiActionReversePatches +{ + [HarmonyReversePatch(HarmonyReversePatchType.Snapshot)] + [HarmonyPatch(typeof(EffectManager), nameof(EffectManager.GetValue))] + public static float ProjectileGetValue(PassiveEffects _passiveEffect, ItemValue _originalItemValue = null, float _originalValue = 0f, EntityAlive _entity = null, Recipe _recipe = null, FastTags tags = default(FastTags), bool calcEquipment = true, bool calcHoldingItem = true, bool calcProgression = true, bool calcBuffs = true, bool calcChallenges = true, int craftingTier = 1, bool useMods = true, bool _useDurability = false) + { + IEnumerable Transpiler(IEnumerable instructions) + { + if (instructions == null) + return null; + var codes = instructions.ToList(); + + var mtd_modify = AccessTools.Method(typeof(ItemValue), nameof(ItemValue.ModifyValue)); + for (int i = 0; i < codes.Count; i++) + { + var code = codes[i]; + if (code.Calls(mtd_modify) && codes[i - 9].opcode == OpCodes.Ldarg_1) + { + code.operand = AccessTools.Method(typeof(MultiActionProjectileRewrites), nameof(MultiActionProjectileRewrites.ProjectileValueModifyValue)); + } + } + return codes; + } + + _ = Transpiler(null); + return _originalValue; + } + + [HarmonyReversePatch] + [HarmonyPatch(typeof(ProjectileMoveScript), nameof(ProjectileMoveScript.Fire))] + public static void ProjectileFire(this ProjectileMoveScript __instance, Vector3 _idealStartPosition, Vector3 _flyDirection, Entity _firingEntity, int _hmOverride = 0, float _radius = 0f) + { + IEnumerable Transpiler(IEnumerable instructions) + { + if (instructions == null) + return null; + + var codes = instructions.ToList(); + + FieldInfo fld_launcher = AccessTools.Field(typeof(ProjectileMoveScript), nameof(ProjectileMoveScript.itemValueLauncher)); + FieldInfo fld_projectile = AccessTools.Field(typeof(ProjectileMoveScript), nameof(ProjectileMoveScript.itemValueProjectile)); + MethodInfo mtd_getvalue = AccessTools.Method(typeof(EffectManager), nameof(EffectManager.GetValue)); + MethodInfo mtd_getvaluenew = AccessTools.Method(typeof(MultiActionReversePatches), nameof(ProjectileGetValue)); + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].LoadsField(fld_launcher)) + { + codes[i].operand = fld_projectile; + } + else if (codes[i].Calls(mtd_getvalue)) + { + codes[i].operand = mtd_getvaluenew; + } + } + return codes; + } + _ = Transpiler(null); + } +} \ No newline at end of file diff --git a/Harmony/MultiBarrelPatches.cs b/Harmony/MultiBarrelPatches.cs new file mode 100644 index 0000000..792e517 --- /dev/null +++ b/Harmony/MultiBarrelPatches.cs @@ -0,0 +1,155 @@ +using HarmonyLib; +using KFCommonUtilityLib.Scripts.StaticManagers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Emit; +using System.Text; +using System.Threading.Tasks; + +namespace KFCommonUtilityLib.Harmony +{ + [HarmonyPatch] + public class MultiBarrelPatches + { + [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.ExecuteAction))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_ExecuteAction_ItemActionRanged(IEnumerable 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)); + + Label loopStart = generator.DefineLabel(); + Label loopCondi = generator.DefineLabel(); + LocalBuilder lbd_data_module = generator.DeclareLocal(typeof(ActionModuleMultiBarrel.MultiBarrelData)); + LocalBuilder lbd_ismb = generator.DeclareLocal(typeof(bool)); + 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); + Label lbl = generator.DefineLabel(); + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldnull), + new CodeInstruction(OpCodes.Stloc_S, lbd_data_module), + new CodeInstruction(OpCodes.Ldarg_1), + new CodeInstruction(OpCodes.Ldloca_S, lbd_data_module), + CodeInstruction.Call(typeof(MultiBarrelPatches), nameof(IsMultiBarrelData)), + new CodeInstruction(OpCodes.Stloc_S, lbd_ismb), + new CodeInstruction(OpCodes.Ldc_I4_0), + new CodeInstruction(OpCodes.Stloc_S, lbd_i), + new CodeInstruction(OpCodes.Ldloc_S, lbd_ismb), + new CodeInstruction(OpCodes.Brfalse_S, lbl), + 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), + new CodeInstruction(OpCodes.Ldc_I4_1).WithLabels(lbl), + new CodeInstruction(OpCodes.Stloc_S, lbd_rounds), + new CodeInstruction(OpCodes.Br_S, loopCondi), + }); + i += 16; + } + //one round multi shot check + else if (codes[i].Calls(mtd_consume)) + { + Label lbl = generator.DefineLabel(); + codes[i - 2].WithLabels(lbl); + codes.InsertRange(i - 2, new[] + { + new CodeInstruction(OpCodes.Ldloc_S, lbd_ismb), + new CodeInstruction(OpCodes.Brfalse_S, lbl), + 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 += 8; + } + //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_ismb).WithLabels(origin.ExtractLabels()), + new CodeInstruction(OpCodes.Brfalse_S, lbl_pre), + new CodeInstruction(OpCodes.Ldloc_S, lbd_data_module), + 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, lbd_i).WithLabels(lbl_pre), + //new CodeInstruction(OpCodes.Dup), + //new CodeInstruction(OpCodes.Ldloc_S, lbd_rounds), + //CodeInstruction.Call(typeof(MultiBarrelPatches), nameof(MultiBarrelPatches.LogInfo)), + 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_ismb), + new CodeInstruction(OpCodes.Brfalse_S, lbl_post), + 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; + } + + [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 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 dataModule) + { + dataModule.Instance.SetCurrentBarrel(__instance.actionData.invData.itemValue.Meta); + } + } + + private static bool IsMultiBarrelData(ItemActionData data, out ActionModuleMultiBarrel.MultiBarrelData dataModule) + { + return (dataModule = (data as IModuleContainerFor)?.Instance) != null; + } + + private static void LogInfo(int cur, int max) => Log.Out($"max rounds {max}, cur {cur}"); + } +} diff --git a/Harmony/Patches.cs b/Harmony/Patches.cs new file mode 100644 index 0000000..04f086d --- /dev/null +++ b/Harmony/Patches.cs @@ -0,0 +1,1325 @@ +using HarmonyLib; +using KFCommonUtilityLib.Scripts.StaticManagers; +using KFCommonUtilityLib.Scripts.Utilities; +using System; +using System.Collections.Generic; +using UniLinq; +using System.Reflection; +using System.Reflection.Emit; +using System.Xml.Linq; +using UnityEngine; +using KFCommonUtilityLib.Scripts.NetPackages; +using KFCommonUtilityLib; + +[HarmonyPatch] +public static class CommonUtilityPatch +{ + //fix reloading issue and onSelfRangedBurstShot timing + public static void FakeReload(EntityAlive holdingEntity, ItemActionRanged.ItemActionDataRanged _actionData) + { + if (!holdingEntity) + return; + _actionData.isReloading = true; + _actionData.isWeaponReloading = true; + holdingEntity.MinEventContext.ItemActionData = _actionData; + holdingEntity.FireEvent(MinEventTypes.onReloadStart, true); + _actionData.isReloading = false; + _actionData.isWeaponReloading = false; + _actionData.isReloadCancelled = false; + _actionData.isWeaponReloadCancelled = false; + holdingEntity.FireEvent(MinEventTypes.onReloadStop); + + AnimationAmmoUpdateState.SetAmmoCountForEntity(holdingEntity, holdingEntity.inventory.holdingItemIdx); + } + + [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.SwapAmmoType))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_SwapAmmoType_ItemActionRanged(IEnumerable instructions) + { + var codes = new List(instructions); + + for (int i = 0; i < codes.Count; ++i) + { + if (codes[i].opcode == OpCodes.Ret) + { + codes.InsertRange(i, new CodeInstruction[] + { + new CodeInstruction(OpCodes.Ldarg_1), + new CodeInstruction(OpCodes.Ldloc_0), + CodeInstruction.Call(typeof(CommonUtilityPatch), nameof(FakeReload)) + }); + break; + } + } + + return codes; + } + + [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.ExecuteAction))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_ExecuteAction_ItemActionRanged(IEnumerable instructions) + { + var codes = new List(instructions); + var mtd_fire_event = AccessTools.Method(typeof(EntityAlive), nameof(EntityAlive.FireEvent)); + var mtd_get_model_layer = AccessTools.Method(typeof(EntityAlive), nameof(EntityAlive.GetModelLayer)); + var mtd_get_perc_left = AccessTools.PropertyGetter(typeof(ItemValue), nameof(ItemValue.PercentUsesLeft)); + + int take = -1, insert = -1; + for (int i = 0; i < codes.Count; ++i) + { + if (codes[i].opcode == OpCodes.Ldc_I4_S && codes[i].OperandIs((int)MinEventTypes.onSelfRangedBurstShotEnd) && codes[i + 2].Calls(mtd_fire_event)) + take = i - 3; + else if (codes[i].Calls(mtd_get_model_layer)) + insert = i + 2; + } + + if (take < insert) + { + var list = codes.GetRange(take, 6); + codes.InsertRange(insert, list); + codes.RemoveRange(take, 6); + } + + return codes; + } + //fix recoil animation does not match weapon RPM + private static int weaponFireHash = Animator.StringToHash("WeaponFire"); + private static int aimHash = Animator.StringToHash("IsAiming"); + private static HashSet hash_shot_state = new HashSet(); + private static HashSet hash_aimshot_state = new HashSet(); + + public static void InitShotStates() + { + string[] weapons = + { + "fpvAK47", + "fpvMagnum", + "fpvRocketLauncher", + "fpvSawedOffShotgun", + "fpvBlunderbuss", + "fpvCrossbow", + "fpvPistol", + "fpvHuntingRifle", + "fpvSMG", + "fpvSniperRifle", + "M60", + "fpvDoubleBarrelShotgun", + //"fpvJunkTurret", + "fpvTacticalAssaultRifle", + "fpvDesertEagle", + "fpvAutoShotgun", + "fpvSharpShooterRifle", + "fpvPipeMachineGun", + "fpvPipeRifle", + "fpvPipeRevolver", + "fpvPipeShotgun", + "fpvLeverActionRifle", + }; + foreach (string weapon in weapons) + { + hash_shot_state.Add(Animator.StringToHash(weapon + "Fire")); + hash_aimshot_state.Add(Animator.StringToHash(weapon + "AimFire")); + } + } + + [HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.OnFired))] + [HarmonyPostfix] + private static void Postfix_OnFired_EntityPlayerLocal(EntityPlayerLocal __instance) + { + if (!__instance.bFirstPersonView) + return; + + ItemActionRanged.ItemActionDataRanged _rangedData; + if ((_rangedData = __instance.inventory.holdingItemData.actionData[0] as ItemActionRanged.ItemActionDataRanged) == null && (_rangedData = __instance.inventory.holdingItemData.actionData[1] as ItemActionRanged.ItemActionDataRanged) == null) + return; + + if (_rangedData.invData.model.TryGetComponent(out var targets) && targets.ItemFpv) + return; + + var anim = (__instance.emodel.avatarController as AvatarLocalPlayerController).FPSArms.Animator; + if (anim.IsInTransition(0)) + return; + + var curState = anim.GetCurrentAnimatorStateInfo(0); + if (curState.length > _rangedData.Delay) + { + bool aimState = anim.GetBool(aimHash); + short shotState = 0; + if (hash_shot_state.Contains(curState.shortNameHash)) + shotState = 1; + else if (hash_aimshot_state.Contains(curState.shortNameHash)) + shotState = 2; + if (shotState == 0 || (shotState == 1 && aimState) || (shotState == 2 && !aimState)) + { + if (shotState > 0) + anim.ResetTrigger(weaponFireHash); + return; + } + + //current state, layer 0, offset 0 + anim.PlayInFixedTime(0, 0, 0); + anim.ResetTrigger(weaponFireHash); + if (_rangedData.invData.itemValue.Meta == 0) + { + __instance.emodel.avatarController.CancelEvent(weaponFireHash); + Log.Out("Cancel fire event because meta is 0"); + } + } + } + + //[HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.ItemActionEffects))] + //[HarmonyPostfix] + //private static void Postfix_ItemActionEffects_ItemActionRanged(ItemActionData _actionData, int _firingState) + //{ + // if (_firingState == 0 && _actionData.invData.holdingEntity is EntityPlayerLocal && !(_actionData.invData.itemValue.ItemClass.Actions[0] is ItemActionCatapult)) + // { + // _actionData.invData.holdingEntity?.emodel.avatarController.CancelEvent(weaponFireHash); + // //Log.Out("Cancel fire event because firing state is 0\n" + StackTraceUtility.ExtractStackTrace()); + // } + //} + + //[HarmonyPatch(typeof(GameManager), "gmUpdate")] + //[HarmonyTranspiler] + //private static IEnumerable Transpiler_gmUpdate_GameManager(IEnumerable instructions) + //{ + // var codes = new List(instructions); + // var mtd_unload = AccessTools.Method(typeof(Resources), nameof(Resources.UnloadUnusedAssets)); + // var fld_duration = AccessTools.Field(typeof(GameManager), "unloadAssetsDuration"); + + // for (int i = 0; i < codes.Count; ++i) + // { + // if (codes[i].opcode == OpCodes.Call && codes[i].Calls(mtd_unload)) + // { + // for (int j = i; j >= 0; --j) + // { + // if (codes[j].opcode == OpCodes.Ldfld && codes[j].LoadsField(fld_duration) && codes[j + 1].opcode == OpCodes.Ldc_R4) + // codes[j + 1].operand = (float)codes[j + 1].operand / 2; + // } + // break; + // } + // } + + // return codes; + //} + + //internal static void ForceUpdateGC() + //{ + // if (GameManager.IsDedicatedServer) + // return; + // if (GameManager.frameCount % 18000 == 0) + // { + // long rss = GetRSS.GetCurrentRSS(); + // if (rss / 1024 / 1024 > 6144) + // { + // Log.Out("Memory usage exceeds threshold, now performing garbage collection..."); + // GC.Collect(); + // } + // } + //} + + //altmode workarounds + //deprecated by action module + private static void ParseAltRequirements(XElement _node) + { + string itemName = _node.GetAttribute("name"); + if (string.IsNullOrEmpty(itemName)) + { + return; + } + ItemClass item = ItemClass.GetItemClass(itemName); + for (int i = 0; i < item.Actions.Length; i++) + { + if (item.Actions[i] is ItemActionAltMode _alt) + _alt.ParseAltRequirements(_node, i); + } + } + + [HarmonyPatch(typeof(ItemClassesFromXml), nameof(ItemClassesFromXml.parseItem))] + [HarmonyPostfix] + private static void Postfix_parseItem_ItemClassesFromXml(XElement _node) + { + ParseAltRequirements(_node); + } + + [HarmonyPatch(typeof(ItemClass), nameof(ItemClass.ExecuteAction))] + [HarmonyPrefix] + private static bool Prefix_ExecuteAction_ItemClass(ItemClass __instance, int _actionIdx, ItemInventoryData _data, bool _bReleased) + { + if (!_bReleased && __instance.Actions[_actionIdx] is ItemActionAltMode _alt) + _alt.SetAltRequirement(_data.actionData[_actionIdx]); + + return true; + } + + [HarmonyPatch(typeof(DynamicProperties), nameof(DynamicProperties.Parse))] + [HarmonyPrefix] + private static bool Prefix_Parse_DynamicProperties(XElement elementProperty) + { + if (elementProperty.Name.LocalName != "property") + return false; + return true; + } + + //MinEventParams workarounds + [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.fireShot))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_fireShot_ItemActionRanged(IEnumerable instructions) + { + var codes = new List(instructions); + + var fld_ranged_tag = AccessTools.Field(typeof(ItemActionAttack), nameof(ItemActionAttack.RangedTag)); + var fld_params = AccessTools.Field(typeof(EntityAlive), nameof(EntityAlive.MinEventContext)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].LoadsField(fld_ranged_tag)) + { + if (!codes[i + 3].LoadsField(fld_params)) + { + codes.InsertRange(i + 2, new CodeInstruction[] + { + new CodeInstruction(OpCodes.Ldloc_1), + CodeInstruction.LoadField(typeof(EntityAlive), nameof(EntityAlive.MinEventContext)), + new CodeInstruction(OpCodes.Dup), + new CodeInstruction(OpCodes.Ldloc, 10), + CodeInstruction.LoadField(typeof(WorldRayHitInfo), nameof(WorldRayHitInfo.hit)), + CodeInstruction.LoadField(typeof(HitInfoDetails), nameof(HitInfoDetails.pos)), + CodeInstruction.StoreField(typeof(MinEventParams), nameof(MinEventParams.Position)), + new CodeInstruction(OpCodes.Ldloc_1), + CodeInstruction.Call(typeof(EntityAlive), nameof(EntityAlive.GetPosition)), + CodeInstruction.StoreField(typeof(MinEventParams), nameof(MinEventParams.StartPosition)) + }); + } + break; + } + } + + return codes; + } + + [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.OnHoldingUpdate))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_OnHoldingUpdate_ItemActionRanged(IEnumerable instructions) + { + var mtd_release = AccessTools.Method(typeof(ItemActionRanged), nameof(ItemActionRanged.triggerReleased)); + var codes = instructions.ToList(); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_release)) + { + codes[i + 1].labels.Clear(); + codes[i + 1].MoveLabelsFrom(codes[i - 20]); + codes.RemoveRange(i - 20, 21); + break; + } + } + + return codes; + } + + [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.triggerReleased))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_triggerReleased_ItemActionRanged(IEnumerable instructions) + { + var mtd_effect = AccessTools.Method(typeof(IGameManager), nameof(IGameManager.ItemActionEffectsServer)); + var mtd_data = AccessTools.Method(typeof(ItemActionRanged), nameof(ItemActionRanged.getUserData)); + var codes = instructions.ToList(); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_effect)) + { + codes.InsertRange(i, new CodeInstruction[] + { + new CodeInstruction(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Ldarg_1), + new CodeInstruction(OpCodes.Callvirt, mtd_data) + }); + codes.RemoveAt(i - 1); + break; + } + } + + return codes; + } + + [HarmonyPatch(typeof(GameManager), nameof(GameManager.StartGame))] + [HarmonyPrefix] + private static bool Prefix_StartGame_GameManager() + { + CustomEffectEnumManager.InitFinal(); + return true; + } + + [HarmonyPatch(typeof(PassiveEffect), nameof(PassiveEffect.ParsePassiveEffect))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_ParsePassiveEffect_PassiveEffect(IEnumerable instructions) + { + var codes = instructions.ToList(); + MethodInfo mtd_enum_parse = AccessTools.Method(typeof(EnumUtils), nameof(EnumUtils.Parse), new[] { typeof(string), typeof(bool) }, new[] { typeof(PassiveEffects) }); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_enum_parse)) + { + codes.Insert(i + 1, CodeInstruction.Call(typeof(CustomEffectEnumManager), nameof(CustomEffectEnumManager.RegisterOrGetEnum), new[] { typeof(string), typeof(bool) }, new[] { typeof(PassiveEffects) })); + codes.RemoveAt(i); + break; + } + } + + return codes; + } + + [HarmonyPatch(typeof(MinEventActionBase), nameof(MinEventActionBase.ParseXmlAttribute))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_ParseXmlAttribute_MinEventActionBase(IEnumerable instructions) + { + var codes = instructions.ToList(); + MethodInfo mtd_enum_parse = AccessTools.Method(typeof(EnumUtils), nameof(EnumUtils.Parse), new[] { typeof(string), typeof(bool) }, new[] { typeof(MinEventTypes) }); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_enum_parse)) + { + codes.Insert(i + 1, CodeInstruction.Call(typeof(CustomEffectEnumManager), nameof(CustomEffectEnumManager.RegisterOrGetEnum), new[] { typeof(string), typeof(bool) }, new[] { typeof(MinEventTypes) })); + codes.RemoveAt(i); + break; + } + } + + return codes; + } + + [HarmonyPatch(typeof(ItemActionDynamicMelee), nameof(ItemActionDynamicMelee.OnHoldingUpdate))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_OnHoldingUpdate_ItemActionDynamicMelee(IEnumerable instructions) + { + var codes = instructions.ToList(); + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Is(OpCodes.Ldc_R4, 0.1f)) + { + codes.RemoveRange(i, 2); + break; + } + } + return codes; + } + + [HarmonyPatch(typeof(ItemActionDynamicMelee), nameof(ItemActionDynamicMelee.canStartAttack))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_canStartAttack_ItemActionDynamicMelee(IEnumerable instructions) + { + var codes = instructions.ToList(); + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Is(OpCodes.Ldc_R4, 0.1f)) + { + codes.RemoveRange(i, 2); + break; + } + } + return codes; + } + + /// + /// projectile direct hit damage percent + /// removed due to new explosion damage passives + /// + /// + /// + //[HarmonyPatch(typeof(ProjectileMoveScript), nameof(ProjectileMoveScript.checkCollision))] + //[HarmonyTranspiler] + //private static IEnumerable Transpiler_checkCollision_ProjectileMoveScript(IEnumerable instructions) + //{ + // var codes = instructions.ToList(); + // var fld_strain = AccessTools.Field(typeof(ItemActionLauncher.ItemActionDataLauncher), nameof(ItemActionLauncher.ItemActionDataLauncher.strainPercent)); + // var mtd_block = AccessTools.Method(typeof(ItemActionAttack), nameof(ItemActionAttack.GetDamageBlock)); + + // for (int i = 0; i < codes.Count; i++) + // { + // if (codes[i].LoadsField(fld_strain)) + // { + // codes.InsertRange(i + 1, new CodeInstruction[] + // { + // new CodeInstruction(OpCodes.Ldarg_0), + // CodeInstruction.LoadField(typeof(ProjectileMoveScript), nameof(ProjectileMoveScript.itemValueProjectile)), + // new CodeInstruction(OpCodes.Ldloc_S, 4), + // CodeInstruction.Call(typeof(CommonUtilityPatch), codes[i - 3].Calls(mtd_block) ? nameof(GetProjectileBlockDamagePerc) : nameof(GetProjectileEntityDamagePerc)), + // new CodeInstruction(OpCodes.Mul) + // }); + // } + // } + + // return codes; + //} + + //public static float GetProjectileBlockDamagePerc(ItemValue _itemValue, EntityAlive _holdingEntity) + //{ + // return EffectManager.GetValue(CustomEnums.ProjectileImpactDamagePercentBlock, _itemValue, 1, _holdingEntity, null); + //} + + //public static float GetProjectileEntityDamagePerc(ItemValue _itemValue, EntityAlive _holdingEntity) + //{ + // return EffectManager.GetValue(CustomEnums.ProjectileImpactDamagePercentEntity, _itemValue, 1, _holdingEntity, null); + //} + + /// + /// force tpv crosshair + /// + /// + /// + [HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.guiDrawCrosshair))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_guiDrawCrosshair_EntityPlayerLocal(IEnumerable instructions) + { + var codes = instructions.ToList(); + + FieldInfo fld_debug = AccessTools.Field(typeof(ItemAction), nameof(ItemAction.ShowDistanceDebugInfo)); + + for ( int i = 0; i < codes.Count; i++ ) + { + if (codes[i].LoadsField(fld_debug)) + { + var label = codes[i - 1].operand; + codes.InsertRange(i, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.bFirstPersonView)), + new CodeInstruction(OpCodes.Brfalse_S, label) + }); + break; + } + } + + return codes; + } + + /// + /// correctly apply muzzle flash silence with modifications + /// + /// + [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.OnModificationsChanged))] + [HarmonyPostfix] + private static void Postfix_OnModificationsChanged_ItemActionRanged(ItemActionData _data) + { + ItemActionRanged.ItemActionDataRanged itemActionDataRanged = _data as ItemActionRanged.ItemActionDataRanged; + if (itemActionDataRanged.SoundStart.Contains("silenced")) + { + itemActionDataRanged.IsFlashSuppressed = true; + } + + //should fix stuck on switching item? + itemActionDataRanged.isReloadCancelled = false; + itemActionDataRanged.isWeaponReloadCancelled = false; + itemActionDataRanged.isReloading = false; + itemActionDataRanged.isWeaponReloading = false; + itemActionDataRanged.isChangingAmmoType = false; + } + + #region item tags modifier + + /// + /// should handle swapping mod + /// first check if the mod to install can be installed after current mod is removed + /// then check if any other mod requires or conflicts current mod + /// + /// + /// + /// + [HarmonyPatch(typeof(XUiC_ItemPartStack), nameof(XUiC_ItemPartStack.CanSwap))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_CanSwap_XUiC_ItemPartStack(IEnumerable instructions, ILGenerator generator) + { + var codes = instructions.ToList(); + + LocalBuilder lbd_tags_if_remove_prev = generator.DeclareLocal(typeof(FastTags)); + LocalBuilder lbd_tags_if_install_new = generator.DeclareLocal(typeof(FastTags)); + MethodInfo mtd_get_item_class = AccessTools.PropertyGetter(typeof(ItemValue), nameof(ItemValue.ItemClass)); + MethodInfo mtd_has_any_tags = AccessTools.Method(typeof(ItemClass), nameof(ItemClass.HasAnyTags)); + MethodInfo mtd_test_any_set = AccessTools.Method(typeof(FastTags), nameof(FastTags.Test_AnySet)); + FieldInfo fld_mod = AccessTools.Field(typeof(ItemValue), nameof(ItemValue.Modifications)); + FieldInfo fld_installable_tags = AccessTools.Field(typeof(ItemClassModifier), nameof(ItemClassModifier.InstallableTags)); + + for (int i = 3; i < codes.Count; i++) + { + //get current tags + if (codes[i].opcode == OpCodes.Stloc_2) + { + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldloc_1), + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(XUiC_ItemPartStack), "itemValue"), + CodeInstruction.Call(typeof(LocalItemTagsManager), nameof(LocalItemTagsManager.GetTagsAsIfNotInstalled)), + new CodeInstruction(OpCodes.Stloc_S, lbd_tags_if_remove_prev), + new CodeInstruction(OpCodes.Ldloc_1), + new CodeInstruction(OpCodes.Ldloc_0), + CodeInstruction.Call(typeof(LocalItemTagsManager), nameof(LocalItemTagsManager.GetTagsAsIfInstalled)), + new CodeInstruction(OpCodes.Stloc_S, lbd_tags_if_install_new) + }); + i += 10; + Log.Out("mod 1!!!"); + } + //replace checking tags + else if (codes[i].Calls(mtd_has_any_tags) && codes[i - 3].opcode == OpCodes.Ldloc_2) + { + if (codes[i - 1].LoadsField(fld_installable_tags) && (codes[i + 1].opcode == OpCodes.Brtrue || codes[i + 1].opcode == OpCodes.Brtrue_S)) + { + var lbl_prev = codes[i + 4].ExtractLabels(); + var lbl_jump = generator.DefineLabel(); + codes[i + 4].WithLabels(lbl_jump); + codes.InsertRange(i + 4, new[] + { + new CodeInstruction(OpCodes.Ldloc_1).WithLabels(lbl_prev), + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(XUiC_ItemPartStack), "itemValue"), + new CodeInstruction(OpCodes.Ldloc_0), + CodeInstruction.Call(typeof(LocalItemTagsManager), nameof(LocalItemTagsManager.CanSwapMod)), + new CodeInstruction(OpCodes.Brtrue, lbl_jump), + new CodeInstruction(OpCodes.Ldc_I4_0), + new CodeInstruction(OpCodes.Ret) + }); + } + codes[i - 3].opcode = OpCodes.Ldloca_S; + codes[i - 3].operand = lbd_tags_if_remove_prev; + codes[i].opcode = OpCodes.Call; + codes[i].operand = mtd_test_any_set; + Log.Out("mod 2!!!"); + } + } + + return codes; + } + + [HarmonyPatch(typeof(XUiC_ItemCosmeticStack), nameof(XUiC_ItemCosmeticStack.CanSwap))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_CanSwap_XUiC_ItemCosmeticStack(IEnumerable instructions, ILGenerator generator) + { + var codes = instructions.ToList(); + + LocalBuilder lbd_tags_if_remove_prev = generator.DeclareLocal(typeof(FastTags)); + LocalBuilder lbd_tags_if_install_new = generator.DeclareLocal(typeof(FastTags)); + LocalBuilder lbd_item_being_assembled = generator.DeclareLocal(typeof(ItemValue)); + MethodInfo mtd_get_item_class = AccessTools.PropertyGetter(typeof(ItemValue), nameof(ItemValue.ItemClass)); + MethodInfo mtd_has_any_tags = AccessTools.Method(typeof(ItemClass), nameof(ItemClass.HasAnyTags)); + MethodInfo mtd_test_any_set = AccessTools.Method(typeof(FastTags), nameof(FastTags.Test_AnySet)); + MethodInfo mtd_get_xui = AccessTools.PropertyGetter(typeof(XUiController), nameof(XUiController.xui)); + MethodInfo mtd_get_cur_item = AccessTools.PropertyGetter(typeof(XUiM_AssembleItem), nameof(XUiM_AssembleItem.CurrentItem)); + FieldInfo fld_cos = AccessTools.Field(typeof(ItemValue), nameof(ItemValue.CosmeticMods)); + FieldInfo fld_installable_tags = AccessTools.Field(typeof(ItemClassModifier), nameof(ItemClassModifier.InstallableTags)); + + for (int i = 3; i < codes.Count; i++) + { + //get current tags + if ((codes[i].opcode == OpCodes.Brtrue || codes[i].opcode == OpCodes.Brtrue_S) && codes[i - 1].opcode == OpCodes.Ldloc_0) + { + codes.InsertRange(i + 3, new[] + { + new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(codes[i + 3]), + new CodeInstruction(OpCodes.Call, mtd_get_xui), + CodeInstruction.LoadField(typeof(XUi), nameof(XUi.AssembleItem)), + new CodeInstruction(OpCodes.Callvirt, mtd_get_cur_item), + CodeInstruction.LoadField(typeof(ItemStack), nameof(ItemStack.itemValue)), + new CodeInstruction(OpCodes.Stloc_S, lbd_item_being_assembled), + new CodeInstruction(OpCodes.Ldloc_S, lbd_item_being_assembled), + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(XUiC_ItemCosmeticStack), "itemValue"), + CodeInstruction.Call(typeof(LocalItemTagsManager), nameof(LocalItemTagsManager.GetTagsAsIfNotInstalled)), + new CodeInstruction(OpCodes.Stloc_S, lbd_tags_if_remove_prev), + new CodeInstruction(OpCodes.Ldloc_S, lbd_item_being_assembled), + new CodeInstruction(OpCodes.Ldloc_0), + CodeInstruction.Call(typeof(LocalItemTagsManager), nameof(LocalItemTagsManager.GetTagsAsIfInstalled)), + new CodeInstruction(OpCodes.Stloc_S, lbd_tags_if_install_new) + }); + i += 18; + Log.Out("cos 1!!!"); + } + //replace checking tags + else if (codes[i].Calls(mtd_has_any_tags) && codes[i - 3].Calls(mtd_get_item_class)) + { + if (codes[i - 1].LoadsField(fld_installable_tags) && (codes[i + 1].opcode == OpCodes.Brtrue || codes[i + 1].opcode == OpCodes.Brtrue_S)) + { + var lbl_prev = codes[i + 4].ExtractLabels(); + var lbl_jump = generator.DefineLabel(); + codes[i + 4].WithLabels(lbl_jump); + codes.InsertRange(i + 4, new[] + { + new CodeInstruction(OpCodes.Ldloc_S, lbd_item_being_assembled).WithLabels(lbl_prev), + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(XUiC_ItemPartStack), "itemValue"), + new CodeInstruction(OpCodes.Ldloc_0), + CodeInstruction.Call(typeof(LocalItemTagsManager), nameof(LocalItemTagsManager.CanSwapMod)), + new CodeInstruction(OpCodes.Brtrue, lbl_jump), + new CodeInstruction(OpCodes.Ldc_I4_0), + new CodeInstruction(OpCodes.Ret) + }); + } + codes[i - 8].MoveLabelsTo(codes[i - 3]); + codes[i - 3].opcode = OpCodes.Ldloca_S; + codes[i - 3].operand = lbd_tags_if_remove_prev; + codes[i].opcode = OpCodes.Call; + codes[i].operand = mtd_test_any_set; + codes.RemoveRange(i - 8, 5); + i -= 5; + Log.Out("cos 2!!!"); + } + } + + return codes; + } + + /// + /// check if other mods relies on this one + /// + /// + /// + /// + [HarmonyPatch(typeof(XUiC_ItemPartStack), "CanRemove")] + [HarmonyPostfix] + private static void Postfix_CanRemove_XUiC_ItemPartStack(ref bool __result, XUiC_ItemPartStack __instance) + { + if (__result && __instance.xui?.AssembleItem?.CurrentItem?.itemValue is ItemValue itemValue) + { + ItemClass itemClass = itemValue.ItemClass; + FastTags tagsAfterRemove = LocalItemTagsManager.GetTagsAsIfNotInstalled(itemValue, __instance.itemValue); + if (tagsAfterRemove.IsEmpty) + { + __result = false; + return; + } + + foreach (var mod in itemValue.Modifications) + { + if (mod.IsEmpty()) + continue; + ItemClassModifier modClass = mod.ItemClass as ItemClassModifier; + if (modClass == null || !tagsAfterRemove.Test_AnySet(modClass.InstallableTags) || tagsAfterRemove.Test_AnySet(modClass.DisallowedTags)) + { + __result = false; + return; + } + } + } + } + + [HarmonyPatch(typeof(XUiC_ItemCosmeticStack), "CanRemove")] + [HarmonyPostfix] + private static void Postfix_CanRemove_XUiC_ItemCosmeticStack(ref bool __result, XUiC_ItemCosmeticStack __instance) + { + if (__result && __instance.xui?.AssembleItem?.CurrentItem?.itemValue is ItemValue itemValue) + { + ItemClass itemClass = itemValue.ItemClass; + FastTags tagsAfterRemove = LocalItemTagsManager.GetTagsAsIfNotInstalled(itemValue, __instance.itemValue); + if (tagsAfterRemove.IsEmpty) + { + __result = false; + return; + } + + foreach (var mod in itemValue.CosmeticMods) + { + if (mod.IsEmpty()) + continue; + ItemClassModifier modClass = mod.ItemClass as ItemClassModifier; + if (modClass == null || !tagsAfterRemove.Test_AnySet(modClass.InstallableTags) || tagsAfterRemove.Test_AnySet(modClass.DisallowedTags)) + { + __result = false; + return; + } + } + } + } + + /// + /// should update the gear icon? + /// + /// + /// + /// + [HarmonyPatch(typeof(XUiC_ItemStack), nameof(XUiC_ItemStack.updateLockTypeIcon))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_updateLockTypeIcon_XUiC_ItemStack(IEnumerable instructions, ILGenerator generator) + { + var codes = instructions.ToList(); + + LocalBuilder lbd_tags = generator.DeclareLocal(typeof(FastTags)); + MethodInfo mtd_has_any_tags = AccessTools.Method(typeof(ItemClass), nameof(ItemClass.HasAnyTags)); + MethodInfo mtd_test_any_set = AccessTools.Method(typeof(FastTags), nameof(FastTags.Test_AnySet)); + MethodInfo mtd_get_item_class = AccessTools.PropertyGetter(typeof(ItemValue), nameof(ItemValue.ItemClass)); + MethodInfo mtd_get_cur_item = AccessTools.PropertyGetter(typeof(XUiM_AssembleItem), nameof(XUiM_AssembleItem.CurrentItem)); + MethodInfo mtd_get_xui = AccessTools.PropertyGetter(typeof(XUiController), nameof(XUiController.xui)); + + for (int i = 3; i < codes.Count; i++) + { + //get current tags + if ((codes[i].opcode == OpCodes.Brfalse_S || codes[i].opcode == OpCodes.Brfalse) && codes[i - 1].Calls(mtd_get_cur_item)) + { + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Call, mtd_get_xui), + CodeInstruction.LoadField(typeof(XUi), nameof(XUi.AssembleItem)), + new CodeInstruction(OpCodes.Callvirt, mtd_get_cur_item), + CodeInstruction.LoadField(typeof(ItemStack), nameof(ItemStack.itemValue)), + CodeInstruction.Call(typeof(LocalItemTagsManager), nameof(LocalItemTagsManager.GetTags)), + new CodeInstruction(OpCodes.Stloc_S, lbd_tags) + }); + i += 7; + } + //do not touch check on the modification item + else if (codes[i].Calls(mtd_has_any_tags) && codes[i - 3].Calls(mtd_get_item_class)) + { + codes[i].opcode = OpCodes.Call; + codes[i].operand = mtd_test_any_set; + var insert = new CodeInstruction(OpCodes.Ldloca_S, lbd_tags); + codes[i - 8].MoveLabelsTo(insert); + codes.RemoveRange(i - 8, 6); + codes.Insert(i - 8, insert); + i -= 5; + } + } + + return codes; + } + + /// + /// when installing new mod, use modified tags to check for compatibility + /// + /// + /// + /// + [HarmonyPatch(typeof(XUiM_AssembleItem), nameof(XUiM_AssembleItem.AddPartToItem))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_AddPartToItem_XUiM_AssembleItem(IEnumerable instructions, ILGenerator generator) + { + var codes = instructions.ToList(); + + LocalBuilder lbd_tags_cur = generator.DeclareLocal(typeof(FastTags)); + LocalBuilder lbd_tags_after_install = generator.DeclareLocal(typeof(FastTags)); + MethodInfo mtd_has_any_tags = AccessTools.Method(typeof(ItemClass), nameof(ItemClass.HasAnyTags)); + MethodInfo mtd_test_any_set = AccessTools.Method(typeof(FastTags), nameof(FastTags.Test_AnySet)); + MethodInfo mtd_get_item_class = AccessTools.PropertyGetter(typeof(ItemValue), nameof(ItemValue.ItemClass)); + MethodInfo mtd_get_cur_item = AccessTools.PropertyGetter(typeof(XUiM_AssembleItem), nameof(XUiM_AssembleItem.CurrentItem)); + MethodInfo mtd_is_empty = AccessTools.Method(typeof(ItemValue), nameof(ItemValue.IsEmpty)); + FieldInfo fld_cos = AccessTools.Field(typeof(ItemValue), nameof(ItemValue.CosmeticMods)); + FieldInfo fld_mod = AccessTools.Field(typeof(ItemValue), nameof(ItemValue.Modifications)); + FieldInfo fld_installable_tags = AccessTools.Field(typeof(ItemClassModifier), nameof(ItemClassModifier.InstallableTags)); + + for (int i = 3; i < codes.Count; i++) + { + //get current tags + if (codes[i].opcode == OpCodes.Stloc_0) + { + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Call, mtd_get_cur_item), + CodeInstruction.LoadField(typeof(ItemStack), nameof(ItemStack.itemValue)), + new CodeInstruction(OpCodes.Dup), + CodeInstruction.Call(typeof(LocalItemTagsManager), nameof(LocalItemTagsManager.GetTags)), + new CodeInstruction(OpCodes.Stloc_S, lbd_tags_cur), + new CodeInstruction(OpCodes.Ldloc_0), + CodeInstruction.Call(typeof(LocalItemTagsManager), nameof(LocalItemTagsManager.GetTagsAsIfInstalled)), + new CodeInstruction(OpCodes.Stloc_S, lbd_tags_after_install) + }); + i += 9; + } + //do not touch check on the modification item, check if current mod can be installed + else if (codes[i].Calls(mtd_has_any_tags) && codes[i - 3].Calls(mtd_get_item_class)) + { + if (codes[i - 1].LoadsField(fld_installable_tags)) + { + codes.InsertRange(i + 2, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Call, mtd_get_cur_item), + CodeInstruction.LoadField(typeof(ItemStack), nameof(ItemStack.itemValue)), + new CodeInstruction(OpCodes.Ldloc_0), + CodeInstruction.Call(typeof(LocalItemTagsManager), nameof(LocalItemTagsManager.CanInstallMod)), + new CodeInstruction(OpCodes.Brfalse, codes[i + 1].operand) + }); + } + codes[i].opcode = OpCodes.Call; + codes[i].operand = mtd_test_any_set; + var insert = new CodeInstruction(OpCodes.Ldloca_S, lbd_tags_cur); + codes[i - 6].MoveLabelsTo(insert); + codes.RemoveRange(i - 6, 4); + codes.Insert(i - 6, insert); + i -= 3; + } + } + + return codes; + } + + #endregion + + //change when aiming events are fired + [HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.SetMoveState))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_SetMoveState_EntityPlayerLocal(IEnumerable instructions) + { + var codes = instructions.ToList(); + + FieldInfo fld_msa = AccessTools.Field(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.moveStateAiming)); + + for (int i = 0; i < codes.Count - 2; i++) + { + if (codes[i].LoadsField(fld_msa) && codes[i + 2].opcode == OpCodes.Ldloc_1) + { + codes[i - 2].MoveLabelsTo(codes[i + 13]); + codes.RemoveRange(i - 2, 15); + break; + } + } + + return codes; + } + + [HarmonyPatch(typeof(EntityAlive), nameof(EntityAlive.AimingGun), MethodType.Setter)] + [HarmonyPrefix] + private static bool Prefix_AimingGun_EntityAlive(bool value, EntityAlive __instance) + { + if (__instance is EntityPlayerLocal && __instance.inventory != null) + { + bool isAimingGun = __instance.AimingGun; + if (value != isAimingGun) + { + __instance.FireEvent(value ? MinEventTypes.onSelfAimingGunStart : MinEventTypes.onSelfAimingGunStop, true); +#if DEBUG + Log.Out(value ? "START AIMING GUN FIRED" : "STOP AIMING GUN FIRED"); +#endif + } + } + return true; + } + + [HarmonyPatch(typeof(ItemActionAttack), nameof(ItemActionAttack.HasRadial))] + [HarmonyPostfix] + private static void Postfix_HasRadial_ItemActionAttack(ref bool __result) + { + EntityPlayerLocal player = GameManager.Instance.World?.GetPrimaryPlayer(); + int index = MultiActionManager.GetActionIndexForEntity(player); + List actionDatas = player.inventory?.holdingItemData?.actionData; + if (actionDatas != null && actionDatas.Count > index && actionDatas[index] is ItemActionRanged.ItemActionDataRanged rangedData && (rangedData.isReloading || rangedData.isWeaponReloading)) + { + __result = false; + } + } + + [HarmonyPatch(typeof(ItemActionAttack), nameof(ItemActionAttack.SetupRadial))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_SetupRadial_ItemActionAttack(IEnumerable instructions, ILGenerator generator) + { + var codes = instructions.ToList(); + + var fld_usable = AccessTools.Field(typeof(ItemClass), nameof(ItemClass.UsableUnderwater)); + + var lbd_states = generator.DeclareLocal(typeof(bool[])); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].opcode == OpCodes.Stloc_0) + { + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldloc_0), + new CodeInstruction(OpCodes.Ldarg_2), + new CodeInstruction(OpCodes.Ldc_I4_M1), + CodeInstruction.Call(typeof(CommonUtilityPatch), nameof(CommonUtilityPatch.GetUnusableItemEntries)), + new CodeInstruction(OpCodes.Stloc_S, lbd_states) + }); + i += 4; + } + else if (codes[i].LoadsField(fld_usable)) + { + codes.InsertRange(i + 2, new[] + { + new CodeInstruction(OpCodes.Ldloc_S, lbd_states).WithLabels(codes[i + 2].ExtractLabels()), + new CodeInstruction(OpCodes.Ldloc_2), + CodeInstruction.Call(typeof(CommonUtilityPatch), nameof(CommonUtilityPatch.IsAmmoDisabled)), + new CodeInstruction(OpCodes.Brtrue_S, codes[i + 1].operand) + }); + break; + } + } + + return codes; + } + + [HarmonyPatch(typeof(ItemAction), nameof(ItemAction.StartHolding))] + [HarmonyPostfix] + private static void Postfix_StartHolding_ItemAction(ItemActionData _data, ItemAction __instance) + { + if (__instance is ItemActionAttack itemActionAttack && _data.invData.holdingEntity is EntityPlayerLocal player) + { + var arr_disabled_ammo = GetUnusableItemEntries(itemActionAttack.MagazineItemNames, player, _data.indexInEntityOfAction); + if (arr_disabled_ammo == null) + { + return; + } + var itemValue = _data.invData.itemValue; + int cur_index = itemValue.GetSelectedAmmoIndexByActionIndex(_data.indexInEntityOfAction); + if (arr_disabled_ammo[cur_index]) + { + int first_enabled_index = Mathf.Max(Array.IndexOf(arr_disabled_ammo, false), 0); + + var mapping = MultiActionManager.GetMappingForEntity(player.entityId); + if (mapping != null) + { + if (_data.indexInEntityOfAction == mapping.CurMetaIndex) + { + itemValue.SelectedAmmoTypeIndex = (byte)first_enabled_index; + } + else + { + itemValue.SetMetadata(MultiActionUtils.ActionSelectedAmmoNames[mapping.indices.GetMetaIndexForActionIndex(_data.indexInEntityOfAction)], first_enabled_index, TypedMetadataValue.TypeTag.Integer); + } + _data.invData.holdingEntity.inventory.CallOnToolbeltChangedInternal(); + } + else + { + itemValue.SelectedAmmoTypeIndex = (byte)(first_enabled_index); + } + } + } + } + + public static bool[] GetUnusableItemEntries(string[] ammoNames, EntityPlayerLocal player, int actionIndex = -1) + { + if (ammoNames == null) + { + return null; + } + if (actionIndex < 0) + { + actionIndex = MultiActionManager.GetActionIndexForEntity(player); + } + string str_disabled_ammo_names = player.inventory.holdingItemItemValue.GetPropertyOverrideForAction("DisableAmmo", "", actionIndex); + //Log.Out($"checking disabled ammo: {str_disabled_ammo_names}\n{StackTraceUtility.ExtractStackTrace()}"); + bool[] arr_disable_states = new bool[ammoNames.Length]; + if(!string.IsNullOrEmpty(str_disabled_ammo_names)) + { + string[] arr_disabled_ammo_names = str_disabled_ammo_names.Split(',', StringSplitOptions.RemoveEmptyEntries); + foreach (var name in arr_disabled_ammo_names) + { + int index = Array.IndexOf(ammoNames, name.Trim()); + if (index >= 0) + { + arr_disable_states[index] = true; + if (ConsoleCmdReloadLog.LogInfo) + Log.Out($"ammo {ammoNames[index]} is disabled"); + } + } + } + return arr_disable_states; + } + + private static bool IsAmmoDisabled(bool[] ammoStates, int index) + { + if (ammoStates == null || ammoStates.Length <= index) + { + return false; + } + return ammoStates[index]; + } + + //dont spread onSelfItemActivate/onSelfItemDeactivate to attachments + //handle start holding + [HarmonyPatch(typeof(Inventory), nameof(Inventory.syncHeldItem))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_syncHeldItem_Inventory(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var prop_itemvalue = AccessTools.PropertyGetter(typeof(Inventory), nameof(Inventory.holdingItemItemValue)); + var mtd_fireevent = AccessTools.Method(typeof(ItemValue), nameof(ItemValue.FireEvent)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_fireevent) && codes[i - 5].Calls(prop_itemvalue)) + { + codes[i] = CodeInstruction.Call(typeof(MinEffectController), nameof(MinEffectController.FireEvent)); + codes.InsertRange(i - 4, new[] + { + CodeInstruction.Call(typeof(ItemValue), "get_ItemClass"), + CodeInstruction.LoadField(typeof(ItemClass), nameof(ItemClass.Effects)) + }); + i += 2; + } + } + + return codes; + } + + //handle radial activation + [HarmonyPatch(typeof(XUiC_Radial), nameof(XUiC_Radial.handleActivatableItemCommand))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_handleActivatableItemCommand_XUiC_Radial(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var mtd_fireevent = AccessTools.Method(typeof(ItemValue), nameof(ItemValue.FireEvent)); + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_fireevent)) + { + codes[i] = CodeInstruction.Call(typeof(MinEffectController), nameof(MinEffectController.FireEvent)); + codes.InsertRange(i - 2, new[] + { + CodeInstruction.Call(typeof(ItemValue), "get_ItemClass"), + CodeInstruction.LoadField(typeof(ItemClass), nameof(ItemClass.Effects)) + }); + i += 2; + } + } + return codes; + } + + //handle equipments + [HarmonyPatch(typeof(Equipment), nameof(Equipment.SetSlotItem))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_SetSlotItem_Equipment(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var mtd_fireevent = AccessTools.Method(typeof(ItemValue), nameof(ItemValue.FireEvent)); + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_fireevent) && codes[i - 5].opcode == OpCodes.Ldloc_0 && codes[i - 4].OperandIs((int)MinEventTypes.onSelfItemDeactivate)) + { + codes[i] = CodeInstruction.Call(typeof(MinEffectController), nameof(MinEffectController.FireEvent)); + codes.InsertRange(i - 4, new[] + { + CodeInstruction.Call(typeof(ItemValue), "get_ItemClass"), + CodeInstruction.LoadField(typeof(ItemClass), nameof(ItemClass.Effects)) + }); + i += 2; + } + } + return codes; + } + + [HarmonyPatch(typeof(GameManager), nameof(GameManager.DropContentOfLootContainerServer))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_DropContentOfLootContainerServer_GameManager(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var setter_localscale = AccessTools.PropertySetter(typeof(Transform), nameof(Transform.localScale)); + + for (var i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(setter_localscale)) + { + codes.RemoveRange(i - 6, 7); + break; + } + } + + return codes; + } + + [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.onHoldingEntityFired))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_onHoldingEntityFired_ItemActionRanged(IEnumerable instructions) + { + var codes = instructions.ToList(); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].opcode == OpCodes.Ldc_R4 && codes[i].operand is 5f) + { + codes.RemoveAt(i); + codes.InsertRange(i, new[] + { + new CodeInstruction(OpCodes.Ldarg_1), + CodeInstruction.Call(typeof(CommonUtilityPatch), nameof(CommonUtilityPatch.GetMaxSpread)) + }); + break; + } + } + return codes; + } + + private static float GetMaxSpread(ItemActionData _data) + { + return EffectManager.GetValue(CustomEnums.MaxWeaponSpread, _data.invData.itemValue, 5f, _data.invData.holdingEntity); + } + + [HarmonyPatch(typeof(NetEntityDistributionEntry), nameof(NetEntityDistributionEntry.getSpawnPacket))] + [HarmonyPrefix] + private static bool Prefix_getSpawnPacket_NetEntityDistributionEntry(NetEntityDistributionEntry __instance, ref NetPackage __result) + { + if (__instance.trackedEntity is EntityAlive ea) + { + __result = NetPackageManager.GetPackage().Setup(new EntityCreationData(__instance.trackedEntity, true), (EntityAlive)__instance.trackedEntity); + return false; + } + return true; + } + + [HarmonyPatch(typeof(World), nameof(World.SpawnEntityInWorld))] + [HarmonyPostfix] + private static void Postfix_SpawnEntityInWorld_World(Entity _entity) + { + if (_entity is EntityAlive ea && !ea.isEntityRemote) + { + ea.FireEvent(CustomEnums.onSelfFirstCVarSync); + } + } + + [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.ItemActionEffects))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_ItemActionEffects_ItemActionRanged(IEnumerable instructions) + { + var codes = instructions.ToList(); + + for (int i = 0; i < codes.Count - 2; i++) + { + if (codes[i].opcode == OpCodes.Ldloc_S && ((LocalBuilder)codes[i].operand).LocalIndex == 9 && codes[i + 2].Branches(out _)) + { + codes.InsertRange(i + 3, new[] + { + new CodeInstruction(OpCodes.Ldloc_S, codes[i].operand).WithLabels(codes[i + 3].ExtractLabels()), + CodeInstruction.Call(typeof(CommonUtilityPatch), nameof(AddTmpMuzzleFlash)), + }); + i += 5; + } + else if (codes[i].opcode == OpCodes.Ldloc_S && ((LocalBuilder)codes[i].operand).LocalIndex == 12 && codes[i + 2].Branches(out _)) + { + codes.InsertRange(i + 3, new[] + { + new CodeInstruction(OpCodes.Ldloc_S, codes[i].operand).WithLabels(codes[i + 3].ExtractLabels()), + CodeInstruction.Call(typeof(CommonUtilityPatch), nameof(AddTmpMuzzleFlash)), + }); + i += 5; + } + } + return codes; + } + + private static void AddTmpMuzzleFlash(Transform trans) + { + if (trans.TryGetComponent(out var tmp)) + { + tmp.StopAllCoroutines(); + Component.Destroy(tmp); + } + tmp = trans.AddMissingComponent(); + tmp.life = 5f; + } + + [HarmonyPatch(typeof(vp_FPCamera), nameof(vp_FPCamera.UpdateShakes))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_UpdateShakes_vp_FPCamera(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var fld_shake = AccessTools.Field(typeof(vp_FPCamera), nameof(vp_FPCamera.m_Shake)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].StoresField(fld_shake)) + { + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.Call(typeof(CommonUtilityPatch), nameof(CheckShakeNaN)) + }); + break; + } + } + return codes; + } + + private static void CheckShakeNaN(vp_FPCamera fpcamera) + { + if (float.IsNaN(fpcamera.m_Shake.x) || float.IsNaN(fpcamera.m_Shake.y) || float.IsNaN(fpcamera.m_Shake.z)) + { + Log.Warning("Shake1 NaN {0}, time {1}, speed {2}, amp {3}", new object[] + { + fpcamera.m_Shake, + Time.time, + fpcamera.ShakeSpeed, + fpcamera.ShakeAmplitude + }); + fpcamera.ShakeSpeed = 0f; + fpcamera.m_Shake = Vector3.zero; + fpcamera.m_Pitch += -1f; + } + } + + //[HarmonyPatch(typeof(EntityBuffs), nameof(EntityBuffs.AddBuff), typeof(string), typeof(Vector3i), typeof(int), typeof(bool), typeof(bool), typeof(float))] + //[HarmonyPostfix] + //private static void Postfix_AddBuff_EntityBuffs(string _name, EntityBuffs __instance, EntityBuffs.BuffStatus __result, bool _netSync) + //{ + // if (_name.StartsWith("eftZombieRandomArmor") || _name.StartsWith("eftZombieArmor")) + // Log.Out($"AddBuff [{_name}] on entity {__instance.parent.GetDebugName()} should sync {_netSync} result {__result.ToStringCached()}\n{StackTraceUtility.ExtractStackTrace()}"); + //} + + //[HarmonyPatch(typeof(EntityBuffs), nameof(EntityBuffs.RemoveBuff))] + //[HarmonyPostfix] + //private static void Postfix_RemoveBuff_EntityBuffs(string _name, EntityBuffs __instance, bool _netSync) + //{ + // if (_name.StartsWith("eftZombieRandomArmor") || _name.StartsWith("eftZombieArmor")) + // Log.Out($"RemoveBuff [{_name}] on entity {__instance.parent.GetDebugName()} should sync {_netSync}\n{StackTraceUtility.ExtractStackTrace()}"); + //} + + //[HarmonyPatch(typeof(Inventory), nameof(Inventory.Execute))] + //[HarmonyPrefix] + //private static void Prefix_Execute_Inventory(Inventory __instance, int _actionIdx, bool _bReleased, PlayerActionsLocal _playerActions = null) + //{ + // Log.Out($"Execute Inventory holding item {__instance.holdingItem.Name} slot {__instance.holdingItemIdx} action index {_actionIdx} released {_bReleased} is holster delay {__instance.IsHolsterDelayActive()} is unholster delay {__instance.IsUnholsterDelayActive()}"); + //} + + //[HarmonyPatch(typeof(Inventory), nameof(Inventory.updateHoldingItem))] + //[HarmonyTranspiler] + //private static IEnumerable Transpiler_updateHoldingItem_Inventory(IEnumerable instructions) + //{ + // var codes = instructions.ToList(); + + // var mtd_setholdingtrans = AccessTools.Method(typeof(Inventory), nameof(Inventory.setHoldingItemTransfrom)); + // var mtd_showrighthand = AccessTools.Method(typeof(Inventory), nameof(Inventory.ShowRightHand)); + // int insert = -1, take = -1; + + // for (int i = 0; i < codes.Count; i++) + // { + // if (codes[i].Calls(mtd_showrighthand)) + // { + // insert = i + 1; + // } + // else if (codes[i].Calls(mtd_setholdingtrans)) + // { + // take = i - 6; + // } + // } + + // if (take > insert) + // { + // var list_take = codes.GetRange(take, 7); + // codes.RemoveRange(take, 7); + // codes.InsertRange(insert, list_take); + // } + + // return codes; + //} + //private static bool exported = false; + //[HarmonyPatch(typeof(EModelSDCS), nameof(EModelSDCS.createModel))] + //[HarmonyPostfix] + //private static void Postfix_test(EModelSDCS __instance) + //{ + // if (!exported) + // { + // exported = true; + // var objects = new[] { __instance.entity.RootTransform.gameObject.GetComponentsInChildren()[1] }; + // Log.Out($"exporting objs: {objects.Length} avatar {objects[0].avatar.name} is human {objects[0].avatar.isHuman}"); + // FbxExporter07.OnExport(objects, @"E:\Unity Projects\AnimationPlayground\Assets\ExportedProject\example_skinned_mesh_with_bones.fbx"); + // Application.Quit(); + // } + //} +} + diff --git a/Harmony/RecoilPatch.cs b/Harmony/RecoilPatch.cs new file mode 100644 index 0000000..46754c2 --- /dev/null +++ b/Harmony/RecoilPatch.cs @@ -0,0 +1,156 @@ +using HarmonyLib; +using KFCommonUtilityLib.Scripts.StaticManagers; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using UnityEngine; +using CameraShake; +using System; + +[HarmonyPatch] +class RecoilPatch +{ + [HarmonyPatch(typeof(EntityPlayerLocal), "Awake")] + [HarmonyPostfix] + private static void Postfix_Awake_EntityPlayerLocal(EntityPlayerLocal __instance) + { + RecoilManager.InitPlayer(__instance); + } + + [HarmonyPatch(typeof(vp_FPCamera), nameof(vp_FPCamera.LateUpdate))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_LateUpdate_vp_FPCamera(IEnumerable instructions) + { + var codes = instructions.ToList(); + + var mtd_update = AccessTools.Method(typeof(vp_FPCamera), nameof(vp_FPCamera.Update3rdPerson)); + var prop_trans = AccessTools.PropertyGetter(typeof(vp_Component), nameof(vp_Component.transform)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].Calls(mtd_update)) + { + codes.InsertRange(i - 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Call, prop_trans), + CodeInstruction.Call(typeof(KFExtensions), nameof(KFExtensions.AddMissingComponent), new Type[]{ typeof(Transform) }, new Type[]{ typeof(CameraShaker) }), + CodeInstruction.Call(typeof(CameraShaker), nameof(CameraShaker.UpdateShake)) + }); + break; + } + } + + return codes; + } + + [HarmonyPatch(typeof(GameManager), nameof(GameManager.SaveAndCleanupWorld))] + [HarmonyPostfix] + private static void Postfix_SaveAndCleanupWorld_GameManager() + { + RecoilManager.Cleanup(); + } + + [HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.OnFired))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_OnFired_EntityPlayerLocal(IEnumerable instructions) + { + var codes = instructions.ToList(); + FieldInfo fld_isfpv = AccessTools.Field(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.bFirstPersonView)); + ConstructorInfo mtd_ctor = AccessTools.Constructor(typeof(Vector2), new[] { typeof(float), typeof(float) }); + + for (int i = 0; i < codes.Count - 1; i++) + { + if (codes[i].Is(OpCodes.Call, mtd_ctor) && codes[i + 1].opcode == OpCodes.Ldloc_0) + { + for (int j = i + 1; j < codes.Count - 1; j++) + { + if (codes[j].opcode == OpCodes.Pop && codes[j + 1].opcode == OpCodes.Ldarg_0) + { + codes.RemoveRange(i + 1, j - i); + break; + } + } + codes.InsertRange(i + 1, new[] + { + new CodeInstruction(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Ldloca_S, 0), + new CodeInstruction(OpCodes.Ldloca_S, 1), + CodeInstruction.Call(typeof(RecoilPatch), nameof(RecoilPatch.ModifyRecoil)) + }); + i += 4; + } + else if (codes[i].LoadsField(fld_isfpv)) + { + codes.RemoveRange(i + 2, codes.Count - i - 3); + codes.InsertRange(i + 2, new[] + { + new CodeInstruction(OpCodes.Ldloc_0), + new CodeInstruction(OpCodes.Ldloc_1), + CodeInstruction.Call(typeof(RecoilManager), nameof(RecoilManager.AddRecoil)) + }); + break; + } + } + return codes; + } + + private static void ModifyRecoil(EntityPlayerLocal player, ref Vector2 kickHor, ref Vector2 kickVer) + { + float multiplierHor = Mathf.Max(1 - EffectManager.GetValue(CustomEnums.KickDegreeHorizontalModifier, player.inventory.holdingItemItemValue, 0f, player), 0); + float multiplierVer = Mathf.Max(1 - EffectManager.GetValue(CustomEnums.KickDegreeVerticalModifier, player.inventory.holdingItemItemValue, 0f, player), 0); + kickHor *= multiplierHor; + kickVer *= multiplierVer; + } + + [HarmonyPatch(typeof(PlayerMoveController), nameof(PlayerMoveController.Update))] + [HarmonyTranspiler] + private static IEnumerable Transpiler_Update_PlayerMoveController(IEnumerable instructions) + { + var codes = instructions.ToList(); + + FieldInfo fld_rotation = AccessTools.Field(typeof(MovementInput), nameof(MovementInput.rotation)); + FieldInfo fld_rx = AccessTools.Field(typeof(Vector3), nameof(Vector3.x)); + FieldInfo fld_ry = AccessTools.Field(typeof(Vector3), nameof(Vector3.y)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].LoadsField(fld_rotation, true)) + { + if (codes[i + 1].LoadsField(fld_rx, true)) + { + for (; i < codes.Count; i++) + { + if (codes[i].opcode == OpCodes.Stind_R4) + { + codes.Insert(i, CodeInstruction.Call(typeof(RecoilManager), nameof(RecoilManager.CompensateX))); + break; + } + } + } + else if (codes[i + 1].LoadsField(fld_ry, true)) + { + for (; i < codes.Count; i++) + { + if (codes[i].opcode == OpCodes.Stind_R4) + { + codes.Insert(i, CodeInstruction.Call(typeof(RecoilManager), nameof(RecoilManager.CompensateY))); + break; + } + } + } + } + } + + return codes; + } + + [HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.MoveByInput))] + [HarmonyPrefix] + private static bool Prefix_MoveByInput_EntityPlayerLocal() + { + RecoilManager.ApplyRecoil(); + return true; + } +} \ No newline at end of file diff --git a/Harmony/SaveRescuePatches.cs b/Harmony/SaveRescuePatches.cs new file mode 100644 index 0000000..847c4b2 --- /dev/null +++ b/Harmony/SaveRescuePatches.cs @@ -0,0 +1,6 @@ +namespace KFCommonUtilityLib.Harmony +{ + public static class SaveRescuePatches + { + } +} diff --git a/KFAttached/Animation.meta b/KFAttached/Animation.meta new file mode 100644 index 0000000..970dbaa --- /dev/null +++ b/KFAttached/Animation.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0794a4eb6ebfc3a42a72e08a17a475cc +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/DebugScripts.meta b/KFAttached/Animation/DebugScripts.meta new file mode 100644 index 0000000..1562796 --- /dev/null +++ b/KFAttached/Animation/DebugScripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8e258bfeacfa28e49abe1c883f258a8b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/DebugScripts/AnimatorActionIndexDebug.cs b/KFAttached/Animation/DebugScripts/AnimatorActionIndexDebug.cs new file mode 100644 index 0000000..f42a73e --- /dev/null +++ b/KFAttached/Animation/DebugScripts/AnimatorActionIndexDebug.cs @@ -0,0 +1,35 @@ +using UnityEngine; + +public class AnimatorActionIndexDebug : StateMachineBehaviour +{ + public override void OnStateMachineEnter(Animator animator, int stateMachinePathHash) + { + Log.Out($"StateMachine enter, Animator action index: {animator.GetInteger("ExecutingActionIndex")}"); + } + + public override void OnStateMachineExit(Animator animator, int stateMachinePathHash) + { + Log.Out($"StateMachine exit, Animator action index: {animator.GetInteger("ExecutingActionIndex")}"); + } + + public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + Log.Out($"State entered!"); + } + + public override void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + if (animator.GetBool("WeaponFire")) + { + Log.Out($"OnStateMove: Fire trigger set, Animator action index: {animator.GetInteger("ExecutingActionIndex")}"); + } + } + + public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + if (animator.GetBool("WeaponFire")) + { + Log.Out($"OnStateUpdate: Fire trigger set, Animator action index: {animator.GetInteger("ExecutingActionIndex")}"); + } + } +} \ No newline at end of file diff --git a/KFAttached/Animation/DebugScripts/AnimatorActionIndexDebug.cs.meta b/KFAttached/Animation/DebugScripts/AnimatorActionIndexDebug.cs.meta new file mode 100644 index 0000000..ac709cd --- /dev/null +++ b/KFAttached/Animation/DebugScripts/AnimatorActionIndexDebug.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c436ffdfb304ce144a66f57ef3e43bc4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/MonoBehaviours.meta b/KFAttached/Animation/MonoBehaviours.meta new file mode 100644 index 0000000..8b17691 --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 90da1ffd0de481442b66207ea3c9263a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/MonoBehaviours/AnimationAimRecoilReferences.cs b/KFAttached/Animation/MonoBehaviours/AnimationAimRecoilReferences.cs new file mode 100644 index 0000000..dcfb689 --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimationAimRecoilReferences.cs @@ -0,0 +1,32 @@ +using UnityEngine; + +[AddComponentMenu("KFAttachments/Binding Helpers/Animation Aim Recoil References")] +public class AnimationAimRecoilReferences : MonoBehaviour +{ + [SerializeField] + private Transform[] aimRecoilTargets; + private Vector3[] initialPositions; + + private void Start() + { + if (aimRecoilTargets != null) + { + initialPositions = new Vector3[aimRecoilTargets.Length]; + for (int i = 0; i < aimRecoilTargets.Length; i++) + { + initialPositions[i] = aimRecoilTargets[i].localPosition; + } + } + } + + public void Rollback() + { + if (aimRecoilTargets != null) + { + for (int i = 0; i < aimRecoilTargets.Length; i++) + { + aimRecoilTargets[i].localPosition = initialPositions[i]; + } + } + } +} \ No newline at end of file diff --git a/KFAttached/Animation/MonoBehaviours/AnimationAimRecoilReferences.cs.meta b/KFAttached/Animation/MonoBehaviours/AnimationAimRecoilReferences.cs.meta new file mode 100644 index 0000000..a0e11e0 --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimationAimRecoilReferences.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9ca4c020de5e68b4ca9902d948462df5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/MonoBehaviours/AnimationDelayRender.cs b/KFAttached/Animation/MonoBehaviours/AnimationDelayRender.cs new file mode 100644 index 0000000..5de4518 --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimationDelayRender.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections; +using System.Collections.Generic; +#if NotEditor +using UniLinq; +#else +using System.Linq; +#endif +using UnityEngine; +using UnityEngine.Jobs; +using Unity.Collections; +using Unity.Jobs; + +[AddComponentMenu("")] +public class AnimationDelayRender : MonoBehaviour +{ +#if NotEditor + [Serializable] + public class TransformTargets + { + public Transform target; + public bool includeChildren; + } + + public struct TransformLocalData + { + public Vector3 localPosition; + public Quaternion localRotation; + public Vector3 localScale; + + public TransformLocalData(Vector3 localPosition, Quaternion localRotation, Vector3 localScale) + { + this.localPosition = localPosition; + this.localRotation = localRotation; + this.localScale = localScale; + } + } + + private struct TransformRestoreJobs : IJobParallelForTransform + { + public NativeArray data; + public void Execute(int index, TransformAccess transform) + { + if (transform.isValid) + { + transform.SetLocalPositionAndRotation(data[index].localPosition, data[index].localRotation); + transform.localScale = data[index].localScale; + } + } + } + + private struct TransformRestoreAndSaveJobs : IJobParallelForTransform + { + public NativeArray data; + public void Execute(int index, TransformAccess transform) + { + if (transform.isValid) + { + TransformLocalData targetData = new TransformLocalData(transform.localPosition, transform.localRotation, transform.localScale); + transform.SetLocalPositionAndRotation(data[index].localPosition, data[index].localRotation); + transform.localScale = data[index].localScale; + data[index] = targetData; + } + } + } + [NonSerialized] + private Transform[] delayTargets; + + private TransformLocalData[] posTargets; + private EntityPlayerLocal player; + + private NativeArray data; + TransformAccessArray transArr; + private JobHandle restoreJob, restoreAndSaveJob; + private bool dataInitialized = false; + + private void Awake() + { +#if NotEditor + player = GameManager.Instance?.World?.GetPrimaryPlayer(); + if (player == null) + { + Destroy(this); + return; + } +#endif + } + + internal void InitializeTarget() + { + var delayTargetsSet = new HashSet(); + foreach (Transform child in transform.GetComponentsInChildren(true).Skip(1)) + { + delayTargetsSet.Add(child); + } + delayTargets = delayTargetsSet.ToArray(); + posTargets = new TransformLocalData[delayTargets.Length]; + ClearNative(); + data = new NativeArray(delayTargets.Length, Allocator.Persistent, NativeArrayOptions.ClearMemory); + transArr = new TransformAccessArray(delayTargets); + } + + private void InitializeData() + { + for (int i = 0; i < delayTargets.Length; i++) + { + Transform target = delayTargets[i]; + if (target) + { + data[i] = new TransformLocalData(target.localPosition, target.localRotation, target.localScale); + } + } + dataInitialized = true; + } + + private void OnEnable() + { + InitializeTarget(); + player.playerCamera?.gameObject.GetOrAddComponent().targets.Add(this); + //var preAnimatorUpdateJob = new TransformRestoreJobs + //{ + // data = data + //}; + //restoreJob = preAnimatorUpdateJob.Schedule(transArr); + StartCoroutine(EndOfFrameCo()); + Log.Out($"Delay render target count: {delayTargets.Length}"); + } + + private void OnDisable() + { + ClearNative(); + player.playerCamera?.gameObject.GetOrAddComponent().targets.Remove(this); + StopAllCoroutines(); + dataInitialized = false; + } + + private void Update() + { + //for (int i = 0; i < delayTargets.Length; i++) + //{ + // Transform target = delayTargets[i]; + // if (target) + // { + // target.localPosition = posTargets[i].localPosition; + // target.localRotation = posTargets[i].localRotation; + // target.localScale = posTargets[i].localScale; + // } + // else + // { + // delayTargets[i] = null; + // } + //} + restoreJob.Complete(); + } + + private void LateUpdate() + { + if (!dataInitialized) + return; + var postAnimationUpdateJob = new TransformRestoreAndSaveJobs + { + data = data + }; + restoreAndSaveJob = postAnimationUpdateJob.Schedule(transArr); + //for (int i = 0; i < delayTargets.Length; i++) + //{ + // Transform target = delayTargets[i]; + // if (target) + // { + // TransformLocalData targetData = new TransformLocalData(target.localPosition, target.localRotation, target.localScale); + // target.localPosition = posTargets[i].localPosition; + // target.localRotation = posTargets[i].localRotation; + // target.localScale = posTargets[i].localScale; + // posTargets[i] = targetData; + // } + // else + // { + // delayTargets[i] = null; + // } + //} + } + + internal void PreCullCallback() + { + restoreAndSaveJob.Complete(); + } + + private IEnumerator EndOfFrameCo() + { + yield return null; + InitializeData(); + while (true) + { + yield return new WaitForEndOfFrame(); + var eofUpdateJob = new TransformRestoreJobs { data = data }; + restoreJob = eofUpdateJob.Schedule(transArr); + } + } + + private void ClearNative() + { + if (data.IsCreated) + { + data.Dispose(); + } + if (transArr.isCreated) + { + transArr.Dispose(); + } + } +#endif +} \ No newline at end of file diff --git a/KFAttached/Animation/MonoBehaviours/AnimationDelayRender.cs.meta b/KFAttached/Animation/MonoBehaviours/AnimationDelayRender.cs.meta new file mode 100644 index 0000000..fff933b --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimationDelayRender.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3ba593a40bea2734a8fb4ea26a7c290e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/MonoBehaviours/AnimationDelayRenderReference.cs b/KFAttached/Animation/MonoBehaviours/AnimationDelayRenderReference.cs new file mode 100644 index 0000000..43b84f1 --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimationDelayRenderReference.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +[AddComponentMenu("")] +internal class AnimationDelayRenderReference : MonoBehaviour +{ +#if NotEditor + internal HashSet targets = new HashSet(); + private void OnPreCull() + { + if (targets.Count > 0) + { + foreach (var target in targets) + { + target.PreCullCallback(); + } + } + } +#endif +} diff --git a/KFAttached/Animation/MonoBehaviours/AnimationDelayRenderReference.cs.meta b/KFAttached/Animation/MonoBehaviours/AnimationDelayRenderReference.cs.meta new file mode 100644 index 0000000..2d8784c --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimationDelayRenderReference.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dfa24b0d9ec212f4eb1f00580cfb4212 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/MonoBehaviours/AnimationFiringEvents.cs b/KFAttached/Animation/MonoBehaviours/AnimationFiringEvents.cs new file mode 100644 index 0000000..8c7de5c --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimationFiringEvents.cs @@ -0,0 +1,31 @@ +using UnityEngine; + +public class AnimationFiringEvents : MonoBehaviour +{ + [SerializeField] + private ParticleSystem[] mainParticles; + + private void Awake() + { + if (mainParticles == null) + return; + foreach (var ps in mainParticles) + { + ps.gameObject.SetActive(true); + var emission = ps.emission; + emission.enabled = false; + ps.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); + var main = ps.main; + main.loop = false; + } + } + + public void Fire(int index) + { + if (mainParticles == null || index < 0 || index >= mainParticles.Length) + return; + GameObject root = mainParticles[index].gameObject; + root.BroadcastMessage("OnEnable", SendMessageOptions.DontRequireReceiver); + mainParticles[index].Emit(1); + } +} diff --git a/KFAttached/Animation/MonoBehaviours/AnimationFiringEvents.cs.meta b/KFAttached/Animation/MonoBehaviours/AnimationFiringEvents.cs.meta new file mode 100644 index 0000000..76e3fe3 --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimationFiringEvents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e2a71dc0034e159458026e1103e44c62 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/MonoBehaviours/AnimationGraphBuilder.cs b/KFAttached/Animation/MonoBehaviours/AnimationGraphBuilder.cs new file mode 100644 index 0000000..380a606 --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimationGraphBuilder.cs @@ -0,0 +1,381 @@ +#if NotEditor +#endif +using System; +using System.Collections.Generic; +using System.Diagnostics; +using UnityEngine; +using UnityEngine.Animations; +using UnityEngine.Playables; + +[AddComponentMenu("")] +public class AnimationGraphBuilder : MonoBehaviour +{ + public enum ParamInWrapper + { + None, + Vanilla, + Weapon, + Attachments, + Both + } + private Animator animator; + private RuntimeAnimatorController vanillaRuntimeController; + private Avatar vanillaAvatar; + private PlayableGraph graph; + private AnimationLayerMixerPlayable mixer; + private AnimatorControllerPlayable weaponControllerPlayable; + private AnimatorControllerPlayable vanillaControllerPlayable; + private Avatar weaponAvatar; + private AvatarMask weaponMask; + private bool isFpv; + private bool isLocalPlayer = true; + private readonly List graphRelatedBehaviours = new List(); + private AnimatorControllerParameter[] parameters; + private readonly Dictionary paramMapping = new Dictionary(); + private readonly Dictionary paramMappingDebug = new Dictionary(); + //private Animator[] childAnimators = Array.Empty(); + + public bool HasWeaponOverride => graph.IsValid(); + //public AnimatorControllerPlayable VanillaPlayable => vanillaControllerPlayable; + //public AnimatorControllerPlayable WeaponPlayable => weaponControllerPlayable; + public AnimatorControllerParameter[] Parameters => parameters; + private AnimationTargetsAbs CurrentTarget { get; set; } + public IAnimatorWrapper VanillaWrapper { get; private set; } + public IAnimatorWrapper WeaponWrapper { get; private set; } + public AttachmentWrapper AttachmentWrapper { get; private set; } + public static IAnimatorWrapper DummyWrapper { get; } = new AnimatorWrapper(null); +#if NotEditor + public EntityPlayer Player { get; private set; } +#endif + + private void Awake() + { + isFpv = transform.name == "baseRigFP"; + animator = GetComponent(); + animator.logWarnings = false; + VanillaWrapper = new AnimatorWrapper(animator); + WeaponWrapper = new AnimatorWrapper(null); + parameters = animator.parameters; + UpdateParamMapping(); + vanillaAvatar = animator.avatar; +#if NotEditor + vanillaRuntimeController = isFpv ? SDCSUtils.FPAnimController : SDCSUtils.TPAnimController; + isLocalPlayer = (Player = GetComponentInParent()) is EntityPlayerLocal; +#else + vanillaRuntimeController = animator.runtimeAnimatorController; +#endif + } + + private void UpdateParamMapping() + { + paramMapping.Clear(); + paramMappingDebug.Clear(); + var paramList = new List(); + paramList.AddRange(animator.parameters); + for (int i = 0; i < VanillaWrapper.GetParameterCount(); i++) + { + paramMapping.Add(VanillaWrapper.GetParameter(i).nameHash, ParamInWrapper.Vanilla); + paramMappingDebug.Add(VanillaWrapper.GetParameter(i).name, ParamInWrapper.Vanilla); + } + if (WeaponWrapper != null && WeaponWrapper.IsValid) + { + for (int i = 0; i < WeaponWrapper.GetParameterCount(); i++) + { + var param = WeaponWrapper.GetParameter(i); + if (paramMapping.ContainsKey(param.nameHash)) + { + paramMapping[param.nameHash] = ParamInWrapper.Both; + paramMappingDebug[param.name] = ParamInWrapper.Both; + } + else + { + paramMapping.Add(param.nameHash, ParamInWrapper.Weapon); + paramMappingDebug.Add(param.name, ParamInWrapper.Weapon); + paramList.Add(param); + } + } + } + if (AttachmentWrapper != null && AttachmentWrapper.IsValid) + { + foreach (var animator in AttachmentWrapper.animators) + { + for (int i = 0; i < animator.parameterCount; i++) + { + var param = animator.GetParameter(i); + if (!paramMapping.ContainsKey(param.nameHash)) + { + paramMapping[param.nameHash] = ParamInWrapper.Attachments; + paramList.Add(param); + } + } + } + } + parameters = paramList.ToArray(); + } + + public ParamInWrapper GetWrapperRoleByParamHash(int nameHash) + { + if (paramMapping.TryGetValue(nameHash, out var role)) + return role; + return ParamInWrapper.None; + } + + public ParamInWrapper GetWrapperRoleByParamName(string name) + { + if (string.IsNullOrEmpty(name) || !paramMapping.TryGetValue(Animator.StringToHash(name), out var role)) + return ParamInWrapper.None; + return role; + } + + public ParamInWrapper GetWrapperRoleByParam(AnimatorControllerParameter param) + { + if (param == null || !paramMapping.TryGetValue(param.nameHash, out var role)) + return ParamInWrapper.None; + return role; + } + + public void SetChildFloat(int nameHash, float value) + { + if (AttachmentWrapper != null && AttachmentWrapper.IsValid) + { + AttachmentWrapper.SetFloat(nameHash, value); + } + } + + public void SetChildBool(int nameHash, bool value) + { + if (AttachmentWrapper != null && AttachmentWrapper.IsValid) + { + AttachmentWrapper.SetBool(nameHash, value); + } + } + + public void SetChildInteger(int nameHash, int value) + { + if (AttachmentWrapper != null && AttachmentWrapper.IsValid) + { + AttachmentWrapper.SetInteger(nameHash, value); + } + } + + public void SetChildTrigger(int nameHash) + { + if (AttachmentWrapper != null && AttachmentWrapper.IsValid) + { + AttachmentWrapper.SetTrigger(nameHash); + } + } + + public void ResetChildTrigger(int nameHash) + { + if (AttachmentWrapper != null && AttachmentWrapper.IsValid) + { + AttachmentWrapper.ResetTrigger(nameHash); + } + } + + private void InitGraph() + { + animator.runtimeAnimatorController = null; + if (graph.IsValid()) + { + return; + } + + graph = PlayableGraph.Create(); + mixer = AnimationLayerMixerPlayable.Create(graph, 2); + var output = AnimationPlayableOutput.Create(graph, "output", animator); + output.SetSourcePlayable(mixer); + InitVanilla(); + graph.Play(); + } + + private void InitVanilla() + { + vanillaControllerPlayable = AnimatorControllerPlayable.Create(graph, vanillaRuntimeController); + mixer.ConnectInput(0, vanillaControllerPlayable, 0, isFpv ? 0 : 1.0f); + mixer.SetLayerAdditive(0, false); + } + + public void InitWeapon(Transform weaponRoot, RuntimeAnimatorController weaponRuntimeController, AvatarMask weaponMask) + { + InitGraph(); + InitBehaviours(weaponRoot); + weaponAvatar = AvatarBuilder.BuildGenericAvatar(gameObject, "Origin"); + animator.avatar = weaponAvatar; + weaponControllerPlayable = AnimatorControllerPlayable.Create(graph, weaponRuntimeController); + mixer.ConnectInput(1, weaponControllerPlayable, 0, 1.0f); + mixer.SetLayerAdditive(1, false); + if (weaponMask) + { + mixer.SetLayerMaskFromAvatarMask(1, weaponMask); + } + this.weaponMask = weaponMask; + } + + private void DestroyGraph() + { + CleanupBehaviours(); + weaponMask = null; + if (graph.IsValid()) + { + graph.Destroy(); + } + if (animator) + { + animator.runtimeAnimatorController = vanillaRuntimeController; + animator.avatar = vanillaAvatar; + } + } + + private void DestroyWeapon() + { + CleanupBehaviours(); + weaponMask = null; + if (weaponControllerPlayable.IsValid()) + { + mixer.DisconnectInput(1); + weaponControllerPlayable.Destroy(); + } + animator.avatar = vanillaAvatar; + Destroy(weaponAvatar); + animator.runtimeAnimatorController = vanillaRuntimeController; + } + + public void SetCurrentTarget(AnimationTargetsAbs target) + { + if (CurrentTarget == target) + { + UpdateChildAnimatorArray(target); + return; + } + + var sw = new Stopwatch(); + sw.Start(); +#if NotEditor + bool wasCrouching = VanillaWrapper != null && VanillaWrapper.IsValid && VanillaWrapper.GetBool(AvatarController.isCrouchingHash); +#endif + //var rb = animator.transform.AddMissingComponent(); + //rb.enabled = false; + if (CurrentTarget) + { + CurrentTarget.SetEnabled(false); + } + + bool useGraph = target && (target is PlayGraphTargets || (target.ItemTpv && !isFpv)); + if (HasWeaponOverride) + { + if (useGraph) + { + DestroyWeapon(); + } + else + { + DestroyGraph(); + } + } + + CurrentTarget = target; + if (target) + { + target.SetEnabled(true); + } + // Log.Out($"\n#=================Rebuild Start"); + //#if NotEditor + // Log.Out($"Remaining RigLayers on build:\n{string.Join("\n", rb.layers.Select(layer => layer.name))}"); + //#endif + // animator.UnbindAllSceneHandles(); + // animator.UnbindAllStreamHandles(); + // rb.enabled = true; + // animator.Rebind(); + // Log.Out($"#=================Rebuild Finish\n"); + animator.WriteDefaultValues(); + + if (useGraph) + { + VanillaWrapper = new PlayableWrapper(vanillaControllerPlayable); + WeaponWrapper = new PlayableWrapper(weaponControllerPlayable); + } + else + { + VanillaWrapper = new AnimatorWrapper(animator); + if (target) + { + WeaponWrapper = new AnimatorWrapper(target.ItemAnimator); + } + else + { + WeaponWrapper = DummyWrapper; + } + } + + UpdateChildAnimatorArray(target); + UpdateParamMapping(); +#if NotEditor + animator.SetWrappedBool(AvatarController.isCrouchingHash, wasCrouching); + if (isFpv) + { + VanillaWrapper.Play("idle", 0, 0f); + VanillaWrapper.SetInteger(AvatarController.weaponHoldTypeHash, -1); + } + if (wasCrouching && !isFpv && VanillaWrapper.GetLayerCount() > 4) + { + VanillaWrapper.Play("2HGeneric", 4, 0); + } +#endif + sw.Stop(); + Log.Out($"changing animation target to {(target ? target.name : "null")} took {sw.ElapsedMilliseconds}"); + } + + private void UpdateChildAnimatorArray(AnimationTargetsAbs target) + { + Animator[] childAnimators; + if (target && target.ItemCurrentOrDefault) + { + List animators = new List(); + foreach (Transform trans in target.ItemCurrentOrDefault) + { + animators.AddRange(trans.GetComponentsInChildren()); + } + if (target.ItemAnimator) + { + animators.Remove(target.ItemAnimator); + } + childAnimators = animators.ToArray(); + } + else + { + childAnimators = Array.Empty(); + } + AttachmentWrapper = new AttachmentWrapper(childAnimators); + } + + private void InitBehaviours(Transform weaponRoot) + { + foreach (var scripts in weaponRoot.GetComponents()) + { + var behaviour = scripts.Init(transform, isLocalPlayer); + if (behaviour) + { + graphRelatedBehaviours.Add(behaviour); + } + } + } + + private void CleanupBehaviours() + { + foreach (var behaviour in graphRelatedBehaviours) + { + if (behaviour) + { + (behaviour as IPlayableGraphRelated)?.Disable(transform); + } + } + graphRelatedBehaviours.Clear(); + } + + private void OnDisable() + { + SetCurrentTarget(null); + } +} diff --git a/KFAttached/Animation/MonoBehaviours/AnimationGraphBuilder.cs.meta b/KFAttached/Animation/MonoBehaviours/AnimationGraphBuilder.cs.meta new file mode 100644 index 0000000..db29aa2 --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimationGraphBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 43f465f7371212e4f8ce8241007afd67 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/MonoBehaviours/AnimationProceduralRecoildAbs.cs b/KFAttached/Animation/MonoBehaviours/AnimationProceduralRecoildAbs.cs new file mode 100644 index 0000000..2cdb205 --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimationProceduralRecoildAbs.cs @@ -0,0 +1,6 @@ +using UnityEngine; + +public abstract class AnimationProceduralRecoildAbs : MonoBehaviour +{ + public abstract void AddRecoil(Vector3 positionMultiplier, Vector3 rotationMultiplier); +} diff --git a/KFAttached/Animation/MonoBehaviours/AnimationProceduralRecoildAbs.cs.meta b/KFAttached/Animation/MonoBehaviours/AnimationProceduralRecoildAbs.cs.meta new file mode 100644 index 0000000..c519f8e --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimationProceduralRecoildAbs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1ee261f617ba41e469d725bd8f2cb3c5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/MonoBehaviours/AnimationRandomRecoil.cs b/KFAttached/Animation/MonoBehaviours/AnimationRandomRecoil.cs new file mode 100644 index 0000000..1b038e7 --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimationRandomRecoil.cs @@ -0,0 +1,163 @@ +using DG.Tweening; +using Unity.Mathematics; +using UnityEngine; + +[AddComponentMenu("KFAttachments/Binding Helpers/Animation Random Recoil")] +public class AnimationRandomRecoil : AnimationProceduralRecoildAbs, IPlayableGraphRelated +#if UNITY_EDITOR + , ISerializationCallbackReceiver +#endif +{ + [Header("Targets")] + [SerializeField] private Transform target; + [SerializeField, HideInInspector] private string targetName; + [SerializeField] private Transform pivot; + [SerializeField, HideInInspector] private string pivotName; + [Header("Rotation")] + //[SerializeField] private Vector3 minRotation = new Vector3(-5, -2, -2); + //[SerializeField] private Vector3 maxRotation = new Vector3(0, 2, 2); + [SerializeField] private Vector3 randomRotationMin = new Vector3(-3, -1, -1); + [SerializeField] private Vector3 randomRotationMax = new Vector3(-1, 1, 1); + [Header("Kickback")] + //[SerializeField] private Vector3 minKickback = new Vector3(0, 0, -0.05f); + //[SerializeField] private Vector3 maxKickback = new Vector3(0, 0, 0); + [SerializeField] private Vector3 randomKickbackMin = new Vector3(0, 0, -0.025f); + [SerializeField] private Vector3 randomKickbackMax = new Vector3(0, 0, -0.01f); + [Header("Recoil")] + [SerializeField, Range(0, 0.1f)] private float tweenInDuration = 0.04f; + [SerializeField, Range(0, 5)] private float tweenOutDuration = 1.5f; + [Header("Return")] + [SerializeField, Range(1, 5)] private float elasticAmplitude = 1f; + [SerializeField, Range(0, 1)] private float elasticPeriod = 0.5f; + + private Vector3 targetRotation = Vector3.zero; + private Vector3 targetPosition = Vector3.zero; + private Vector3 currentRotation = Vector3.zero; + private Vector3 currentPosition = Vector3.zero; + private Sequence seq; + //private bool isTweeningIn = true; + + public override void AddRecoil(Vector3 positionMultiplier, Vector3 rotationMultiplier) + { + if (target && pivot) + { + targetPosition = Vector3.Scale(KFExtensions.Random(randomKickbackMin, randomKickbackMax), positionMultiplier); + targetRotation = Vector3.Scale(KFExtensions.Random(randomRotationMin, randomRotationMax), rotationMultiplier); + GameObject targetObj = target.gameObject; + RecreateSeq(); + } + } + + private void OnEnable() + { + ResetSeq(); + } + + private void OnDisable() + { + ResetSeq(); + } + + private void ResetSeq() + { + seq?.Rewind(false); + if (target) + { + target.localEulerAngles = Vector3.zero; + target.localPosition = Vector3.zero; + } + } + + private void RecreateSeq() + { + currentPosition = Vector3.zero; + currentRotation = Vector3.zero; + seq?.Kill(false); + seq = DOTween.Sequence() + //.InsertCallback(0, () => isTweeningIn = true) + .Insert(0, DOTween.To(() => currentRotation, (rot) => currentRotation = rot, targetRotation, tweenInDuration).SetEase(Ease.OutCubic)) + .Insert(0, DOTween.To(() => currentPosition, (pos) => currentPosition = pos, targetPosition, tweenInDuration).SetEase(Ease.OutCubic)) + //.InsertCallback(tweenInDuration, () => isTweeningIn = false) + .Insert(tweenInDuration, DOTween.To(() => currentRotation, (rot) => currentRotation = rot, Vector3.zero, tweenOutDuration).SetEase(Ease.OutElastic, elasticAmplitude, elasticPeriod)) + .Insert(tweenInDuration, DOTween.To(() => currentPosition, (rot) => currentPosition = rot, Vector3.zero, tweenOutDuration).SetEase(Ease.OutElastic, elasticAmplitude, elasticPeriod)) + .OnUpdate(UpdateTransform) + .SetAutoKill(true) + .SetRecyclable(); + } + + private void UpdateTransform() + { + if (target) + { + target.localEulerAngles = Vector3.zero; + target.localPosition = Vector3.zero; + target.RotateAroundPivot(pivot, currentRotation); + target.localPosition += currentPosition; + } + + //if (!isTweeningIn) + //{ + // targetRotation = currentRotation; + // targetPosition = currentPosition; + //} + } + + public MonoBehaviour Init(Transform playerAnimatorTrans, bool isLocalPlayer) + { + if (isLocalPlayer) + { + var copy = playerAnimatorTrans.AddMissingComponent(); + copy.enabled = true; + if (target) + { + copy.target = this.target; + } + else + { + copy.target = playerAnimatorTrans.FindInAllChildren(targetName); + } + if (pivot) + { + copy.pivot = pivot; + } + else + { + copy.pivot = playerAnimatorTrans.FindInAllChildren(pivotName); + } + copy.randomRotationMin = this.randomRotationMin; + copy.randomRotationMax = this.randomRotationMax; + copy.randomKickbackMin = this.randomKickbackMin; + copy.randomKickbackMax = this.randomKickbackMax; + copy.tweenInDuration = this.tweenInDuration; + copy.tweenOutDuration = this.tweenOutDuration; + copy.elasticAmplitude = this.elasticAmplitude; + copy.elasticPeriod = this.elasticPeriod; + return copy; + } + return null; + } + + public void Disable(Transform playerAnimatorTrans) + { + enabled = false; + } + +#if UNITY_EDITOR + public void OnBeforeSerialize() + { + if (target) + { + targetName = target.name; + } + if (pivot) + { + pivotName = pivot.name; + } + } + + public void OnAfterDeserialize() + { + + } +#endif +} \ No newline at end of file diff --git a/KFAttached/Animation/MonoBehaviours/AnimationRandomRecoil.cs.meta b/KFAttached/Animation/MonoBehaviours/AnimationRandomRecoil.cs.meta new file mode 100644 index 0000000..6321f3e --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimationRandomRecoil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bd683a2a71b72f04f88532f056986903 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/MonoBehaviours/AnimationRandomSound.cs b/KFAttached/Animation/MonoBehaviours/AnimationRandomSound.cs new file mode 100644 index 0000000..a2dd72b --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimationRandomSound.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using Random = UnityEngine.Random; + +[AddComponentMenu("KFAttachments/Utils/Animation Random Sound")] +public class AnimationRandomSound : MonoBehaviour, ISerializationCallbackReceiver +{ + [SerializeField] + public List audioSourcesEditor; + [NonSerialized] + private List audioSources; + + [HideInInspector] + [SerializeField] + private List list_groupnames; + [HideInInspector] + [SerializeField] + private List list_clips; + [HideInInspector] + [SerializeField] + private List list_sources; + [HideInInspector] + [SerializeField] + private List list_clip_indices; + [HideInInspector] + [SerializeField] + private int serializedCount = 0; + + public void OnAfterDeserialize() + { + audioSources = new List(); + for (int i = 0; i < serializedCount; i++) + { + int index = (i == 0 ? 0 : list_clip_indices[i - 1]); + int count = list_clip_indices[i] - index; + audioSources.Add(new AudioSourceGroup() + { + groupName = list_groupnames[i], + clips = list_clips.Skip(index).Take(count).ToArray(), + source = list_sources[i], + }); + } + } + + public void OnBeforeSerialize() + { + if (audioSourcesEditor != null && audioSourcesEditor.Count > 0) + { + serializedCount = 0; + list_groupnames = new List(); + list_clips = new List(); + list_clip_indices = new List(); + list_sources = new List(); + for (int i = 0; i < audioSourcesEditor.Count; i++) + { + list_groupnames.Add(audioSourcesEditor[i].groupName); + list_clips.AddRange(audioSourcesEditor[i].clips); + list_sources.Add(audioSourcesEditor[i].source); + list_clip_indices.Add(list_clips.Count); + serializedCount++; + } + } + } + + public void PlayRandomClip(string group) + { + if (audioSources == null) + return; + + //#if NotEditor + // Log.Out($"play random clip {group}, groups: {string.Join("| ", audioSources.Select(g => g.groupName + $"clips: {string.Join(", ", g.clips.Select(c => c.name))}"))}"); + //#endif + AudioSourceGroup asg = null; + foreach (var audioSourceGroup in audioSources) + { + if (audioSourceGroup.groupName == group) + { + asg = audioSourceGroup; + break; + } + } + + if (asg == null) + { + return; + } + + int random = Random.Range(0, asg.clips.Length); + //asg.source.clip = asg.clips[random]; + asg.source.PlayOneShot(asg.clips[random]); + //#if NotEditor + // Log.Out($"play clip {asg.clips[random].name}"); + //#endif + } +} diff --git a/KFAttached/Animation/MonoBehaviours/AnimationRandomSound.cs.meta b/KFAttached/Animation/MonoBehaviours/AnimationRandomSound.cs.meta new file mode 100644 index 0000000..8159dac --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimationRandomSound.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 517d2eb7e1af8a740a6f8ae7cb775f5f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/MonoBehaviours/AnimationReloadEvents.cs b/KFAttached/Animation/MonoBehaviours/AnimationReloadEvents.cs new file mode 100644 index 0000000..58d7610 --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimationReloadEvents.cs @@ -0,0 +1,356 @@ +#if NotEditor +using KFCommonUtilityLib; + +#endif +using System.Collections; +using UnityEngine; + +[AddComponentMenu("KFAttachments/Utils/Animation Reload Events")] +public class AnimationReloadEvents : MonoBehaviour, IPlayableGraphRelated +{ + private void Awake() + { + animator = GetComponent(); +#if NotEditor + player = GetComponentInParent(); +#endif + } + + public void OnReloadFinish() + { + OnReloadAmmo(); + OnReloadEnd(); + } + + public void OnReloadAmmo() + { +#if NotEditor + if (actionData == null || !actionData.isReloading) + { +#if DEBUG + Log.Out($"ANIMATION RELOAD EVENT NOT RELOADING : {actionData?.invData.item.Name ?? "null"}"); +#endif + return; + } + if (!actionData.isReloadCancelled) + { + player.MinEventContext.ItemActionData = actionData; + ItemValue item = ItemClass.GetItem(actionRanged.MagazineItemNames[actionData.invData.itemValue.SelectedAmmoTypeIndex], false); + int magSize = actionRanged.GetMaxAmmoCount(actionData); + actionData.reloadAmount = GetAmmoCountToReload(player, item, magSize); + if (actionData.reloadAmount > 0) + { + actionData.invData.itemValue.Meta = Utils.FastMin(actionData.invData.itemValue.Meta + actionData.reloadAmount, magSize); + if (actionData.invData.item.Properties.Values[ItemClass.PropSoundIdle] != null) + { + actionData.invData.holdingEntitySoundID = -1; + } + } +#if DEBUG + Log.Out($"ANIMATION RELOAD EVENT AMMO : {actionData.invData.item.Name}"); +#endif + } +#endif + } + + public void OnReloadEnd() + { + StopAllCoroutines(); + animator.SetWrappedBool(Animator.StringToHash("Reload"), false); + animator.SetWrappedBool(Animator.StringToHash("IsReloading"), false); + animator.speed = 1f; +#if NotEditor + cancelReloadCo = null; + if (actionData == null || !actionData.isReloading) + { + return; + } + actionData.isReloading = false; + actionData.isWeaponReloading = false; + actionData.invData.holdingEntity.MinEventContext.ItemActionData = actionData; + actionData.invData.holdingEntity.FireEvent(MinEventTypes.onReloadStop, true); + actionData.invData.holdingEntity.OnReloadEnd(); + actionData.invData.holdingEntity.inventory.CallOnToolbeltChangedInternal(); + AnimationAmmoUpdateState.SetAmmoCountForEntity(actionData.invData.holdingEntity, actionData.invData.slotIdx); + actionData.isReloadCancelled = false; + actionData.isWeaponReloadCancelled = false; + actionData.isChangingAmmoType = false; + if (actionData is IModuleContainerFor dataModule) + { + dataModule.Instance.SetCurrentBarrel(actionData.invData.itemValue.Meta); + } +#if DEBUG + Log.Out($"ANIMATION RELOAD EVENT FINISHED : {actionData.invData.item.Name}"); +#endif + actionData = null; +#endif + } + + public void OnPartialReloadEnd() + { +#if NotEditor + if (actionData == null) + { + return; + } + + player.MinEventContext.ItemActionData = actionData; + ItemValue ammo = ItemClass.GetItem(actionRanged.MagazineItemNames[actionData.invData.itemValue.SelectedAmmoTypeIndex], false); + int magSize = (int)EffectManager.GetValue(PassiveEffects.MagazineSize, actionData.invData.itemValue, (float)actionRanged.BulletsPerMagazine, player); + int partialReloadCount = (int)EffectManager.GetValue(CustomEnums.PartialReloadCount, actionData.invData.itemValue, 1, player); + actionData.reloadAmount = GetPartialReloadCount(player, ammo, magSize, partialReloadCount); + if (actionData.reloadAmount > 0) + { + actionData.invData.itemValue.Meta = Utils.FastMin(actionData.invData.itemValue.Meta + actionData.reloadAmount, magSize); + if (actionData.invData.item.Properties.Values[ItemClass.PropSoundIdle] != null) + { + actionData.invData.holdingEntitySoundID = -1; + } + } + AnimationAmmoUpdateState.SetAmmoCountForEntity(actionData.invData.holdingEntity, actionData.invData.slotIdx); + + if (actionData.isReloadCancelled || actionData.isWeaponReloadCancelled || actionData.invData.itemValue.Meta >= magSize || player.GetItemCount(ammo) <= 0) + { + Log.Out("Partial reload finished"); + animator.SetWrappedBool(Animator.StringToHash("IsReloading"), false); + } +#endif + } + +#if NotEditor + //public bool ReloadUpdatedThisFrame => reloadUpdatedThisFrame; + //private bool reloadUpdatedThisFrame = false; + //internal void OnReloadUpdate() + //{ + // reloadUpdatedThisFrame = true; + //} + + //private void OnAnimatorMove() + //{ + // if (actionData != null) + // { + // //if (actionData.isReloading && !reloadUpdatedThisFrame) + // //{ + // // Log.Warning("Animator not sending update msg this frame, reloading is cancelled!"); + // // actionData.isReloadCancelled = true; + // // OnReloadFinish(); + // //} + // } + // else + // { + // //Log.Warning("actionData is null!"); + // } + // reloadUpdatedThisFrame = false; + //} + + public void OnReloadStart(int actionIndex) + { + if (player == null) + { + player = GetComponentInParent(); + } + actionData = player.inventory.holdingItemData.actionData[actionIndex] as ItemActionRanged.ItemActionDataRanged; + actionRanged = (ItemActionRanged)player.inventory.holdingItem.Actions[actionIndex]; + if (actionData == null || actionData.isReloading) + { + return; + } + + if (actionData.invData.item.Properties.Values[ItemClass.PropSoundIdle] != null && actionData.invData.holdingEntitySoundID >= 0) + { + Audio.Manager.Stop(actionData.invData.holdingEntity.entityId, actionData.invData.item.Properties.Values[ItemClass.PropSoundIdle]); + } + actionData.wasAiming = actionData.invData.holdingEntity.AimingGun; + if (actionData.invData.holdingEntity.AimingGun && actionData.invData.item.Actions[1] is ItemActionZoom) + { + actionData.invData.holdingEntity.inventory.Execute(1, false, null); + actionData.invData.holdingEntity.inventory.Execute(1, true, null); + } + if (animator.GetCurrentAnimatorClipInfo(0).Length != 0 && animator.GetCurrentAnimatorClipInfo(0)[0].clip.events.Length == 0) + { + if (actionRanged.SoundReload != null) + { + player.PlayOneShot(actionRanged.SoundReload.Value, false); + } + } + else if (animator.GetNextAnimatorClipInfo(0).Length != 0 && animator.GetNextAnimatorClipInfo(0)[0].clip.events.Length == 0 && actionRanged.SoundReload != null) + { + player.PlayOneShot(actionRanged.SoundReload.Value, false); + } + + ItemValue itemValue = actionData.invData.itemValue; + actionData.invData.holdingEntity.MinEventContext.ItemActionData = actionData; + int magSize = (int)EffectManager.GetValue(PassiveEffects.MagazineSize, itemValue, actionRanged.BulletsPerMagazine, actionData.invData.holdingEntity); + ItemActionLauncher itemActionLauncher = actionRanged as ItemActionLauncher; + if (itemActionLauncher != null && itemValue.Meta < magSize) + { + ItemValue ammoValue = ItemClass.GetItem(actionRanged.MagazineItemNames[itemValue.SelectedAmmoTypeIndex], false); + if (ConsoleCmdReloadLog.LogInfo) + Log.Out($"loading ammo {ammoValue.ItemClass.Name}"); + ItemActionLauncher.ItemActionDataLauncher itemActionDataLauncher = actionData as ItemActionLauncher.ItemActionDataLauncher; + if (itemActionDataLauncher.isChangingAmmoType) + { + if (ConsoleCmdReloadLog.LogInfo) + Log.Out($"is changing ammo type {itemActionDataLauncher.isChangingAmmoType}"); + itemActionLauncher.DeleteProjectiles(actionData); + itemActionDataLauncher.isChangingAmmoType = false; + } + int projectileCount = 1; + if (!actionData.invData.holdingEntity.isEntityRemote) + { + projectileCount = (itemActionLauncher.HasInfiniteAmmo(actionData) ? magSize : GetAmmoCount(actionData.invData.holdingEntity, ammoValue, magSize)); + projectileCount *= getProjectileCount(itemActionDataLauncher); + } + int times = 1; + IModuleContainerFor dataModule = actionData as IModuleContainerFor; + if (dataModule != null && dataModule.Instance.oneRoundMultishot) + { + times = dataModule.Instance.roundsPerShot; + } + for (int j = itemActionDataLauncher.projectileInstance.Count; j < projectileCount; j++) + { + for (int i = 0; i < times; i++) + { + if (dataModule != null) + { + itemActionDataLauncher.projectileJoint = dataModule.Instance.projectileJoints[i]; + } + itemActionDataLauncher.projectileInstance.Add(itemActionLauncher.instantiateProjectile(actionData, Vector3.zero)); + } + } + } + actionData.isReloading = true; + actionData.isWeaponReloading = true; + actionData.invData.holdingEntity.FireEvent(MinEventTypes.onReloadStart, true); +#if DEBUG + Log.Out($"ANIMATION EVENT RELOAD START : {actionData.invData.item.Name}"); +#endif + } + + private Coroutine cancelReloadCo = null; + + public void DelayForceCancelReload(float delay) + { + if (cancelReloadCo == null) + cancelReloadCo = StartCoroutine(ForceCancelReloadCo(delay)); + } + + private void OnDisable() + { + if (animator) + { + OnReloadEnd(); + } + } + + private IEnumerator ForceCancelReloadCo(float delay) + { + yield return new WaitForSecondsRealtime(delay); + if (actionData != null && (actionData.isReloading || actionData.isWeaponReloading) && (actionData.isReloadCancelled || actionData.isWeaponReloadCancelled)) + OnReloadEnd(); + cancelReloadCo = null; + } + + public int GetAmmoCountToReload(EntityAlive ea, ItemValue ammo, int modifiedMagazineSize) + { + int meta = actionData.invData.itemValue.Meta; + int target = modifiedMagazineSize - meta; + if (actionRanged.HasInfiniteAmmo(actionData)) + { + if (actionRanged.AmmoIsPerMagazine) + { + return modifiedMagazineSize; + } + return target; + } + + int res = 0; + if (ea.bag.GetItemCount(ammo, -1, -1, true) > 0) + { + if (actionRanged.AmmoIsPerMagazine) + { + return modifiedMagazineSize * ea.bag.DecItem(ammo, 1, false, null); + } + res = ea.bag.DecItem(ammo, target, false, null); + if (res == target) + { + return res; + } + } + + if (actionRanged.AmmoIsPerMagazine) + { + return modifiedMagazineSize * ea.inventory.DecItem(ammo, 1, false, null); + } + + if (ea.inventory.GetItemCount(ammo, false, -1, -1, true) <= 0) + { + return res; + } + return res + actionData.invData.holdingEntity.inventory.DecItem(ammo, target - res, false, null); + } + + public int GetPartialReloadCount(EntityAlive ea, ItemValue ammo, int modifiedMagazineSize, int partialReloadCount) + { + int meta = actionData.invData.itemValue.Meta; + int target = Mathf.Min(partialReloadCount, modifiedMagazineSize - meta); + if (actionRanged.HasInfiniteAmmo(actionData)) + { + return target; + } + + int res = 0; + if (ea.bag.GetItemCount(ammo) > 0) + { + res = ea.bag.DecItem(ammo, target); + if (res == target) + { + return res; + } + } + + if (ea.inventory.GetItemCount(ammo) <= 0) + { + return res; + } + return res + actionData.invData.holdingEntity.inventory.DecItem(ammo, target - res); + } + + public int GetAmmoCount(EntityAlive ea, ItemValue ammo, int modifiedMagazineSize) + { + return Mathf.Min(ea.bag.GetItemCount(ammo, -1, -1, true) + ea.inventory.GetItemCount(ammo, false, -1, -1, true) + actionData.invData.itemValue.Meta, modifiedMagazineSize); + } + + public int getProjectileCount(ItemActionData _data) + { + int rps = 1; + ItemInventoryData invD = _data != null ? _data.invData : null; + if (invD != null) + { + ItemClass item = invD.itemValue != null ? invD.itemValue.ItemClass : null; + rps = (int)EffectManager.GetValue(PassiveEffects.RoundRayCount, invD.itemValue, rps, invD.holdingEntity); + } + return rps > 0 ? rps : 1; + } + + public EntityAlive player; + public ItemActionRanged.ItemActionDataRanged actionData; + public ItemActionRanged actionRanged; +#endif + private Animator animator; + + public MonoBehaviour Init(Transform playerAnimatorTrans, bool isLocalPlayer) + { + var copy = playerAnimatorTrans.AddMissingComponent(); + if (copy) + { + copy.enabled = true; + } + return copy; + } + + public void Disable(Transform playerAnimatorTrans) + { + enabled = false; + } +} diff --git a/KFAttached/Animation/MonoBehaviours/AnimationReloadEvents.cs.meta b/KFAttached/Animation/MonoBehaviours/AnimationReloadEvents.cs.meta new file mode 100644 index 0000000..91dd052 --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimationReloadEvents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c245bc8d9e873ae40ac1f6078f9611c8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/MonoBehaviours/AnimationSmokeParticle.cs b/KFAttached/Animation/MonoBehaviours/AnimationSmokeParticle.cs new file mode 100644 index 0000000..ae904bb --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimationSmokeParticle.cs @@ -0,0 +1,17 @@ +using UnityEngine; + +public class AnimationSmokeParticle : MonoBehaviour +{ + private ParticleSystem ps; + + private void Awake() + { + if (!TryGetComponent(out ps)) + Destroy(this); + } + + private void OnEnable() + { + ps.Clear(true); + } +} diff --git a/KFAttached/Animation/MonoBehaviours/AnimationSmokeParticle.cs.meta b/KFAttached/Animation/MonoBehaviours/AnimationSmokeParticle.cs.meta new file mode 100644 index 0000000..0fe0883 --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimationSmokeParticle.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 173c51f67c407a94388cc82dddafc06f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/MonoBehaviours/AnimatorWrapper.meta b/KFAttached/Animation/MonoBehaviours/AnimatorWrapper.meta new file mode 100644 index 0000000..13c8ac2 --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimatorWrapper.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 74718732aa3d8274fb470008c5d9e819 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/MonoBehaviours/AnimatorWrapper/AnimatorWrapper.cs b/KFAttached/Animation/MonoBehaviours/AnimatorWrapper/AnimatorWrapper.cs new file mode 100644 index 0000000..5b7e20d --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimatorWrapper/AnimatorWrapper.cs @@ -0,0 +1,131 @@ +using System.Collections.Generic; +using UnityEngine; + +public class AnimatorWrapper : IAnimatorWrapper +{ + private Animator animator; + + public bool IsValid => animator; + + public AnimatorWrapper(Animator animator) => this.animator = animator; + + public void CrossFade(string stateName, float transitionDuration) => animator.CrossFade(stateName, transitionDuration); + + public void CrossFade(string stateName, float transitionDuration, int layer) => animator.CrossFade(stateName, transitionDuration, layer); + + public void CrossFade(string stateName, float transitionDuration, int layer, float normalizedTime) => animator.CrossFade(stateName, transitionDuration, layer, normalizedTime); + + public void CrossFade(int stateNameHash, float transitionDuration) => animator.CrossFade(stateNameHash, transitionDuration); + + public void CrossFade(int stateNameHash, float transitionDuration, int layer) => animator.CrossFade(stateNameHash, transitionDuration, layer); + + public void CrossFade(int stateNameHash, float transitionDuration, int layer, float normalizedTime) => animator.CrossFade(stateNameHash, transitionDuration, layer, normalizedTime); + + public void CrossFadeInFixedTime(string stateName, float transitionDuration) => animator.CrossFadeInFixedTime(stateName, transitionDuration); + + public void CrossFadeInFixedTime(string stateName, float transitionDuration, int layer) => animator.CrossFadeInFixedTime(stateName, transitionDuration, layer); + + public void CrossFadeInFixedTime(string stateName, float transitionDuration, int layer, float fixedTime) => animator.CrossFadeInFixedTime(stateName, transitionDuration, layer, fixedTime); + + public void CrossFadeInFixedTime(int stateNameHash, float transitionDuration) => animator.CrossFadeInFixedTime(stateNameHash, transitionDuration); + + public void CrossFadeInFixedTime(int stateNameHash, float transitionDuration, int layer) => animator.CrossFadeInFixedTime(stateNameHash, transitionDuration, layer); + + public void CrossFadeInFixedTime(int stateNameHash, float transitionDuration, int layer, float fixedTime) => animator.CrossFadeInFixedTime(stateNameHash, transitionDuration, layer, fixedTime); + + public AnimatorTransitionInfo GetAnimatorTransitionInfo(int layerIndex) => animator.GetAnimatorTransitionInfo(layerIndex); + + public bool GetBool(string name) => animator.GetBool(name); + + public bool GetBool(int id) => animator.GetBool(id); + + public AnimatorClipInfo[] GetCurrentAnimatorClipInfo(int layerIndex) => animator.GetCurrentAnimatorClipInfo(layerIndex); + + public void GetCurrentAnimatorClipInfo(int layerIndex, List clips) => animator.GetCurrentAnimatorClipInfo(layerIndex, clips); + + public int GetCurrentAnimatorClipInfoCount(int layerIndex) => animator.GetCurrentAnimatorClipInfoCount(layerIndex); + + public AnimatorStateInfo GetCurrentAnimatorStateInfo(int layerIndex) => animator.GetCurrentAnimatorStateInfo(layerIndex); + + public float GetFloat(string name) => animator.GetFloat(name); + + public float GetFloat(int id) => animator.GetFloat(id); + + public int GetInteger(string name) => animator.GetInteger(name); + + public int GetInteger(int id) => animator.GetInteger(id); + + public int GetLayerCount() => animator.layerCount; + + public int GetLayerIndex(string layerName) => animator.GetLayerIndex(layerName); + + public string GetLayerName(int layerIndex) => animator.GetLayerName(layerIndex); + + public float GetLayerWeight(int layerIndex) => animator.GetLayerWeight(layerIndex); + + public void GetNextAnimatorClipInfo(int layerIndex, List clips) => animator.GetNextAnimatorClipInfo(layerIndex, clips); + + public AnimatorClipInfo[] GetNextAnimatorClipInfo(int layerIndex) => animator.GetNextAnimatorClipInfo(layerIndex); + + public int GetNextAnimatorClipInfoCount(int layerIndex) => animator.GetNextAnimatorClipInfoCount(layerIndex); + + public AnimatorStateInfo GetNextAnimatorStateInfo(int layerIndex) => animator.GetNextAnimatorStateInfo(layerIndex); + + public AnimatorControllerParameter GetParameter(int index) => animator.GetParameter(index); + + public int GetParameterCount() => animator.parameterCount; + + public bool HasState(int layerIndex, int stateID) => animator.HasState(layerIndex, stateID); + + public bool IsInTransition(int layerIndex) => animator.IsInTransition(layerIndex); + + public bool IsParameterControlledByCurve(string name) => animator.IsParameterControlledByCurve(name); + + public bool IsParameterControlledByCurve(int id) => animator.IsParameterControlledByCurve(id); + + public void Play(string stateName) => animator.Play(stateName); + + public void Play(string stateName, int layer) => animator.Play(stateName, layer); + + public void Play(string stateName, int layer, float normalizedTime) => animator.Play(stateName, layer, normalizedTime); + + public void Play(int stateNameHash) => animator.Play(stateNameHash); + + public void Play(int stateNameHash, int layer) => animator.Play(stateNameHash, layer); + + public void Play(int stateNameHash, int layer, float normalizedTime) => animator.Play(stateNameHash, layer, normalizedTime); + + public void PlayInFixedTime(string stateName) => animator.PlayInFixedTime(stateName); + + public void PlayInFixedTime(string stateName, int layer) => animator.PlayInFixedTime(stateName, layer); + + public void PlayInFixedTime(string stateName, int layer, float fixedTime) => animator.PlayInFixedTime(stateName, layer, fixedTime); + + public void PlayInFixedTime(int stateNameHash) => animator.PlayInFixedTime(stateNameHash); + + public void PlayInFixedTime(int stateNameHash, int layer) => animator.PlayInFixedTime(stateNameHash, layer); + + public void PlayInFixedTime(int stateNameHash, int layer, float fixedTime) => animator.PlayInFixedTime(stateNameHash, layer, fixedTime); + + public void ResetTrigger(string name) => animator.ResetTrigger(name); + + public void ResetTrigger(int id) => animator.ResetTrigger(id); + + public void SetBool(string name, bool value) => animator.SetBool(name, value); + + public void SetBool(int id, bool value) => animator.SetBool(id, value); + + public void SetFloat(string name, float value) => animator.SetFloat(name, value); + + public void SetFloat(int id, float value) => animator.SetFloat(id, value); + + public void SetInteger(string name, int value) => animator.SetInteger(name, value); + + public void SetInteger(int id, int value) => animator.SetInteger(id, value); + + public void SetLayerWeight(int layerIndex, float weight) => animator.SetLayerWeight(layerIndex, weight); + + public void SetTrigger(string name) => animator.SetTrigger(name); + + public void SetTrigger(int id) => animator.SetTrigger(id); +} \ No newline at end of file diff --git a/KFAttached/Animation/MonoBehaviours/AnimatorWrapper/AnimatorWrapper.cs.meta b/KFAttached/Animation/MonoBehaviours/AnimatorWrapper/AnimatorWrapper.cs.meta new file mode 100644 index 0000000..127d6d2 --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimatorWrapper/AnimatorWrapper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 330523dd2a309f042a9aafdb565149c5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/MonoBehaviours/AnimatorWrapper/AttachmentWrapper.cs b/KFAttached/Animation/MonoBehaviours/AnimatorWrapper/AttachmentWrapper.cs new file mode 100644 index 0000000..f43abac --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimatorWrapper/AttachmentWrapper.cs @@ -0,0 +1,443 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +public class AttachmentWrapper : IAnimatorWrapper +{ + public Animator[] animators; + public AttachmentWrapper(Animator[] animators) + { + this.animators = animators; + } + + public bool IsValid => animators != null && animators.Length > 0; + + public void CrossFade(string stateName, float transitionDuration) + { + foreach (var animator in animators) + { + animator.CrossFade(stateName, transitionDuration); + } + } + + public void CrossFade(string stateName, float transitionDuration, int layer) + { + foreach (var animator in animators) + { + animator.CrossFade(stateName, transitionDuration, layer); + } + } + + public void CrossFade(string stateName, float transitionDuration, int layer, float normalizedTime) + { + foreach (var animator in animators) + { + animator.CrossFade(stateName, transitionDuration, layer, normalizedTime); + } + } + + public void CrossFade(int stateNameHash, float transitionDuration) + { + foreach (var animator in animators) + { + animator.CrossFade(stateNameHash, transitionDuration); + } + } + + public void CrossFade(int stateNameHash, float transitionDuration, int layer) + { + foreach (var animator in animators) + { + animator.CrossFade(stateNameHash, transitionDuration, layer); + } + } + + public void CrossFade(int stateNameHash, float transitionDuration, int layer, float normalizedTime) + { + foreach (var animator in animators) + { + animator.CrossFade(stateNameHash, transitionDuration, layer, normalizedTime); + } + } + + public void CrossFadeInFixedTime(string stateName, float transitionDuration) + { + foreach (var animator in animators) + { + animator.CrossFadeInFixedTime(stateName, transitionDuration); + } + } + + public void CrossFadeInFixedTime(string stateName, float transitionDuration, int layer) + { + foreach (var animator in animators) + { + animator.CrossFadeInFixedTime(stateName, transitionDuration, layer); + } + } + + public void CrossFadeInFixedTime(string stateName, float transitionDuration, int layer, float fixedTime) + { + foreach (var animator in animators) + { + animator.CrossFadeInFixedTime(stateName, transitionDuration, layer, fixedTime); + } + } + + public void CrossFadeInFixedTime(int stateNameHash, float transitionDuration) + { + foreach (var animator in animators) + { + animator.CrossFadeInFixedTime(stateNameHash, transitionDuration); + } + } + + public void CrossFadeInFixedTime(int stateNameHash, float transitionDuration, int layer) + { + foreach (var animator in animators) + { + animator.CrossFadeInFixedTime(stateNameHash, transitionDuration, layer); + } + } + + public void CrossFadeInFixedTime(int stateNameHash, float transitionDuration, int layer, float fixedTime) + { + foreach (var animator in animators) + { + animator.CrossFadeInFixedTime(stateNameHash, transitionDuration, layer, fixedTime); + } + } + + public AnimatorTransitionInfo GetAnimatorTransitionInfo(int layerIndex) + { + return animators[0].GetAnimatorTransitionInfo(layerIndex); + } + + public bool GetBool(string name) + { + return GetBool(Animator.StringToHash(name)); + } + + public bool GetBool(int id) + { + foreach (var animator in animators) + { + if (animator.GetBool(id)) + return true; + } + return false; + } + + public AnimatorClipInfo[] GetCurrentAnimatorClipInfo(int layerIndex) + { + return animators[0].GetCurrentAnimatorClipInfo(layerIndex); + } + + public void GetCurrentAnimatorClipInfo(int layerIndex, List clips) + { + animators[0].GetCurrentAnimatorClipInfo(layerIndex, clips); + } + + public int GetCurrentAnimatorClipInfoCount(int layerIndex) + { + return animators[0].GetCurrentAnimatorClipInfoCount(layerIndex); + } + + public AnimatorStateInfo GetCurrentAnimatorStateInfo(int layerIndex) + { + return animators[0].GetCurrentAnimatorStateInfo(layerIndex); + } + + public float GetFloat(string name) + { + return GetFloat(Animator.StringToHash(name)); + } + + public float GetFloat(int id) + { + foreach (var animator in animators) + { + float value = animator.GetFloat(id); + if (value != 0) + { + return value; + } + } + return 0; + } + + public int GetInteger(string name) + { + return GetInteger(Animator.StringToHash(name)); + } + + public int GetInteger(int id) + { + foreach (var animator in animators) + { + int value = animator.GetInteger(id); + if (value != 0) + { + return value; + } + } + return 0; + } + + public int GetLayerCount() + { + return animators[0].layerCount; + } + + public int GetLayerIndex(string layerName) + { + return animators[0].GetLayerIndex(layerName); + } + + public string GetLayerName(int layerIndex) + { + return animators[0].GetLayerName(layerIndex); + } + + public float GetLayerWeight(int layerIndex) + { + return animators[0].GetLayerWeight(layerIndex); + } + + public void GetNextAnimatorClipInfo(int layerIndex, List clips) + { + animators[0].GetNextAnimatorClipInfo(layerIndex, clips); + } + + public AnimatorClipInfo[] GetNextAnimatorClipInfo(int layerIndex) + { + return animators[0].GetNextAnimatorClipInfo(layerIndex); + } + + public int GetNextAnimatorClipInfoCount(int layerIndex) + { + return animators[0].GetNextAnimatorClipInfoCount(layerIndex); + } + + public AnimatorStateInfo GetNextAnimatorStateInfo(int layerIndex) + { + return animators[0].GetNextAnimatorStateInfo(layerIndex); + } + + public AnimatorControllerParameter GetParameter(int index) + { + return animators[0].GetParameter(index); + } + + public int GetParameterCount() + { + return animators[0].parameterCount; + } + + public bool HasState(int layerIndex, int stateID) + { + return animators[0].HasState(layerIndex, stateID); + } + + public bool IsInTransition(int layerIndex) + { + return animators[0].IsInTransition(layerIndex); + } + + public bool IsParameterControlledByCurve(string name) + { + return animators[0].IsParameterControlledByCurve(name); + } + + public bool IsParameterControlledByCurve(int id) + { + return animators[0].IsParameterControlledByCurve(id); + } + + public void Play(string stateName) + { + foreach (var animator in animators) + { + animator.Play(stateName); + } + } + + public void Play(string stateName, int layer) + { + foreach (var animator in animators) + { + animator.Play(stateName, layer); + } + } + + public void Play(string stateName, int layer, float normalizedTime) + { + foreach (var animator in animators) + { + animator.Play(stateName, layer, normalizedTime); + } + } + + public void Play(int stateNameHash) + { + foreach (var animator in animators) + { + animator.Play(stateNameHash); + } + } + + public void Play(int stateNameHash, int layer) + { + foreach (var animator in animators) + { + animator.Play(stateNameHash, layer); + } + } + + public void Play(int stateNameHash, int layer, float normalizedTime) + { + foreach (var animator in animators) + { + animator.Play(stateNameHash, layer, normalizedTime); + } + } + + public void PlayInFixedTime(string stateName) + { + foreach (var animator in animators) + { + animator.PlayInFixedTime(stateName); + } + } + + public void PlayInFixedTime(string stateName, int layer) + { + foreach (var animator in animators) + { + animator.PlayInFixedTime(stateName, layer); + } + } + + public void PlayInFixedTime(string stateName, int layer, float fixedTime) + { + foreach (var animator in animators) + { + animator.PlayInFixedTime(stateName, layer, fixedTime); + } + } + + public void PlayInFixedTime(int stateNameHash) + { + foreach (var animator in animators) + { + animator.PlayInFixedTime(stateNameHash); + } + } + + public void PlayInFixedTime(int stateNameHash, int layer) + { + foreach (var animator in animators) + { + animator.PlayInFixedTime(stateNameHash, layer); + } + } + + public void PlayInFixedTime(int stateNameHash, int layer, float fixedTime) + { + foreach (var animator in animators) + { + animator.PlayInFixedTime(stateNameHash, layer, fixedTime); + } + } + + public void ResetTrigger(string name) + { + foreach (var animator in animators) + { + animator.ResetTrigger(name); + } + } + + public void ResetTrigger(int id) + { + foreach (var animator in animators) + { + animator.ResetTrigger(id); + } + } + + public void SetBool(string name, bool value) + { + foreach (var animator in animators) + { + animator.SetBool(name, value); + } + } + + public void SetBool(int id, bool value) + { + foreach (var animator in animators) + { + animator.SetBool(id, value); + } + } + + public void SetFloat(string name, float value) + { + foreach (var animator in animators) + { + animator.SetFloat(name, value); + } + } + + public void SetFloat(int id, float value) + { + foreach (var animator in animators) + { + animator.SetFloat(id, value); + } + } + + public void SetInteger(string name, int value) + { + foreach (var animator in animators) + { + animator.SetInteger(name, value); + } + } + + public void SetInteger(int id, int value) + { + foreach (var animator in animators) + { + animator.SetInteger(id, value); + } + } + + public void SetLayerWeight(int layerIndex, float weight) + { + foreach (var animator in animators) + { + animator.SetLayerWeight(layerIndex, weight); + } + } + + public void SetTrigger(string name) + { + foreach (var animator in animators) + { + animator.SetTrigger(name); + } + } + + public void SetTrigger(int id) + { + foreach (var animator in animators) + { + animator.SetTrigger(id); + } + } +} diff --git a/KFAttached/Animation/MonoBehaviours/AnimatorWrapper/AttachmentWrapper.cs.meta b/KFAttached/Animation/MonoBehaviours/AnimatorWrapper/AttachmentWrapper.cs.meta new file mode 100644 index 0000000..ef17a9e --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimatorWrapper/AttachmentWrapper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9aff1989baed1884fb65a80108d5ab68 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/MonoBehaviours/AnimatorWrapper/PlayableWrapper.cs b/KFAttached/Animation/MonoBehaviours/AnimatorWrapper/PlayableWrapper.cs new file mode 100644 index 0000000..7ef959f --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimatorWrapper/PlayableWrapper.cs @@ -0,0 +1,92 @@ +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Animations; +using UnityEngine.Playables; + +public class PlayableWrapper : IAnimatorWrapper +{ + private AnimatorControllerPlayable playable; + + public PlayableWrapper(AnimatorControllerPlayable playable) + { + this.playable = playable; + } + + public bool IsValid => playable.IsValid(); + + public float GetFloat(string name) => playable.GetFloat(name); + public float GetFloat(int id) => playable.GetFloat(id); + public void SetFloat(string name, float value) => playable.SetFloat(name, value); + public void SetFloat(int id, float value) => playable.SetFloat(id, value); + + public bool GetBool(string name) => playable.GetBool(name); + public bool GetBool(int id) => playable.GetBool(id); + public void SetBool(string name, bool value) => playable.SetBool(name, value); + public void SetBool(int id, bool value) => playable.SetBool(id, value); + + public int GetInteger(string name) => playable.GetInteger(name); + public int GetInteger(int id) => playable.GetInteger(id); + public void SetInteger(string name, int value) => playable.SetInteger(name, value); + public void SetInteger(int id, int value) => playable.SetInteger(id, value); + + public void SetTrigger(string name) => playable.SetTrigger(name); + public void SetTrigger(int id) => playable.SetTrigger(id); + public void ResetTrigger(string name) => playable.ResetTrigger(name); + public void ResetTrigger(int id) => playable.ResetTrigger(id); + + public bool IsParameterControlledByCurve(string name) => playable.IsParameterControlledByCurve(name); + public bool IsParameterControlledByCurve(int id) => playable.IsParameterControlledByCurve(id); + + public int GetLayerCount() => playable.GetLayerCount(); + public string GetLayerName(int layerIndex) => playable.GetLayerName(layerIndex); + public int GetLayerIndex(string layerName) => playable.GetLayerIndex(layerName); + public float GetLayerWeight(int layerIndex) => playable.GetLayerWeight(layerIndex); + public void SetLayerWeight(int layerIndex, float weight) => playable.SetLayerWeight(layerIndex, weight); + + public AnimatorStateInfo GetCurrentAnimatorStateInfo(int layerIndex) => playable.GetCurrentAnimatorStateInfo(layerIndex); + public AnimatorStateInfo GetNextAnimatorStateInfo(int layerIndex) => playable.GetNextAnimatorStateInfo(layerIndex); + public AnimatorTransitionInfo GetAnimatorTransitionInfo(int layerIndex) => playable.GetAnimatorTransitionInfo(layerIndex); + + public AnimatorClipInfo[] GetCurrentAnimatorClipInfo(int layerIndex) => playable.GetCurrentAnimatorClipInfo(layerIndex); + public void GetCurrentAnimatorClipInfo(int layerIndex, List clips) => playable.GetCurrentAnimatorClipInfo(layerIndex, clips); + public void GetNextAnimatorClipInfo(int layerIndex, List clips) => playable.GetNextAnimatorClipInfo(layerIndex, clips); + public int GetCurrentAnimatorClipInfoCount(int layerIndex) => playable.GetCurrentAnimatorClipInfoCount(layerIndex); + public int GetNextAnimatorClipInfoCount(int layerIndex) => playable.GetNextAnimatorClipInfoCount(layerIndex); + public AnimatorClipInfo[] GetNextAnimatorClipInfo(int layerIndex) => playable.GetNextAnimatorClipInfo(layerIndex); + + public bool IsInTransition(int layerIndex) => playable.IsInTransition(layerIndex); + + public int GetParameterCount() => playable.GetParameterCount(); + public AnimatorControllerParameter GetParameter(int index) => playable.GetParameter(index); + + public void CrossFadeInFixedTime(string stateName, float transitionDuration) => playable.CrossFadeInFixedTime(stateName, transitionDuration); + public void CrossFadeInFixedTime(string stateName, float transitionDuration, int layer) => playable.CrossFadeInFixedTime(stateName, transitionDuration, layer); + public void CrossFadeInFixedTime(string stateName, float transitionDuration, int layer, float fixedTime) => playable.CrossFadeInFixedTime(stateName, transitionDuration, layer, fixedTime); + public void CrossFadeInFixedTime(int stateNameHash, float transitionDuration) => playable.CrossFadeInFixedTime(stateNameHash, transitionDuration); + public void CrossFadeInFixedTime(int stateNameHash, float transitionDuration, int layer) => playable.CrossFadeInFixedTime(stateNameHash, transitionDuration, layer); + public void CrossFadeInFixedTime(int stateNameHash, float transitionDuration, int layer, float fixedTime) => playable.CrossFadeInFixedTime(stateNameHash, transitionDuration, layer, fixedTime); + + public void CrossFade(string stateName, float transitionDuration) => playable.CrossFade(stateName, transitionDuration); + public void CrossFade(string stateName, float transitionDuration, int layer) => playable.CrossFade(stateName, transitionDuration, layer); + public void CrossFade(string stateName, float transitionDuration, int layer, float normalizedTime) => playable.CrossFade(stateName, transitionDuration, layer, normalizedTime); + public void CrossFade(int stateNameHash, float transitionDuration) => playable.CrossFade(stateNameHash, transitionDuration); + public void CrossFade(int stateNameHash, float transitionDuration, int layer) => playable.CrossFade(stateNameHash, transitionDuration, layer); + public void CrossFade(int stateNameHash, float transitionDuration, int layer, float normalizedTime) => playable.CrossFade(stateNameHash, transitionDuration, layer, normalizedTime); + + public void PlayInFixedTime(string stateName) => playable.PlayInFixedTime(stateName); + public void PlayInFixedTime(string stateName, int layer) => playable.PlayInFixedTime(stateName, layer); + public void PlayInFixedTime(string stateName, int layer, float fixedTime) => playable.PlayInFixedTime(stateName, layer, fixedTime); + public void PlayInFixedTime(int stateNameHash) => playable.PlayInFixedTime(stateNameHash); + public void PlayInFixedTime(int stateNameHash, int layer) => playable.PlayInFixedTime(stateNameHash, layer); + public void PlayInFixedTime(int stateNameHash, int layer, float fixedTime) => playable.PlayInFixedTime(stateNameHash, layer, fixedTime); + + public void Play(string stateName) => playable.Play(stateName); + public void Play(string stateName, int layer) => playable.Play(stateName, layer); + public void Play(string stateName, int layer, float normalizedTime) => playable.Play(stateName, layer, normalizedTime); + public void Play(int stateNameHash) => playable.Play(stateNameHash); + public void Play(int stateNameHash, int layer) => playable.Play(stateNameHash, layer); + public void Play(int stateNameHash, int layer, float normalizedTime) => playable.Play(stateNameHash, layer, normalizedTime); + + public bool HasState(int layerIndex, int stateID) => playable.HasState(layerIndex, stateID); +} + diff --git a/KFAttached/Animation/MonoBehaviours/AnimatorWrapper/PlayableWrapper.cs.meta b/KFAttached/Animation/MonoBehaviours/AnimatorWrapper/PlayableWrapper.cs.meta new file mode 100644 index 0000000..cce2821 --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/AnimatorWrapper/PlayableWrapper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: af94698a131639a4ba070db09410d746 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/MonoBehaviours/IAnimatorWrapper.cs b/KFAttached/Animation/MonoBehaviours/IAnimatorWrapper.cs new file mode 100644 index 0000000..11699af --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/IAnimatorWrapper.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.Animations; +using UnityEngine.Playables; + +public interface IAnimatorWrapper +{ + bool IsValid { get; } + float GetFloat(string name); + float GetFloat(int id); + void SetFloat(string name, float value); + void SetFloat(int id, float value); + bool GetBool(string name); + bool GetBool(int id); + void SetBool(string name, bool value); + void SetBool(int id, bool value); + int GetInteger(string name); + int GetInteger(int id); + void SetInteger(string name, int value); + void SetInteger(int id, int value); + void SetTrigger(string name); + void SetTrigger(int id); + void ResetTrigger(string name); + void ResetTrigger(int id); + bool IsParameterControlledByCurve(string name); + bool IsParameterControlledByCurve(int id); + int GetLayerCount(); + string GetLayerName(int layerIndex); + int GetLayerIndex(string layerName); + float GetLayerWeight(int layerIndex); + void SetLayerWeight(int layerIndex, float weight); + AnimatorStateInfo GetCurrentAnimatorStateInfo(int layerIndex); + AnimatorStateInfo GetNextAnimatorStateInfo(int layerIndex); + AnimatorTransitionInfo GetAnimatorTransitionInfo(int layerIndex); + AnimatorClipInfo[] GetCurrentAnimatorClipInfo(int layerIndex); + void GetCurrentAnimatorClipInfo(int layerIndex, List clips); + void GetNextAnimatorClipInfo(int layerIndex, List clips); + int GetCurrentAnimatorClipInfoCount(int layerIndex); + int GetNextAnimatorClipInfoCount(int layerIndex); + AnimatorClipInfo[] GetNextAnimatorClipInfo(int layerIndex); + bool IsInTransition(int layerIndex); + int GetParameterCount(); + AnimatorControllerParameter GetParameter(int index); + void CrossFadeInFixedTime(string stateName, float transitionDuration); + void CrossFadeInFixedTime(string stateName, float transitionDuration, int layer); + void CrossFadeInFixedTime(string stateName, float transitionDuration, int layer, float fixedTime); + void CrossFadeInFixedTime(int stateNameHash, float transitionDuration); + void CrossFadeInFixedTime(int stateNameHash, float transitionDuration, int layer); + void CrossFadeInFixedTime(int stateNameHash, float transitionDuration, int layer, float fixedTime); + void CrossFade(string stateName, float transitionDuration); + void CrossFade(string stateName, float transitionDuration, int layer); + void CrossFade(string stateName, float transitionDuration, int layer, float normalizedTime); + void CrossFade(int stateNameHash, float transitionDuration); + void CrossFade(int stateNameHash, float transitionDuration, int layer); + void CrossFade(int stateNameHash, float transitionDuration, int layer, float normalizedTime); + void PlayInFixedTime(string stateName); + void PlayInFixedTime(string stateName, int layer); + void PlayInFixedTime(string stateName, int layer, float fixedTime); + void PlayInFixedTime(int stateNameHash); + void PlayInFixedTime(int stateNameHash, int layer); + void PlayInFixedTime(int stateNameHash, int layer, float fixedTime); + void Play(string stateName); + void Play(string stateName, int layer); + void Play(string stateName, int layer, float normalizedTime); + void Play(int stateNameHash); + void Play(int stateNameHash, int layer); + void Play(int stateNameHash, int layer, float normalizedTime); + bool HasState(int layerIndex, int stateID); +} \ No newline at end of file diff --git a/KFAttached/Animation/MonoBehaviours/IAnimatorWrapper.cs.meta b/KFAttached/Animation/MonoBehaviours/IAnimatorWrapper.cs.meta new file mode 100644 index 0000000..971660e --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/IAnimatorWrapper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3b52da8de87636d42aa5130c88c5fe4d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/MonoBehaviours/IPlayableGraphRelated.cs b/KFAttached/Animation/MonoBehaviours/IPlayableGraphRelated.cs new file mode 100644 index 0000000..59496b8 --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/IPlayableGraphRelated.cs @@ -0,0 +1,7 @@ +using UnityEngine; + +public interface IPlayableGraphRelated +{ + MonoBehaviour Init(Transform playerAnimatorTrans, bool isLocalPlayer); + void Disable(Transform playerAnimatorTrans); +} \ No newline at end of file diff --git a/KFAttached/Animation/MonoBehaviours/IPlayableGraphRelated.cs.meta b/KFAttached/Animation/MonoBehaviours/IPlayableGraphRelated.cs.meta new file mode 100644 index 0000000..9f38484 --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/IPlayableGraphRelated.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 952699e2d9986c94bb12291f3d5e948a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/MonoBehaviours/RigWeightOverTime.cs b/KFAttached/Animation/MonoBehaviours/RigWeightOverTime.cs new file mode 100644 index 0000000..59b256f --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/RigWeightOverTime.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections; +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("KFAttachments/Utils/Rig Weight Over Time")] +public class RigWeightOverTime : MonoBehaviour +{ + //[SerializeField] + //private Transform source; + //[SerializeField] + //private Transform target; + [SerializeReference] + private Rig[] rigs; + //[SerializeField] + //private float distanceThreshold; + //[SerializeField] + //private float distanceMax; + //private float distanceRange; + + private (Coroutine co, bool active) copair; + ////[SerializeField] + ////private bool logDistance = false; + + //private void Awake() + //{ + // distanceRange = distanceMax - distanceThreshold; + // if (distanceRange == 0) + // { + // throw new DivideByZeroException("Max distance is equal to threshold distance!"); + // } + //} + + public void OnEnable() + { + if (rigs != null) + { + SetWeight(1); + } + } + + public void OnDisable() + { + if (copair.co != null) + { + StopCoroutine(copair.co); + } + SetWeight(0); + } + + public void SetRigWeight(AnimationEvent ev) + { + if (copair.co != null) + { + StopCoroutine(copair.co); + } + bool active = Convert.ToBoolean(ev.intParameter); + copair = (StartCoroutine(UpdateWeight(ev.floatParameter, active)), active); + } + + private IEnumerator UpdateWeight(float time, bool active) + { + if (rigs == null) + { + yield break; + } + + if (time == 0) + { + SetWeight(active ? 1 : 0); + yield break; + } + + float curTime = 0; + while (curTime < time) + { + float ratio = curTime / time; + float weight = Mathf.Lerp(0, 1, active ? ratio : (1 - ratio)); + SetWeight(weight); + curTime += Time.deltaTime; + Log.Out("Set weight: " + weight); + yield return null; + } + SetWeight(active ? 1 : 0); + } + + public void SetWeight(float weight) + { + foreach (var rig in rigs) + { + rig.weight = weight; + } + } + + // private void Update() + // { + // StartCoroutine(UpdateWeight()); + // } + + // private IEnumerator UpdateWeight() + // { + // if(distanceRange == 0 || rigs == null) + // { + // yield break; + // } + // yield return new WaitForEndOfFrame(); + // float distance = Vector3.Distance(source.position, target.position); + // float weight = Mathf.Lerp(0, 1, (distanceMax - distance) / distanceRange); + // foreach (Rig rig in rigs) + // { + // rig.weight = Mathf.Lerp(rig.weight, weight, 0.5f); + // if(weight > 0 && weight < 1) + // Log.Out("ratio: " + ((distanceMax - distance) / distanceRange).ToString() + " weight: " + weight.ToString()); + // } + + //#if UNITY_EDITOR + // if (logDistance) + // { + // Log.Out(Vector3.Distance(source.position, target.position).ToString()); + // } + //#endif + // } +} \ No newline at end of file diff --git a/KFAttached/Animation/MonoBehaviours/RigWeightOverTime.cs.meta b/KFAttached/Animation/MonoBehaviours/RigWeightOverTime.cs.meta new file mode 100644 index 0000000..1c6bb50 --- /dev/null +++ b/KFAttached/Animation/MonoBehaviours/RigWeightOverTime.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3b22553f7e0b6104dac02ca4c8af2c99 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/StateMachineBehaviours.meta b/KFAttached/Animation/StateMachineBehaviours.meta new file mode 100644 index 0000000..6f38ce2 --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 664ef95b745768746a23509d7b250fdd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/StateMachineBehaviours/AnimationAimRecoilResetState.cs b/KFAttached/Animation/StateMachineBehaviours/AnimationAimRecoilResetState.cs new file mode 100644 index 0000000..04e1aff --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours/AnimationAimRecoilResetState.cs @@ -0,0 +1,14 @@ +using UnityEngine; + +public class AnimationAimRecoilResetState : StateMachineBehaviour +{ + public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + animator.GetComponent()?.Rollback(); + } + + public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + animator.GetComponent()?.Rollback(); + } +} diff --git a/KFAttached/Animation/StateMachineBehaviours/AnimationAimRecoilResetState.cs.meta b/KFAttached/Animation/StateMachineBehaviours/AnimationAimRecoilResetState.cs.meta new file mode 100644 index 0000000..683f40c --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours/AnimationAimRecoilResetState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 248236ebc388e4c4d8e3f700b7444ab6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/StateMachineBehaviours/AnimationAmmoUpdateState.cs b/KFAttached/Animation/StateMachineBehaviours/AnimationAmmoUpdateState.cs new file mode 100644 index 0000000..184fea9 --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours/AnimationAmmoUpdateState.cs @@ -0,0 +1,66 @@ +#if NotEditor +using KFCommonUtilityLib.Scripts.StaticManagers; +using KFCommonUtilityLib.Scripts.Utilities; +#endif +using UnityEngine; + +public class AnimationAmmoUpdateState : StateMachineBehaviour +{ +#if NotEditor + private static int[] hash_states = new[] + { + Animator.StringToHash("ammoCount"), + Animator.StringToHash("ammoCount1"), + Animator.StringToHash("ammoCount2"), + Animator.StringToHash("ammoCount3"), + Animator.StringToHash("ammoCount4") + }; + private InventorySlotGurad slotGuard = new InventorySlotGurad(); + + public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + //var player = GameManager.Instance.World?.GetPrimaryPlayer(); + var player = animator.GetComponentInParent(); + if(slotGuard.IsValid(player)) + { + SetAmmoCountForEntity(player, slotGuard.Slot); + } + } + + public static void SetAmmoCountForEntity(EntityAlive entity, int slot) + { + if (entity) + { + var invData = entity.inventory?.slots?[slot]; + if (invData?.actionData != null) + { + var mapping = MultiActionManager.GetMappingForEntity(entity.entityId); + if (mapping != null) + { + var metaIndices = mapping.indices; + for (int i = 0; i < mapping.ModeCount; i++) + { + int metaIndex = metaIndices.GetMetaIndexForMode(i); + int meta = invData.itemValue.GetMetaByMode(i); + entity.emodel.avatarController.UpdateInt(hash_states[metaIndex], meta); + if (ConsoleCmdReloadLog.LogInfo) + { + Log.Out($"Setting ammoCount{(metaIndex > 0 ? metaIndex.ToString() : "")} to {meta}, stack trace:\n{StackTraceUtility.ExtractStackTrace()}"); + } + //animator.SetInteger(hash_states[metaIndex], meta); + } + } + else + { + entity.emodel.avatarController.UpdateInt(hash_states[0], invData.itemValue.Meta); + if (ConsoleCmdReloadLog.LogInfo) + { + Log.Out($"Setting ammoCount to {invData.itemValue.Meta}, stack trace:\n{StackTraceUtility.ExtractStackTrace()}"); + } + //animator.SetInteger(hash_states[0], entity.inventory.holdingItemItemValue.Meta); + } + } + } + } +#endif +} \ No newline at end of file diff --git a/KFAttached/Animation/StateMachineBehaviours/AnimationAmmoUpdateState.cs.meta b/KFAttached/Animation/StateMachineBehaviours/AnimationAmmoUpdateState.cs.meta new file mode 100644 index 0000000..d109c5f --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours/AnimationAmmoUpdateState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 87658b6a8bedcd14e83febff2157048c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/StateMachineBehaviours/AnimationCustomMeleeAttackState.cs b/KFAttached/Animation/StateMachineBehaviours/AnimationCustomMeleeAttackState.cs new file mode 100644 index 0000000..cd000c8 --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours/AnimationCustomMeleeAttackState.cs @@ -0,0 +1,266 @@ +using System.Collections; +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +using UnityEditor.Animations; +#endif + +public class AnimationCustomMeleeAttackState : StateMachineBehaviour +#if UNITY_EDITOR + , ISerializationCallbackReceiver +#endif +{ + public float RaycastTime = 0.3f; + public float CustomGrazeCastTime = 0.3f; + public float CustomGrazeCastDuration = 0f; + public float ImpactDuration = 0.01f; + [Range(0.01f, 1f)] + public float ImpactPlaybackSpeed = 1f; + [Range(0.01f, 1f)] + public float attackDurationNormalized = 1f; + [Range(0f, 360f)] + public float SwingAngle = 0f; + [Range(-180f, 180f)] + public float SwingDegrees = 0f; + + [SerializeField] + private float ClipLength = 0f; + +#if UNITY_EDITOR + public void OnBeforeSerialize() + { + var context = AnimatorController.FindStateMachineBehaviourContext(this); + if (context != null && context.Length > 0) + { + var state = context[0].animatorObject as AnimatorState; + if (state != null) + { + var clip = state.motion as AnimationClip; + if (clip != null) + { + ClipLength = clip.length; + } + } + } + } + + public void OnAfterDeserialize() + { + } +#endif + +#if NotEditor + private readonly int AttackSpeedHash = Animator.StringToHash("MeleeAttackSpeed"); + private float calculatedRaycastTime; + private float calculatedGrazeTime; + private float calculatedGrazeDuration; + private float calculatedImpactDuration; + private float calculatedImpactPlaybackSpeed; + private bool hasFired; + private int actionIndex; + private float originalMeleeAttackSpeed; + //private bool playingImpact; + private EntityAlive entity; + private float attacksPerMinute; + private float speedMultiplierToKeep = 1f; + private InventorySlotGurad slotGurad = new InventorySlotGurad(); + + public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + //if (playingImpact) + //{ + // return; + //} + hasFired = false; + actionIndex = animator.GetWrappedInt(AvatarController.itemActionIndexHash); + entity = animator.GetComponentInParent(); + if (!slotGurad.IsValid(entity)) + { + return; + } + //Log.Out("State entered!"); + //AnimatorClipInfo[] array = animator.GetNextAnimatorClipInfo(layerIndex); + float length = ClipLength * attackDurationNormalized; + ////if (array.Length == 0) + ////{ + // var array = animator.GetCurrentAnimatorClipInfo(layerIndex); + // if (array.Length == 0) + // { + // if (float.IsInfinity(stateInfo.length)) + // { + // Log.Out($"Invalid clips!"); + // return; + // } + // length = stateInfo.length; + // } + // else + // { + // length = array[0].clip.length; + // } + ////} + ////else + ////{ + //// length = array[0].clip.length; + ////} + //length *= attackDurationNormalized; + attacksPerMinute = 60f / length; + FastTags fastTags = ((actionIndex != 1) ? ItemActionAttack.PrimaryTag : ItemActionAttack.SecondaryTag); + ItemValue holdingItemItemValue = entity.inventory.holdingItemItemValue; + ItemClass itemClass = holdingItemItemValue.ItemClass; + if (itemClass != null) + { + fastTags |= itemClass.ItemTags; + } + originalMeleeAttackSpeed = EffectManager.GetValue(PassiveEffects.AttacksPerMinute, holdingItemItemValue, attacksPerMinute, entity, null, fastTags) / 60f * length; + animator.SetWrappedFloat(AttackSpeedHash, originalMeleeAttackSpeed); + speedMultiplierToKeep = originalMeleeAttackSpeed; + ItemClass holdingItem = entity.inventory.holdingItem; + holdingItem.Properties.ParseFloat((actionIndex != 1) ? "Action0.RaycastTime" : "Action1.RaycastTime", ref RaycastTime); + float impactDuration = -1f; + holdingItem.Properties.ParseFloat((actionIndex != 1) ? "Action0.ImpactDuration" : "Action1.ImpactDuration", ref impactDuration); + if (impactDuration >= 0f) + { + ImpactDuration = impactDuration * originalMeleeAttackSpeed; + } + holdingItem.Properties.ParseFloat((actionIndex != 1) ? "Action0.ImpactPlaybackSpeed" : "Action1.ImpactPlaybackSpeed", ref ImpactPlaybackSpeed); + if (originalMeleeAttackSpeed != 0f) + { + calculatedRaycastTime = RaycastTime / originalMeleeAttackSpeed; + calculatedGrazeTime = CustomGrazeCastTime / originalMeleeAttackSpeed; + calculatedGrazeDuration = CustomGrazeCastDuration / originalMeleeAttackSpeed; + calculatedImpactDuration = ImpactDuration / originalMeleeAttackSpeed; + calculatedImpactPlaybackSpeed = ImpactPlaybackSpeed * originalMeleeAttackSpeed; + } + else + { + calculatedRaycastTime = 0.001f; + calculatedGrazeTime = 0.001f; + calculatedGrazeDuration = 0.001f; + calculatedImpactDuration = 0.001f; + calculatedImpactPlaybackSpeed = 0.001f; + } + if (ConsoleCmdReloadLog.LogInfo) + { + Log.Out($"original: raycast time {RaycastTime} impact duration {ImpactDuration} impact playback speed {ImpactPlaybackSpeed} clip length {length}/{stateInfo.length}"); + Log.Out($"calculated: raycast time {calculatedRaycastTime} impact duration {calculatedImpactDuration} impact playback speed {calculatedImpactPlaybackSpeed} speed multiplier {originalMeleeAttackSpeed}"); + } + GameManager.Instance.StartCoroutine(impactStart(animator, layerIndex, length)); + GameManager.Instance.StartCoroutine(customGrazeStart(length)); + } + + private IEnumerator impactStart(Animator animator, int layer, float length) + { + yield return new WaitForSeconds(Mathf.Max(calculatedRaycastTime, 0)); + if (!hasFired) + { + hasFired = true; + if (entity != null && !entity.isEntityRemote && actionIndex >= 0) + { + ItemActionDynamicMelee.ItemActionDynamicMeleeData itemActionDynamicMeleeData = entity.inventory.holdingItemData.actionData[actionIndex] as ItemActionDynamicMelee.ItemActionDynamicMeleeData; + if (itemActionDynamicMeleeData != null) + { + if ((entity.inventory.holdingItem.Actions[actionIndex] as ItemActionDynamicMelee).Raycast(itemActionDynamicMeleeData)) + { + GameManager.Instance.StartCoroutine(impactStop(animator, layer, length)); + } + } + } + } + yield break; + } + + private IEnumerator impactStop(Animator animator, int layer, float length) + { + //playingImpact = true; + //animator.Play(0, layer, Mathf.Min(1f, calculatedRaycastTime * attackDurationNormalized / length)); + if (animator) + { + //Log.Out("Impact start!"); + animator.SetWrappedFloat(AttackSpeedHash, calculatedImpactPlaybackSpeed); + } + speedMultiplierToKeep = calculatedImpactPlaybackSpeed; + yield return new WaitForSeconds(calculatedImpactDuration); + if (animator) + { + //Log.Out("Impact stop!"); + animator.SetWrappedFloat(AttackSpeedHash, originalMeleeAttackSpeed); + } + speedMultiplierToKeep = originalMeleeAttackSpeed; + //playingImpact = false; + yield break; + } + + private IEnumerator customGrazeStart(float length) + { + if (ConsoleCmdReloadLog.LogInfo) + { + Log.Out($"Custom graze time: {calculatedGrazeTime} original {CustomGrazeCastTime}"); + } + yield return new WaitForSeconds(calculatedGrazeTime); + if (entity != null && !entity.isEntityRemote && actionIndex >= 0) + { + ItemActionDynamicMelee.ItemActionDynamicMeleeData itemActionDynamicMeleeData = entity.inventory.holdingItemData.actionData[actionIndex] as ItemActionDynamicMelee.ItemActionDynamicMeleeData; + if (itemActionDynamicMeleeData != null) + { + GameManager.Instance.StartCoroutine(customGrazeUpdate(itemActionDynamicMeleeData)); + } + } + } + + private IEnumerator customGrazeUpdate(ItemActionDynamicMelee.ItemActionDynamicMeleeData data) + { + if (ConsoleCmdReloadLog.LogInfo) + { + Log.Out($"Custom graze duration: {calculatedGrazeDuration} original {CustomGrazeCastDuration}"); + } + if (calculatedGrazeDuration <= 0f) + { + yield break; + } + float grazeStart = Time.time; + float normalizedTime = 0f; + var action = entity.inventory.holdingItem.Actions[actionIndex] as ItemActionDynamicMelee; + while (normalizedTime <= 1) + { + if (!slotGurad.IsValid(data.invData.holdingEntity)) + { + Log.Out($"Invalid graze!"); + yield break; + } + float originalSwingAngle = action.SwingAngle; + float originalSwingDegrees = action.SwingDegrees; + action.SwingAngle = SwingAngle; + action.SwingDegrees = SwingDegrees; + bool grazeResult = action.GrazeCast(data, normalizedTime); + if (ConsoleCmdReloadLog.LogInfo) + { + Log.Out($"GrazeCast {grazeResult}!"); + } + action.SwingAngle = originalSwingAngle; + action.SwingDegrees = originalSwingDegrees; + yield return null; + normalizedTime = (Time.time - grazeStart) / calculatedGrazeDuration; + } + yield break; + } + + public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + //if (entity != null && !entity.isEntityRemote && actionIndex >= 0 && entity.inventory.holdingItemData.actionData[actionIndex] is ItemActionDynamicMelee.ItemActionDynamicMeleeData) + //{ + // animator.SetFloat(AttackSpeedHash, originalMeleeAttackSpeed); + //} + animator.SetWrappedFloat(AttackSpeedHash, originalMeleeAttackSpeed); + } + + public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + //float normalizedTime = stateInfo.normalizedTime; + //if (float.IsInfinity(normalizedTime) || float.IsNaN(normalizedTime)) + //{ + // animator.Play(animator.GetNextAnimatorStateInfo(layerIndex).shortNameHash, layerIndex); + //} + animator.SetWrappedFloat(AttackSpeedHash, speedMultiplierToKeep); + } +#endif +} \ No newline at end of file diff --git a/KFAttached/Animation/StateMachineBehaviours/AnimationCustomMeleeAttackState.cs.meta b/KFAttached/Animation/StateMachineBehaviours/AnimationCustomMeleeAttackState.cs.meta new file mode 100644 index 0000000..05aad62 --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours/AnimationCustomMeleeAttackState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fef84a89943f52c418487a560eb789c3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/StateMachineBehaviours/AnimationCustomReloadState.cs b/KFAttached/Animation/StateMachineBehaviours/AnimationCustomReloadState.cs new file mode 100644 index 0000000..393d996 --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours/AnimationCustomReloadState.cs @@ -0,0 +1,94 @@ +#if NotEditor +using KFCommonUtilityLib.Scripts.StaticManagers; +#endif +using UnityEngine; + +public class AnimationCustomReloadState : StateMachineBehaviour +{ + [SerializeField] + private float ForceCancelReloadDelay = 1f; + [SerializeField] + private bool DoNotForceCancel = false; + + public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + animator.speed = 1f; + animator.SetWrappedBool(Animator.StringToHash("Reload"), false); + animator.SetWrappedBool(Animator.StringToHash("IsReloading"), true); +#if NotEditor + if (player == null) + { + player = animator.GetComponentInParent(); + } + int actionIndex = MultiActionManager.GetActionIndexForEntity(player); +#if DEBUG + Log.Out($"start reload {actionIndex}"); +#endif + actionData = player.inventory.holdingItemData.actionData[actionIndex] as ItemActionRanged.ItemActionDataRanged; + if (eventBridge == null) + { + eventBridge = animator.GetComponent(); + } + +#if DEBUG + Log.Out($"ANIMATOR STATE ENTER : {actionData.invData.item.Name}"); +#endif + eventBridge.OnReloadStart(actionIndex); + //eventBridge.OnReloadUpdate(); +#endif + } + + public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + animator.speed = 1f; +#if NotEditor + //eventBridge.OnReloadUpdate(); + if (actionData == null) + { + return; + } + if (actionData.isReloading) + { + eventBridge.OnReloadFinish(); + } +#endif + //actionData.isReloading = false; + //actionData.isReloadCancelled = false; + //actionData.isChangingAmmoType = false; +#if DEBUG && NotEditor + Log.Out($"ANIMATOR STATE EXIT : {actionData.invData.item.Name}"); +#endif + } + +#if NotEditor + public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + //eventBridge.OnReloadUpdate(); + if (actionData == null) + { + return; + } + if (actionData.isReloadCancelled) + { + animator.speed = 30f; + + if (!DoNotForceCancel) + { + eventBridge.DelayForceCancelReload(ForceCancelReloadDelay); + } +#if DEBUG + Log.Out($"ANIMATOR UPDATE: RELOAD CANCELLED, ANIMATOR SPEED {animator.speed}"); +#endif + } + if (!actionData.isReloadCancelled && actionData.isReloading) + { + actionData.invData.holdingEntity.MinEventContext.ItemActionData = actionData; + actionData.invData.holdingEntity.FireEvent(MinEventTypes.onReloadUpdate, true); + } + } + + private ItemActionRanged.ItemActionDataRanged actionData; + private EntityAlive player; + private AnimationReloadEvents eventBridge; +#endif +} \ No newline at end of file diff --git a/KFAttached/Animation/StateMachineBehaviours/AnimationCustomReloadState.cs.meta b/KFAttached/Animation/StateMachineBehaviours/AnimationCustomReloadState.cs.meta new file mode 100644 index 0000000..b347794 --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours/AnimationCustomReloadState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8f156c87129f85044a3336ade4d1af2c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/StateMachineBehaviours/AnimationInspectFix.cs b/KFAttached/Animation/StateMachineBehaviours/AnimationInspectFix.cs new file mode 100644 index 0000000..a8da630 --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours/AnimationInspectFix.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.Animations.Rigging; + +public class AnimationInspectFix : MonoBehaviour, IPlayableGraphRelated +{ + [SerializeField] + private string inspectName = "Inspect"; + [SerializeField] + private int layer = 0; + [SerializeField, Range(0, 1)] + private float finishTime = 1; + [SerializeField] + private bool useStateTag = false; + private static int inspectHash = Animator.StringToHash("weaponInspect"); + private IAnimatorWrapper wrapper; + + private void Awake() + { + } + + private void Update() + { + if (wrapper == null || !wrapper.IsValid) + { + var animator = GetComponent(); + if (!animator) + { + Destroy(this); + return; + } + wrapper = animator.GetItemAnimatorWrapper(); + } + if (useStateTag) + { + var stateInfo = wrapper.GetCurrentAnimatorStateInfo(layer); + if (stateInfo.IsTag(inspectName) && stateInfo.normalizedTime < finishTime) + { + wrapper.ResetTrigger(inspectHash); + } + } + else + { + var transInfo = wrapper.GetAnimatorTransitionInfo(layer); + if (transInfo.IsUserName(inspectName) && transInfo.normalizedTime < finishTime) + { + wrapper.ResetTrigger(inspectHash); + } + } + } + + public MonoBehaviour Init(Transform playerAnimatorTrans, bool isLocalPlayer) + { + enabled = false; + var copy = isLocalPlayer ? playerAnimatorTrans.AddMissingComponent() : null; + if (copy) + { + copy.enabled = true; + } + return copy; + } + + public void Disable(Transform playerAnimatorTrans) + { + enabled = false; + } +} \ No newline at end of file diff --git a/KFAttached/Animation/StateMachineBehaviours/AnimationInspectFix.cs.meta b/KFAttached/Animation/StateMachineBehaviours/AnimationInspectFix.cs.meta new file mode 100644 index 0000000..a6f7481 --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours/AnimationInspectFix.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 26c7436085cc1924a882f62d81f262a6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/StateMachineBehaviours/AnimationLockAction.cs b/KFAttached/Animation/StateMachineBehaviours/AnimationLockAction.cs new file mode 100644 index 0000000..0be35dc --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours/AnimationLockAction.cs @@ -0,0 +1,41 @@ +#if NotEditor +using KFCommonUtilityLib; +using KFCommonUtilityLib.Scripts.StaticManagers; +#endif +using UnityEngine; + +public class AnimationLockAction : StateMachineBehaviour +{ + public bool lockReload = false; +#if NotEditor + private InventorySlotGurad slotGuard = new InventorySlotGurad(); + public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + var player = animator.GetComponentInParent(); + if (slotGuard.IsValid(player)) + { + if (player.inventory.holdingItemData.actionData[MultiActionManager.GetActionIndexForEntity(player)] is IModuleContainerFor lockData) + { + lockData.Instance.isLocked = true; + if (lockReload) + { + lockData.Instance.isReloadLocked = true; + } + } + } + } + + public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + var player = animator.GetComponentInParent(); + if (slotGuard.IsValid(player)) + { + if (player.inventory.holdingItemData.actionData[MultiActionManager.GetActionIndexForEntity(player)] is IModuleContainerFor lockData) + { + lockData.Instance.isLocked = false; + lockData.Instance.isReloadLocked = false; + } + } + } +#endif +} diff --git a/KFAttached/Animation/StateMachineBehaviours/AnimationLockAction.cs.meta b/KFAttached/Animation/StateMachineBehaviours/AnimationLockAction.cs.meta new file mode 100644 index 0000000..7d68de6 --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours/AnimationLockAction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 41af3d052f5a46a4c86c3ffbaf4c7918 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/StateMachineBehaviours/AnimationMultiStageReloadState.cs b/KFAttached/Animation/StateMachineBehaviours/AnimationMultiStageReloadState.cs new file mode 100644 index 0000000..073810a --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours/AnimationMultiStageReloadState.cs @@ -0,0 +1,85 @@ +#if NotEditor +using KFCommonUtilityLib.Scripts.StaticManagers; +using UAI; + +#endif +using UnityEngine; + +public class AnimationMultiStageReloadState : StateMachineBehaviour +{ + [SerializeField] + private bool speedUpOnCancel; + [SerializeField] + private bool immediateCancel; + [SerializeField] + private float ForceCancelReloadDelay = 1f; + [SerializeField] + private bool DoNotForceCancel = false; + + private AnimationReloadEvents eventBridge; + + public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + animator.SetWrappedBool(Animator.StringToHash("Reload"), false); + if (eventBridge == null) + { + eventBridge = animator.GetComponent(); + } + if (stateInfo.IsTag("ReloadStart")) + { + animator.speed = 1f; + animator.SetWrappedBool(Animator.StringToHash("IsReloading"), true); +#if NotEditor + EntityAlive player = animator.GetComponentInParent(); + int actionIndex = MultiActionManager.GetActionIndexForEntity(player); + eventBridge.OnReloadStart(actionIndex); +#if DEBUG + Log.Out($"start reload {actionIndex}"); +#endif +#endif + } + } + +#if NotEditor + public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + var actionData = eventBridge.actionData; + if (actionData == null) + { + return; + } + + if (actionData.isReloadCancelled) + { + if (speedUpOnCancel) + { + Log.Out("Speed up animation!"); + animator.speed = 30; + } + + if (immediateCancel) + { + animator.SetBool("IsReloading", false); + } + + if (!DoNotForceCancel) + { + eventBridge.DelayForceCancelReload(ForceCancelReloadDelay); + } + } + + if (actionData.isReloading && animator.GetBool("IsReloading")) + { + actionData.invData.holdingEntity.MinEventContext.ItemActionData = actionData; + actionData.invData.holdingEntity.FireEvent(MinEventTypes.onReloadUpdate, true); + } + } + + public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + animator.speed = 1f; + if (stateInfo.IsTag("ReloadEnd")) + eventBridge?.OnReloadFinish(); + } +#endif +} \ No newline at end of file diff --git a/KFAttached/Animation/StateMachineBehaviours/AnimationMultiStageReloadState.cs.meta b/KFAttached/Animation/StateMachineBehaviours/AnimationMultiStageReloadState.cs.meta new file mode 100644 index 0000000..508c3a1 --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours/AnimationMultiStageReloadState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a36e127f3f92d4f4a8c3e3fdabe6e1d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/StateMachineBehaviours/AnimationRandomRecoilState.cs b/KFAttached/Animation/StateMachineBehaviours/AnimationRandomRecoilState.cs new file mode 100644 index 0000000..479ae5b --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours/AnimationRandomRecoilState.cs @@ -0,0 +1,12 @@ +using UnityEngine; + +public class AnimationRandomRecoilState : StateMachineBehaviour +{ + [SerializeField] private Vector3 positionMultiplier = Vector3.one; + [SerializeField] private Vector3 rotationMultiplier = Vector3.one; + + public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + animator.GetComponent()?.AddRecoil(positionMultiplier, rotationMultiplier); + } +} diff --git a/KFAttached/Animation/StateMachineBehaviours/AnimationRandomRecoilState.cs.meta b/KFAttached/Animation/StateMachineBehaviours/AnimationRandomRecoilState.cs.meta new file mode 100644 index 0000000..c6afd49 --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours/AnimationRandomRecoilState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a853564a7472fea44b9ba42cbf3ae654 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/StateMachineBehaviours/AnimationResetRigWeightState.cs b/KFAttached/Animation/StateMachineBehaviours/AnimationResetRigWeightState.cs new file mode 100644 index 0000000..3000c5a --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours/AnimationResetRigWeightState.cs @@ -0,0 +1,9 @@ +using UnityEngine; + +public class AnimationResetRigWeightState : StateMachineBehaviour +{ + public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + animator.GetComponent()?.SetWeight(0); + } +} diff --git a/KFAttached/Animation/StateMachineBehaviours/AnimationResetRigWeightState.cs.meta b/KFAttached/Animation/StateMachineBehaviours/AnimationResetRigWeightState.cs.meta new file mode 100644 index 0000000..9125b5d --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours/AnimationResetRigWeightState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9cfcbf05cdb32514bab41d81a761cd4d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/StateMachineBehaviours/AnimationRigLayerController.cs b/KFAttached/Animation/StateMachineBehaviours/AnimationRigLayerController.cs new file mode 100644 index 0000000..fea881f --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours/AnimationRigLayerController.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.Animations; +using UnityEngine.Animations.Rigging; + +public class AnimationRigLayerController : StateMachineBehaviour, ISerializationCallbackReceiver +{ +#if UNITY_EDITOR + [Serializable] + public struct State + { + public byte layer; + public bool enable; + } + [SerializeField] + public State[] layerStatesEditor; +#endif + [SerializeField, HideInInspector] + private int[] layers; + + public void OnAfterDeserialize() + { + + } + + public void OnBeforeSerialize() + { +#if UNITY_EDITOR + if(layerStatesEditor != null) + { + layers = new int[layerStatesEditor.Length]; + for (int i = 0; i < layerStatesEditor.Length; i++) + { + layers[i] = layerStatesEditor[i].layer | (layerStatesEditor[i].enable ? 0 : 0x8000); + } + } +#endif + } + + public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + if (layers == null) + return; + + RigBuilder rigBuilder = animator.GetComponent(); + if (rigBuilder && rigBuilder.layers != null) + { + foreach (var layer in layers) + { + int realLayer = layer & 0x7fff; + if (realLayer >= rigBuilder.layers.Count) + { + continue; + } + rigBuilder.layers[realLayer].active = (layer & 0x8000) <= 0; + } + } + } +} \ No newline at end of file diff --git a/KFAttached/Animation/StateMachineBehaviours/AnimationRigLayerController.cs.meta b/KFAttached/Animation/StateMachineBehaviours/AnimationRigLayerController.cs.meta new file mode 100644 index 0000000..e3e3ac5 --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours/AnimationRigLayerController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dbb26fa6f1fef8f45b8eaf5702207898 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Animation/StateMachineBehaviours/AnimatorRandomSwitch.cs b/KFAttached/Animation/StateMachineBehaviours/AnimatorRandomSwitch.cs new file mode 100644 index 0000000..04809b6 --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours/AnimatorRandomSwitch.cs @@ -0,0 +1,45 @@ +using UnityEngine; + +[AddComponentMenu("KFAttachments/Utils/Animator Random Switch")] +public class AnimatorRandomSwitch : StateMachineBehaviour +{ + [SerializeField] + private string parameter; + [SerializeField] + private int stateCount; + + private int[] stateHits; + int totalHits; + + private void Awake() + { + stateHits = new int[stateCount]; + for (int i = 0; i < stateCount; i++) + { + stateHits[i] = 1; + } + totalHits = stateCount; + } + + public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + int rand = Random.Range(0, totalHits); + int cur = 0; + bool found = false; + for (int i = 0; i < stateHits.Length; i++) + { + cur += stateHits[i]; + if (cur > rand && !found) + { + animator.SetInteger(parameter, i); + found = true; + stateHits[i] = 1; + } + else + { + stateHits[i] = 2; + } + } + totalHits = stateCount * 2 - 1; + } +} \ No newline at end of file diff --git a/KFAttached/Animation/StateMachineBehaviours/AnimatorRandomSwitch.cs.meta b/KFAttached/Animation/StateMachineBehaviours/AnimatorRandomSwitch.cs.meta new file mode 100644 index 0000000..252d344 --- /dev/null +++ b/KFAttached/Animation/StateMachineBehaviours/AnimatorRandomSwitch.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c083a728c43231842a2e50a3c04b4a11 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/FPSPack.meta b/KFAttached/FPSPack.meta new file mode 100644 index 0000000..45f332c --- /dev/null +++ b/KFAttached/FPSPack.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3e3819a4a6a281d4bbdf89ab29424071 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/FPSPack/Demo.meta b/KFAttached/FPSPack/Demo.meta new file mode 100644 index 0000000..5c9b755 --- /dev/null +++ b/KFAttached/FPSPack/Demo.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1b7fe40f86d8131479dac6a7eb9f7f66 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/FPSPack/Demo/FPSDemoGUI.cs b/KFAttached/FPSPack/Demo/FPSDemoGUI.cs new file mode 100644 index 0000000..512f247 --- /dev/null +++ b/KFAttached/FPSPack/Demo/FPSDemoGUI.cs @@ -0,0 +1,68 @@ +using UnityEngine; + +public class FPSDemoGUI : MonoBehaviour +{ + public GameObject[] Prefabs; + public Transform muzzleFlashPoint; + public GameObject Gun; + public float reactivateTime = 4; + public Light Sun; + + private int currentNomber; + private GameObject currentInstance; + private GUIStyle guiStyleHeader = new GUIStyle(); + private float sunIntensity; + float dpiScale; + + // Use this for initialization + void Start() + { + if (Screen.dpi < 1) dpiScale = 1; + if (Screen.dpi < 200) dpiScale = 1; + else dpiScale = Screen.dpi / 200f; + guiStyleHeader.fontSize = (int)(15f * dpiScale); + guiStyleHeader.normal.textColor = new Color(0.15f, 0.15f, 0.15f); + currentInstance = Instantiate(Prefabs[currentNomber], transform.position, transform.rotation) as GameObject; + var reactivator = currentInstance.AddComponent(); + reactivator.TimeDelayToReactivate = reactivateTime; + sunIntensity = Sun.intensity; + } + + private void OnGUI() + { + if (GUI.Button(new Rect(10 * dpiScale, 15 * dpiScale, 135 * dpiScale, 37 * dpiScale), "PREVIOUS EFFECT")) + { + ChangeCurrent(-1); + } + if (GUI.Button(new Rect(160 * dpiScale, 15 * dpiScale, 135 * dpiScale, 37 * dpiScale), "NEXT EFFECT")) + { + ChangeCurrent(+1); + } + sunIntensity = GUI.HorizontalSlider(new Rect(10 * dpiScale, 70 * dpiScale, 285 * dpiScale, 15 * dpiScale), sunIntensity, 0, 0.6f); + Sun.intensity = sunIntensity; + GUI.Label(new Rect(300 * dpiScale, 70 * dpiScale, 30 * dpiScale, 30 * dpiScale), "SUN INTENSITY", guiStyleHeader); + GUI.Label(new Rect(400 * dpiScale, 15 * dpiScale, 100 * dpiScale, 20 * dpiScale), "Prefab name is \"" + Prefabs[currentNomber].name + "\" \r\nHold any mouse button that would move the camera", guiStyleHeader); + } + // Update is called once per frame + void ChangeCurrent(int delta) + { + currentNomber += delta; + if (currentNomber > Prefabs.Length - 1) + currentNomber = 0; + else if (currentNomber < 0) + currentNomber = Prefabs.Length - 1; + if (currentInstance != null) Destroy(currentInstance); + if (currentNomber < 10) + { + currentInstance = Instantiate(Prefabs[currentNomber], transform.position, transform.rotation) as GameObject; + Gun.SetActive(false); + } + else + { + currentInstance = Instantiate(Prefabs[currentNomber], muzzleFlashPoint.position, muzzleFlashPoint.rotation) as GameObject; + Gun.SetActive(true); + } + var reactivator = currentInstance.AddComponent(); + reactivator.TimeDelayToReactivate = reactivateTime; + } +} diff --git a/KFAttached/FPSPack/Demo/FPSDemoGUI.cs.meta b/KFAttached/FPSPack/Demo/FPSDemoGUI.cs.meta new file mode 100644 index 0000000..2e295ea --- /dev/null +++ b/KFAttached/FPSPack/Demo/FPSDemoGUI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7f44a7ad42765c94b896425517325f1a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/FPSPack/Demo/FPSDemoReactivator.cs b/KFAttached/FPSPack/Demo/FPSDemoReactivator.cs new file mode 100644 index 0000000..486635f --- /dev/null +++ b/KFAttached/FPSPack/Demo/FPSDemoReactivator.cs @@ -0,0 +1,19 @@ +using UnityEngine; + +public class FPSDemoReactivator : MonoBehaviour +{ + + public float StartDelay = 0; + public float TimeDelayToReactivate = 3; + + void Start() + { + InvokeRepeating("Reactivate", StartDelay, TimeDelayToReactivate); + } + + void Reactivate() + { + gameObject.SetActive(false); + gameObject.SetActive(true); + } +} diff --git a/KFAttached/FPSPack/Demo/FPSDemoReactivator.cs.meta b/KFAttached/FPSPack/Demo/FPSDemoReactivator.cs.meta new file mode 100644 index 0000000..ca4d1c1 --- /dev/null +++ b/KFAttached/FPSPack/Demo/FPSDemoReactivator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 77e369a1e74c2d84ead64ef026b1c433 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/FPSPack/Demo/FPSFireManager.cs b/KFAttached/FPSPack/Demo/FPSFireManager.cs new file mode 100644 index 0000000..dd45d1c --- /dev/null +++ b/KFAttached/FPSPack/Demo/FPSFireManager.cs @@ -0,0 +1,49 @@ +using UnityEngine; + +public class FPSFireManager : MonoBehaviour +{ + public ImpactInfo[] ImpactElemets = new ImpactInfo[0]; + public float BulletDistance = 100; + public GameObject ImpactEffect; + + void Update() + { + if (Input.GetMouseButtonDown(0)) + { + RaycastHit hit; + var ray = new Ray(transform.position, transform.forward); + if (Physics.Raycast(ray, out hit, BulletDistance)) + { + var effect = GetImpactEffect(hit.transform.gameObject); + if (effect == null) + return; + var effectIstance = Instantiate(effect, hit.point, new Quaternion()) as GameObject; + ImpactEffect.SetActive(false); + ImpactEffect.SetActive(true); + effectIstance.transform.LookAt(hit.point + hit.normal); + Destroy(effectIstance, 4); + } + + } + } + + [System.Serializable] + public class ImpactInfo + { + public MaterialType.MaterialTypeEnum MaterialType; + public GameObject ImpactEffect; + } + + GameObject GetImpactEffect(GameObject impactedGameObject) + { + var materialType = impactedGameObject.GetComponent(); + if (materialType == null) + return null; + foreach (var impactInfo in ImpactElemets) + { + if (impactInfo.MaterialType == materialType.TypeOfMaterial) + return impactInfo.ImpactEffect; + } + return null; + } +} diff --git a/KFAttached/FPSPack/Demo/FPSFireManager.cs.meta b/KFAttached/FPSPack/Demo/FPSFireManager.cs.meta new file mode 100644 index 0000000..caa05df --- /dev/null +++ b/KFAttached/FPSPack/Demo/FPSFireManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eabaea637d046b649916223ab9952a50 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/FPSPack/Demo/MouseLock.cs b/KFAttached/FPSPack/Demo/MouseLock.cs new file mode 100644 index 0000000..868c2be --- /dev/null +++ b/KFAttached/FPSPack/Demo/MouseLock.cs @@ -0,0 +1,22 @@ +using UnityEngine; + +public class MouseLock : MonoBehaviour +{ + + // Use this for initialization + void Start() + { + + } + + // Update is called once per frame + void Update() + { +#if UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 + Screen.lockCursor = true; +#else + Cursor.visible = false; +#endif + + } +} diff --git a/KFAttached/FPSPack/Demo/MouseLock.cs.meta b/KFAttached/FPSPack/Demo/MouseLock.cs.meta new file mode 100644 index 0000000..8798665 --- /dev/null +++ b/KFAttached/FPSPack/Demo/MouseLock.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fc19b6a3661472848bf60b7ac730c242 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/FPSPack/FPSLightCurves.cs b/KFAttached/FPSPack/FPSLightCurves.cs new file mode 100644 index 0000000..dcd8cbb --- /dev/null +++ b/KFAttached/FPSPack/FPSLightCurves.cs @@ -0,0 +1,47 @@ +using UnityEngine; + +public class FPSLightCurves : MonoBehaviour +{ + public AnimationCurve LightCurve = AnimationCurve.EaseInOut(0, 0, 1, 1); + public float GraphTimeMultiplier = 1, GraphIntensityMultiplier = 1; + + private bool canUpdate; + private bool firstUpdate; + private float startTime; + private Light lightSource; + + private void Awake() + { + lightSource = GetComponent(); + } + + private void OnEnable() + { + lightSource.intensity = LightCurve.Evaluate(0); + if (firstUpdate) + { + firstUpdate = false; + return; + } + startTime = Time.time; + canUpdate = true; + } + + private void OnDisable() + { + firstUpdate = true; + canUpdate = false; + } + + private void Update() + { + var time = Time.time - startTime; + if (canUpdate) + { + var eval = LightCurve.Evaluate(time / GraphTimeMultiplier) * GraphIntensityMultiplier; + lightSource.intensity = eval; + } + if (time >= GraphTimeMultiplier) + canUpdate = false; + } +} \ No newline at end of file diff --git a/KFAttached/FPSPack/FPSLightCurves.cs.meta b/KFAttached/FPSPack/FPSLightCurves.cs.meta new file mode 100644 index 0000000..a5ce8c0 --- /dev/null +++ b/KFAttached/FPSPack/FPSLightCurves.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 22b1c2a9f789e3e47a7c58fa60e17e5d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/FPSPack/FPSParticleSystemScaler.cs b/KFAttached/FPSPack/FPSParticleSystemScaler.cs new file mode 100644 index 0000000..9f10735 --- /dev/null +++ b/KFAttached/FPSPack/FPSParticleSystemScaler.cs @@ -0,0 +1,62 @@ +using UnityEngine; + +#if UNITY_EDITOR +using UnityEditor; +#endif + +[ExecuteInEditMode] +public class FPSParticleSystemScaler : MonoBehaviour +{ + public float particlesScale = 1.0f; + + float oldScale; + + void Start() + { + oldScale = particlesScale; + } + + void Update() + { +#if UNITY_EDITOR + if (Mathf.Abs(oldScale - particlesScale) > 0.0001f && particlesScale > 0) + { + transform.localScale = new Vector3(particlesScale, particlesScale, particlesScale); + float scale = particlesScale / oldScale; + var ps = this.GetComponentsInChildren(); + + foreach (ParticleSystem particles in ps) + { + particles.startSize *= scale; + particles.startSpeed *= scale; + particles.gravityModifier *= scale; + + SerializedObject serializedObject = new SerializedObject(particles); + serializedObject.FindProperty("ClampVelocityModule.magnitude.scalar").floatValue *= scale; + serializedObject.FindProperty("ClampVelocityModule.x.scalar").floatValue *= scale; + serializedObject.FindProperty("ClampVelocityModule.y.scalar").floatValue *= scale; + serializedObject.FindProperty("ClampVelocityModule.z.scalar").floatValue *= scale; + serializedObject.FindProperty("VelocityModule.x.scalar").floatValue *= scale; + serializedObject.FindProperty("VelocityModule.y.scalar").floatValue *= scale; + serializedObject.FindProperty("VelocityModule.z.scalar").floatValue *= scale; + serializedObject.FindProperty("ColorBySpeedModule.range").vector2Value *= scale; + serializedObject.FindProperty("RotationBySpeedModule.range").vector2Value *= scale; + serializedObject.FindProperty("ForceModule.x.scalar").floatValue *= scale; + serializedObject.FindProperty("ForceModule.y.scalar").floatValue *= scale; + serializedObject.FindProperty("ForceModule.z.scalar").floatValue *= scale; + serializedObject.FindProperty("SizeBySpeedModule.range").vector2Value *= scale; + + serializedObject.ApplyModifiedProperties(); + } + + var trails = this.GetComponentsInChildren(); + foreach (TrailRenderer trail in trails) + { + trail.startWidth *= scale; + trail.endWidth *= scale; + } + oldScale = particlesScale; + } +#endif + } +} \ No newline at end of file diff --git a/KFAttached/FPSPack/FPSParticleSystemScaler.cs.meta b/KFAttached/FPSPack/FPSParticleSystemScaler.cs.meta new file mode 100644 index 0000000..2036e77 --- /dev/null +++ b/KFAttached/FPSPack/FPSParticleSystemScaler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c035660ec1d9fd64da7beb32f35b7437 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/FPSPack/FPSRandomRotateAngle.cs b/KFAttached/FPSPack/FPSRandomRotateAngle.cs new file mode 100644 index 0000000..6dc1556 --- /dev/null +++ b/KFAttached/FPSPack/FPSRandomRotateAngle.cs @@ -0,0 +1,29 @@ +using UnityEngine; + +public class FPSRandomRotateAngle : MonoBehaviour +{ + public bool RotateX; + public bool RotateY; + public bool RotateZ = true; + + private Transform t; + + // Use this for initialization + void Awake() + { + t = transform; + } + + // Update is called once per frame + void OnEnable() + { + var rotateVector = Vector3.zero; + if (RotateX) + rotateVector.x = Random.Range(0, 360); + if (RotateY) + rotateVector.y = Random.Range(0, 360); + if (RotateZ) + rotateVector.z = Random.Range(0, 360); + t.Rotate(rotateVector); + } +} diff --git a/KFAttached/FPSPack/FPSRandomRotateAngle.cs.meta b/KFAttached/FPSPack/FPSRandomRotateAngle.cs.meta new file mode 100644 index 0000000..efdc17c --- /dev/null +++ b/KFAttached/FPSPack/FPSRandomRotateAngle.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a7a83f50808621b43bd26cd127393a40 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/FPSPack/FPSShaderColorGradient.cs b/KFAttached/FPSPack/FPSShaderColorGradient.cs new file mode 100644 index 0000000..1b2e903 --- /dev/null +++ b/KFAttached/FPSPack/FPSShaderColorGradient.cs @@ -0,0 +1,61 @@ +using UnityEngine; + +public class FPSShaderColorGradient : MonoBehaviour +{ + public string ShaderProperty = "_TintColor"; + public int MaterialID = 0; + public Gradient Color = new Gradient(); + public float TimeMultiplier = 1; + + private bool canUpdate; + private Material matInstance; + private int propertyID; + private float startTime; + private Color oldColor; + + // Use this for initialization + private void Start() + { + var rend = GetComponent(); + if (rend != null) + { + var mats = rend.materials; + if (MaterialID >= mats.Length) + Debug.Log("ShaderColorGradient: Material ID more than shader materials count."); + matInstance = mats[MaterialID]; + } + else + { + var proj = GetComponent(); + var projMat = proj.material; + if (!projMat.name.EndsWith("(Instance)")) + matInstance = new Material(projMat) { name = projMat.name + " (Instance)" }; + else + matInstance = projMat; + proj.material = matInstance; + } + + if (!matInstance.HasProperty(ShaderProperty)) + Debug.Log("ShaderColorGradient: Shader not have \"" + ShaderProperty + "\" property"); + propertyID = Shader.PropertyToID(ShaderProperty); + oldColor = matInstance.GetColor(propertyID); + } + + private void OnEnable() + { + startTime = Time.time; + canUpdate = true; + } + + private void Update() + { + var time = Time.time - startTime; + if (canUpdate) + { + var eval = Color.Evaluate(time / TimeMultiplier); + matInstance.SetColor(propertyID, eval * oldColor); + } + if (time >= TimeMultiplier) + canUpdate = false; + } +} \ No newline at end of file diff --git a/KFAttached/FPSPack/FPSShaderColorGradient.cs.meta b/KFAttached/FPSPack/FPSShaderColorGradient.cs.meta new file mode 100644 index 0000000..87a19e1 --- /dev/null +++ b/KFAttached/FPSPack/FPSShaderColorGradient.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1f6f8e03260dedb45a05a2c5f5eea632 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/FPSPack/FPSShaderFloatCurves.cs b/KFAttached/FPSPack/FPSShaderFloatCurves.cs new file mode 100644 index 0000000..62c7aa1 --- /dev/null +++ b/KFAttached/FPSPack/FPSShaderFloatCurves.cs @@ -0,0 +1,57 @@ +using UnityEngine; + +public class FPSShaderFloatCurves : MonoBehaviour +{ + public string ShaderProperty = "_BumpAmt"; + public int MaterialID = 0; + public AnimationCurve FloatPropertyCurve = AnimationCurve.EaseInOut(0, 0, 1, 1); + public float GraphTimeMultiplier = 1, GraphScaleMultiplier = 1; + + private bool canUpdate; + private Material matInstance; + private int propertyID; + private float startTime; + + private void Start() + { + var rend = GetComponent(); + if (rend != null) + { + var mats = rend.materials; + if (MaterialID >= mats.Length) + Debug.Log("ShaderColorGradient: Material ID more than shader materials count."); + matInstance = mats[MaterialID]; + } + else + { + var proj = GetComponent(); + var projMat = proj.material; + if (!projMat.name.EndsWith("(Instance)")) + matInstance = new Material(projMat) { name = projMat.name + " (Instance)" }; + else + matInstance = projMat; + proj.material = matInstance; + } + if (!matInstance.HasProperty(ShaderProperty)) + Debug.Log("ShaderColorGradient: Shader not have \"" + ShaderProperty + "\" property"); + propertyID = Shader.PropertyToID(ShaderProperty); + } + + private void OnEnable() + { + startTime = Time.time; + canUpdate = true; + } + + private void Update() + { + var time = Time.time - startTime; + if (canUpdate) + { + var eval = FloatPropertyCurve.Evaluate(time / GraphTimeMultiplier) * GraphScaleMultiplier; + matInstance.SetFloat(propertyID, eval); + } + if (time >= GraphTimeMultiplier) + canUpdate = false; + } +} \ No newline at end of file diff --git a/KFAttached/FPSPack/FPSShaderFloatCurves.cs.meta b/KFAttached/FPSPack/FPSShaderFloatCurves.cs.meta new file mode 100644 index 0000000..f66aa89 --- /dev/null +++ b/KFAttached/FPSPack/FPSShaderFloatCurves.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 91b9007bb8bdfdc4cb4e31998935a777 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/FPSPack/MaterialType.cs b/KFAttached/FPSPack/MaterialType.cs new file mode 100644 index 0000000..5ade33c --- /dev/null +++ b/KFAttached/FPSPack/MaterialType.cs @@ -0,0 +1,22 @@ +using UnityEngine; + +public class MaterialType : MonoBehaviour +{ + + public MaterialTypeEnum TypeOfMaterial = MaterialTypeEnum.Plaster; + + [System.Serializable] + public enum MaterialTypeEnum + { + Plaster, + Metall, + Folliage, + Rock, + Wood, + Brick, + Concrete, + Dirt, + Glass, + Water + } +} diff --git a/KFAttached/FPSPack/MaterialType.cs.meta b/KFAttached/FPSPack/MaterialType.cs.meta new file mode 100644 index 0000000..3790b3d --- /dev/null +++ b/KFAttached/FPSPack/MaterialType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e86c216f014d903439060cd429cb53a2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/GG Camera Shake.meta b/KFAttached/GG Camera Shake.meta new file mode 100644 index 0000000..efb3463 --- /dev/null +++ b/KFAttached/GG Camera Shake.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d1bfb6a4682934f44be265da6de48898 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/GG Camera Shake/Runtime.meta b/KFAttached/GG Camera Shake/Runtime.meta new file mode 100644 index 0000000..f67faf5 --- /dev/null +++ b/KFAttached/GG Camera Shake/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 721197a8e82a4304097f3e0991293e4e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/GG Camera Shake/Runtime/Attenuator.cs b/KFAttached/GG Camera Shake/Runtime/Attenuator.cs new file mode 100644 index 0000000..7164a34 --- /dev/null +++ b/KFAttached/GG Camera Shake/Runtime/Attenuator.cs @@ -0,0 +1,66 @@ +using UnityEngine; + +namespace CameraShake +{ + /// + /// Contains methods for changing strength and direction of shakes depending on their position. + /// + public static class Attenuator + { + /// + /// Returns multiplier for the strength of a shake, based on source and camera positions. + /// + public static float Strength(StrengthAttenuationParams pars, Vector3 sourcePosition, Vector3 cameraPosition) + { + Vector3 vec = cameraPosition - sourcePosition; + float distance = Vector3.Scale(pars.axesMultiplier, vec).magnitude; + float strength = Mathf.Clamp01(1 - (distance - pars.clippingDistance) / pars.falloffScale); + + return Power.Evaluate(strength, pars.falloffDegree); + } + + /// + /// Returns displacement, opposite to the direction to the source in camera's local space. + /// + public static Displacement Direction(Vector3 sourcePosition, Vector3 cameraPosition, Quaternion cameraRotation) + { + Displacement direction = Displacement.Zero; + direction.position = (cameraPosition - sourcePosition).normalized; + direction.position = Quaternion.Inverse(cameraRotation) * direction.position; + + direction.eulerAngles.x = direction.position.z; + direction.eulerAngles.y = direction.position.x; + direction.eulerAngles.z = -direction.position.x; + + return direction; + } + + [System.Serializable] + public class StrengthAttenuationParams + { + /// + /// Radius in which shake doesn't lose strength. + /// + [Tooltip("Radius in which shake doesn't lose strength.")] + public float clippingDistance = 10; + + /// + /// Defines how fast strength falls with distance. + /// + [Tooltip("How fast strength falls with distance.")] + public float falloffScale = 50; + + /// + /// Power of the falloff function. + /// + [Tooltip("Power of the falloff function.")] + public Degree falloffDegree = Degree.Quadratic; + + /// + /// Contribution of each axis to distance. E. g. (1, 1, 0) for a 2D game in XY plane. + /// + [Tooltip("Contribution of each axis to distance. E. g. (1, 1, 0) for a 2D game in XY plane.")] + public Vector3 axesMultiplier = Vector3.one; + } + } +} diff --git a/KFAttached/GG Camera Shake/Runtime/Attenuator.cs.meta b/KFAttached/GG Camera Shake/Runtime/Attenuator.cs.meta new file mode 100644 index 0000000..54db59f --- /dev/null +++ b/KFAttached/GG Camera Shake/Runtime/Attenuator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 07aa6a8400d46274f9cbc5f3714e6a9b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/GG Camera Shake/Runtime/BounceShake.cs b/KFAttached/GG Camera Shake/Runtime/BounceShake.cs new file mode 100644 index 0000000..55e08ba --- /dev/null +++ b/KFAttached/GG Camera Shake/Runtime/BounceShake.cs @@ -0,0 +1,133 @@ +using UnityEngine; + +namespace CameraShake +{ + public class BounceShake : ICameraShake + { + readonly Params pars; + readonly AnimationCurve moveCurve = AnimationCurve.EaseInOut(0, 0, 1, 1); + readonly Vector3? sourcePosition = null; + + float attenuation = 1; + Displacement direction; + Displacement previousWaypoint; + Displacement currentWaypoint; + int bounceIndex; + float t; + + /// + /// Creates an instance of BounceShake. + /// + /// Parameters of the shake. + /// World position of the source of the shake. + public BounceShake(Params parameters, Vector3? sourcePosition = null) + { + this.sourcePosition = sourcePosition; + pars = parameters; + Displacement rnd = Displacement.InsideUnitSpheres(); + direction = Displacement.Scale(rnd, pars.axesMultiplier).Normalized; + } + + /// + /// Creates an instance of BounceShake. + /// + /// Parameters of the shake. + /// Initial direction of the shake motion. + /// World position of the source of the shake. + public BounceShake(Params parameters, Displacement initialDirection, Vector3? sourcePosition = null) + { + this.sourcePosition = sourcePosition; + pars = parameters; + direction = Displacement.Scale(initialDirection, pars.axesMultiplier).Normalized; + } + + public Displacement CurrentDisplacement { get; private set; } + public bool IsFinished { get; private set; } + public void Initialize(Vector3 cameraPosition, Quaternion cameraRotation) + { + attenuation = sourcePosition == null ? + 1 : Attenuator.Strength(pars.attenuation, sourcePosition.Value, cameraPosition); + currentWaypoint = attenuation * direction.ScaledBy(pars.positionStrength, pars.rotationStrength); + } + + public void Update(float deltaTime, Vector3 cameraPosition, Quaternion cameraRotation) + { + if (t < 1) + { + + t += deltaTime * pars.freq; + if (pars.freq == 0) t = 1; + + CurrentDisplacement = Displacement.Lerp(previousWaypoint, currentWaypoint, + moveCurve.Evaluate(t)); + } + else + { + t = 0; + CurrentDisplacement = currentWaypoint; + previousWaypoint = currentWaypoint; + bounceIndex++; + if (bounceIndex > pars.numBounces) + { + IsFinished = true; + return; + } + + Displacement rnd = Displacement.InsideUnitSpheres(); + direction = -direction + + pars.randomness * Displacement.Scale(rnd, pars.axesMultiplier).Normalized; + direction = direction.Normalized; + float decayValue = 1 - (float)bounceIndex / pars.numBounces; + currentWaypoint = decayValue * decayValue * attenuation + * direction.ScaledBy(pars.positionStrength, pars.rotationStrength); + } + } + + [System.Serializable] + public class Params + { + /// + /// Strength of the shake for positional axes. + /// + [Tooltip("Strength of the shake for positional axes.")] + public float positionStrength = 0.05f; + + /// + /// Strength of the shake for rotational axes. + /// + [Tooltip("Strength of the shake for rotational axes.")] + public float rotationStrength = 0.1f; + + /// + /// Preferred direction of shaking. + /// + [Tooltip("Preferred direction of shaking.")] + public Displacement axesMultiplier = new Displacement(Vector2.one, Vector3.forward); + + /// + /// Frequency of shaking. + /// + [Tooltip("Frequency of shaking.")] + public float freq = 25; + + /// + /// Number of vibrations before stop. + /// + [Tooltip("Number of vibrations before stop.")] + public int numBounces = 5; + + /// + /// Randomness of motion. + /// + [Range(0, 1)] + [Tooltip("Randomness of motion.")] + public float randomness = 0.5f; + + /// + /// How strength falls with distance from the shake source. + /// + [Tooltip("How strength falls with distance from the shake source.")] + public Attenuator.StrengthAttenuationParams attenuation; + } + } +} diff --git a/KFAttached/GG Camera Shake/Runtime/BounceShake.cs.meta b/KFAttached/GG Camera Shake/Runtime/BounceShake.cs.meta new file mode 100644 index 0000000..786925b --- /dev/null +++ b/KFAttached/GG Camera Shake/Runtime/BounceShake.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1a8a4f5340889954aa6de7013b117066 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/GG Camera Shake/Runtime/CameraShakePresets.cs b/KFAttached/GG Camera Shake/Runtime/CameraShakePresets.cs new file mode 100644 index 0000000..b5d9757 --- /dev/null +++ b/KFAttached/GG Camera Shake/Runtime/CameraShakePresets.cs @@ -0,0 +1,113 @@ +using UnityEngine; + +namespace CameraShake +{ + /// + /// Contains shorthands for creating common shake types. + /// + public class CameraShakePresets + { + readonly CameraShaker shaker; + + public CameraShakePresets(CameraShaker shaker) + { + this.shaker = shaker; + } + + /// + /// Suitable for short and snappy shakes in 2D. Moves camera in X and Y axes and rotates it in Z axis. + /// + /// Strength of motion in X and Y axes. + /// Strength of rotation in Z axis. + /// Frequency of shaking. + /// Number of vibrations before stop. + public void ShortShake2D( + float positionStrength = 0.08f, + float rotationStrength = 0.1f, + float freq = 25, + int numBounces = 5) + { + BounceShake.Params pars = new BounceShake.Params + { + positionStrength = positionStrength, + rotationStrength = rotationStrength, + freq = freq, + numBounces = numBounces + }; + shaker.RegisterShake(new BounceShake(pars)); + } + + /// + /// Suitable for longer and stronger shakes in 3D. Rotates camera in all three axes. + /// + /// Strength of the shake. + /// Frequency of shaking. + /// Number of vibrations before stop. + public void ShortShake3D( + float strength = 0.3f, + float freq = 25, + int numBounces = 5) + { + BounceShake.Params pars = new BounceShake.Params + { + axesMultiplier = new Displacement(Vector3.zero, new Vector3(1, 1, 0.4f)), + rotationStrength = strength, + freq = freq, + numBounces = numBounces + }; + shaker.RegisterShake(new BounceShake(pars)); + } + + /// + /// Suitable for longer and stronger shakes in 2D. Moves camera in X and Y axes and rotates it in Z axis. + /// + /// Strength of motion in X and Y axes. + /// Strength of rotation in Z axis. + /// Duration of the shake. + public void Explosion2D( + float positionStrength = 1f, + float rotationStrength = 3, + float duration = 0.5f) + { + PerlinShake.NoiseMode[] modes = + { + new PerlinShake.NoiseMode(8, 1), + new PerlinShake.NoiseMode(20, 0.3f) + }; + Envelope.EnvelopeParams envelopePars = new Envelope.EnvelopeParams(); + envelopePars.decay = duration <= 0 ? 1 : 1 / duration; + PerlinShake.Params pars = new PerlinShake.Params + { + strength = new Displacement(new Vector3(1, 1) * positionStrength, Vector3.forward * rotationStrength), + noiseModes = modes, + envelope = envelopePars, + }; + shaker.RegisterShake(new PerlinShake(pars)); + } + + /// + /// Suitable for longer and stronger shakes in 3D. Rotates camera in all three axes. + /// + /// Strength of the shake. + /// Duration of the shake. + public void Explosion3D( + float strength = 8f, + float duration = 0.7f) + { + PerlinShake.NoiseMode[] modes = + { + new PerlinShake.NoiseMode(6, 1), + new PerlinShake.NoiseMode(20, 0.2f) + }; + Envelope.EnvelopeParams envelopePars = new Envelope.EnvelopeParams(); + envelopePars.decay = duration <= 0 ? 1 : 1 / duration; + PerlinShake.Params pars = new PerlinShake.Params + { + strength = new Displacement(Vector3.zero, new Vector3(1, 1, 0.5f) * strength), + noiseModes = modes, + envelope = envelopePars, + }; + shaker.RegisterShake(new PerlinShake(pars)); + } + } +} diff --git a/KFAttached/GG Camera Shake/Runtime/CameraShakePresets.cs.meta b/KFAttached/GG Camera Shake/Runtime/CameraShakePresets.cs.meta new file mode 100644 index 0000000..41c2927 --- /dev/null +++ b/KFAttached/GG Camera Shake/Runtime/CameraShakePresets.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 27cd455dd46fc6a47b7cc4f29f710304 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/GG Camera Shake/Runtime/CameraShaker.cs b/KFAttached/GG Camera Shake/Runtime/CameraShaker.cs new file mode 100644 index 0000000..6c3d3b1 --- /dev/null +++ b/KFAttached/GG Camera Shake/Runtime/CameraShaker.cs @@ -0,0 +1,98 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace CameraShake +{ + /// + /// Camera shaker component registeres new shakes, holds a list of active shakes, and applies them to the camera additively. + /// + public class CameraShaker : MonoBehaviour + { + public static CameraShaker Instance; + public static CameraShakePresets Presets; + + readonly List activeShakes = new List(); + + [Tooltip("Transform which will be affected by the shakes.\n\nCameraShaker will set this transform's local position and rotation.")] + [SerializeField] + Transform cameraTransform; + + + [Tooltip("Scales the strength of all shakes.")] + [Range(0, 1)] + [SerializeField] + public float StrengthMultiplier = 1; + + public CameraShakePresets ShakePresets; + + + /// + /// Adds a shake to the list of active shakes. + /// + public static void Shake(ICameraShake shake) + { + if (IsInstanceNull()) return; + Instance.RegisterShake(shake); + } + + /// + /// Adds a shake to the list of active shakes. + /// + public void RegisterShake(ICameraShake shake) + { + shake.Initialize(cameraTransform.position, + cameraTransform.rotation); + activeShakes.Add(shake); + } + + /// + /// Sets the transform which will be affected by the shakes. + /// + public void SetCameraTransform(Transform cameraTransform) + { + cameraTransform.localPosition = Vector3.zero; + cameraTransform.localEulerAngles = Vector3.zero; + this.cameraTransform = cameraTransform; + } + + private void Awake() + { + Instance = this; + ShakePresets = new CameraShakePresets(this); + Presets = ShakePresets; + if (cameraTransform == null) + cameraTransform = transform; + } + + public void UpdateShake() + { + if (cameraTransform == null) return; + + Displacement cameraDisplacement = Displacement.Zero; + for (int i = activeShakes.Count - 1; i >= 0; i--) + { + if (activeShakes[i].IsFinished) + { + activeShakes.RemoveAt(i); + } + else + { + activeShakes[i].Update(Time.deltaTime, cameraTransform.position, cameraTransform.rotation); + cameraDisplacement += activeShakes[i].CurrentDisplacement; + } + } + cameraTransform.localPosition = transform.localPosition + StrengthMultiplier * cameraDisplacement.position; + cameraTransform.localRotation *= Quaternion.Euler(StrengthMultiplier * cameraDisplacement.eulerAngles); + } + + private static bool IsInstanceNull() + { + if (Instance == null) + { + Debug.LogError("CameraShaker Instance is missing!"); + return true; + } + return false; + } + } +} diff --git a/KFAttached/GG Camera Shake/Runtime/CameraShaker.cs.meta b/KFAttached/GG Camera Shake/Runtime/CameraShaker.cs.meta new file mode 100644 index 0000000..ea1fa09 --- /dev/null +++ b/KFAttached/GG Camera Shake/Runtime/CameraShaker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2a257897ce04dc64eb5ae266c62846be +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/GG Camera Shake/Runtime/Displacement.cs b/KFAttached/GG Camera Shake/Runtime/Displacement.cs new file mode 100644 index 0000000..5a3ff8e --- /dev/null +++ b/KFAttached/GG Camera Shake/Runtime/Displacement.cs @@ -0,0 +1,98 @@ +using UnityEngine; + +namespace CameraShake +{ + /// + /// Representation of translation and rotation. + /// + [System.Serializable] + public struct Displacement + { + public Vector3 position; + public Vector3 eulerAngles; + + public Displacement(Vector3 position, Vector3 eulerAngles) + { + this.position = position; + this.eulerAngles = eulerAngles; + } + + public Displacement(Vector3 position) + { + this.position = position; + this.eulerAngles = Vector3.zero; + } + + public static Displacement Zero + { + get + { + return new Displacement(Vector3.zero, Vector3.zero); + } + } + + public static Displacement operator +(Displacement a, Displacement b) + { + return new Displacement(a.position + b.position, + b.eulerAngles + a.eulerAngles); + } + + public static Displacement operator -(Displacement a, Displacement b) + { + return new Displacement(a.position - b.position, + b.eulerAngles - a.eulerAngles); + } + + public static Displacement operator -(Displacement disp) + { + return new Displacement(-disp.position, -disp.eulerAngles); + } + + public static Displacement operator *(Displacement coords, float number) + { + return new Displacement(coords.position * number, + coords.eulerAngles * number); + } + + public static Displacement operator *(float number, Displacement coords) + { + return coords * number; + } + + public static Displacement operator /(Displacement coords, float number) + { + return new Displacement(coords.position / number, + coords.eulerAngles / number); + } + + public static Displacement Scale(Displacement a, Displacement b) + { + return new Displacement(Vector3.Scale(a.position, b.position), + Vector3.Scale(b.eulerAngles, a.eulerAngles)); + } + + public static Displacement Lerp(Displacement a, Displacement b, float t) + { + return new Displacement(Vector3.Lerp(a.position, b.position, t), + Vector3.Lerp(a.eulerAngles, b.eulerAngles, t)); + } + + public Displacement ScaledBy(float posScale, float rotScale) + { + return new Displacement(position * posScale, eulerAngles * rotScale); + } + + public Displacement Normalized + { + get + { + return new Displacement(position.normalized, eulerAngles.normalized); + } + } + + public static Displacement InsideUnitSpheres() + { + return new Displacement(Random.insideUnitSphere, Random.insideUnitSphere); + } + } +} \ No newline at end of file diff --git a/KFAttached/GG Camera Shake/Runtime/Displacement.cs.meta b/KFAttached/GG Camera Shake/Runtime/Displacement.cs.meta new file mode 100644 index 0000000..3e612a0 --- /dev/null +++ b/KFAttached/GG Camera Shake/Runtime/Displacement.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 22dc566280f4a704d958e931abdb9fc4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/GG Camera Shake/Runtime/Envelope.cs b/KFAttached/GG Camera Shake/Runtime/Envelope.cs new file mode 100644 index 0000000..21c5a28 --- /dev/null +++ b/KFAttached/GG Camera Shake/Runtime/Envelope.cs @@ -0,0 +1,168 @@ +using UnityEngine; + +namespace CameraShake +{ + /// + /// Controls strength of the shake over time. + /// + public class Envelope : IAmplitudeController + { + readonly EnvelopeParams pars; + readonly EnvelopeControlMode controlMode; + + float amplitude; + float targetAmplitude; + float sustainEndTime; + bool finishWhenAmplitudeZero; + bool finishImmediately; + EnvelopeState state; + + /// + /// Creates an Envelope instance. + /// + /// Envelope parameters. + /// Pass Auto for a single shake, or Manual for controlling strength manually. + public Envelope(EnvelopeParams pars, float initialTargetAmplitude, EnvelopeControlMode controlMode) + { + this.pars = pars; + this.controlMode = controlMode; + SetTarget(initialTargetAmplitude); + } + + /// + /// The value by which you want to multiply shake displacement. + /// + public float Intensity { get; private set; } + + public bool IsFinished + { + get + { + if (finishImmediately) return true; + return (finishWhenAmplitudeZero || controlMode == EnvelopeControlMode.Auto) + && amplitude <= 0 && targetAmplitude <= 0; + } + } + + public void Finish() + { + finishWhenAmplitudeZero = true; + SetTarget(0); + } + + public void FinishImmediately() + { + finishImmediately = true; + } + + /// + /// Update is called every frame by the shake. + /// + public void Update(float deltaTime) + { + if (IsFinished) return; + + if (state == EnvelopeState.Increase) + { + if (pars.attack > 0) + amplitude += deltaTime * pars.attack; + if (amplitude > targetAmplitude || pars.attack <= 0) + { + amplitude = targetAmplitude; + state = EnvelopeState.Sustain; + if (controlMode == EnvelopeControlMode.Auto) + sustainEndTime = Time.time + pars.sustain; + } + } + else + { + if (state == EnvelopeState.Decrease) + { + + if (pars.decay > 0) + amplitude -= deltaTime * pars.decay; + if (amplitude < targetAmplitude || pars.decay <= 0) + { + amplitude = targetAmplitude; + state = EnvelopeState.Sustain; + } + } + else + { + if (controlMode == EnvelopeControlMode.Auto && Time.time > sustainEndTime) + { + SetTarget(0); + } + } + } + + amplitude = Mathf.Clamp01(amplitude); + Intensity = Power.Evaluate(amplitude, pars.degree); + } + + public void SetTargetAmplitude(float value) + { + if (controlMode == EnvelopeControlMode.Manual && !finishWhenAmplitudeZero) + { + SetTarget(value); + } + } + + private void SetTarget(float value) + { + targetAmplitude = Mathf.Clamp01(value); + state = targetAmplitude > amplitude ? EnvelopeState.Increase : EnvelopeState.Decrease; + } + + + [System.Serializable] + public class EnvelopeParams + { + /// + /// How fast the amplitude rises. + /// + [Tooltip("How fast the amplitude increases.")] + public float attack = 10; + + /// + /// How long in seconds the amplitude holds a maximum value. + /// + [Tooltip("How long in seconds the amplitude holds maximum value.")] + public float sustain = 0; + + /// + /// How fast the amplitude falls. + /// + [Tooltip("How fast the amplitude decreases.")] + public float decay = 1f; + + /// + /// Power in which the amplitude is raised to get intensity. + /// + [Tooltip("Power in which the amplitude is raised to get intensity.")] + public Degree degree = Degree.Cubic; + } + + public enum EnvelopeControlMode { Auto, Manual } + + public enum EnvelopeState { Sustain, Increase, Decrease } + } + + public interface IAmplitudeController + { + /// + /// Sets value to which amplitude will move over time. + /// + void SetTargetAmplitude(float value); + + /// + /// Sets amplitude to zero and finishes the shake when zero is reached. + /// + void Finish(); + + /// + /// Immediately finishes the shake. + /// + void FinishImmediately(); + } +} diff --git a/KFAttached/GG Camera Shake/Runtime/Envelope.cs.meta b/KFAttached/GG Camera Shake/Runtime/Envelope.cs.meta new file mode 100644 index 0000000..f7208e4 --- /dev/null +++ b/KFAttached/GG Camera Shake/Runtime/Envelope.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 13084b7de651ead408d3f5ef8c6837b8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/GG Camera Shake/Runtime/ICameraShake.cs b/KFAttached/GG Camera Shake/Runtime/ICameraShake.cs new file mode 100644 index 0000000..ab29f2d --- /dev/null +++ b/KFAttached/GG Camera Shake/Runtime/ICameraShake.cs @@ -0,0 +1,27 @@ +using UnityEngine; + +namespace CameraShake +{ + public interface ICameraShake + { + /// + /// Represents current position and rotation of the camera according to the shake. + /// + Displacement CurrentDisplacement { get; } + + /// + /// Shake system will dispose the shake on the first frame when this is true. + /// + bool IsFinished { get; } + + /// + /// CameraShaker calls this when the shake is added to the list of active shakes. + /// + void Initialize(Vector3 cameraPosition, Quaternion cameraRotation); + + /// + /// CameraShaker calls this every frame on active shakes. + /// + void Update(float deltaTime, Vector3 cameraPosition, Quaternion cameraRotation); + } +} diff --git a/KFAttached/GG Camera Shake/Runtime/ICameraShake.cs.meta b/KFAttached/GG Camera Shake/Runtime/ICameraShake.cs.meta new file mode 100644 index 0000000..f41b72e --- /dev/null +++ b/KFAttached/GG Camera Shake/Runtime/ICameraShake.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a062a4046bad80b469554a84b9b30cab +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/GG Camera Shake/Runtime/KickShake.cs b/KFAttached/GG Camera Shake/Runtime/KickShake.cs new file mode 100644 index 0000000..dc56c41 --- /dev/null +++ b/KFAttached/GG Camera Shake/Runtime/KickShake.cs @@ -0,0 +1,121 @@ +using UnityEngine; + +namespace CameraShake +{ + public class KickShake : ICameraShake + { + readonly Params pars; + readonly Vector3? sourcePosition; + readonly bool attenuateStrength; + + Displacement direction; + Displacement prevWaypoint; + Displacement currentWaypoint; + bool release; + float t; + + /// + /// Creates an instance of KickShake in the direction from the source to the camera. + /// + /// Parameters of the shake. + /// World position of the source of the shake. + /// Change strength depending on distance from the camera? + public KickShake(Params parameters, Vector3 sourcePosition, bool attenuateStrength) + { + pars = parameters; + this.sourcePosition = sourcePosition; + this.attenuateStrength = attenuateStrength; + } + + /// + /// Creates an instance of KickShake. + /// + /// Parameters of the shake. + /// Direction of the kick. + public KickShake(Params parameters, Displacement direction) + { + pars = parameters; + this.direction = direction.Normalized; + } + + public Displacement CurrentDisplacement { get; private set; } + public bool IsFinished { get; private set; } + + + public void Initialize(Vector3 cameraPosition, Quaternion cameraRotation) + { + if (sourcePosition != null) + { + direction = Attenuator.Direction(sourcePosition.Value, cameraPosition, cameraRotation); + if (attenuateStrength) + direction *= Attenuator.Strength(pars.attenuation, sourcePosition.Value, cameraPosition); + } + currentWaypoint = Displacement.Scale(direction, pars.strength); + } + + public void Update(float deltaTime, Vector3 cameraPosition, Quaternion cameraRotation) + { + if (t < 1) + { + Move(deltaTime, + release ? pars.releaseTime : pars.attackTime, + release ? pars.releaseCurve : pars.attackCurve); + } + else + { + CurrentDisplacement = currentWaypoint; + prevWaypoint = currentWaypoint; + if (release) + { + IsFinished = true; + return; + } + else + { + release = true; + t = 0; + currentWaypoint = Displacement.Zero; + } + } + } + + private void Move(float deltaTime, float duration, AnimationCurve curve) + { + if (duration > 0) + t += deltaTime / duration; + else + t = 1; + CurrentDisplacement = Displacement.Lerp(prevWaypoint, currentWaypoint, curve.Evaluate(t)); + } + + [System.Serializable] + public class Params + { + /// + /// Strength of the shake for each axis. + /// + [Tooltip("Strength of the shake for each axis.")] + public Displacement strength = new Displacement(Vector3.zero, Vector3.one); + + /// + /// How long it takes to move forward. + /// + [Tooltip("How long it takes to move forward.")] + public float attackTime = 0.05f; + public AnimationCurve attackCurve = AnimationCurve.EaseInOut(0, 0, 1, 1); + + /// + /// How long it takes to move back. + /// + [Tooltip("How long it takes to move back.")] + public float releaseTime = 0.2f; + public AnimationCurve releaseCurve = AnimationCurve.EaseInOut(0, 0, 1, 1); + + /// + /// How strength falls with distance from the shake source. + /// + [Tooltip("How strength falls with distance from the shake source.")] + public Attenuator.StrengthAttenuationParams attenuation; + } + } +} diff --git a/KFAttached/GG Camera Shake/Runtime/KickShake.cs.meta b/KFAttached/GG Camera Shake/Runtime/KickShake.cs.meta new file mode 100644 index 0000000..9f084d9 --- /dev/null +++ b/KFAttached/GG Camera Shake/Runtime/KickShake.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: aacae9308ca14a04a8174bd35d85a1be +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/GG Camera Shake/Runtime/PerlinShake.cs b/KFAttached/GG Camera Shake/Runtime/PerlinShake.cs new file mode 100644 index 0000000..9e254b2 --- /dev/null +++ b/KFAttached/GG Camera Shake/Runtime/PerlinShake.cs @@ -0,0 +1,144 @@ +using UnityEngine; + +namespace CameraShake +{ + public class PerlinShake : ICameraShake + { + readonly Params pars; + readonly Envelope envelope; + + public IAmplitudeController AmplitudeController; + + Vector2[] seeds; + float time; + Vector3? sourcePosition; + float norm; + + /// + /// Creates an instance of PerlinShake. + /// + /// Parameters of the shake. + /// Maximum amplitude of the shake. + /// World position of the source of the shake. + /// Pass true if you want to control amplitude manually. + public PerlinShake( + Params parameters, + float maxAmplitude = 1, + Vector3? sourcePosition = null, + bool manualStrengthControl = false) + { + pars = parameters; + envelope = new Envelope(pars.envelope, maxAmplitude, + manualStrengthControl ? + Envelope.EnvelopeControlMode.Manual : Envelope.EnvelopeControlMode.Auto); + AmplitudeController = envelope; + this.sourcePosition = sourcePosition; + } + + public Displacement CurrentDisplacement { get; private set; } + public bool IsFinished { get; private set; } + + public void Initialize(Vector3 cameraPosition, Quaternion cameraRotation) + { + seeds = new Vector2[pars.noiseModes.Length]; + norm = 0; + for (int i = 0; i < seeds.Length; i++) + { + seeds[i] = Random.insideUnitCircle * 20; + norm += pars.noiseModes[i].amplitude; + } + } + + public void Update(float deltaTime, Vector3 cameraPosition, Quaternion cameraRotation) + { + if (envelope.IsFinished) + { + IsFinished = true; + return; + } + time += deltaTime; + envelope.Update(deltaTime); + + Displacement disp = Displacement.Zero; + for (int i = 0; i < pars.noiseModes.Length; i++) + { + disp += pars.noiseModes[i].amplitude / norm * + SampleNoise(seeds[i], pars.noiseModes[i].freq); + } + + CurrentDisplacement = envelope.Intensity * Displacement.Scale(disp, pars.strength); + if (sourcePosition != null) + CurrentDisplacement *= Attenuator.Strength(pars.attenuation, sourcePosition.Value, cameraPosition); + } + + private Displacement SampleNoise(Vector2 seed, float freq) + { + Vector3 position = new Vector3( + Mathf.PerlinNoise(seed.x + time * freq, seed.y), + Mathf.PerlinNoise(seed.x, seed.y + time * freq), + Mathf.PerlinNoise(seed.x + time * freq, seed.y + time * freq)); + position -= Vector3.one * 0.5f; + + Vector3 rotation = new Vector3( + Mathf.PerlinNoise(-seed.x - time * freq, -seed.y), + Mathf.PerlinNoise(-seed.x, -seed.y - time * freq), + Mathf.PerlinNoise(-seed.x - time * freq, -seed.y - time * freq)); + rotation -= Vector3.one * 0.5f; + + return new Displacement(position, rotation); + } + + + [System.Serializable] + public class Params + { + /// + /// Strength of the shake for each axis. + /// + [Tooltip("Strength of the shake for each axis.")] + public Displacement strength = new Displacement(Vector3.zero, new Vector3(2, 2, 0.8f)); + + /// + /// Layers of perlin noise with different frequencies. + /// + [Tooltip("Layers of perlin noise with different frequencies.")] + public NoiseMode[] noiseModes = { new NoiseMode(12, 1) }; + + /// + /// Strength over time. + /// + [Tooltip("Strength of the shake over time.")] + public Envelope.EnvelopeParams envelope; + + /// + /// How strength falls with distance from the shake source. + /// + [Tooltip("How strength falls with distance from the shake source.")] + public Attenuator.StrengthAttenuationParams attenuation; + } + + + [System.Serializable] + public struct NoiseMode + { + public NoiseMode(float freq, float amplitude) + { + this.freq = freq; + this.amplitude = amplitude; + } + + /// + /// Frequency multiplier for the noise. + /// + [Tooltip("Frequency multiplier for the noise.")] + public float freq; + + /// + /// Amplitude of the mode. + /// + [Tooltip("Amplitude of the mode.")] + [Range(0, 1)] + public float amplitude; + } + } +} diff --git a/KFAttached/GG Camera Shake/Runtime/PerlinShake.cs.meta b/KFAttached/GG Camera Shake/Runtime/PerlinShake.cs.meta new file mode 100644 index 0000000..d3911c2 --- /dev/null +++ b/KFAttached/GG Camera Shake/Runtime/PerlinShake.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: de97a9ba28783f34cae2c44c1d4446d5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/GG Camera Shake/Runtime/Power.cs b/KFAttached/GG Camera Shake/Runtime/Power.cs new file mode 100644 index 0000000..9dd5864 --- /dev/null +++ b/KFAttached/GG Camera Shake/Runtime/Power.cs @@ -0,0 +1,24 @@ +namespace CameraShake +{ + public static class Power + { + public static float Evaluate(float value, Degree degree) + { + switch (degree) + { + case Degree.Linear: + return value; + case Degree.Quadratic: + return value * value; + case Degree.Cubic: + return value * value * value; + case Degree.Quadric: + return value * value * value * value; + default: + return value; + } + } + } + + public enum Degree { Linear, Quadratic, Cubic, Quadric } +} diff --git a/KFAttached/GG Camera Shake/Runtime/Power.cs.meta b/KFAttached/GG Camera Shake/Runtime/Power.cs.meta new file mode 100644 index 0000000..89ea42d --- /dev/null +++ b/KFAttached/GG Camera Shake/Runtime/Power.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ae1c138ac39e5704daa47650c0a286d0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/KFCommonUtilityLib.asmdef b/KFAttached/KFCommonUtilityLib.asmdef new file mode 100644 index 0000000..2d0426f --- /dev/null +++ b/KFAttached/KFCommonUtilityLib.asmdef @@ -0,0 +1,20 @@ +{ + "name": "KFCommonUtilityLib", + "rootNamespace": "", + "references": [ + "GUID:7f7d1af65c2641843945d409d28f2e20", + "GUID:2665a8d13d1b3f18800f46e256720795", + "GUID:d8b63aba1907145bea998dd612889d6b", + "GUID:6055be8ebefd69e48b49212b09b47b2f", + "GUID:d60799ab2a985554ea1a39cd38695018" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/KFAttached/KFCommonUtilityLib.asmdef.meta b/KFAttached/KFCommonUtilityLib.asmdef.meta new file mode 100644 index 0000000..e137f71 --- /dev/null +++ b/KFAttached/KFCommonUtilityLib.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 63c0eca1c28248248af7caf2a72a6055 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/KFUtilAttached.meta b/KFAttached/KFUtilAttached.meta new file mode 100644 index 0000000..9779b0a --- /dev/null +++ b/KFAttached/KFUtilAttached.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 72825f9e8d606284ba3dd8559d6e91fc +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/KFUtilAttached/ApexWeaponHudControllerBase.cs b/KFAttached/KFUtilAttached/ApexWeaponHudControllerBase.cs new file mode 100644 index 0000000..5156048 --- /dev/null +++ b/KFAttached/KFUtilAttached/ApexWeaponHudControllerBase.cs @@ -0,0 +1,154 @@ +using System; +using TMPro; +using UnityEngine; + +public class ApexWeaponHudControllerBase : MonoBehaviour +{ + [SerializeField] + protected ComputeShader cptShader; + [SerializeField, Range(0, 100)] + protected int interPerc; + [SerializeField] + protected TMP_Text boundText; + [SerializeField] + protected TMP_Text[] miscText; + [SerializeField] + protected Renderer screenRenderer; + [SerializeField] + protected Texture maskTexture; + [SerializeField] + protected int matIndex; + [SerializeField, Range(0, 32)] + protected int depth = 0; + [SerializeField] + protected RenderTextureFormat renderTextureFormat = RenderTextureFormat.Default; + [SerializeField] + protected FilterMode filterMode = FilterMode.Point; + [SerializeField] + protected UnityEngine.Experimental.Rendering.GraphicsFormat graphicsFormat = UnityEngine.Experimental.Rendering.GraphicsFormat.R8G8B8A8_SRGB; + [SerializeField] + private string kernalName; + [SerializeField, Range(0, 1)] + protected float xScale = 1, yScale = 1; + protected int kernalIndex = -1; + protected Material mat; + protected int xGroupCount, yGroupCount; + protected CustomRenderTexture targetTexture; + protected static bool shaderEnabled; + protected static bool stateChecked = false; + //max count, elem count, inter pixels, map size + protected readonly int[] dataArray = new int[3]; + protected Color color = Color.white; + + protected static readonly int id_color = Shader.PropertyToID("color"); + protected static readonly int id_dataArray = Shader.PropertyToID("dataArray"); + protected static readonly int id_Mask = Shader.PropertyToID("Mask"); + protected static readonly int id_EmissionMap = Shader.PropertyToID("EmissionMap"); + protected static readonly int id_EmissionColor = Shader.PropertyToID("_EmissionColor"); + + protected virtual void Awake() + { + if (!stateChecked) + { + shaderEnabled = SystemInfo.supportsComputeShaders && SystemInfo.graphicsDeviceType != UnityEngine.Rendering.GraphicsDeviceType.Null && !Application.isBatchMode; + stateChecked = true; + Console.WriteLine("Compute shader support: " + shaderEnabled); + } + + dataArray[0] = 1; + dataArray[1] = 0; + dataArray[2] = interPerc; + xGroupCount = (int)(maskTexture.width * xScale / 8); + yGroupCount = (int)(maskTexture.height * yScale / 8); + mat = screenRenderer.materials[matIndex]; + } + + protected virtual void OnEnable() + { + if (!shaderEnabled) + return; + + if (targetTexture == null) + { + targetTexture = new CustomRenderTexture(maskTexture.width, maskTexture.height, renderTextureFormat) + { + enableRandomWrite = true, + updateMode = CustomRenderTextureUpdateMode.OnDemand, + depth = depth, + useMipMap = false, + autoGenerateMips = false, + filterMode = filterMode, + graphicsFormat = graphicsFormat, + wrapMode = TextureWrapMode.Clamp + }; + } + + if (!targetTexture.IsCreated()) + targetTexture.Create(); + + mat.SetTexture("_EmissionMap", targetTexture); + mat.SetColor(id_EmissionColor, Color.white); + mat.EnableKeyword("_EMISSION"); + if (cptShader.HasKernel(kernalName)) + kernalIndex = cptShader.FindKernel(kernalName); + } + + protected virtual void OnDisable() + { + + } + + protected virtual void OnDestroy() + { + OnDisable(); + if (targetTexture != null) + targetTexture.Release(); + } + + public virtual void SetColor(Color color) + { + if (boundText != null) + boundText.color = color; + if (miscText != null) + foreach (var t in miscText) + t.color = color; + + this.color = color; + //mat.SetColor(id_EmissionColor, color); + if (CanDispatch()) + Dispatch(dataArray); + } + + public virtual void SetText(string text) + { + if (text.StartsWith("#")) + { + dataArray[0] = Mathf.Max(int.Parse(text.Substring(1)), 1); + } + else + { + if (boundText != null) + boundText.SetText(text); + dataArray[1] = int.Parse(text); + } + + if (CanDispatch()) + Dispatch(dataArray); + } + + protected virtual bool CanDispatch() + { + return shaderEnabled && kernalIndex >= 0; + } + + protected virtual void Dispatch(int[] dataArray) + { + cptShader.SetInts(id_dataArray, dataArray); + cptShader.SetVector(id_color, color); + cptShader.SetTexture(kernalIndex, id_Mask, maskTexture); + cptShader.SetTexture(kernalIndex, id_EmissionMap, targetTexture, 0); + cptShader.Dispatch(kernalIndex, xGroupCount, yGroupCount, 1); + //targetTexture.GenerateMips(); + //targetTexture.Update(); + } +} diff --git a/KFAttached/KFUtilAttached/ApexWeaponHudControllerBase.cs.meta b/KFAttached/KFUtilAttached/ApexWeaponHudControllerBase.cs.meta new file mode 100644 index 0000000..d33b3ba --- /dev/null +++ b/KFAttached/KFUtilAttached/ApexWeaponHudControllerBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b0e18e2e55ec5014aad7a8808cd65efa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/KFUtilAttached/ChargeUpController.cs b/KFAttached/KFUtilAttached/ChargeUpController.cs new file mode 100644 index 0000000..e5356a7 --- /dev/null +++ b/KFAttached/KFUtilAttached/ChargeUpController.cs @@ -0,0 +1,17 @@ +using UnityEngine; + +[AddComponentMenu("KFAttachments/Weapon Display Controllers/Charge Up controller")] +public class ChargeUpController : MonoBehaviour +{ + [SerializeField] + private WeaponLabelControllerChargeUp controller; + private void OnEnable() + { + controller.StartChargeUp(); + } + + private void OnDisable() + { + controller.StopChargeUp(); + } +} diff --git a/KFAttached/KFUtilAttached/ChargeUpController.cs.meta b/KFAttached/KFUtilAttached/ChargeUpController.cs.meta new file mode 100644 index 0000000..4df5374 --- /dev/null +++ b/KFAttached/KFUtilAttached/ChargeUpController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ccb8b18bea7ff8843a380b9e611799de +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/KFUtilAttached/MuzzlePositionBinding.cs b/KFAttached/KFUtilAttached/MuzzlePositionBinding.cs new file mode 100644 index 0000000..447e379 --- /dev/null +++ b/KFAttached/KFUtilAttached/MuzzlePositionBinding.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +[AddComponentMenu("Muzzle Position Binding")] +public class MuzzlePositionBinding : MonoBehaviour +{ + [SerializeField] + private Transform muzzleTrans; + [SerializeField] + private Vector3 newMuzzlePosition; + private Vector3 initialPosition; + private void Awake() + { + if (muzzleTrans) + initialPosition = transform.localPosition; + } + + private void OnEnable() + { + if (muzzleTrans) + muzzleTrans.localPosition = newMuzzlePosition; + } + + private void OnDisable() + { + if (muzzleTrans) + muzzleTrans.localPosition = initialPosition; + } +} diff --git a/KFAttached/KFUtilAttached/MuzzlePositionBinding.cs.meta b/KFAttached/KFUtilAttached/MuzzlePositionBinding.cs.meta new file mode 100644 index 0000000..9248bb9 --- /dev/null +++ b/KFAttached/KFUtilAttached/MuzzlePositionBinding.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5d4b7e316af9faf4f9697b0d60f96793 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/KFUtilAttached/RigActivationBinding.cs b/KFAttached/KFUtilAttached/RigActivationBinding.cs new file mode 100644 index 0000000..b2acc11 --- /dev/null +++ b/KFAttached/KFUtilAttached/RigActivationBinding.cs @@ -0,0 +1,69 @@ +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("KFAttachments/Binding Helpers/Rig Activation Binding")] +public class RigActivationBinding : MonoBehaviour +{ + [SerializeField] + private RigLayer[] bindings; + [SerializeField] + private RigLayer[] inverseBindings; + [SerializeField] + private RigLayer[] enableOnDisable; + [SerializeField] + private RigLayer[] disableOnEnable; + + private void OnEnable() + { + //Log.Out(gameObject.name + " OnEnable!"); + if (bindings != null) + { + foreach (RigLayer t in bindings) + if (t != null) + t.active = true; + } + if (inverseBindings != null) + { + foreach (RigLayer t in inverseBindings) + { + if (t != null) + t.active = false; + } + } + if (disableOnEnable != null) + { + foreach (RigLayer t in disableOnEnable) + { + if (t != null) + t.active = false; + } + } + } + + private void OnDisable() + { + //Log.Out(gameObject.name + " OnDisable!"); + if (bindings != null) + { + foreach (RigLayer t in bindings) + if (t != null) + t.active = false; + } + if (inverseBindings != null) + { + foreach (RigLayer t in inverseBindings) + { + if (t != null) + t.active = true; + } + } + if (enableOnDisable != null) + { + foreach (RigLayer t in enableOnDisable) + { + if (t != null) + t.active = true; + } + } + } +} diff --git a/KFAttached/KFUtilAttached/RigActivationBinding.cs.meta b/KFAttached/KFUtilAttached/RigActivationBinding.cs.meta new file mode 100644 index 0000000..d59db40 --- /dev/null +++ b/KFAttached/KFUtilAttached/RigActivationBinding.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 01778f8886cb5864c842e544741586b5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/KFUtilAttached/TransformActivationBinding.cs b/KFAttached/KFUtilAttached/TransformActivationBinding.cs new file mode 100644 index 0000000..8d67275 --- /dev/null +++ b/KFAttached/KFUtilAttached/TransformActivationBinding.cs @@ -0,0 +1,141 @@ +using System.Collections; +using UnityEngine; + +[AddComponentMenu("KFAttachments/Binding Helpers/Transform Activation Binding")] +public class TransformActivationBinding : MonoBehaviour +{ + [SerializeField] + internal GameObject[] bindings; + [SerializeField] + private GameObject[] inverseBindings; + [SerializeField] + private GameObject[] enableOnDisable; + [SerializeField] + private GameObject[] disableOnEnable; + [SerializeField] + private GameObject[] enableOnEnable; + [SerializeField] + private GameObject[] disableOnDisable; + [SerializeField] + private string[] animatorParamBindings; + internal AnimationTargetsAbs targets; + + private void OnEnable() + { + //Log.Out(gameObject.name + " OnEnable!"); + if (bindings != null) + { + foreach (GameObject t in bindings) + { + if (t) + t.SetActive(true); + } + } + if (inverseBindings != null) + { + foreach (GameObject t in inverseBindings) + { + if (t) + t.SetActive(false); + } + } + if (disableOnEnable != null) + { + foreach (GameObject t in disableOnEnable) + { + if (t) + t.SetActive(false); + } + } + if (enableOnEnable != null) + { + foreach (GameObject t in enableOnEnable) + { + if (t) + t.SetActive(true); + } + } +#if NotEditor + ThreadManager.StartCoroutine(UpdateBool(true)); +#else + UpdateBoolEditor(true); +#endif + } + + private void OnDisable() + { + //Log.Out(gameObject.name + " OnDisable!"); + if (bindings != null) + { + foreach (GameObject t in bindings) + if (t) + t.SetActive(false); + } + if (inverseBindings != null) + { + foreach (GameObject t in inverseBindings) + { + if (t) + t.SetActive(true); + } + } + if (enableOnDisable != null) + { + foreach (GameObject t in enableOnDisable) + { + if (t) + t.SetActive(true); + } + } + if (disableOnDisable != null) + { + foreach (GameObject t in disableOnDisable) + { + if (t) + t.SetActive(true); + } + } +#if NotEditor + ThreadManager.StartCoroutine(UpdateBool(false)); +#else + UpdateBoolEditor(false); +#endif + } + +#if NotEditor + internal IEnumerator UpdateBool(bool enabled) + { + yield return new WaitForEndOfFrame(); + if (animatorParamBindings != null && targets && targets.IsAnimationSet) + { + foreach (string str in animatorParamBindings) + { + if (str != null) + { + targets.GraphBuilder.Player.emodel.avatarController.UpdateBool(str, enabled); + } + } + } + } +#else + internal void UpdateBoolEditor(bool enabled) + { + if (animatorParamBindings != null && targets && targets.IsAnimationSet) + { + IAnimatorWrapper animator = targets.GraphBuilder.WeaponWrapper; + if (animator == null || !animator.IsValid) + { + Log.Warning($"animator wrapper invalid!"); + return; + } + foreach (string str in animatorParamBindings) + { + if (str != null) + { + animator.SetBool(str, enabled); + } + } + } + } +#endif +} diff --git a/KFAttached/KFUtilAttached/TransformActivationBinding.cs.meta b/KFAttached/KFUtilAttached/TransformActivationBinding.cs.meta new file mode 100644 index 0000000..1f7e5ff --- /dev/null +++ b/KFAttached/KFUtilAttached/TransformActivationBinding.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2f2bc8183b119f347a32259111b5d1f2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/KFUtilAttached/WeaponColorController.cs b/KFAttached/KFUtilAttached/WeaponColorController.cs new file mode 100644 index 0000000..a3371a4 --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponColorController.cs @@ -0,0 +1,16 @@ +using UnityEngine; + +[AddComponentMenu("KFAttachments/Weapon Display Controllers/Weapon Color Controller")] +public class WeaponColorController : WeaponColorControllerBase +{ + [SerializeField] + protected Renderer[] renderers; + + public override bool setMaterialColor(int renderer_index, int material_index, int nameId, Color data) + { + if (renderers == null || renderers.Length <= renderer_index || !renderers[renderer_index].gameObject.activeInHierarchy || renderers[renderer_index].materials.Length <= material_index) + return false; + renderers[renderer_index].materials[material_index].SetColor(nameId, data); + return true; + } +} diff --git a/KFAttached/KFUtilAttached/WeaponColorController.cs.meta b/KFAttached/KFUtilAttached/WeaponColorController.cs.meta new file mode 100644 index 0000000..4e10292 --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponColorController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dcfafc95230152c408908547f15202e9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/KFUtilAttached/WeaponColorControllerBase.cs b/KFAttached/KFUtilAttached/WeaponColorControllerBase.cs new file mode 100644 index 0000000..abf2380 --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponColorControllerBase.cs @@ -0,0 +1,6 @@ +using UnityEngine; + +public abstract class WeaponColorControllerBase : MonoBehaviour +{ + public abstract bool setMaterialColor(int renderer_index, int material_index, int nameId, Color data); +} diff --git a/KFAttached/KFUtilAttached/WeaponColorControllerBase.cs.meta b/KFAttached/KFUtilAttached/WeaponColorControllerBase.cs.meta new file mode 100644 index 0000000..40ed4b1 --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponColorControllerBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6fe7c9577b97dd8448e83857ceac21db +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/KFUtilAttached/WeaponDataController.cs b/KFAttached/KFUtilAttached/WeaponDataController.cs new file mode 100644 index 0000000..8897ca6 --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponDataController.cs @@ -0,0 +1,24 @@ +using UnityEngine; + +public class WeaponDataController : WeaponLabelControllerBase +{ + [SerializeField] + private WeaponDataHandlerBase[] handlers; + public override bool setLabelColor(int index, Color color) + { + if (handlers == null || index >= handlers.Length || index < 0 || !handlers[index] || !handlers[index].gameObject.activeSelf) + return false; + + handlers[index]?.SetColor(color); + return true; + } + + public override bool setLabelText(int index, string data) + { + if (handlers == null || index >= handlers.Length || index < 0 || !handlers[index] || !handlers[index].gameObject.activeSelf) + return false; + + handlers[index]?.SetText(data); + return true; + } +} \ No newline at end of file diff --git a/KFAttached/KFUtilAttached/WeaponDataController.cs.meta b/KFAttached/KFUtilAttached/WeaponDataController.cs.meta new file mode 100644 index 0000000..0e283fb --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponDataController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9f524ab568d21bf4d8f037fe0dee9c22 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/KFUtilAttached/WeaponDataHandlerBase.cs b/KFAttached/KFUtilAttached/WeaponDataHandlerBase.cs new file mode 100644 index 0000000..a0ca076 --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponDataHandlerBase.cs @@ -0,0 +1,7 @@ +using UnityEngine; + +public abstract class WeaponDataHandlerBase : MonoBehaviour +{ + public abstract void SetColor(Color color); + public abstract void SetText(string text); +} diff --git a/KFAttached/KFUtilAttached/WeaponDataHandlerBase.cs.meta b/KFAttached/KFUtilAttached/WeaponDataHandlerBase.cs.meta new file mode 100644 index 0000000..0796bfe --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponDataHandlerBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3657194e2634c8a4a8aea7e7becd0581 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/KFUtilAttached/WeaponDataHandlerCanvasMask.cs b/KFAttached/KFUtilAttached/WeaponDataHandlerCanvasMask.cs new file mode 100644 index 0000000..a0b0daf --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponDataHandlerCanvasMask.cs @@ -0,0 +1,51 @@ +using UnityEngine; +using UnityEngine.UI; + +public class WeaponDataHandlerCanvasMask : WeaponDataHandlerBase +{ + [SerializeField] + protected RectMask2D mask; + [SerializeField] + protected Image image; + + protected float maxVal = 1, curVal = 1; + //protected bool updated = true; + + //protected virtual void OnEnable() + //{ + // LayoutRebuilder.MarkLayoutForRebuild(mask.rectTransform); + //} + + public override void SetColor(Color color) + { + image.color = color; + } + + public override void SetText(string text) + { + if (text.StartsWith("#")) + { + maxVal = Mathf.Max(float.Parse(text.Substring(1)), 1); + } + else + { + curVal = Mathf.Max(float.Parse(text), 0); + } + if (curVal > maxVal) + maxVal = curVal; + float perc = curVal / maxVal; + Vector4 padding = mask.padding; + padding.w = mask.rectTransform.rect.height * Mathf.Clamp01(1 - perc); + mask.padding = padding; + //updated = true; + } + + //protected virtual void LateUpdate() + //{ + // if (updated) + // { + // LayoutRebuilder.ForceRebuildLayoutImmediate(mask.rectTransform); + // updated = false; + // } + //} +} diff --git a/KFAttached/KFUtilAttached/WeaponDataHandlerCanvasMask.cs.meta b/KFAttached/KFUtilAttached/WeaponDataHandlerCanvasMask.cs.meta new file mode 100644 index 0000000..d10ff1a --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponDataHandlerCanvasMask.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 013c89968af9af042960a4fec49cd708 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/KFUtilAttached/WeaponDataHandlerIndicator.cs b/KFAttached/KFUtilAttached/WeaponDataHandlerIndicator.cs new file mode 100644 index 0000000..154de68 --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponDataHandlerIndicator.cs @@ -0,0 +1,71 @@ +using UnityEngine; + +public class WeaponDataHandlerIndicator : WeaponDataHandlerCanvasMask +{ + [SerializeField] + protected RectTransform indicator; + [SerializeField] + protected float offset; + [SerializeField, ColorUsage(true, true)] + protected Color normalColor; + [SerializeField, ColorUsage(true, true)] + protected Color warningColor; + + protected float level = 0; + + //private void Start() + //{ + // LayoutRebuilder.MarkLayoutForRebuild(indicator.parent.GetComponent()); + //} + + //protected override void OnEnable() + //{ + // base.OnEnable(); + // LayoutRebuilder.MarkLayoutForRebuild(indicator.parent.GetComponent()); + //} + + public override void SetText(string text) + { + if (text.StartsWith("$")) + { + level = Mathf.Clamp01(float.Parse(text.Substring(1)) / maxVal); + SetIndicatorPos(); + //updated = true; + } + else + { + float prevMax = maxVal; + base.SetText(text); + if (prevMax != maxVal) + { + level = prevMax * level / maxVal; + SetIndicatorPos(); + } + } + //Log.Out($"Setting text {text} max {maxVal} cur {curVal} level {level} indicator position {indicator.position.y} mask position {mask.padding.w}"); + if (curVal / maxVal < level) + { + SetColor(warningColor); + } + else + { + SetColor(normalColor); + } + } + + private void SetIndicatorPos() + { + Vector3 pos = indicator.anchoredPosition; + pos.y = mask.rectTransform.rect.height * level + offset; + indicator.anchoredPosition = pos; + } + + //protected override void LateUpdate() + //{ + // if (updated) + // { + // LayoutRebuilder.ForceRebuildLayoutImmediate(indicator.parent.GetComponent()); + // } + // base.LateUpdate(); + //} +} diff --git a/KFAttached/KFUtilAttached/WeaponDataHandlerIndicator.cs.meta b/KFAttached/KFUtilAttached/WeaponDataHandlerIndicator.cs.meta new file mode 100644 index 0000000..d9d10c3 --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponDataHandlerIndicator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e993a995fe33f8742bd17bf4b5cae305 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/KFUtilAttached/WeaponDataHandlerTMP.cs b/KFAttached/KFUtilAttached/WeaponDataHandlerTMP.cs new file mode 100644 index 0000000..5e002e7 --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponDataHandlerTMP.cs @@ -0,0 +1,17 @@ +using TMPro; +using UnityEngine; + +public class WeaponDataHandlerTMP : WeaponDataHandlerBase +{ + [SerializeField] + private TMP_Text label; + public override void SetColor(Color color) + { + label.color = color; + } + + public override void SetText(string text) + { + label.SetText(text); + } +} diff --git a/KFAttached/KFUtilAttached/WeaponDataHandlerTMP.cs.meta b/KFAttached/KFUtilAttached/WeaponDataHandlerTMP.cs.meta new file mode 100644 index 0000000..d6a2f87 --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponDataHandlerTMP.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b6ee4e451d350e048954e14bbccf6e6b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/KFUtilAttached/WeaponLabelController.cs b/KFAttached/KFUtilAttached/WeaponLabelController.cs new file mode 100644 index 0000000..0f518f7 --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponLabelController.cs @@ -0,0 +1,24 @@ +using UnityEngine; + +[AddComponentMenu("KFAttachments/Weapon Display Controllers/Weapon Label Controller TextMesh")] +public class WeaponLabelController : WeaponLabelControllerBase +{ + [SerializeField] + private TextMesh[] labels; + + public override bool setLabelText(int index, string data) + { + if (labels == null || labels.Length <= index || !labels[index] || !labels[index].gameObject.activeSelf) + return false; + labels[index].text = data; + return true; + } + + public override bool setLabelColor(int index, Color color) + { + if (labels == null || labels.Length <= index || !labels[index] || !labels[index].gameObject.activeSelf) + return false; + labels[index].color = color; + return true; + } +} diff --git a/KFAttached/KFUtilAttached/WeaponLabelController.cs.meta b/KFAttached/KFUtilAttached/WeaponLabelController.cs.meta new file mode 100644 index 0000000..31f5a97 --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponLabelController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a067c009a9175a34db35886e48ce0129 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/KFUtilAttached/WeaponLabelControllerBase.cs b/KFAttached/KFUtilAttached/WeaponLabelControllerBase.cs new file mode 100644 index 0000000..4a9acb3 --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponLabelControllerBase.cs @@ -0,0 +1,7 @@ +using UnityEngine; + +public abstract class WeaponLabelControllerBase : MonoBehaviour +{ + public abstract bool setLabelText(int index, string data); + public abstract bool setLabelColor(int index, Color color); +} diff --git a/KFAttached/KFUtilAttached/WeaponLabelControllerBase.cs.meta b/KFAttached/KFUtilAttached/WeaponLabelControllerBase.cs.meta new file mode 100644 index 0000000..8aca399 --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponLabelControllerBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cc5547a942561564bb81d0ee893e4100 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/KFUtilAttached/WeaponLabelControllerBatch.cs b/KFAttached/KFUtilAttached/WeaponLabelControllerBatch.cs new file mode 100644 index 0000000..1eefeb0 --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponLabelControllerBatch.cs @@ -0,0 +1,36 @@ +using UnityEngine; + +namespace KFCommonUtilityLib.KFAttached.KFUtilAttached +{ + public class WeaponLabelControllerBatch : WeaponLabelControllerBase + { + [SerializeField] + private WeaponLabelControllerBase[] controllers; + + public override bool setLabelColor(int index, Color color) + { + bool flag = false; + foreach (var controller in controllers) + { + if (controller && controller.isActiveAndEnabled) + { + flag |= controller.setLabelColor(index, color); + } + } + return flag; + } + + public override bool setLabelText(int index, string data) + { + bool flag = false; + foreach (var controller in controllers) + { + if (controller && controller.isActiveAndEnabled) + { + flag |= controller.setLabelText(index, data); + } + } + return flag; + } + } +} diff --git a/KFAttached/KFUtilAttached/WeaponLabelControllerBatch.cs.meta b/KFAttached/KFUtilAttached/WeaponLabelControllerBatch.cs.meta new file mode 100644 index 0000000..6c89d6a --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponLabelControllerBatch.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9bdf1f2fdda9aae4c80dd6e0bdba81d4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/KFUtilAttached/WeaponLabelControllerChargeUp.cs b/KFAttached/KFUtilAttached/WeaponLabelControllerChargeUp.cs new file mode 100644 index 0000000..62784f7 --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponLabelControllerChargeUp.cs @@ -0,0 +1,61 @@ +using System.Collections; +using UnityEngine; + +[AddComponentMenu("KFAttachments/Weapon Display Controllers/Weapon Label Controller Charge Up")] +public class WeaponLabelControllerChargeUp : ApexWeaponHudControllerBase +{ + [SerializeField, Range(0.001f, 1f)] + protected float tickTime; + [SerializeField, Range(1, 1000)] + protected int updateTicks = 1; + protected Coroutine curChargeProc; + protected bool isChargeRunning = false; + internal void StartChargeUp() + { + if (shaderEnabled && (curChargeProc == null || !isChargeRunning)) + curChargeProc = StartCoroutine(ChargeUp()); + } + + internal void StopChargeUp() + { + if (shaderEnabled && curChargeProc != null) + { + StopCoroutine(curChargeProc); + curChargeProc = null; + isChargeRunning = false; + } + } + + protected override void OnDisable() + { + StopChargeUp(); + } + + private IEnumerator ChargeUp() + { + isChargeRunning = true; + float chargeLeap = (float)dataArray[0] / updateTicks; + int[] chargeArray = new int[4]; + float curChargeCount = 0; + dataArray.CopyTo(chargeArray, 0); + int max = dataArray[1]; + chargeArray[1] = (int)curChargeCount; + while (chargeArray[1] <= max) + { + Dispatch(chargeArray); + if (chargeArray[1] == max) + break; + yield return new WaitForSecondsRealtime(tickTime); + max = dataArray[1]; + curChargeCount += chargeLeap; + chargeArray[1] = Mathf.Min((int)curChargeCount, max); + } + isChargeRunning = false; + yield break; + } + + protected override bool CanDispatch() + { + return base.CanDispatch() && !isChargeRunning; + } +} diff --git a/KFAttached/KFUtilAttached/WeaponLabelControllerChargeUp.cs.meta b/KFAttached/KFUtilAttached/WeaponLabelControllerChargeUp.cs.meta new file mode 100644 index 0000000..1d0e1d4 --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponLabelControllerChargeUp.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 24181b5d7224798438ea0bb73892de70 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/KFUtilAttached/WeaponLabelControllerDevotion.cs b/KFAttached/KFUtilAttached/WeaponLabelControllerDevotion.cs new file mode 100644 index 0000000..f0b0fb4 --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponLabelControllerDevotion.cs @@ -0,0 +1,25 @@ +using UnityEngine; + +[AddComponentMenu("KFAttachments/Weapon Display Controllers/Weapon Label Controller Devotion")] +public class WeaponLabelControllerDevotion : WeaponLabelControllerBase +{ + [SerializeField] + private ApexWeaponHudControllerBase[] controllers; + public override bool setLabelColor(int index, Color color) + { + if (controllers == null || index >= controllers.Length || !controllers[index] || !controllers[index].gameObject.activeSelf) + return false; + + controllers[index].SetColor(color); + return true; + } + + public override bool setLabelText(int index, string data) + { + if (controllers == null || index >= controllers.Length || !controllers[index] || !controllers[index].gameObject.activeSelf) + return false; + + controllers[index].SetText(data); + return true; + } +} diff --git a/KFAttached/KFUtilAttached/WeaponLabelControllerDevotion.cs.meta b/KFAttached/KFUtilAttached/WeaponLabelControllerDevotion.cs.meta new file mode 100644 index 0000000..a7bc76d --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponLabelControllerDevotion.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6bc6e113f61659f489e67db4beb49b7d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/KFUtilAttached/WeaponTextProController.cs b/KFAttached/KFUtilAttached/WeaponTextProController.cs new file mode 100644 index 0000000..ba73e82 --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponTextProController.cs @@ -0,0 +1,25 @@ +using TMPro; +using UnityEngine; + +[AddComponentMenu("KFAttachments/Weapon Display Controllers/Weapon Text Controller TMP")] +public class WeaponTextProController : WeaponLabelControllerBase +{ + [SerializeField] + private TMP_Text[] labels; + + public override bool setLabelText(int index, string data) + { + if (labels == null || labels.Length <= index || !labels[index] || !labels[index].gameObject.activeSelf) + return false; + labels[index].SetText(data); + return true; + } + + public override bool setLabelColor(int index, Color color) + { + if (labels == null || labels.Length <= index || !labels[index] || !labels[index].gameObject.activeSelf) + return false; + labels[index].color = color; + return true; + } +} diff --git a/KFAttached/KFUtilAttached/WeaponTextProController.cs.meta b/KFAttached/KFUtilAttached/WeaponTextProController.cs.meta new file mode 100644 index 0000000..a63b0c5 --- /dev/null +++ b/KFAttached/KFUtilAttached/WeaponTextProController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 93439c08074a2ef42b5c4da798001216 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/LeanTween.meta b/KFAttached/LeanTween.meta new file mode 100644 index 0000000..eb22f9c --- /dev/null +++ b/KFAttached/LeanTween.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5e6a0fa47acf54892bbdae89028eaec3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/LeanTween/Framework.meta b/KFAttached/LeanTween/Framework.meta new file mode 100644 index 0000000..5e492d8 --- /dev/null +++ b/KFAttached/LeanTween/Framework.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a3f23ec8eb7c24f0bbb1d41bf96c154f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/LeanTween/Framework/LTDescr.cs b/KFAttached/LeanTween/Framework/LTDescr.cs new file mode 100644 index 0000000..3de2355 --- /dev/null +++ b/KFAttached/LeanTween/Framework/LTDescr.cs @@ -0,0 +1,2695 @@ +//namespace DentedPixel{ +using System; +using UnityEngine; + +/** +* Internal Representation of a Tween
+*
+* This class represents all of the optional parameters you can pass to a method (it also represents the internal representation of the tween).

+* Optional Parameters are passed at the end of every method:
+*
+*   Example:
+*   LeanTween.moveX( gameObject, 1f, 1f).setEase( LeanTweenType.easeInQuad ).setDelay(1f);
+*
+* You can pass the optional parameters in any order, and chain on as many as you wish.
+* You can also pass parameters at a later time by saving a reference to what is returned.
+*
+* Retrieve a unique id for the tween by using the "id" property. You can pass this to LeanTween.pause, LeanTween.resume, LeanTween.cancel, LeanTween.isTweening methods
+*
+*   

Example:

+*   int id = LeanTween.moveX(gameObject, 1f, 3f).id;
+*
  // pause a specific tween
+*   LeanTween.pause(id);
+*
  // resume later
+*   LeanTween.resume(id);
+*
  // check if it is tweening before kicking of a new tween
+*   if( LeanTween.isTweening( id ) ){
+*       LeanTween.cancel( id );
+*       LeanTween.moveZ(gameObject, 10f, 3f);
+*   }
+* @class LTDescr +* @constructor +*/ +public class LTDescr +{ + public bool toggle; + public bool useEstimatedTime; + public bool useFrames; + public bool useManualTime; + public bool usesNormalDt; + public bool hasInitiliazed; + public bool hasExtraOnCompletes; + public bool hasPhysics; + public bool onCompleteOnRepeat; + public bool onCompleteOnStart; + public bool useRecursion; + public float ratioPassed; + public float passed; + public float delay; + public float time; + public float speed; + public float lastVal; + private uint _id; + public int loopCount; + public uint counter = uint.MaxValue; + public float direction; + public float directionLast; + public float overshoot; + public float period; + public float scale; + public bool destroyOnComplete; + public Transform trans; + internal Vector3 fromInternal; + public Vector3 from { get { return this.fromInternal; } set { this.fromInternal = value; } } + internal Vector3 toInternal; + public Vector3 to { get { return this.toInternal; } set { this.toInternal = value; } } + internal Vector3 diff; + internal Vector3 diffDiv2; + public TweenAction type; + private LeanTweenType easeType; + public LeanTweenType loopType; + + public bool hasUpdateCallback; + + public EaseTypeDelegate easeMethod; + public ActionMethodDelegate easeInternal { get; set; } + public ActionMethodDelegate initInternal { get; set; } + public delegate Vector3 EaseTypeDelegate(); + public delegate void ActionMethodDelegate(); +#if !UNITY_3_5 && !UNITY_4_0 && !UNITY_4_0_1 && !UNITY_4_1 && !UNITY_4_2 + public SpriteRenderer spriteRen; +#endif + +#if !UNITY_3_5 && !UNITY_4_0 && !UNITY_4_0_1 && !UNITY_4_1 && !UNITY_4_2 && !UNITY_4_3 && !UNITY_4_5 + public RectTransform rectTransform; + public UnityEngine.UI.Text uiText; + public UnityEngine.UI.Image uiImage; + public UnityEngine.UI.RawImage rawImage; + public UnityEngine.Sprite[] sprites; +#endif + + // Convenience Getters + public Transform toTrans + { + get + { + return optional.toTrans; + } + } + + public LTDescrOptional _optional = new LTDescrOptional(); + + public override string ToString() + { + return (trans != null ? "name:" + trans.gameObject.name : "gameObject:null") + " toggle:" + toggle + " passed:" + passed + " time:" + time + " delay:" + delay + " direction:" + direction + " from:" + from + " to:" + to + " diff:" + diff + " type:" + type + " ease:" + easeType + " useEstimatedTime:" + useEstimatedTime + " id:" + id + " hasInitiliazed:" + hasInitiliazed; + } + + public LTDescr() + { + + } + + [System.Obsolete("Use 'LeanTween.cancel( id )' instead")] + public LTDescr cancel(GameObject gameObject) + { + // Debug.Log("canceling id:"+this._id+" this.uniqueId:"+this.uniqueId+" go:"+this.trans.gameObject); + if (gameObject == this.trans.gameObject) + LeanTween.removeTween((int)this._id, this.uniqueId); + return this; + } + + public int uniqueId + { + get + { + uint toId = _id | counter << 16; + + /*uint backId = toId & 0xFFFF; + uint backCounter = toId >> 16; + if(_id!=backId || backCounter!=counter){ + Debug.LogError("BAD CONVERSION toId:"+_id); + }*/ + + return (int)toId; + } + } + + public int id + { + get + { + return uniqueId; + } + } + + public LTDescrOptional optional + { + get + { + return _optional; + } + set + { + this._optional = value; + } + } + + public void reset() + { + this.toggle = this.useRecursion = this.usesNormalDt = true; + this.trans = null; + this.spriteRen = null; + this.passed = this.delay = this.lastVal = 0.0f; + this.hasUpdateCallback = this.useEstimatedTime = this.useFrames = this.hasInitiliazed = this.onCompleteOnRepeat = this.destroyOnComplete = this.onCompleteOnStart = this.useManualTime = this.hasExtraOnCompletes = false; + this.easeType = LeanTweenType.linear; + this.loopType = LeanTweenType.once; + this.loopCount = 0; + this.direction = this.directionLast = this.overshoot = this.scale = 1.0f; + this.period = 0.3f; + this.speed = -1f; + this.easeMethod = this.easeLinear; + this.from = this.to = Vector3.zero; + this._optional.reset(); + } + + // Initialize and Internal Methods + + public LTDescr setFollow() + { + this.type = TweenAction.FOLLOW; + return this; + } + + public LTDescr setMoveX() + { + this.type = TweenAction.MOVE_X; + this.initInternal = () => { this.fromInternal.x = trans.position.x; }; + this.easeInternal = () => { trans.position = new Vector3(easeMethod().x, trans.position.y, trans.position.z); }; + return this; + } + + public LTDescr setMoveY() + { + this.type = TweenAction.MOVE_Y; + this.initInternal = () => { this.fromInternal.x = trans.position.y; }; + this.easeInternal = () => { trans.position = new Vector3(trans.position.x, easeMethod().x, trans.position.z); }; + return this; + } + + public LTDescr setMoveZ() + { + this.type = TweenAction.MOVE_Z; + this.initInternal = () => { this.fromInternal.x = trans.position.z; }; ; + this.easeInternal = () => { trans.position = new Vector3(trans.position.x, trans.position.y, easeMethod().x); }; + return this; + } + + public LTDescr setMoveLocalX() + { + this.type = TweenAction.MOVE_LOCAL_X; + this.initInternal = () => { this.fromInternal.x = trans.localPosition.x; }; + this.easeInternal = () => { trans.localPosition = new Vector3(easeMethod().x, trans.localPosition.y, trans.localPosition.z); }; + return this; + } + + public LTDescr setMoveLocalY() + { + this.type = TweenAction.MOVE_LOCAL_Y; + this.initInternal = () => { this.fromInternal.x = trans.localPosition.y; }; + this.easeInternal = () => { trans.localPosition = new Vector3(trans.localPosition.x, easeMethod().x, trans.localPosition.z); }; + return this; + } + + public LTDescr setMoveLocalZ() + { + this.type = TweenAction.MOVE_LOCAL_Z; + this.initInternal = () => { this.fromInternal.x = trans.localPosition.z; }; + this.easeInternal = () => { trans.localPosition = new Vector3(trans.localPosition.x, trans.localPosition.y, easeMethod().x); }; + return this; + } + + private void initFromInternal() { this.fromInternal.x = 0; } + + public LTDescr setOffset(Vector3 offset) + { + this.toInternal = offset; + return this; + } + + public LTDescr setMoveCurved() + { + this.type = TweenAction.MOVE_CURVED; + this.initInternal = this.initFromInternal; + this.easeInternal = () => + { + newVect = easeMethod(); + val = newVect.x; + if (this._optional.path.orientToPath) + { + if (this._optional.path.orientToPath2d) + { + this._optional.path.place2d(trans, val); + } + else + { + this._optional.path.place(trans, val); + } + } + else + { + trans.position = this._optional.path.point(val); + } + }; + return this; + } + + public LTDescr setMoveCurvedLocal() + { + this.type = TweenAction.MOVE_CURVED_LOCAL; + this.initInternal = this.initFromInternal; + this.easeInternal = () => + { + newVect = easeMethod(); + val = newVect.x; + if (this._optional.path.orientToPath) + { + if (this._optional.path.orientToPath2d) + { + this._optional.path.placeLocal2d(trans, val); + } + else + { + this._optional.path.placeLocal(trans, val); + } + } + else + { + trans.localPosition = this._optional.path.point(val); + } + }; + return this; + } + + public LTDescr setMoveSpline() + { + this.type = TweenAction.MOVE_SPLINE; + this.initInternal = this.initFromInternal; + this.easeInternal = () => + { + newVect = easeMethod(); + val = newVect.x; + if (this._optional.spline.orientToPath) + { + if (this._optional.spline.orientToPath2d) + { + this._optional.spline.place2d(trans, val); + } + else + { + this._optional.spline.place(trans, val); + } + } + else + { + trans.position = this._optional.spline.point(val); + } + }; + return this; + } + + public LTDescr setMoveSplineLocal() + { + this.type = TweenAction.MOVE_SPLINE_LOCAL; + this.initInternal = this.initFromInternal; + this.easeInternal = () => + { + newVect = easeMethod(); + val = newVect.x; + if (this._optional.spline.orientToPath) + { + if (this._optional.spline.orientToPath2d) + { + this._optional.spline.placeLocal2d(trans, val); + } + else + { + this._optional.spline.placeLocal(trans, val); + } + } + else + { + trans.localPosition = this._optional.spline.point(val); + } + }; + return this; + } + + public LTDescr setScaleX() + { + this.type = TweenAction.SCALE_X; + this.initInternal = () => { this.fromInternal.x = trans.localScale.x; }; + this.easeInternal = () => { trans.localScale = new Vector3(easeMethod().x, trans.localScale.y, trans.localScale.z); }; + return this; + } + + public LTDescr setScaleY() + { + this.type = TweenAction.SCALE_Y; + this.initInternal = () => { this.fromInternal.x = trans.localScale.y; }; + this.easeInternal = () => { trans.localScale = new Vector3(trans.localScale.x, easeMethod().x, trans.localScale.z); }; + return this; + } + + public LTDescr setScaleZ() + { + this.type = TweenAction.SCALE_Z; + this.initInternal = () => { this.fromInternal.x = trans.localScale.z; }; + this.easeInternal = () => { trans.localScale = new Vector3(trans.localScale.x, trans.localScale.y, easeMethod().x); }; + return this; + } + + public LTDescr setRotateX() + { + this.type = TweenAction.ROTATE_X; + this.initInternal = () => { this.fromInternal.x = trans.eulerAngles.x; this.toInternal.x = LeanTween.closestRot(this.fromInternal.x, this.toInternal.x); }; + this.easeInternal = () => { trans.eulerAngles = new Vector3(easeMethod().x, trans.eulerAngles.y, trans.eulerAngles.z); }; + return this; + } + + public LTDescr setRotateY() + { + this.type = TweenAction.ROTATE_Y; + this.initInternal = () => { this.fromInternal.x = trans.eulerAngles.y; this.toInternal.x = LeanTween.closestRot(this.fromInternal.x, this.toInternal.x); }; + this.easeInternal = () => { trans.eulerAngles = new Vector3(trans.eulerAngles.x, easeMethod().x, trans.eulerAngles.z); }; + return this; + } + + public LTDescr setRotateZ() + { + this.type = TweenAction.ROTATE_Z; + this.initInternal = () => + { + this.fromInternal.x = trans.eulerAngles.z; + this.toInternal.x = LeanTween.closestRot(this.fromInternal.x, this.toInternal.x); + }; + this.easeInternal = () => { trans.eulerAngles = new Vector3(trans.eulerAngles.x, trans.eulerAngles.y, easeMethod().x); }; + return this; + } + + public LTDescr setRotateAround() + { + this.type = TweenAction.ROTATE_AROUND; + this.initInternal = () => + { + this.fromInternal.x = 0f; + this._optional.origRotation = trans.rotation; + }; + this.easeInternal = () => + { + newVect = easeMethod(); + val = newVect.x; + Vector3 origPos = trans.localPosition; + Vector3 rotateAroundPt = (Vector3)trans.TransformPoint(this._optional.point); + // Debug.Log("this._optional.point:"+this._optional.point); + trans.RotateAround(rotateAroundPt, this._optional.axis, -this._optional.lastVal); + Vector3 diff = origPos - trans.localPosition; + + trans.localPosition = origPos - diff; // Subtract the amount the object has been shifted over by the rotate, to get it back to it's orginal position + trans.rotation = this._optional.origRotation; + + rotateAroundPt = (Vector3)trans.TransformPoint(this._optional.point); + trans.RotateAround(rotateAroundPt, this._optional.axis, val); + + this._optional.lastVal = val; + }; + return this; + } + + public LTDescr setRotateAroundLocal() + { + this.type = TweenAction.ROTATE_AROUND_LOCAL; + this.initInternal = () => + { + this.fromInternal.x = 0f; + this._optional.origRotation = trans.localRotation; + }; + this.easeInternal = () => + { + newVect = easeMethod(); + val = newVect.x; + Vector3 origPos = trans.localPosition; + trans.RotateAround((Vector3)trans.TransformPoint(this._optional.point), trans.TransformDirection(this._optional.axis), -this._optional.lastVal); + Vector3 diff = origPos - trans.localPosition; + + trans.localPosition = origPos - diff; // Subtract the amount the object has been shifted over by the rotate, to get it back to it's orginal position + trans.localRotation = this._optional.origRotation; + Vector3 rotateAroundPt = (Vector3)trans.TransformPoint(this._optional.point); + trans.RotateAround(rotateAroundPt, trans.TransformDirection(this._optional.axis), val); + + this._optional.lastVal = val; + }; + return this; + } + + public LTDescr setAlpha() + { + this.type = TweenAction.ALPHA; + this.initInternal = () => + { +#if UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_1 || UNITY_4_2 + if(trans.gameObject.renderer){ this.fromInternal.x = trans.gameObject.renderer.material.color.a; }else if(trans.childCount>0){ foreach (Transform child in trans) { if(child.gameObject.renderer!=null){ Color col = child.gameObject.renderer.material.color; this.fromInternal.x = col.a; break; }}} + this.easeInternal = this.alpha; + break; +#else + SpriteRenderer ren = trans.GetComponent(); + if (ren != null) + { + this.fromInternal.x = ren.color.a; + } + else + { + if (trans.GetComponent() != null && trans.GetComponent().material.HasProperty("_Color")) + { + this.fromInternal.x = trans.GetComponent().material.color.a; + } + else if (trans.GetComponent() != null && trans.GetComponent().material.HasProperty("_TintColor")) + { + Color col = trans.GetComponent().material.GetColor("_TintColor"); + this.fromInternal.x = col.a; + } + else if (trans.childCount > 0) + { + foreach (Transform child in trans) + { + if (child.gameObject.GetComponent() != null) + { + Color col = child.gameObject.GetComponent().material.color; + this.fromInternal.x = col.a; + break; + } + } + } + } +#endif + + this.easeInternal = () => + { + val = easeMethod().x; +#if UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_1 || UNITY_4_2 + alphaRecursive(this.trans, val, this.useRecursion); +#else + if (this.spriteRen != null) + { + this.spriteRen.color = new Color(this.spriteRen.color.r, this.spriteRen.color.g, this.spriteRen.color.b, val); + alphaRecursiveSprite(this.trans, val); + } + else + { + alphaRecursive(this.trans, val, this.useRecursion); + } +#endif + }; + + }; + this.easeInternal = () => + { + newVect = easeMethod(); + val = newVect.x; +#if UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_1 || UNITY_4_2 + alphaRecursive(this.trans, val, this.useRecursion); +#else + if (this.spriteRen != null) + { + this.spriteRen.color = new Color(this.spriteRen.color.r, this.spriteRen.color.g, this.spriteRen.color.b, val); + alphaRecursiveSprite(this.trans, val); + } + else + { + alphaRecursive(this.trans, val, this.useRecursion); + } +#endif + }; + return this; + } + + public LTDescr setTextAlpha() + { + this.type = TweenAction.TEXT_ALPHA; + this.initInternal = () => + { + this.uiText = trans.GetComponent(); + this.fromInternal.x = this.uiText != null ? this.uiText.color.a : 1f; + }; + this.easeInternal = () => { textAlphaRecursive(trans, easeMethod().x, this.useRecursion); }; + return this; + } + + public LTDescr setAlphaVertex() + { + this.type = TweenAction.ALPHA_VERTEX; + this.initInternal = () => { this.fromInternal.x = trans.GetComponent().mesh.colors32[0].a; }; + this.easeInternal = () => + { + newVect = easeMethod(); + val = newVect.x; + Mesh mesh = trans.GetComponent().mesh; + Vector3[] vertices = mesh.vertices; + Color32[] colors = new Color32[vertices.Length]; + if (colors.Length == 0) + { //MaxFW fix: add vertex colors if the mesh doesn't have any + Color32 transparentWhiteColor32 = new Color32(0xff, 0xff, 0xff, 0x00); + colors = new Color32[mesh.vertices.Length]; + for (int k = 0; k < colors.Length; k++) + colors[k] = transparentWhiteColor32; + mesh.colors32 = colors; + }// fix end + Color32 c = mesh.colors32[0]; + c = new Color(c.r, c.g, c.b, val); + for (int k = 0; k < vertices.Length; k++) + colors[k] = c; + mesh.colors32 = colors; + }; + return this; + } + + public LTDescr setColor() + { + this.type = TweenAction.COLOR; + this.initInternal = () => + { +#if UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_1 || UNITY_4_2 + if(trans.gameObject.renderer){ + this.setFromColor( trans.gameObject.renderer.material.color ); + }else if(trans.childCount>0){ + foreach (Transform child in trans) { + if(child.gameObject.renderer!=null){ + this.setFromColor( child.gameObject.renderer.material.color ); + break; + } + } + } +#else + SpriteRenderer renColor = trans.GetComponent(); + if (renColor != null) + { + this.setFromColor(renColor.color); + } + else + { + if (trans.GetComponent() != null && trans.GetComponent().material.HasProperty("_Color")) + { + Color col = trans.GetComponent().material.color; + this.setFromColor(col); + } + else if (trans.GetComponent() != null && trans.GetComponent().material.HasProperty("_TintColor")) + { + Color col = trans.GetComponent().material.GetColor("_TintColor"); + this.setFromColor(col); + } + else if (trans.childCount > 0) + { + foreach (Transform child in trans) + { + if (child.gameObject.GetComponent() != null) + { + Color col = child.gameObject.GetComponent().material.color; + this.setFromColor(col); + break; + } + } + } + } +#endif + }; + this.easeInternal = () => + { + newVect = easeMethod(); + val = newVect.x; + Color toColor = tweenColor(this, val); + +#if !UNITY_3_5 && !UNITY_4_0 && !UNITY_4_0_1 && !UNITY_4_1 && !UNITY_4_2 + + if (this.spriteRen != null) + { + this.spriteRen.color = toColor; + colorRecursiveSprite(trans, toColor); + } + else + { +#endif + // Debug.Log("val:"+val+" tween:"+tween+" tween.diff:"+tween.diff); + if (this.type == TweenAction.COLOR) + colorRecursive(trans, toColor, this.useRecursion); + +#if !UNITY_3_5 && !UNITY_4_0 && !UNITY_4_0_1 && !UNITY_4_1 && !UNITY_4_2 + } +#endif + if (dt != 0f && this._optional.onUpdateColor != null) + { + this._optional.onUpdateColor(toColor); + } + else if (dt != 0f && this._optional.onUpdateColorObject != null) + { + this._optional.onUpdateColorObject(toColor, this._optional.onUpdateParam); + } + }; + return this; + } + + public LTDescr setCallbackColor() + { + this.type = TweenAction.CALLBACK_COLOR; + this.initInternal = () => { this.diff = new Vector3(1.0f, 0.0f, 0.0f); }; + this.easeInternal = () => + { + newVect = easeMethod(); + val = newVect.x; + Color toColor = tweenColor(this, val); + +#if !UNITY_3_5 && !UNITY_4_0 && !UNITY_4_0_1 && !UNITY_4_1 && !UNITY_4_2 + if (this.spriteRen != null) + { + this.spriteRen.color = toColor; + colorRecursiveSprite(trans, toColor); + } + else + { +#endif + // Debug.Log("val:"+val+" tween:"+tween+" tween.diff:"+tween.diff); + if (this.type == TweenAction.COLOR) + colorRecursive(trans, toColor, this.useRecursion); + +#if !UNITY_3_5 && !UNITY_4_0 && !UNITY_4_0_1 && !UNITY_4_1 && !UNITY_4_2 + } +#endif + if (dt != 0f && this._optional.onUpdateColor != null) + { + this._optional.onUpdateColor(toColor); + } + else if (dt != 0f && this._optional.onUpdateColorObject != null) + { + this._optional.onUpdateColorObject(toColor, this._optional.onUpdateParam); + } + }; + return this; + } + + +#if !UNITY_3_5 && !UNITY_4_0 && !UNITY_4_0_1 && !UNITY_4_1 && !UNITY_4_2 && !UNITY_4_3 && !UNITY_4_5 + + public LTDescr setTextColor() + { + this.type = TweenAction.TEXT_COLOR; + this.initInternal = () => + { + this.uiText = trans.GetComponent(); + this.setFromColor(this.uiText != null ? this.uiText.color : Color.white); + }; + this.easeInternal = () => + { + newVect = easeMethod(); + val = newVect.x; + Color toColor = tweenColor(this, val); + this.uiText.color = toColor; + if (dt != 0f && this._optional.onUpdateColor != null) + this._optional.onUpdateColor(toColor); + + if (this.useRecursion && trans.childCount > 0) + textColorRecursive(this.trans, toColor); + }; + return this; + } + + public LTDescr setCanvasAlpha() + { + this.type = TweenAction.CANVAS_ALPHA; + this.initInternal = () => + { + this.uiImage = trans.GetComponent(); + if (this.uiImage != null) + { + this.fromInternal.x = this.uiImage.color.a; + } + else + { + this.rawImage = trans.GetComponent(); + if (this.rawImage != null) + { + this.fromInternal.x = this.rawImage.color.a; + } + else + { + this.fromInternal.x = 1f; + } + } + + }; + this.easeInternal = () => + { + newVect = easeMethod(); + val = newVect.x; + if (this.uiImage != null) + { + Color c = this.uiImage.color; c.a = val; this.uiImage.color = c; + } + else if (this.rawImage != null) + { + Color c = this.rawImage.color; c.a = val; this.rawImage.color = c; + } + if (this.useRecursion) + { + alphaRecursive(this.rectTransform, val, 0); + textAlphaChildrenRecursive(this.rectTransform, val); + } + }; + return this; + } + + public LTDescr setCanvasGroupAlpha() + { + this.type = TweenAction.CANVASGROUP_ALPHA; + this.initInternal = () => { this.fromInternal.x = trans.GetComponent().alpha; }; + this.easeInternal = () => { this.trans.GetComponent().alpha = easeMethod().x; }; + return this; + } + + public LTDescr setCanvasColor() + { + this.type = TweenAction.CANVAS_COLOR; + this.initInternal = () => + { + this.uiImage = trans.GetComponent(); + if (this.uiImage == null) + { + this.rawImage = trans.GetComponent(); + this.setFromColor(this.rawImage != null ? this.rawImage.color : Color.white); + } + else + { + this.setFromColor(this.uiImage.color); + } + + }; + this.easeInternal = () => + { + newVect = easeMethod(); + val = newVect.x; + Color toColor = tweenColor(this, val); + if (this.uiImage != null) + { + this.uiImage.color = toColor; + } + else if (this.rawImage != null) + { + this.rawImage.color = toColor; + } + + if (dt != 0f && this._optional.onUpdateColor != null) + this._optional.onUpdateColor(toColor); + + if (this.useRecursion) + colorRecursive(this.rectTransform, toColor); + }; + return this; + } + + public LTDescr setCanvasMoveX() + { + this.type = TweenAction.CANVAS_MOVE_X; + this.initInternal = () => { this.fromInternal.x = this.rectTransform.anchoredPosition3D.x; }; + this.easeInternal = () => { Vector3 c = this.rectTransform.anchoredPosition3D; this.rectTransform.anchoredPosition3D = new Vector3(easeMethod().x, c.y, c.z); }; + return this; + } + + public LTDescr setCanvasMoveY() + { + this.type = TweenAction.CANVAS_MOVE_Y; + this.initInternal = () => { this.fromInternal.x = this.rectTransform.anchoredPosition3D.y; }; + this.easeInternal = () => { Vector3 c = this.rectTransform.anchoredPosition3D; this.rectTransform.anchoredPosition3D = new Vector3(c.x, easeMethod().x, c.z); }; + return this; + } + + public LTDescr setCanvasMoveZ() + { + this.type = TweenAction.CANVAS_MOVE_Z; + this.initInternal = () => { this.fromInternal.x = this.rectTransform.anchoredPosition3D.z; }; + this.easeInternal = () => { Vector3 c = this.rectTransform.anchoredPosition3D; this.rectTransform.anchoredPosition3D = new Vector3(c.x, c.y, easeMethod().x); }; + return this; + } + + private void initCanvasRotateAround() + { + this.lastVal = 0.0f; + this.fromInternal.x = 0.0f; + this._optional.origRotation = this.rectTransform.rotation; + } + + public LTDescr setCanvasRotateAround() + { + this.type = TweenAction.CANVAS_ROTATEAROUND; + this.initInternal = this.initCanvasRotateAround; + this.easeInternal = () => + { + newVect = easeMethod(); + val = newVect.x; + RectTransform rect = this.rectTransform; + Vector3 origPos = rect.localPosition; + rect.RotateAround((Vector3)rect.TransformPoint(this._optional.point), this._optional.axis, -val); + Vector3 diff = origPos - rect.localPosition; + + rect.localPosition = origPos - diff; // Subtract the amount the object has been shifted over by the rotate, to get it back to it's orginal position + rect.rotation = this._optional.origRotation; + rect.RotateAround((Vector3)rect.TransformPoint(this._optional.point), this._optional.axis, val); + }; + return this; + } + + public LTDescr setCanvasRotateAroundLocal() + { + this.type = TweenAction.CANVAS_ROTATEAROUND_LOCAL; + this.initInternal = this.initCanvasRotateAround; + this.easeInternal = () => + { + newVect = easeMethod(); + val = newVect.x; + RectTransform rect = this.rectTransform; + Vector3 origPos = rect.localPosition; + rect.RotateAround((Vector3)rect.TransformPoint(this._optional.point), rect.TransformDirection(this._optional.axis), -val); + Vector3 diff = origPos - rect.localPosition; + + rect.localPosition = origPos - diff; // Subtract the amount the object has been shifted over by the rotate, to get it back to it's orginal position + rect.rotation = this._optional.origRotation; + rect.RotateAround((Vector3)rect.TransformPoint(this._optional.point), rect.TransformDirection(this._optional.axis), val); + }; + return this; + } + + public LTDescr setCanvasPlaySprite() + { + this.type = TweenAction.CANVAS_PLAYSPRITE; + this.initInternal = () => + { + this.uiImage = trans.GetComponent(); + this.fromInternal.x = 0f; + }; + this.easeInternal = () => + { + newVect = easeMethod(); + val = newVect.x; + int frame = (int)Mathf.Round(val); + this.uiImage.sprite = this.sprites[frame]; + }; + return this; + } + + public LTDescr setCanvasMove() + { + this.type = TweenAction.CANVAS_MOVE; + this.initInternal = () => { this.fromInternal = this.rectTransform.anchoredPosition3D; }; + this.easeInternal = () => { this.rectTransform.anchoredPosition3D = easeMethod(); }; + return this; + } + + public LTDescr setCanvasScale() + { + this.type = TweenAction.CANVAS_SCALE; + this.initInternal = () => { this.from = this.rectTransform.localScale; }; + this.easeInternal = () => { this.rectTransform.localScale = easeMethod(); }; + return this; + } + + public LTDescr setCanvasSizeDelta() + { + this.type = TweenAction.CANVAS_SIZEDELTA; + this.initInternal = () => { this.from = this.rectTransform.sizeDelta; }; + this.easeInternal = () => { this.rectTransform.sizeDelta = easeMethod(); }; + return this; + } +#endif + + private void callback() { newVect = easeMethod(); val = newVect.x; } + + public LTDescr setCallback() + { + this.type = TweenAction.CALLBACK; + this.initInternal = () => { }; + this.easeInternal = this.callback; + return this; + } + public LTDescr setValue3() + { + this.type = TweenAction.VALUE3; + this.initInternal = () => { }; + this.easeInternal = this.callback; + return this; + } + + public LTDescr setMove() + { + this.type = TweenAction.MOVE; + this.initInternal = () => { this.from = trans.position; }; + this.easeInternal = () => + { + newVect = easeMethod(); + trans.position = newVect; + }; + return this; + } + + public LTDescr setMoveLocal() + { + this.type = TweenAction.MOVE_LOCAL; + this.initInternal = () => { this.from = trans.localPosition; }; + this.easeInternal = () => + { + newVect = easeMethod(); + trans.localPosition = newVect; + }; + return this; + } + + public LTDescr setMoveToTransform() + { + this.type = TweenAction.MOVE_TO_TRANSFORM; + this.initInternal = () => { this.from = trans.position; }; + this.easeInternal = () => + { + this.to = this._optional.toTrans.position; + this.diff = this.to - this.from; + this.diffDiv2 = this.diff * 0.5f; + + newVect = easeMethod(); + this.trans.position = newVect; + }; + return this; + } + + public LTDescr setRotate() + { + this.type = TweenAction.ROTATE; + this.initInternal = () => { this.from = trans.eulerAngles; this.to = new Vector3(LeanTween.closestRot(this.fromInternal.x, this.toInternal.x), LeanTween.closestRot(this.from.y, this.to.y), LeanTween.closestRot(this.from.z, this.to.z)); }; + this.easeInternal = () => + { + newVect = easeMethod(); + trans.eulerAngles = newVect; + }; + return this; + } + + public LTDescr setRotateLocal() + { + this.type = TweenAction.ROTATE_LOCAL; + this.initInternal = () => { this.from = trans.localEulerAngles; this.to = new Vector3(LeanTween.closestRot(this.fromInternal.x, this.toInternal.x), LeanTween.closestRot(this.from.y, this.to.y), LeanTween.closestRot(this.from.z, this.to.z)); }; + this.easeInternal = () => + { + newVect = easeMethod(); + trans.localEulerAngles = newVect; + }; + return this; + } + + public LTDescr setScale() + { + this.type = TweenAction.SCALE; + this.initInternal = () => { this.from = trans.localScale; }; + this.easeInternal = () => + { + newVect = easeMethod(); + trans.localScale = newVect; + }; + return this; + } + + public LTDescr setGUIMove() + { + this.type = TweenAction.GUI_MOVE; + this.initInternal = () => { this.from = new Vector3(this._optional.ltRect.rect.x, this._optional.ltRect.rect.y, 0); }; + this.easeInternal = () => { Vector3 v = easeMethod(); this._optional.ltRect.rect = new Rect(v.x, v.y, this._optional.ltRect.rect.width, this._optional.ltRect.rect.height); }; + return this; + } + + public LTDescr setGUIMoveMargin() + { + this.type = TweenAction.GUI_MOVE_MARGIN; + this.initInternal = () => { this.from = new Vector2(this._optional.ltRect.margin.x, this._optional.ltRect.margin.y); }; + this.easeInternal = () => { Vector3 v = easeMethod(); this._optional.ltRect.margin = new Vector2(v.x, v.y); }; + return this; + } + + public LTDescr setGUIScale() + { + this.type = TweenAction.GUI_SCALE; + this.initInternal = () => { this.from = new Vector3(this._optional.ltRect.rect.width, this._optional.ltRect.rect.height, 0); }; + this.easeInternal = () => { Vector3 v = easeMethod(); this._optional.ltRect.rect = new Rect(this._optional.ltRect.rect.x, this._optional.ltRect.rect.y, v.x, v.y); }; + return this; + } + + public LTDescr setGUIAlpha() + { + this.type = TweenAction.GUI_ALPHA; + this.initInternal = () => { this.fromInternal.x = this._optional.ltRect.alpha; }; + this.easeInternal = () => { this._optional.ltRect.alpha = easeMethod().x; }; + return this; + } + + public LTDescr setGUIRotate() + { + this.type = TweenAction.GUI_ROTATE; + this.initInternal = () => + { + if (this._optional.ltRect.rotateEnabled == false) + { + this._optional.ltRect.rotateEnabled = true; + this._optional.ltRect.resetForRotation(); + } + + this.fromInternal.x = this._optional.ltRect.rotation; + }; + this.easeInternal = () => { this._optional.ltRect.rotation = easeMethod().x; }; + return this; + } + + public LTDescr setDelayedSound() + { + this.type = TweenAction.DELAYED_SOUND; + this.initInternal = () => { this.hasExtraOnCompletes = true; }; + this.easeInternal = this.callback; + return this; + } + + public LTDescr setTarget(Transform trans) + { + this.optional.toTrans = trans; + return this; + } + + private void init() + { + this.hasInitiliazed = true; + + usesNormalDt = !(useEstimatedTime || useManualTime || useFrames); // only set this to true if it uses non of the other timing modes + + if (useFrames) + this.optional.initFrameCount = Time.frameCount; + + if (this.time <= 0f) // avoid dividing by zero + this.time = Mathf.Epsilon; + + if (this.initInternal != null) + this.initInternal(); + + this.diff = this.to - this.from; + this.diffDiv2 = this.diff * 0.5f; + + if (this._optional.onStart != null) + this._optional.onStart(); + + if (this.onCompleteOnStart) + callOnCompletes(); + + if (this.speed >= 0) + { + initSpeed(); + } + } + + private void initSpeed() + { + if (this.type == TweenAction.MOVE_CURVED || this.type == TweenAction.MOVE_CURVED_LOCAL) + { + this.time = this._optional.path.distance / this.speed; + } + else if (this.type == TweenAction.MOVE_SPLINE || this.type == TweenAction.MOVE_SPLINE_LOCAL) + { + this.time = this._optional.spline.distance / this.speed; + } + else + { + this.time = (this.to - this.from).magnitude / this.speed; + } + } + + public static float val; + public static float dt; + public static Vector3 newVect; + + /** + * If you need a tween to happen immediately instead of waiting for the next Update call, you can force it with this method + * + * @method updateNow + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.moveX(gameObject, 5f, 0f ).updateNow(); + */ + public LTDescr updateNow() + { + updateInternal(); + return this; + } + + public bool updateInternal() + { + + float directionLocal = this.direction; + if (this.usesNormalDt) + { + dt = LeanTween.dtActual; + } + else if (this.useEstimatedTime) + { + dt = LeanTween.dtEstimated; + } + else if (this.useFrames) + { + dt = this.optional.initFrameCount == 0 ? 0 : 1; + this.optional.initFrameCount = Time.frameCount; + } + else if (this.useManualTime) + { + dt = LeanTween.dtManual; + } + + // Debug.Log ("tween:" + this+ " dt:"+dt); + if (this.delay <= 0f && directionLocal != 0f) + { + if (trans == null) + return true; + + // initialize if has not done so yet + if (!this.hasInitiliazed) + this.init(); + + dt = dt * directionLocal; + this.passed += dt; + + this.passed = Mathf.Clamp(this.passed, 0f, this.time); + + this.ratioPassed = (this.passed / this.time); // need to clamp when finished so it will finish at the exact spot and not overshoot + + this.easeInternal(); + + if (this.hasUpdateCallback) + this._optional.callOnUpdate(val, this.ratioPassed); + + bool isTweenFinished = directionLocal > 0f ? this.passed >= this.time : this.passed <= 0f; + // Debug.Log("lt "+this+" dt:"+dt+" fin:"+isTweenFinished); + if (isTweenFinished) + { // increment or flip tween + this.loopCount--; + if (this.loopType == LeanTweenType.pingPong) + { + this.direction = 0.0f - directionLocal; + } + else + { + this.passed = Mathf.Epsilon; + } + + isTweenFinished = this.loopCount == 0 || this.loopType == LeanTweenType.once; // only return true if it is fully complete + + if (isTweenFinished == false && this.onCompleteOnRepeat && this.hasExtraOnCompletes) + callOnCompletes(); // this only gets called if onCompleteOnRepeat is set to true, otherwise LeanTween class takes care of calling it + + return isTweenFinished; + } + } + else + { + this.delay -= dt; + } + + return false; + } + + public void callOnCompletes() + { + if (this.type == TweenAction.GUI_ROTATE) + this._optional.ltRect.rotateFinished = true; + + if (this.type == TweenAction.DELAYED_SOUND) + { + AudioSource.PlayClipAtPoint((AudioClip)this._optional.onCompleteParam, this.to, this.from.x); + } + if (this._optional.onComplete != null) + { + this._optional.onComplete(); + } + else if (this._optional.onCompleteObject != null) + { + this._optional.onCompleteObject(this._optional.onCompleteParam); + } + } + + // Helper Methods + + public LTDescr setFromColor(Color col) + { + this.from = new Vector3(0.0f, col.a, 0.0f); + this.diff = new Vector3(1.0f, 0.0f, 0.0f); + this._optional.axis = new Vector3(col.r, col.g, col.b); + return this; + } + + private static void alphaRecursive(Transform transform, float val, bool useRecursion = true) + { + Renderer renderer = transform.gameObject.GetComponent(); + if (renderer != null) + { + foreach (Material mat in renderer.materials) + { + if (mat.HasProperty("_Color")) + { + mat.color = new Color(mat.color.r, mat.color.g, mat.color.b, val); + } + else if (mat.HasProperty("_TintColor")) + { + Color col = mat.GetColor("_TintColor"); + mat.SetColor("_TintColor", new Color(col.r, col.g, col.b, val)); + } + } + } + if (useRecursion && transform.childCount > 0) + { + foreach (Transform child in transform) + { + alphaRecursive(child, val); + } + } + } + + private static void colorRecursive(Transform transform, Color toColor, bool useRecursion = true) + { + Renderer ren = transform.gameObject.GetComponent(); + if (ren != null) + { + foreach (Material mat in ren.materials) + { + mat.color = toColor; + } + } + if (useRecursion && transform.childCount > 0) + { + foreach (Transform child in transform) + { + colorRecursive(child, toColor); + } + } + } + +#if !UNITY_3_5 && !UNITY_4_0 && !UNITY_4_0_1 && !UNITY_4_1 && !UNITY_4_2 && !UNITY_4_3 && !UNITY_4_5 + + private static void alphaRecursive(RectTransform rectTransform, float val, int recursiveLevel = 0) + { + if (rectTransform.childCount > 0) + { + foreach (RectTransform child in rectTransform) + { + UnityEngine.UI.MaskableGraphic uiImage = child.GetComponent(); + if (uiImage != null) + { + Color c = uiImage.color; c.a = val; uiImage.color = c; + } + else + { + uiImage = child.GetComponent(); + if (uiImage != null) + { + Color c = uiImage.color; c.a = val; uiImage.color = c; + } + } + + alphaRecursive(child, val, recursiveLevel + 1); + } + } + } + + private static void alphaRecursiveSprite(Transform transform, float val) + { + if (transform.childCount > 0) + { + foreach (Transform child in transform) + { + SpriteRenderer ren = child.GetComponent(); + if (ren != null) + ren.color = new Color(ren.color.r, ren.color.g, ren.color.b, val); + alphaRecursiveSprite(child, val); + } + } + } + + private static void colorRecursiveSprite(Transform transform, Color toColor) + { + if (transform.childCount > 0) + { + foreach (Transform child in transform) + { + SpriteRenderer ren = transform.gameObject.GetComponent(); + if (ren != null) + ren.color = toColor; + colorRecursiveSprite(child, toColor); + } + } + } + + private static void colorRecursive(RectTransform rectTransform, Color toColor) + { + + if (rectTransform.childCount > 0) + { + foreach (RectTransform child in rectTransform) + { + UnityEngine.UI.MaskableGraphic uiImage = child.GetComponent(); + if (uiImage != null) + { + uiImage.color = toColor; + } + else + { + uiImage = child.GetComponent(); + if (uiImage != null) + uiImage.color = toColor; + } + colorRecursive(child, toColor); + } + } + } + + private static void textAlphaChildrenRecursive(Transform trans, float val, bool useRecursion = true) + { + + if (useRecursion && trans.childCount > 0) + { + foreach (Transform child in trans) + { + UnityEngine.UI.Text uiText = child.GetComponent(); + if (uiText != null) + { + Color c = uiText.color; + c.a = val; + uiText.color = c; + } + textAlphaChildrenRecursive(child, val); + } + } + } + + private static void textAlphaRecursive(Transform trans, float val, bool useRecursion = true) + { + UnityEngine.UI.Text uiText = trans.GetComponent(); + if (uiText != null) + { + Color c = uiText.color; + c.a = val; + uiText.color = c; + } + if (useRecursion && trans.childCount > 0) + { + foreach (Transform child in trans) + { + textAlphaRecursive(child, val); + } + } + } + + private static void textColorRecursive(Transform trans, Color toColor) + { + if (trans.childCount > 0) + { + foreach (Transform child in trans) + { + UnityEngine.UI.Text uiText = child.GetComponent(); + if (uiText != null) + { + uiText.color = toColor; + } + textColorRecursive(child, toColor); + } + } + } +#endif + + private static Color tweenColor(LTDescr tween, float val) + { + Vector3 diff3 = tween._optional.point - tween._optional.axis; + float diffAlpha = tween.to.y - tween.from.y; + return new Color(tween._optional.axis.x + diff3.x * val, tween._optional.axis.y + diff3.y * val, tween._optional.axis.z + diff3.z * val, tween.from.y + diffAlpha * val); + } + + /** + * Pause a tween + * + * @method pause + * @return {LTDescr} LTDescr an object that distinguishes the tween + */ + public LTDescr pause() + { + if (this.direction != 0.0f) + { // check if tween is already paused + this.directionLast = this.direction; + this.direction = 0.0f; + } + + return this; + } + + /** + * Resume a paused tween + * + * @method resume + * @return {LTDescr} LTDescr an object that distinguishes the tween + */ + public LTDescr resume() + { + this.direction = this.directionLast; + + return this; + } + + /** + * Set Axis optional axis for tweens where it is relevant + * + * @method setAxis + * @param {Vector3} axis either the tween rotates around, or the direction it faces in the case of setOrientToPath + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.move( ltLogo, path, 1.0f ).setEase(LeanTweenType.easeOutQuad).setOrientToPath(true).setAxis(Vector3.forward); + */ + public LTDescr setAxis(Vector3 axis) + { + this._optional.axis = axis; + return this; + } + + /** + * Delay the start of a tween + * + * @method setDelay + * @param {float} float time The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.moveX(gameObject, 5f, 2.0f ).setDelay( 1.5f ); + */ + public LTDescr setDelay(float delay) + { + this.delay = delay; + + return this; + } + + /** + * Set the type of easing used for the tween.
+ * + * + * @method setEase + * @param {LeanTweenType} easeType:LeanTweenType the easing type to use + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.moveX(gameObject, 5f, 2.0f ).setEase( LeanTweenType.easeInBounce ); + */ + public LTDescr setEase(LeanTweenType easeType) + { + + switch (easeType) + { + case LeanTweenType.linear: + setEaseLinear(); break; + case LeanTweenType.easeOutQuad: + setEaseOutQuad(); break; + case LeanTweenType.easeInQuad: + setEaseInQuad(); break; + case LeanTweenType.easeInOutQuad: + setEaseInOutQuad(); break; + case LeanTweenType.easeInCubic: + setEaseInCubic(); break; + case LeanTweenType.easeOutCubic: + setEaseOutCubic(); break; + case LeanTweenType.easeInOutCubic: + setEaseInOutCubic(); break; + case LeanTweenType.easeInQuart: + setEaseInQuart(); break; + case LeanTweenType.easeOutQuart: + setEaseOutQuart(); break; + case LeanTweenType.easeInOutQuart: + setEaseInOutQuart(); break; + case LeanTweenType.easeInQuint: + setEaseInQuint(); break; + case LeanTweenType.easeOutQuint: + setEaseOutQuint(); break; + case LeanTweenType.easeInOutQuint: + setEaseInOutQuint(); break; + case LeanTweenType.easeInSine: + setEaseInSine(); break; + case LeanTweenType.easeOutSine: + setEaseOutSine(); break; + case LeanTweenType.easeInOutSine: + setEaseInOutSine(); break; + case LeanTweenType.easeInExpo: + setEaseInExpo(); break; + case LeanTweenType.easeOutExpo: + setEaseOutExpo(); break; + case LeanTweenType.easeInOutExpo: + setEaseInOutExpo(); break; + case LeanTweenType.easeInCirc: + setEaseInCirc(); break; + case LeanTweenType.easeOutCirc: + setEaseOutCirc(); break; + case LeanTweenType.easeInOutCirc: + setEaseInOutCirc(); break; + case LeanTweenType.easeInBounce: + setEaseInBounce(); break; + case LeanTweenType.easeOutBounce: + setEaseOutBounce(); break; + case LeanTweenType.easeInOutBounce: + setEaseInOutBounce(); break; + case LeanTweenType.easeInBack: + setEaseInBack(); break; + case LeanTweenType.easeOutBack: + setEaseOutBack(); break; + case LeanTweenType.easeInOutBack: + setEaseInOutBack(); break; + case LeanTweenType.easeInElastic: + setEaseInElastic(); break; + case LeanTweenType.easeOutElastic: + setEaseOutElastic(); break; + case LeanTweenType.easeInOutElastic: + setEaseInOutElastic(); break; + case LeanTweenType.punch: + setEasePunch(); break; + case LeanTweenType.easeShake: + setEaseShake(); break; + case LeanTweenType.easeSpring: + setEaseSpring(); break; + default: + setEaseLinear(); break; + } + + return this; + } + + public LTDescr setEaseLinear() { this.easeType = LeanTweenType.linear; this.easeMethod = this.easeLinear; return this; } + + public LTDescr setEaseSpring() { this.easeType = LeanTweenType.easeSpring; this.easeMethod = this.easeSpring; return this; } + + public LTDescr setEaseInQuad() { this.easeType = LeanTweenType.easeInQuad; this.easeMethod = this.easeInQuad; return this; } + + public LTDescr setEaseOutQuad() { this.easeType = LeanTweenType.easeOutQuad; this.easeMethod = this.easeOutQuad; return this; } + + public LTDescr setEaseInOutQuad() { this.easeType = LeanTweenType.easeInOutQuad; this.easeMethod = this.easeInOutQuad; return this; } + + public LTDescr setEaseInCubic() { this.easeType = LeanTweenType.easeInCubic; this.easeMethod = this.easeInCubic; return this; } + + public LTDescr setEaseOutCubic() { this.easeType = LeanTweenType.easeOutCubic; this.easeMethod = this.easeOutCubic; return this; } + + public LTDescr setEaseInOutCubic() { this.easeType = LeanTweenType.easeInOutCubic; this.easeMethod = this.easeInOutCubic; return this; } + + public LTDescr setEaseInQuart() { this.easeType = LeanTweenType.easeInQuart; this.easeMethod = this.easeInQuart; return this; } + + public LTDescr setEaseOutQuart() { this.easeType = LeanTweenType.easeOutQuart; this.easeMethod = this.easeOutQuart; return this; } + + public LTDescr setEaseInOutQuart() { this.easeType = LeanTweenType.easeInOutQuart; this.easeMethod = this.easeInOutQuart; return this; } + + public LTDescr setEaseInQuint() { this.easeType = LeanTweenType.easeInQuint; this.easeMethod = this.easeInQuint; return this; } + + public LTDescr setEaseOutQuint() { this.easeType = LeanTweenType.easeOutQuint; this.easeMethod = this.easeOutQuint; return this; } + + public LTDescr setEaseInOutQuint() { this.easeType = LeanTweenType.easeInOutQuint; this.easeMethod = this.easeInOutQuint; return this; } + + public LTDescr setEaseInSine() { this.easeType = LeanTweenType.easeInSine; this.easeMethod = this.easeInSine; return this; } + + public LTDescr setEaseOutSine() { this.easeType = LeanTweenType.easeOutSine; this.easeMethod = this.easeOutSine; return this; } + + public LTDescr setEaseInOutSine() { this.easeType = LeanTweenType.easeInOutSine; this.easeMethod = this.easeInOutSine; return this; } + + public LTDescr setEaseInExpo() { this.easeType = LeanTweenType.easeInExpo; this.easeMethod = this.easeInExpo; return this; } + + public LTDescr setEaseOutExpo() { this.easeType = LeanTweenType.easeOutExpo; this.easeMethod = this.easeOutExpo; return this; } + + public LTDescr setEaseInOutExpo() { this.easeType = LeanTweenType.easeInOutExpo; this.easeMethod = this.easeInOutExpo; return this; } + + public LTDescr setEaseInCirc() { this.easeType = LeanTweenType.easeInCirc; this.easeMethod = this.easeInCirc; return this; } + + public LTDescr setEaseOutCirc() { this.easeType = LeanTweenType.easeOutCirc; this.easeMethod = this.easeOutCirc; return this; } + + public LTDescr setEaseInOutCirc() { this.easeType = LeanTweenType.easeInOutCirc; this.easeMethod = this.easeInOutCirc; return this; } + + public LTDescr setEaseInBounce() { this.easeType = LeanTweenType.easeInBounce; this.easeMethod = this.easeInBounce; return this; } + + public LTDescr setEaseOutBounce() { this.easeType = LeanTweenType.easeOutBounce; this.easeMethod = this.easeOutBounce; return this; } + + public LTDescr setEaseInOutBounce() { this.easeType = LeanTweenType.easeInOutBounce; this.easeMethod = this.easeInOutBounce; return this; } + + public LTDescr setEaseInBack() { this.easeType = LeanTweenType.easeInBack; this.easeMethod = this.easeInBack; return this; } + + public LTDescr setEaseOutBack() { this.easeType = LeanTweenType.easeOutBack; this.easeMethod = this.easeOutBack; return this; } + + public LTDescr setEaseInOutBack() { this.easeType = LeanTweenType.easeInOutBack; this.easeMethod = this.easeInOutBack; return this; } + + public LTDescr setEaseInElastic() { this.easeType = LeanTweenType.easeInElastic; this.easeMethod = this.easeInElastic; return this; } + + public LTDescr setEaseOutElastic() { this.easeType = LeanTweenType.easeOutElastic; this.easeMethod = this.easeOutElastic; return this; } + + public LTDescr setEaseInOutElastic() { this.easeType = LeanTweenType.easeInOutElastic; this.easeMethod = this.easeInOutElastic; return this; } + + public LTDescr setEasePunch() { this._optional.animationCurve = LeanTween.punch; this.toInternal.x = this.from.x + this.to.x; this.easeMethod = this.tweenOnCurve; return this; } + + public LTDescr setEaseShake() { this._optional.animationCurve = LeanTween.shake; this.toInternal.x = this.from.x + this.to.x; this.easeMethod = this.tweenOnCurve; return this; } + + private Vector3 tweenOnCurve() + { + return new Vector3(this.from.x + (this.diff.x) * this._optional.animationCurve.Evaluate(ratioPassed), + this.from.y + (this.diff.y) * this._optional.animationCurve.Evaluate(ratioPassed), + this.from.z + (this.diff.z) * this._optional.animationCurve.Evaluate(ratioPassed)); + } + + // Vector3 Ease Methods + + private Vector3 easeInOutQuad() + { + val = this.ratioPassed * 2f; + + if (val < 1f) + { + val = val * val; + return new Vector3(this.diffDiv2.x * val + this.from.x, this.diffDiv2.y * val + this.from.y, this.diffDiv2.z * val + this.from.z); + } + val = (1f - val) * (val - 3f) + 1f; + return new Vector3(this.diffDiv2.x * val + this.from.x, this.diffDiv2.y * val + this.from.y, this.diffDiv2.z * val + this.from.z); + } + + private Vector3 easeInQuad() + { + val = ratioPassed * ratioPassed; + return new Vector3(this.diff.x * val + this.from.x, this.diff.y * val + this.from.y, this.diff.z * val + this.from.z); + } + + private Vector3 easeOutQuad() + { + val = this.ratioPassed; + val = -val * (val - 2f); + return (this.diff * val + this.from); + } + + private Vector3 easeLinear() + { + val = this.ratioPassed; + return new Vector3(this.from.x + this.diff.x * val, this.from.y + this.diff.y * val, this.from.z + this.diff.z * val); + } + + private Vector3 easeSpring() + { + val = Mathf.Clamp01(this.ratioPassed); + val = (Mathf.Sin(val * Mathf.PI * (0.2f + 2.5f * val * val * val)) * Mathf.Pow(1f - val, 2.2f) + val) * (1f + (1.2f * (1f - val))); + return this.from + this.diff * val; + } + + private Vector3 easeInCubic() + { + val = this.ratioPassed * this.ratioPassed * this.ratioPassed; + return new Vector3(this.diff.x * val + this.from.x, this.diff.y * val + this.from.y, this.diff.z * val + this.from.z); + } + + private Vector3 easeOutCubic() + { + val = this.ratioPassed - 1f; + val = (val * val * val + 1); + return new Vector3(this.diff.x * val + this.from.x, this.diff.y * val + this.from.y, this.diff.z * val + this.from.z); + } + + private Vector3 easeInOutCubic() + { + val = this.ratioPassed * 2f; + if (val < 1f) + { + val = val * val * val; + return new Vector3(this.diffDiv2.x * val + this.from.x, this.diffDiv2.y * val + this.from.y, this.diffDiv2.z * val + this.from.z); + } + val -= 2f; + val = val * val * val + 2f; + return new Vector3(this.diffDiv2.x * val + this.from.x, this.diffDiv2.y * val + this.from.y, this.diffDiv2.z * val + this.from.z); + } + + private Vector3 easeInQuart() + { + val = this.ratioPassed * this.ratioPassed * this.ratioPassed * this.ratioPassed; + return diff * val + this.from; + } + + private Vector3 easeOutQuart() + { + val = this.ratioPassed - 1f; + val = -(val * val * val * val - 1); + return new Vector3(this.diff.x * val + this.from.x, this.diff.y * val + this.from.y, this.diff.z * val + this.from.z); + } + + private Vector3 easeInOutQuart() + { + val = this.ratioPassed * 2f; + if (val < 1f) + { + val = val * val * val * val; + return new Vector3(this.diffDiv2.x * val + this.from.x, this.diffDiv2.y * val + this.from.y, this.diffDiv2.z * val + this.from.z); + } + val -= 2f; + // val = (val * val * val * val - 2f); + return -this.diffDiv2 * (val * val * val * val - 2f) + this.from; + } + + private Vector3 easeInQuint() + { + val = this.ratioPassed; + val = val * val * val * val * val; + return new Vector3(this.diff.x * val + this.from.x, this.diff.y * val + this.from.y, this.diff.z * val + this.from.z); + } + + private Vector3 easeOutQuint() + { + val = this.ratioPassed - 1f; + val = (val * val * val * val * val + 1f); + return new Vector3(this.diff.x * val + this.from.x, this.diff.y * val + this.from.y, this.diff.z * val + this.from.z); + } + + private Vector3 easeInOutQuint() + { + val = this.ratioPassed * 2f; + if (val < 1f) + { + val = val * val * val * val * val; + return new Vector3(this.diffDiv2.x * val + this.from.x, this.diffDiv2.y * val + this.from.y, this.diffDiv2.z * val + this.from.z); + } + val -= 2f; + val = (val * val * val * val * val + 2f); + return new Vector3(this.diffDiv2.x * val + this.from.x, this.diffDiv2.y * val + this.from.y, this.diffDiv2.z * val + this.from.z); + } + + private Vector3 easeInSine() + { + val = -Mathf.Cos(this.ratioPassed * LeanTween.PI_DIV2); + return new Vector3(this.diff.x * val + this.diff.x + this.from.x, this.diff.y * val + this.diff.y + this.from.y, this.diff.z * val + this.diff.z + this.from.z); + } + + private Vector3 easeOutSine() + { + val = Mathf.Sin(this.ratioPassed * LeanTween.PI_DIV2); + return new Vector3(this.diff.x * val + this.from.x, this.diff.y * val + this.from.y, this.diff.z * val + this.from.z); + } + + private Vector3 easeInOutSine() + { + val = -(Mathf.Cos(Mathf.PI * this.ratioPassed) - 1f); + return new Vector3(this.diffDiv2.x * val + this.from.x, this.diffDiv2.y * val + this.from.y, this.diffDiv2.z * val + this.from.z); + } + + private Vector3 easeInExpo() + { + val = Mathf.Pow(2f, 10f * (this.ratioPassed - 1f)); + return new Vector3(this.diff.x * val + this.from.x, this.diff.y * val + this.from.y, this.diff.z * val + this.from.z); + } + + private Vector3 easeOutExpo() + { + val = (-Mathf.Pow(2f, -10f * this.ratioPassed) + 1f); + return new Vector3(this.diff.x * val + this.from.x, this.diff.y * val + this.from.y, this.diff.z * val + this.from.z); + } + + private Vector3 easeInOutExpo() + { + val = this.ratioPassed * 2f; + if (val < 1) return this.diffDiv2 * Mathf.Pow(2, 10 * (val - 1)) + this.from; + val--; + return this.diffDiv2 * (-Mathf.Pow(2, -10 * val) + 2) + this.from; + } + + private Vector3 easeInCirc() + { + val = -(Mathf.Sqrt(1f - this.ratioPassed * this.ratioPassed) - 1f); + return new Vector3(this.diff.x * val + this.from.x, this.diff.y * val + this.from.y, this.diff.z * val + this.from.z); + } + + private Vector3 easeOutCirc() + { + val = this.ratioPassed - 1f; + val = Mathf.Sqrt(1f - val * val); + + return new Vector3(this.diff.x * val + this.from.x, this.diff.y * val + this.from.y, this.diff.z * val + this.from.z); + } + + private Vector3 easeInOutCirc() + { + val = this.ratioPassed * 2f; + if (val < 1f) + { + val = -(Mathf.Sqrt(1f - val * val) - 1f); + return new Vector3(this.diffDiv2.x * val + this.from.x, this.diffDiv2.y * val + this.from.y, this.diffDiv2.z * val + this.from.z); + } + val -= 2f; + val = (Mathf.Sqrt(1f - val * val) + 1f); + return new Vector3(this.diffDiv2.x * val + this.from.x, this.diffDiv2.y * val + this.from.y, this.diffDiv2.z * val + this.from.z); + } + + private Vector3 easeInBounce() + { + val = this.ratioPassed; + val = 1f - val; + return new Vector3(this.diff.x - LeanTween.easeOutBounce(0, this.diff.x, val) + this.from.x, + this.diff.y - LeanTween.easeOutBounce(0, this.diff.y, val) + this.from.y, + this.diff.z - LeanTween.easeOutBounce(0, this.diff.z, val) + this.from.z); + } + + private Vector3 easeOutBounce() + { + val = ratioPassed; + float valM, valN; // bounce values + if (val < (valM = 1 - 1.75f * this.overshoot / 2.75f)) + { + val = 1 / valM / valM * val * val; + } + else if (val < (valN = 1 - .75f * this.overshoot / 2.75f)) + { + val -= (valM + valN) / 2; + // first bounce, height: 1/4 + val = 7.5625f * val * val + 1 - .25f * this.overshoot * this.overshoot; + } + else if (val < (valM = 1 - .25f * this.overshoot / 2.75f)) + { + val -= (valM + valN) / 2; + // second bounce, height: 1/16 + val = 7.5625f * val * val + 1 - .0625f * this.overshoot * this.overshoot; + } + else + { // valN = 1 + val -= (valM + 1) / 2; + // third bounce, height: 1/64 + val = 7.5625f * val * val + 1 - .015625f * this.overshoot * this.overshoot; + } + return this.diff * val + this.from; + } + + private Vector3 easeInOutBounce() + { + val = this.ratioPassed * 2f; + if (val < 1f) + { + return new Vector3(LeanTween.easeInBounce(0, this.diff.x, val) * 0.5f + this.from.x, + LeanTween.easeInBounce(0, this.diff.y, val) * 0.5f + this.from.y, + LeanTween.easeInBounce(0, this.diff.z, val) * 0.5f + this.from.z); + } + else + { + val = val - 1f; + return new Vector3(LeanTween.easeOutBounce(0, this.diff.x, val) * 0.5f + this.diffDiv2.x + this.from.x, + LeanTween.easeOutBounce(0, this.diff.y, val) * 0.5f + this.diffDiv2.y + this.from.y, + LeanTween.easeOutBounce(0, this.diff.z, val) * 0.5f + this.diffDiv2.z + this.from.z); + } + } + + private Vector3 easeInBack() + { + val = this.ratioPassed; + val /= 1; + float s = 1.70158f * this.overshoot; + return this.diff * (val) * val * ((s + 1) * val - s) + this.from; + } + + private Vector3 easeOutBack() + { + float s = 1.70158f * this.overshoot; + val = (this.ratioPassed / 1) - 1; + val = ((val) * val * ((s + 1) * val + s) + 1); + return this.diff * val + this.from; + } + + private Vector3 easeInOutBack() + { + float s = 1.70158f * this.overshoot; + val = this.ratioPassed * 2f; + if ((val) < 1) + { + s *= (1.525f) * overshoot; + return this.diffDiv2 * (val * val * (((s) + 1) * val - s)) + this.from; + } + val -= 2; + s *= (1.525f) * overshoot; + val = ((val) * val * (((s) + 1) * val + s) + 2); + return this.diffDiv2 * val + this.from; + } + + private Vector3 easeInElastic() + { + return new Vector3(LeanTween.easeInElastic(this.from.x, this.to.x, this.ratioPassed, this.overshoot, this.period), + LeanTween.easeInElastic(this.from.y, this.to.y, this.ratioPassed, this.overshoot, this.period), + LeanTween.easeInElastic(this.from.z, this.to.z, this.ratioPassed, this.overshoot, this.period)); + } + + private Vector3 easeOutElastic() + { + return new Vector3(LeanTween.easeOutElastic(this.from.x, this.to.x, this.ratioPassed, this.overshoot, this.period), + LeanTween.easeOutElastic(this.from.y, this.to.y, this.ratioPassed, this.overshoot, this.period), + LeanTween.easeOutElastic(this.from.z, this.to.z, this.ratioPassed, this.overshoot, this.period)); + } + + private Vector3 easeInOutElastic() + { + return new Vector3(LeanTween.easeInOutElastic(this.from.x, this.to.x, this.ratioPassed, this.overshoot, this.period), + LeanTween.easeInOutElastic(this.from.y, this.to.y, this.ratioPassed, this.overshoot, this.period), + LeanTween.easeInOutElastic(this.from.z, this.to.z, this.ratioPassed, this.overshoot, this.period)); + } + + /** + * Set how far past a tween will overshoot for certain ease types (compatible: easeInBack, easeInOutBack, easeOutBack, easeOutElastic, easeInElastic, easeInOutElastic).
+ * @method setOvershoot + * @param {float} overshoot:float how far past the destination it will go before settling in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.moveX(gameObject, 5f, 2.0f ).setEase( LeanTweenType.easeOutBack ).setOvershoot(2f); + */ + public LTDescr setOvershoot(float overshoot) + { + this.overshoot = overshoot; + return this; + } + + /** + * Set how short the iterations are for certain ease types (compatible: easeOutElastic, easeInElastic, easeInOutElastic).
+ * @method setPeriod + * @param {float} period:float how short the iterations are that the tween will animate at (default 0.3f) + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.moveX(gameObject, 5f, 2.0f ).setEase( LeanTweenType.easeOutElastic ).setPeriod(0.3f); + */ + public LTDescr setPeriod(float period) + { + this.period = period; + return this; + } + + /** + * Set how large the effect is for certain ease types (compatible: punch, shake, animation curves).
+ * @method setScale + * @param {float} scale:float how much the ease will be multiplied by (default 1f) + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.moveX(gameObject, 5f, 2.0f ).setEase( LeanTweenType.punch ).setScale(2f); + */ + public LTDescr setScale(float scale) + { + this.scale = scale; + return this; + } + + /** + * Set the type of easing used for the tween with a custom curve.
+ * @method setEase (AnimationCurve) + * @param {AnimationCurve} easeDefinition:AnimationCurve an AnimationCure that describes the type of easing you want, this is great for when you want a unique type of movement + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.moveX(gameObject, 5f, 2.0f ).setEase( LeanTweenType.easeInBounce ); + */ + public LTDescr setEase(AnimationCurve easeCurve) + { + this._optional.animationCurve = easeCurve; + this.easeMethod = this.tweenOnCurve; + this.easeType = LeanTweenType.animationCurve; + return this; + } + + /** + * Set the end that the GameObject is tweening towards + * @method setTo + * @param {Vector3} to:Vector3 point at which you want the tween to reach + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LTDescr descr = LeanTween.move( cube, Vector3.up, new Vector3(1f,3f,0f), 1.0f ).setEase( LeanTweenType.easeInOutBounce );
+ * // Later your want to change your destination or your destiation is constantly moving
+ * descr.setTo( new Vector3(5f,10f,3f) );
+ */ + public LTDescr setTo(Vector3 to) + { + if (this.hasInitiliazed) + { + this.to = to; + this.diff = to - this.from; + } + else + { + this.to = to; + } + + return this; + } + + public LTDescr setTo(Transform to) + { + this._optional.toTrans = to; + return this; + } + + /** + * Set the beginning of the tween + * @method setFrom + * @param {Vector3} from:Vector3 the point you would like the tween to start at + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LTDescr descr = LeanTween.move( cube, Vector3.up, new Vector3(1f,3f,0f), 1.0f ).setFrom( new Vector3(5f,10f,3f) );
+ */ + public LTDescr setFrom(Vector3 from) + { + if (this.trans) + { + this.init(); + } + this.from = from; + // this.hasInitiliazed = true; // this is set, so that the "from" value isn't overwritten later on when the tween starts + this.diff = this.to - this.from; + this.diffDiv2 = this.diff * 0.5f; + return this; + } + + public LTDescr setFrom(float from) + { + return setFrom(new Vector3(from, 0f, 0f)); + } + + public LTDescr setDiff(Vector3 diff) + { + this.diff = diff; + return this; + } + + public LTDescr setHasInitialized(bool has) + { + this.hasInitiliazed = has; + return this; + } + + public LTDescr setId(uint id, uint global_counter) + { + this._id = id; + this.counter = global_counter; + // Debug.Log("Global counter:"+global_counter); + return this; + } + + /** + * Set the point of time the tween will start in + * @method setPassed + * @param {float} passedTime:float the length of time in seconds the tween will start in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * int tweenId = LeanTween.moveX(gameObject, 5f, 2.0f ).id;
+ * // Later
+ * LTDescr descr = description( tweenId );
+ * descr.setPassed( 1f );
+ */ + public LTDescr setPassed(float passed) + { + this.passed = passed; + return this; + } + + /** + * Set the finish time of the tween + * @method setTime + * @param {float} finishTime:float the length of time in seconds you wish the tween to complete in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * int tweenId = LeanTween.moveX(gameObject, 5f, 2.0f ).id;
+ * // Later
+ * LTDescr descr = description( tweenId );
+ * descr.setTime( 1f );
+ */ + public LTDescr setTime(float time) + { + float passedTimeRatio = this.passed / this.time; + this.passed = time * passedTimeRatio; + this.time = time; + return this; + } + + /** + * Set the finish time of the tween + * @method setSpeed + * @param {float} speed:float the speed in unity units per second you wish the object to travel (overrides the given time) + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.moveLocalZ( gameObject, 10f, 1f).setSpeed(0.2f) // the given time is ignored when speed is set
+ */ + public LTDescr setSpeed(float speed) + { + this.speed = speed; + if (this.hasInitiliazed) + initSpeed(); + return this; + } + + /** + * Set the tween to repeat a number of times. + * @method setRepeat + * @param {int} repeatNum:int the number of times to repeat the tween. -1 to repeat infinite times + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.moveX(gameObject, 5f, 2.0f ).setRepeat( 10 ).setLoopPingPong(); + */ + public LTDescr setRepeat(int repeat) + { + this.loopCount = repeat; + if ((repeat > 1 && this.loopType == LeanTweenType.once) || (repeat < 0 && this.loopType == LeanTweenType.once)) + { + this.loopType = LeanTweenType.clamp; + } + if (this.type == TweenAction.CALLBACK || this.type == TweenAction.CALLBACK_COLOR) + { + this.setOnCompleteOnRepeat(true); + } + return this; + } + + public LTDescr setLoopType(LeanTweenType loopType) + { + this.loopType = loopType; + return this; + } + + public LTDescr setUseEstimatedTime(bool useEstimatedTime) + { + this.useEstimatedTime = useEstimatedTime; + this.usesNormalDt = false; + return this; + } + + /** + * Set ignore time scale when tweening an object when you want the animation to be time-scale independent (ignores the Time.timeScale value). Great for pause screens, when you want all other action to be stopped (or slowed down) + * @method setIgnoreTimeScale + * @param {bool} useUnScaledTime:bool whether to use the unscaled time or not + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.moveX(gameObject, 5f, 2.0f ).setRepeat( 2 ).setIgnoreTimeScale( true ); + */ + public LTDescr setIgnoreTimeScale(bool useUnScaledTime) + { + this.useEstimatedTime = useUnScaledTime; + this.usesNormalDt = false; + return this; + } + + /** + * Use frames when tweening an object, when you don't want the animation to be time-frame independent... + * @method setUseFrames + * @param {bool} useFrames:bool whether to use estimated time or not + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.moveX(gameObject, 5f, 2.0f ).setRepeat( 2 ).setUseFrames( true ); + */ + public LTDescr setUseFrames(bool useFrames) + { + this.useFrames = useFrames; + this.usesNormalDt = false; + return this; + } + + public LTDescr setUseManualTime(bool useManualTime) + { + this.useManualTime = useManualTime; + this.usesNormalDt = false; + return this; + } + + public LTDescr setLoopCount(int loopCount) + { + this.loopType = LeanTweenType.clamp; + this.loopCount = loopCount; + return this; + } + + /** + * No looping involved, just run once (the default) + * @method setLoopOnce + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.moveX(gameObject, 5f, 2.0f ).setLoopOnce(); + */ + public LTDescr setLoopOnce() { this.loopType = LeanTweenType.once; return this; } + + /** + * When the animation gets to the end it starts back at where it began + * @method setLoopClamp + * @param {int} loops:int (defaults to -1) how many times you want the loop to happen (-1 for an infinite number of times) + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.moveX(gameObject, 5f, 2.0f ).setLoopClamp( 2 ); + */ + public LTDescr setLoopClamp() + { + this.loopType = LeanTweenType.clamp; + if (this.loopCount == 0) + this.loopCount = -1; + return this; + } + public LTDescr setLoopClamp(int loops) + { + this.loopCount = loops; + return this; + } + + /** + * When the animation gets to the end it then tweens back to where it started (and on, and on) + * @method setLoopPingPong + * @param {int} loops:int (defaults to -1) how many times you want the loop to happen in both directions (-1 for an infinite number of times). Passing a value of 1 will cause the object to go towards and back from it's destination once. + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.moveX(gameObject, 5f, 2.0f ).setLoopPingPong( 2 ); + */ + public LTDescr setLoopPingPong() + { + this.loopType = LeanTweenType.pingPong; + if (this.loopCount == 0) + this.loopCount = -1; + return this; + } + public LTDescr setLoopPingPong(int loops) + { + this.loopType = LeanTweenType.pingPong; + this.loopCount = loops == -1 ? loops : loops * 2; + return this; + } + + /** + * Have a method called when the tween finishes + * @method setOnComplete + * @param {Action} onComplete:Action the method that should be called when the tween is finished ex: tweenFinished(){ } + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.moveX(gameObject, 5f, 2.0f ).setOnComplete( tweenFinished ); + */ + public LTDescr setOnComplete(Action onComplete) + { + this._optional.onComplete = onComplete; + this.hasExtraOnCompletes = true; + return this; + } + + /** + * Have a method called when the tween finishes + * @method setOnComplete (object) + * @param {Action} onComplete:Action the method that should be called when the tween is finished ex: tweenFinished( object myObj ){ } + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * object tweenFinishedObj = "hi" as object; + * LeanTween.moveX(gameObject, 5f, 2.0f ).setOnComplete( tweenFinished, tweenFinishedObj ); + */ + public LTDescr setOnComplete(Action onComplete) + { + this._optional.onCompleteObject = onComplete; + this.hasExtraOnCompletes = true; + return this; + } + public LTDescr setOnComplete(Action onComplete, object onCompleteParam) + { + this._optional.onCompleteObject = onComplete; + this.hasExtraOnCompletes = true; + if (onCompleteParam != null) + this._optional.onCompleteParam = onCompleteParam; + return this; + } + + /** + * Pass an object to along with the onComplete Function + * @method setOnCompleteParam + * @param {object} onComplete:object an object that + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.delayedCall(1.5f, enterMiniGameStart).setOnCompleteParam( new object[]{""+5} );

+ * void enterMiniGameStart( object val ){
+ *  object[] arr = (object [])val;
+ *  int lvl = int.Parse((string)arr[0]);
+ * }
+ */ + public LTDescr setOnCompleteParam(object onCompleteParam) + { + this._optional.onCompleteParam = onCompleteParam; + this.hasExtraOnCompletes = true; + return this; + } + + + /** + * Have a method called on each frame that the tween is being animated (passes a float value) + * @method setOnUpdate + * @param {Action} onUpdate:Action a method that will be called on every frame with the float value of the tweened object + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.moveX(gameObject, 5f, 2.0f ).setOnUpdate( tweenMoved );
+ *
+ * void tweenMoved( float val ){ }
+ */ + public LTDescr setOnUpdate(Action onUpdate) + { + this._optional.onUpdateFloat = onUpdate; + this.hasUpdateCallback = true; + return this; + } + public LTDescr setOnUpdateRatio(Action onUpdate) + { + this._optional.onUpdateFloatRatio = onUpdate; + this.hasUpdateCallback = true; + return this; + } + + public LTDescr setOnUpdateObject(Action onUpdate) + { + this._optional.onUpdateFloatObject = onUpdate; + this.hasUpdateCallback = true; + return this; + } + public LTDescr setOnUpdateVector2(Action onUpdate) + { + this._optional.onUpdateVector2 = onUpdate; + this.hasUpdateCallback = true; + return this; + } + public LTDescr setOnUpdateVector3(Action onUpdate) + { + this._optional.onUpdateVector3 = onUpdate; + this.hasUpdateCallback = true; + return this; + } + public LTDescr setOnUpdateColor(Action onUpdate) + { + this._optional.onUpdateColor = onUpdate; + this.hasUpdateCallback = true; + return this; + } + public LTDescr setOnUpdateColor(Action onUpdate) + { + this._optional.onUpdateColorObject = onUpdate; + this.hasUpdateCallback = true; + return this; + } + +#if !UNITY_FLASH + + public LTDescr setOnUpdate(Action onUpdate) + { + this._optional.onUpdateColor = onUpdate; + this.hasUpdateCallback = true; + return this; + } + + public LTDescr setOnUpdate(Action onUpdate) + { + this._optional.onUpdateColorObject = onUpdate; + this.hasUpdateCallback = true; + return this; + } + + /** + * Have a method called on each frame that the tween is being animated (passes a float value and a object) + * @method setOnUpdate (object) + * @param {Action} onUpdate:Action a method that will be called on every frame with the float value of the tweened object, and an object of the person's choosing + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.moveX(gameObject, 5f, 2.0f ).setOnUpdate( tweenMoved ).setOnUpdateParam( myObject );
+ *
+ * void tweenMoved( float val, object obj ){ }
+ */ + public LTDescr setOnUpdate(Action onUpdate, object onUpdateParam = null) + { + this._optional.onUpdateFloatObject = onUpdate; + this.hasUpdateCallback = true; + if (onUpdateParam != null) + this._optional.onUpdateParam = onUpdateParam; + return this; + } + + public LTDescr setOnUpdate(Action onUpdate, object onUpdateParam = null) + { + this._optional.onUpdateVector3Object = onUpdate; + this.hasUpdateCallback = true; + if (onUpdateParam != null) + this._optional.onUpdateParam = onUpdateParam; + return this; + } + + public LTDescr setOnUpdate(Action onUpdate, object onUpdateParam = null) + { + this._optional.onUpdateVector2 = onUpdate; + this.hasUpdateCallback = true; + if (onUpdateParam != null) + this._optional.onUpdateParam = onUpdateParam; + return this; + } + + /** + * Have a method called on each frame that the tween is being animated (passes a float value) + * @method setOnUpdate (Vector3) + * @param {Action} onUpdate:Action a method that will be called on every frame with the float value of the tweened object + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.moveX(gameObject, 5f, 2.0f ).setOnUpdate( tweenMoved );
+ *
+ * void tweenMoved( Vector3 val ){ }
+ */ + public LTDescr setOnUpdate(Action onUpdate, object onUpdateParam = null) + { + this._optional.onUpdateVector3 = onUpdate; + this.hasUpdateCallback = true; + if (onUpdateParam != null) + this._optional.onUpdateParam = onUpdateParam; + return this; + } +#endif + + + /** + * Have an object passed along with the onUpdate method + * @method setOnUpdateParam + * @param {object} onUpdateParam:object an object that will be passed along with the onUpdate method + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.moveX(gameObject, 5f, 2.0f ).setOnUpdate( tweenMoved ).setOnUpdateParam( myObject );
+ *
+ * void tweenMoved( float val, object obj ){ }
+ */ + public LTDescr setOnUpdateParam(object onUpdateParam) + { + this._optional.onUpdateParam = onUpdateParam; + return this; + } + + /** + * While tweening along a curve, set this property to true, to be perpendicalur to the path it is moving upon + * @method setOrientToPath + * @param {bool} doesOrient:bool whether the gameobject will orient to the path it is animating along + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.move( ltLogo, path, 1.0f ).setEase(LeanTweenType.easeOutQuad).setOrientToPath(true).setAxis(Vector3.forward);
+ */ + public LTDescr setOrientToPath(bool doesOrient) + { + if (this.type == TweenAction.MOVE_CURVED || this.type == TweenAction.MOVE_CURVED_LOCAL) + { + if (this._optional.path == null) + this._optional.path = new LTBezierPath(); + this._optional.path.orientToPath = doesOrient; + } + else + { + this._optional.spline.orientToPath = doesOrient; + } + return this; + } + + /** + * While tweening along a curve, set this property to true, to be perpendicalur to the path it is moving upon + * @method setOrientToPath2d + * @param {bool} doesOrient:bool whether the gameobject will orient to the path it is animating along + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.move( ltLogo, path, 1.0f ).setEase(LeanTweenType.easeOutQuad).setOrientToPath2d(true).setAxis(Vector3.forward);
+ */ + public LTDescr setOrientToPath2d(bool doesOrient2d) + { + setOrientToPath(doesOrient2d); + if (this.type == TweenAction.MOVE_CURVED || this.type == TweenAction.MOVE_CURVED_LOCAL) + { + this._optional.path.orientToPath2d = doesOrient2d; + } + else + { + this._optional.spline.orientToPath2d = doesOrient2d; + } + return this; + } + + public LTDescr setRect(LTRect rect) + { + this._optional.ltRect = rect; + return this; + } + + public LTDescr setRect(Rect rect) + { + this._optional.ltRect = new LTRect(rect); + return this; + } + + public LTDescr setPath(LTBezierPath path) + { + this._optional.path = path; + return this; + } + + /** + * Set the point at which the GameObject will be rotated around + * @method setPoint + * @param {Vector3} point:Vector3 point at which you want the object to rotate around (local space) + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.rotateAround( cube, Vector3.up, 360.0f, 1.0f ) .setPoint( new Vector3(1f,0f,0f) ) .setEase( LeanTweenType.easeInOutBounce );
+ */ + public LTDescr setPoint(Vector3 point) + { + this._optional.point = point; + return this; + } + + public LTDescr setDestroyOnComplete(bool doesDestroy) + { + this.destroyOnComplete = doesDestroy; + return this; + } + + public LTDescr setAudio(object audio) + { + this._optional.onCompleteParam = audio; + return this; + } + + /** + * Set the onComplete method to be called at the end of every loop cycle (also applies to the delayedCall method) + * @method setOnCompleteOnRepeat + * @param {bool} isOn:bool does call onComplete on every loop cycle + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.delayedCall(gameObject,0.3f, delayedMethod).setRepeat(4).setOnCompleteOnRepeat(true); + */ + public LTDescr setOnCompleteOnRepeat(bool isOn) + { + this.onCompleteOnRepeat = isOn; + return this; + } + + /** + * Set the onComplete method to be called at the beginning of the tween (it will still be called when it is completed as well) + * @method setOnCompleteOnStart + * @param {bool} isOn:bool does call onComplete at the start of the tween + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.delayedCall(gameObject, 2f, ()=>{
// Flash an object 5 times + *  LeanTween.alpha(gameObject, 0f, 1f);
+ *  LeanTween.alpha(gameObject, 1f, 0f).setDelay(1f);
+ * }).setOnCompleteOnStart(true).setRepeat(5);
+ */ + public LTDescr setOnCompleteOnStart(bool isOn) + { + this.onCompleteOnStart = isOn; + return this; + } + +#if !UNITY_3_5 && !UNITY_4_0 && !UNITY_4_0_1 && !UNITY_4_1 && !UNITY_4_2 && !UNITY_4_3 && !UNITY_4_5 + public LTDescr setRect(RectTransform rect) + { + this.rectTransform = rect; + return this; + } + + public LTDescr setSprites(UnityEngine.Sprite[] sprites) + { + this.sprites = sprites; + return this; + } + + public LTDescr setFrameRate(float frameRate) + { + this.time = this.sprites.Length / frameRate; + return this; + } +#endif + + /** + * Have a method called when the tween starts + * @method setOnStart + * @param {Action<>} onStart:Action<> the method that should be called when the tween is starting ex: tweenStarted( ){ } + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * C#:
+ * LeanTween.moveX(gameObject, 5f, 2.0f ).setOnStart( ()=>{ Debug.Log("I started!"); }); + * Javascript:
+ * LeanTween.moveX(gameObject, 5f, 2.0f ).setOnStart( function(){ Debug.Log("I started!"); } ); + */ + public LTDescr setOnStart(Action onStart) + { + this._optional.onStart = onStart; + return this; + } + + /** + * Set the direction of a tween -1f for backwards 1f for forwards (currently only bezier and spline paths are supported) + * @method setDirection + * @param {float} direction:float the direction that the tween should run, -1f for backwards 1f for forwards + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.moveSpline(gameObject, new Vector3[]{new Vector3(0f,0f,0f),new Vector3(1f,0f,0f),new Vector3(1f,0f,0f),new Vector3(1f,0f,1f)}, 1.5f).setDirection(-1f);
+ */ + + public LTDescr setDirection(float direction) + { + if (this.direction != -1f && this.direction != 1f) + { + Debug.LogWarning("You have passed an incorrect direction of '" + direction + "', direction must be -1f or 1f"); + return this; + } + + if (this.direction != direction) + { + // Debug.Log("reverse path:"+this.path+" spline:"+this._optional.spline+" hasInitiliazed:"+this.hasInitiliazed); + if (this.hasInitiliazed) + { + this.direction = direction; + } + else + { + if (this._optional.path != null) + { + this._optional.path = new LTBezierPath(LTUtility.reverse(this._optional.path.pts)); + } + else if (this._optional.spline != null) + { + this._optional.spline = new LTSpline(LTUtility.reverse(this._optional.spline.pts)); + } + // this.passed = this.time - this.passed; + } + } + + return this; + } + + /** + * Set whether or not the tween will recursively effect an objects children in the hierarchy + * @method setRecursive + * @param {bool} useRecursion:bool whether the tween will recursively effect an objects children in the hierarchy + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.alpha(gameObject, 0f, 1f).setRecursive(true);
+ */ + + public LTDescr setRecursive(bool useRecursion) + { + this.useRecursion = useRecursion; + + return this; + } +} + +//} diff --git a/KFAttached/LeanTween/Framework/LTDescr.cs.meta b/KFAttached/LeanTween/Framework/LTDescr.cs.meta new file mode 100644 index 0000000..141b45c --- /dev/null +++ b/KFAttached/LeanTween/Framework/LTDescr.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 381c8d6fb1acdc348870a7147bc98723 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/LeanTween/Framework/LTDescrOptional.cs b/KFAttached/LeanTween/Framework/LTDescrOptional.cs new file mode 100644 index 0000000..b751b0b --- /dev/null +++ b/KFAttached/LeanTween/Framework/LTDescrOptional.cs @@ -0,0 +1,99 @@ +//namespace DentedPixel{ +using System; +using UnityEngine; + +public class LTDescrOptional +{ + + public Transform toTrans { get; set; } + public Vector3 point { get; set; } + public Vector3 axis { get; set; } + public float lastVal { get; set; } + public Quaternion origRotation { get; set; } + public LTBezierPath path { get; set; } + public LTSpline spline { get; set; } + public AnimationCurve animationCurve; + public int initFrameCount; + public Color color; + + public LTRect ltRect { get; set; } // maybe get rid of this eventually + + public Action onUpdateFloat { get; set; } + public Action onUpdateFloatRatio { get; set; } + public Action onUpdateFloatObject { get; set; } + public Action onUpdateVector2 { get; set; } + public Action onUpdateVector3 { get; set; } + public Action onUpdateVector3Object { get; set; } + public Action onUpdateColor { get; set; } + public Action onUpdateColorObject { get; set; } + public Action onComplete { get; set; } + public Action onCompleteObject { get; set; } + public object onCompleteParam { get; set; } + public object onUpdateParam { get; set; } + public Action onStart { get; set; } + + + // #if !UNITY_3_5 && !UNITY_4_0 && !UNITY_4_0_1 && !UNITY_4_1 && !UNITY_4_2 + // public SpriteRenderer spriteRen { get; set; } + // #endif + // + // #if LEANTWEEN_1 + // public Hashtable optional; + // #endif + // #if !UNITY_3_5 && !UNITY_4_0 && !UNITY_4_0_1 && !UNITY_4_1 && !UNITY_4_2 && !UNITY_4_3 && !UNITY_4_5 + // public RectTransform rectTransform; + // public UnityEngine.UI.Text uiText; + // public UnityEngine.UI.Image uiImage; + // public UnityEngine.Sprite[] sprites; + // #endif + + + public void reset() + { + animationCurve = null; + + this.onUpdateFloat = null; + this.onUpdateFloatRatio = null; + this.onUpdateVector2 = null; + this.onUpdateVector3 = null; + this.onUpdateFloatObject = null; + this.onUpdateVector3Object = null; + this.onUpdateColor = null; + this.onComplete = null; + this.onCompleteObject = null; + this.onCompleteParam = null; + this.onStart = null; + + this.point = Vector3.zero; + this.initFrameCount = 0; + } + + public void callOnUpdate(float val, float ratioPassed) + { + if (this.onUpdateFloat != null) + this.onUpdateFloat(val); + + if (this.onUpdateFloatRatio != null) + { + this.onUpdateFloatRatio(val, ratioPassed); + } + else if (this.onUpdateFloatObject != null) + { + this.onUpdateFloatObject(val, this.onUpdateParam); + } + else if (this.onUpdateVector3Object != null) + { + this.onUpdateVector3Object(LTDescr.newVect, this.onUpdateParam); + } + else if (this.onUpdateVector3 != null) + { + this.onUpdateVector3(LTDescr.newVect); + } + else if (this.onUpdateVector2 != null) + { + this.onUpdateVector2(new Vector2(LTDescr.newVect.x, LTDescr.newVect.y)); + } + } +} + +//} diff --git a/KFAttached/LeanTween/Framework/LTDescrOptional.cs.meta b/KFAttached/LeanTween/Framework/LTDescrOptional.cs.meta new file mode 100644 index 0000000..615e564 --- /dev/null +++ b/KFAttached/LeanTween/Framework/LTDescrOptional.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c1ba8f1ef97134cb39b52ae26678db63 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/LeanTween/Framework/LTSeq.cs b/KFAttached/LeanTween/Framework/LTSeq.cs new file mode 100644 index 0000000..c7b1955 --- /dev/null +++ b/KFAttached/LeanTween/Framework/LTSeq.cs @@ -0,0 +1,243 @@ +using UnityEngine; + +/** +* Internal Representation of a Sequence
+*
+*   

Example:

+* var seq = LeanTween.sequence();
+* seq.append(1f); // delay everything one second
+* seq.append( () => { // fire an event before start
+*  Debug.Log("I have started");
+* });
+* seq.append( LeanTween.move(cube1, Vector3.one * 10f, 1f) ); // do a tween
+* seq.append( (object obj) => { // fire event after tween
+*  var dict = obj as Dictionary;
+*  Debug.Log("We are done now obj value:"+dict["hi"]);
+* }, new Dictionary(){ {"hi","sup"} } );
+* @class LTSeq +* @constructor +*/ +public class LTSeq +{ + + public LTSeq previous; + + public LTSeq current; + + public LTDescr tween; + + public float totalDelay; + + public float timeScale; + + private int debugIter; + + public uint counter; + + public bool toggle = false; + + private uint _id; + + public int id + { + get + { + uint toId = _id | counter << 16; + + /*uint backId = toId & 0xFFFF; + uint backCounter = toId >> 16; + if(_id!=backId || backCounter!=counter){ + Debug.LogError("BAD CONVERSION toId:"+_id); + }*/ + + return (int)toId; + } + } + + public void reset() + { + previous = null; + tween = null; + totalDelay = 0f; + } + + public void init(uint id, uint global_counter) + { + reset(); + _id = id; + + counter = global_counter; + + this.current = this; + } + + private LTSeq addOn() + { + this.current.toggle = true; + LTSeq lastCurrent = this.current; + this.current = LeanTween.sequence(true); + // Debug.Log("this.current:" + this.current.id + " lastCurrent:" + lastCurrent.id); + this.current.previous = lastCurrent; + lastCurrent.toggle = false; + this.current.totalDelay = lastCurrent.totalDelay; + this.current.debugIter = lastCurrent.debugIter + 1; + return current; + } + + private float addPreviousDelays() + { + // Debug.Log("delay:"+delay+" count:"+this.current.count+" this.current.totalDelay:"+this.current.totalDelay); + + LTSeq prev = this.current.previous; + + if (prev != null && prev.tween != null) + { + return this.current.totalDelay + prev.tween.time; + } + return this.current.totalDelay; + } + + /** + * Add a time delay to the sequence + * @method append (delay) + * @param {float} delay:float amount of time to add to the sequence + * @return {LTSeq} LTDescr an object that distinguishes the tween + * var seq = LeanTween.sequence();
+ * seq.append(1f); // delay everything one second
+ * seq.append( LeanTween.move(cube1, Vector3.one * 10f, 1f) ); // do a tween
+ */ + public LTSeq append(float delay) + { + this.current.totalDelay += delay; + + return this.current; + } + + /** + * Add a time delay to the sequence + * @method append (method) + * @param {System.Action} callback:System.Action method you want to be called + * @return {LTSeq} LTSeq an object that you can add tweens, methods and time on to + * @example + * var seq = LeanTween.sequence();
+ * seq.append( () => { // fire an event before start
+ *  Debug.Log("I have started");
+ * });
+ * seq.append( LeanTween.move(cube1, Vector3.one * 10f, 1f) ); // do a tween
+ * seq.append( () => { // fire event after tween
+ *  Debug.Log("We are done now");
+ * });;
+ */ + public LTSeq append(System.Action callback) + { + LTDescr newTween = LeanTween.delayedCall(0f, callback); + // Debug.Log("newTween:" + newTween); + return append(newTween); + } + + /** + * Add a time delay to the sequence + * @method add (method(object)) + * @param {System.Action} callback:System.Action method you want to be called + * @return {LTSeq} LTSeq an object that you can add tweens, methods and time on to + * @example + * var seq = LeanTween.sequence();
+ * seq.append( () => { // fire an event before start
+ *  Debug.Log("I have started");
+ * });
+ * seq.append( LeanTween.move(cube1, Vector3.one * 10f, 1f) ); // do a tween
+ * seq.append((object obj) => { // fire event after tween + *  var dict = obj as Dictionary; + *  Debug.Log("We are done now obj value:"+dict["hi"]); + *  }, new Dictionary(){ {"hi","sup"} } ); + */ + public LTSeq append(System.Action callback, object obj) + { + append(LeanTween.delayedCall(0f, callback).setOnCompleteParam(obj)); + + return addOn(); + } + + public LTSeq append(GameObject gameObject, System.Action callback) + { + append(LeanTween.delayedCall(gameObject, 0f, callback)); + + return addOn(); + } + + public LTSeq append(GameObject gameObject, System.Action callback, object obj) + { + append(LeanTween.delayedCall(gameObject, 0f, callback).setOnCompleteParam(obj)); + + return addOn(); + } + + /** + * Retrieve a sequencer object where you can easily chain together tweens and methods one after another + * + * @method add (tween) + * @return {LTSeq} LTSeq an object that you can add tweens, methods and time on to + * @example + * var seq = LeanTween.sequence();
+ * seq.append( LeanTween.move(cube1, Vector3.one * 10f, 1f) ); // do a move tween
+ * seq.append( LeanTween.rotateAround( avatar1, Vector3.forward, 360f, 1f ) ); // then do a rotate tween
+ */ + public LTSeq append(LTDescr tween) + { + this.current.tween = tween; + + // Debug.Log("tween:" + tween + " delay:" + this.current.totalDelay); + + this.current.totalDelay = addPreviousDelays(); + + tween.setDelay(this.current.totalDelay); + + return addOn(); + } + + public LTSeq insert(LTDescr tween) + { + this.current.tween = tween; + + tween.setDelay(addPreviousDelays()); + + return addOn(); + } + + + public LTSeq setScale(float timeScale) + { + // Debug.Log("this.current:" + this.current.previous.debugIter+" tween:"+this.current.previous.tween); + setScaleRecursive(this.current, timeScale, 500); + + return addOn(); + } + + private void setScaleRecursive(LTSeq seq, float timeScale, int count) + { + if (count > 0) + { + this.timeScale = timeScale; + + // Debug.Log("seq.count:" + count + " seq.tween:" + seq.tween); + seq.totalDelay *= timeScale; + if (seq.tween != null) + { + // Debug.Log("seq.tween.time * timeScale:" + seq.tween.time * timeScale + " seq.totalDelay:"+seq.totalDelay +" time:"+seq.tween.time+" seq.tween.delay:"+seq.tween.delay); + if (seq.tween.time != 0f) + seq.tween.setTime(seq.tween.time * timeScale); + seq.tween.setDelay(seq.tween.delay * timeScale); + } + + if (seq.previous != null) + setScaleRecursive(seq.previous, timeScale, count - 1); + } + } + + public LTSeq reverse() + { + + return addOn(); + } + +} diff --git a/KFAttached/LeanTween/Framework/LTSeq.cs.meta b/KFAttached/LeanTween/Framework/LTSeq.cs.meta new file mode 100644 index 0000000..cb77fb8 --- /dev/null +++ b/KFAttached/LeanTween/Framework/LTSeq.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6c88dbe4cdd9944f198e9796ee394c86 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/LeanTween/Framework/LeanAudio.cs b/KFAttached/LeanTween/Framework/LeanAudio.cs new file mode 100644 index 0000000..5c135c9 --- /dev/null +++ b/KFAttached/LeanTween/Framework/LeanAudio.cs @@ -0,0 +1,471 @@ + +using UnityEngine; + +public class LeanAudioStream +{ + + public int position = 0; + + public AudioClip audioClip; + public float[] audioArr; + + public LeanAudioStream(float[] audioArr) + { + this.audioArr = audioArr; + } + + public void OnAudioRead(float[] data) + { + int count = 0; + while (count < data.Length) + { + data[count] = audioArr[this.position]; + position++; + count++; + } + } + + public void OnAudioSetPosition(int newPosition) + { + this.position = newPosition; + } +} + +/** +* Create Audio dynamically and easily playback +* +* @class LeanAudio +* @constructor +*/ +public class LeanAudio : object +{ + + public static float MIN_FREQEUNCY_PERIOD = 0.000115f; + public static int PROCESSING_ITERATIONS_MAX = 50000; + public static float[] generatedWaveDistances; + public static int generatedWaveDistancesCount = 0; + + private static float[] longList; + + public static LeanAudioOptions options() + { + if (generatedWaveDistances == null) + { + generatedWaveDistances = new float[PROCESSING_ITERATIONS_MAX]; + longList = new float[PROCESSING_ITERATIONS_MAX]; + } + return new LeanAudioOptions(); + } + + public static LeanAudioStream createAudioStream(AnimationCurve volume, AnimationCurve frequency, LeanAudioOptions options = null) + { + if (options == null) + options = new LeanAudioOptions(); + + options.useSetData = false; + + int generatedWavePtsLength = createAudioWave(volume, frequency, options); + createAudioFromWave(generatedWavePtsLength, options); + + return options.stream; + } + + /** + * Create dynamic audio from a set of Animation Curves and other options. + * + * @method createAudio + * @param {AnimationCurve} volumeCurve:AnimationCurve describing the shape of the audios volume (from 0-1). The length of the audio is dicated by the end value here. + * @param {AnimationCurve} frequencyCurve:AnimationCurve describing the width of the oscillations between the sound waves in seconds. Large numbers mean a lower note, while higher numbers mean a tighter frequency and therefor a higher note. Values are usually between 0.01 and 0.000001 (or smaller) + * @param {LeanAudioOptions} options:LeanAudioOptions You can pass any other values in here like vibrato or the frequency you would like the sound to be encoded at. See LeanAudioOptions for more details. + * @return {AudioClip} AudioClip of the procedurally generated audio + * @example + * AnimationCurve volumeCurve = new AnimationCurve( new Keyframe(0f, 1f, 0f, -1f), new Keyframe(1f, 0f, -1f, 0f));
+ * AnimationCurve frequencyCurve = new AnimationCurve( new Keyframe(0f, 0.003f, 0f, 0f), new Keyframe(1f, 0.003f, 0f, 0f));
+ * AudioClip audioClip = LeanAudio.createAudio(volumeCurve, frequencyCurve, LeanAudio.options().setVibrato( new Vector3[]{ new Vector3(0.32f,0f,0f)} ));
+ */ + public static AudioClip createAudio(AnimationCurve volume, AnimationCurve frequency, LeanAudioOptions options = null) + { + if (options == null) + options = new LeanAudioOptions(); + + int generatedWavePtsLength = createAudioWave(volume, frequency, options); + // Debug.Log("generatedWavePtsLength:"+generatedWavePtsLength); + return createAudioFromWave(generatedWavePtsLength, options); + } + + private static int createAudioWave(AnimationCurve volume, AnimationCurve frequency, LeanAudioOptions options) + { + float time = volume[volume.length - 1].time; + int listLength = 0; + // List list = new List(); + + // generatedWaveDistances = new List(); + // float[] vibratoValues = new float[ vibrato.Length ]; + float passed = 0f; + for (int i = 0; i < PROCESSING_ITERATIONS_MAX; i++) + { + float f = frequency.Evaluate(passed); + if (f < MIN_FREQEUNCY_PERIOD) + f = MIN_FREQEUNCY_PERIOD; + float height = volume.Evaluate(passed + 0.5f * f); + if (options.vibrato != null) + { + for (int j = 0; j < options.vibrato.Length; j++) + { + float peakMulti = Mathf.Abs(Mathf.Sin(1.5708f + passed * (1f / options.vibrato[j][0]) * Mathf.PI)); + float diff = (1f - options.vibrato[j][1]); + peakMulti = options.vibrato[j][1] + diff * peakMulti; + height *= peakMulti; + } + } + + + // Debug.Log("i:"+i+" f:"+f+" passed:"+passed+" height:"+height+" time:"+time); + if (passed + 0.5f * f >= time) + break; + if (listLength >= PROCESSING_ITERATIONS_MAX - 1) + { + Debug.LogError("LeanAudio has reached it's processing cap. To avoid this error increase the number of iterations ex: LeanAudio.PROCESSING_ITERATIONS_MAX = " + (PROCESSING_ITERATIONS_MAX * 2)); + break; + } + else + { + int distPoint = listLength / 2; + + //generatedWaveDistances.Add( f ); + passed += f; + + generatedWaveDistances[distPoint] = passed; + //Debug.Log("distPoint:"+distPoint+" passed:"+passed); + + //list.Add( passed ); + //list.Add( i%2==0 ? -height : height ); + + longList[listLength] = passed; + longList[listLength + 1] = i % 2 == 0 ? -height : height; + } + + + + listLength += 2; + + } + + listLength += -2; + generatedWaveDistancesCount = listLength / 2; + + /*float[] wave = new float[ listLength ]; + for(int i = 0; i < wave.Length; i++){ + wave[i] = longList[i]; + }*/ + return listLength; + } + + private static AudioClip createAudioFromWave(int waveLength, LeanAudioOptions options) + { + float time = longList[waveLength - 2]; + float[] audioArr = new float[(int)(options.frequencyRate * time)]; + + int waveIter = 0; + float subWaveDiff = longList[waveIter]; + float subWaveTimeLast = 0f; + float subWaveTime = longList[waveIter]; + float waveHeight = longList[waveIter + 1]; + for (int i = 0; i < audioArr.Length; i++) + { + float passedTime = (float)i / (float)options.frequencyRate; + if (passedTime > longList[waveIter]) + { + subWaveTimeLast = longList[waveIter]; + waveIter += 2; + subWaveDiff = longList[waveIter] - longList[waveIter - 2]; + waveHeight = longList[waveIter + 1]; + // Debug.Log("passed wave i:"+i); + } + subWaveTime = passedTime - subWaveTimeLast; + float ratioElapsed = subWaveTime / subWaveDiff; + + float value = Mathf.Sin(ratioElapsed * Mathf.PI); + + if (options.waveStyle == LeanAudioOptions.LeanAudioWaveStyle.Square) + { + if (value > 0f) + value = 1f; + if (value < 0f) + value = -1f; + } + else if (options.waveStyle == LeanAudioOptions.LeanAudioWaveStyle.Sawtooth) + { + float sign = value > 0f ? 1f : -1f; + if (ratioElapsed < 0.5f) + { + value = (ratioElapsed * 2f) * sign; + } + else + { // 0.5f - 1f + value = (1f - ratioElapsed) * 2f * sign; + } + } + else if (options.waveStyle == LeanAudioOptions.LeanAudioWaveStyle.Noise) + { + float peakMulti = (1f - options.waveNoiseInfluence) + Mathf.PerlinNoise(0f, passedTime * options.waveNoiseScale) * options.waveNoiseInfluence; + + /*if(i<25){ + Debug.Log("passedTime:"+passedTime+" peakMulti:"+peakMulti+" infl:"+options.waveNoiseInfluence); + }*/ + + value *= peakMulti; + } + + //if(i<25) + // Debug.Log("passedTime:"+passedTime+" value:"+value+" ratioElapsed:"+ratioElapsed+" subWaveTime:"+subWaveTime+" subWaveDiff:"+subWaveDiff); + + value *= waveHeight; + + + if (options.modulation != null) + { + for (int k = 0; k < options.modulation.Length; k++) + { + float peakMulti = Mathf.Abs(Mathf.Sin(1.5708f + passedTime * (1f / options.modulation[k][0]) * Mathf.PI)); + float diff = (1f - options.modulation[k][1]); + peakMulti = options.modulation[k][1] + diff * peakMulti; + // if(k<10){ + // Debug.Log("k:"+k+" peakMulti:"+peakMulti+" value:"+value+" after:"+(value*peakMulti)); + // } + value *= peakMulti; + } + } + + audioArr[i] = value; + // Debug.Log("pt:"+pt+" i:"+i+" val:"+audioArr[i]+" len:"+audioArr.Length); + } + + + int lengthSamples = audioArr.Length; + +#if UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7 + bool is3dSound = false; + AudioClip audioClip = AudioClip.Create("Generated Audio", lengthSamples, 1, options.frequencyRate, is3dSound, false); +#else + AudioClip audioClip = null; + if (options.useSetData) + { + audioClip = AudioClip.Create("Generated Audio", lengthSamples, 1, options.frequencyRate, false, null, OnAudioSetPosition); + audioClip.SetData(audioArr, 0); + } + else + { + options.stream = new LeanAudioStream(audioArr); + // Debug.Log("len:"+audioArr.Length+" lengthSamples:"+lengthSamples+" freqRate:"+options.frequencyRate); + audioClip = AudioClip.Create("Generated Audio", lengthSamples, 1, options.frequencyRate, false, options.stream.OnAudioRead, options.stream.OnAudioSetPosition); + options.stream.audioClip = audioClip; + } + +#endif + + return audioClip; + } + + private static void OnAudioSetPosition(int newPosition) + { + + } + + public static AudioClip generateAudioFromCurve(AnimationCurve curve, int frequencyRate = 44100) + { + float curveTime = curve[curve.length - 1].time; + float time = curveTime; + float[] audioArr = new float[(int)(frequencyRate * time)]; + + // Debug.Log("curveTime:"+curveTime+" AudioSettings.outputSampleRate:"+AudioSettings.outputSampleRate); + for (int i = 0; i < audioArr.Length; i++) + { + float pt = (float)i / (float)frequencyRate; + audioArr[i] = curve.Evaluate(pt); + // Debug.Log("pt:"+pt+" i:"+i+" val:"+audioArr[i]+" len:"+audioArr.Length); + } + + int lengthSamples = audioArr.Length;//(int)( (float)frequencyRate * curveTime ); +#if UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7 + bool is3dSound = false; + AudioClip audioClip = AudioClip.Create("Generated Audio", lengthSamples, 1, frequencyRate, is3dSound, false); +#else + AudioClip audioClip = AudioClip.Create("Generated Audio", lengthSamples, 1, frequencyRate, false); +#endif + audioClip.SetData(audioArr, 0); + + return audioClip; + } + + public static AudioSource play(AudioClip audio, float volume) + { + AudioSource audioSource = playClipAt(audio, Vector3.zero); + audioSource.volume = volume; + return audioSource; + } + + public static AudioSource play(AudioClip audio) + { + return playClipAt(audio, Vector3.zero); + } + + public static AudioSource play(AudioClip audio, Vector3 pos) + { + return playClipAt(audio, pos); + } + + public static AudioSource play(AudioClip audio, Vector3 pos, float volume) + { + // Debug.Log("audio length:"+audio.length); + AudioSource audioSource = playClipAt(audio, pos); + audioSource.minDistance = 1f; + //audioSource.pitch = pitch; + audioSource.volume = volume; + + return audioSource; + } + + public static AudioSource playClipAt(AudioClip clip, Vector3 pos) + { + GameObject tempGO = new GameObject(); // create the temp object + tempGO.transform.position = pos; // set its position + AudioSource aSource = tempGO.AddComponent(); // add an audio source + aSource.clip = clip; // define the clip + aSource.Play(); // start the sound + GameObject.Destroy(tempGO, clip.length); // destroy object after clip duration + return aSource; // return the AudioSource reference + } + + public static void printOutAudioClip(AudioClip audioClip, ref AnimationCurve curve, float scaleX = 1f) + { + // Debug.Log("Audio channels:"+audioClip.channels+" frequency:"+audioClip.frequency+" length:"+audioClip.length+" samples:"+audioClip.samples); + float[] samples = new float[audioClip.samples * audioClip.channels]; + audioClip.GetData(samples, 0); + int i = 0; + + Keyframe[] frames = new Keyframe[samples.Length]; + while (i < samples.Length) + { + frames[i] = new Keyframe((float)i * scaleX, samples[i]); + ++i; + } + curve = new AnimationCurve(frames); + } +} + + +/** +* Pass in options to LeanAudio +* +* @class LeanAudioOptions +* @constructor +*/ +public class LeanAudioOptions : object +{ + + public enum LeanAudioWaveStyle + { + Sine, + Square, + Sawtooth, + Noise + } + + public LeanAudioWaveStyle waveStyle = LeanAudioWaveStyle.Sine; + public Vector3[] vibrato; + public Vector3[] modulation; + public int frequencyRate = 44100; + public float waveNoiseScale = 1000; + public float waveNoiseInfluence = 1f; + + public bool useSetData = true; + public LeanAudioStream stream; + + public LeanAudioOptions() { } + + /** + * Set the frequency for the audio is encoded. 44100 is CD quality, but you can usually get away with much lower (or use a lower amount to get a more 8-bit sound). + * + * @method setFrequency + * @param {int} frequencyRate:int of the frequency you wish to encode the AudioClip at + * @return {LeanAudioOptions} LeanAudioOptions describing optional values + * @example + * AnimationCurve volumeCurve = new AnimationCurve( new Keyframe(0f, 1f, 0f, -1f), new Keyframe(1f, 0f, -1f, 0f));
+ * AnimationCurve frequencyCurve = new AnimationCurve( new Keyframe(0f, 0.003f, 0f, 0f), new Keyframe(1f, 0.003f, 0f, 0f));
+ * AudioClip audioClip = LeanAudio.createAudio(volumeCurve, frequencyCurve, LeanAudio.options().setVibrato( new Vector3[]{ new Vector3(0.32f,0f,0f)} ).setFrequency(12100) );
+ */ + public LeanAudioOptions setFrequency(int frequencyRate) + { + this.frequencyRate = frequencyRate; + return this; + } + + /** + * Set details about the shape of the curve by adding vibrato modulations through it (alters the peak values giving it a wah-wah effect). You can add as many as you want to sculpt out more detail in the sound wave. + * + * @method setVibrato + * @param {Vector3[]} vibratoArray:Vector3[] The first value is the period in seconds that you wish to have the vibrato wave fluctuate at. The second value is the minimum height you wish the vibrato wave to dip down to (default is zero). The third is reserved for future effects. + * @return {LeanAudioOptions} LeanAudioOptions describing optional values + * @example + * AnimationCurve volumeCurve = new AnimationCurve( new Keyframe(0f, 1f, 0f, -1f), new Keyframe(1f, 0f, -1f, 0f));
+ * AnimationCurve frequencyCurve = new AnimationCurve( new Keyframe(0f, 0.003f, 0f, 0f), new Keyframe(1f, 0.003f, 0f, 0f));
+ * AudioClip audioClip = LeanAudio.createAudio(volumeCurve, frequencyCurve, LeanAudio.options().setVibrato( new Vector3[]{ new Vector3(0.32f,0.3f,0f)} ).setFrequency(12100) );
+ */ + public LeanAudioOptions setVibrato(Vector3[] vibrato) + { + this.vibrato = vibrato; + return this; + } + + /* + public LeanAudioOptions setModulation( Vector3[] modulation ){ + this.modulation = modulation; + return this; + }*/ + + public LeanAudioOptions setWaveSine() + { + this.waveStyle = LeanAudioWaveStyle.Sine; + return this; + } + + public LeanAudioOptions setWaveSquare() + { + this.waveStyle = LeanAudioWaveStyle.Square; + return this; + } + + public LeanAudioOptions setWaveSawtooth() + { + this.waveStyle = LeanAudioWaveStyle.Sawtooth; + return this; + } + + public LeanAudioOptions setWaveNoise() + { + this.waveStyle = LeanAudioWaveStyle.Noise; + return this; + } + + public LeanAudioOptions setWaveStyle(LeanAudioWaveStyle style) + { + this.waveStyle = style; + return this; + } + + + public LeanAudioOptions setWaveNoiseScale(float waveScale) + { + this.waveNoiseScale = waveScale; + return this; + } + + public LeanAudioOptions setWaveNoiseInfluence(float influence) + { + this.waveNoiseInfluence = influence; + return this; + } +} + + diff --git a/KFAttached/LeanTween/Framework/LeanAudio.cs.meta b/KFAttached/LeanTween/Framework/LeanAudio.cs.meta new file mode 100644 index 0000000..272a1f7 --- /dev/null +++ b/KFAttached/LeanTween/Framework/LeanAudio.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 52e41e970d9353942b27458440bec9eb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/LeanTween/Framework/LeanSmooth.cs b/KFAttached/LeanTween/Framework/LeanSmooth.cs new file mode 100644 index 0000000..fa948fb --- /dev/null +++ b/KFAttached/LeanTween/Framework/LeanSmooth.cs @@ -0,0 +1,363 @@ +using UnityEngine; + +/** +* Use these smooth methods to move one value towards another

+* Example:
fromY = LeanSmooth.spring(fromY, followArrow.localPosition.y, ref velocityY, 1.1f);
+* fromVec3 = LeanSmooth.damp(fromVec3, dude5Title.localPosition, ref velocityVec3, 1.1f);
+* fromColor = LeanSmooth.damp(fromColor, dude5Title.GetComponent().material.color, ref velocityColor, 1.1f);
+* Debug.Log("Smoothed y:" + fromY + " vec3:" + fromVec3 + " color:" + fromColor);
+* +* @class LeanSmooth +*/ + +public class LeanSmooth +{ + + + /** + * Moves one value towards another (eases in and out to destination with no overshoot) + * + * @method LeanSmooth.damp (float) + * @param {float} current:float the current value + * @param {float} target:float the value we are trying to reach + * @param {float} currentVelocity:float the current velocity of the value + * @param {float} smoothTime:float roughly the time it takes to reach the destination + * @param {float} maxSpeed:float the top speed you want the value to move at (defaults to unlimited -1f) + * @param {float} deltaTime:float the difference in time since the method was called (defaults to Time.deltaTime) + * @example + * followVar = LeanSmooth.damp(followVar, destinationVar, ref followVelocity, 1.1f);\n + * Debug.Log("current:"+followVar); + */ + public static float damp(float current, float target, ref float currentVelocity, float smoothTime, float maxSpeed = -1f, float deltaTime = -1f) + { + if (deltaTime < 0f) + deltaTime = Time.deltaTime; + + smoothTime = Mathf.Max(0.0001f, smoothTime); + float num = 2f / smoothTime; + float num2 = num * deltaTime; + float num3 = 1f / (1f + num2 + 0.48f * num2 * num2 + 0.235f * num2 * num2 * num2); + float num4 = current - target; + float num5 = target; + if (maxSpeed > 0f) + { + float num6 = maxSpeed * smoothTime; + num4 = Mathf.Clamp(num4, -num6, num6); + } + target = current - num4; + float num7 = (currentVelocity + num * num4) * deltaTime; + currentVelocity = (currentVelocity - num * num7) * num3; + float num8 = target + (num4 + num7) * num3; + if (num5 - current > 0f == num8 > num5) + { + num8 = num5; + currentVelocity = (num8 - num5) / deltaTime; + } + return num8; + } + + /** + * Moves one value towards another (eases in and out to destination with no overshoot) + * + * @method LeanSmooth.damp (Vector3) + * @param {float} current:Vector3 the current value + * @param {float} target:Vector3 the value we are trying to reach + * @param {float} currentVelocity:Vector3 the current velocity of the value + * @param {float} smoothTime:float roughly the time it takes to reach the destination + * @param {float} maxSpeed:float the top speed you want the value to move at (defaults to unlimited -1f) + * @param {float} deltaTime:float the difference in time since the method was called (defaults to Time.deltaTime) + * @example + * transform.position = LeanSmooth.damp(transform.position, destTrans.position, ref followVelocity, 1.1f);\n + * Debug.Log("current:"+transform.position); + */ + public static Vector3 damp(Vector3 current, Vector3 target, ref Vector3 currentVelocity, float smoothTime, float maxSpeed = -1f, float deltaTime = -1f) + { + float x = damp(current.x, target.x, ref currentVelocity.x, smoothTime, maxSpeed, deltaTime); + float y = damp(current.y, target.y, ref currentVelocity.y, smoothTime, maxSpeed, deltaTime); + float z = damp(current.z, target.z, ref currentVelocity.z, smoothTime, maxSpeed, deltaTime); + + return new Vector3(x, y, z); + } + + /** + * Moves one color value towards another color (eases in and out to destination with no overshoot) + * + * @method LeanSmooth.damp (Color) + * @param {float} current:Color the current value + * @param {float} target:Color the value we are trying to reach + * @param {float} currentVelocity:Color the current velocity of the value + * @param {float} smoothTime:float roughly the time it takes to reach the destination + * @param {float} maxSpeed:float the top speed you want the value to move at (defaults to unlimited -1f) + * @param {float} deltaTime:float the difference in time since the method was called (defaults to Time.deltaTime) + * @example + * fromColor = LeanSmooth.damp(fromColor, transform.GetComponent().material.color, ref velocityColor, 1.1f);\n + * Debug.Log("current:"+fromColor); + */ + public static Color damp(Color current, Color target, ref Color currentVelocity, float smoothTime, float maxSpeed = -1f, float deltaTime = -1f) + { + float r = damp(current.r, target.r, ref currentVelocity.r, smoothTime, maxSpeed, deltaTime); + float g = damp(current.g, target.g, ref currentVelocity.g, smoothTime, maxSpeed, deltaTime); + float b = damp(current.b, target.b, ref currentVelocity.b, smoothTime, maxSpeed, deltaTime); + float a = damp(current.a, target.a, ref currentVelocity.a, smoothTime, maxSpeed, deltaTime); + + return new Color(r, g, b, a); + } + + /** + * Moves one value towards another (eases in and out to destination with possible overshoot bounciness) + * + * @method LeanSmooth.spring (float) + * @param {float} current:float the current value + * @param {float} target:float the value we are trying to reach + * @param {float} currentVelocity:float the current velocity of the value + * @param {float} smoothTime:float roughly the time it takes to reach the destination + * @param {float} maxSpeed:float the top speed you want the value to move at (defaults to unlimited -1f) + * @param {float} deltaTime:float the difference in time since the method was called (defaults to Time.deltaTime) + * @param {float} [friction]:float rate at which the spring is slowed down once it reaches it's destination + * @param {float} [accelRate]:float the rate it accelerates from it's initial position + * @example + * followVar = LeanSmooth.spring(followVar, destinationVar, ref followVelocity, 1.1f);\n + * Debug.Log("current:"+followVar); + */ + public static float spring(float current, float target, ref float currentVelocity, float smoothTime, float maxSpeed = -1f, float deltaTime = -1f, float friction = 2f, float accelRate = 0.5f) + { + if (deltaTime < 0f) + deltaTime = Time.deltaTime; + + float diff = target - current; + + currentVelocity += deltaTime / smoothTime * accelRate * diff; + + currentVelocity *= (1f - deltaTime * friction); + + if (maxSpeed > 0f && maxSpeed < Mathf.Abs(currentVelocity)) + currentVelocity = maxSpeed * Mathf.Sign(currentVelocity); + + float returned = current + currentVelocity; + + return returned; + } + + /** + * Moves one value towards another (eases in and out to destination with possible overshoot bounciness) + * + * @method LeanSmooth.spring (Vector3) + * @param {Vector3} current:float the current value + * @param {Vector3} target:float the value we are trying to reach + * @param {Vector3} currentVelocity:float the current velocity of the value + * @param {float} smoothTime:float roughly the time it takes to reach the destination + * @param {float} maxSpeed:float the top speed you want the value to move at (defaults to unlimited -1f) + * @param {float} deltaTime:float the difference in time since the method was called (defaults to Time.deltaTime) + * @param {float} [friction]:float rate at which the spring is slowed down once it reaches it's destination + * @param {float} [accelRate]:float the rate it accelerates from it's initial position + * @example + * transform.position = LeanSmooth.spring(transform.position, destTrans.position, ref followVelocity, 1.1f);\n + * Debug.Log("current:"+transform.position); + */ + public static Vector3 spring(Vector3 current, Vector3 target, ref Vector3 currentVelocity, float smoothTime, float maxSpeed = -1f, float deltaTime = -1f, float friction = 2f, float accelRate = 0.5f) + { + float x = spring(current.x, target.x, ref currentVelocity.x, smoothTime, maxSpeed, deltaTime, friction, accelRate); + float y = spring(current.y, target.y, ref currentVelocity.y, smoothTime, maxSpeed, deltaTime, friction, accelRate); + float z = spring(current.z, target.z, ref currentVelocity.z, smoothTime, maxSpeed, deltaTime, friction, accelRate); + + return new Vector3(x, y, z); + } + + /** + * Moves one color towards another (eases in and out to destination with possible overshoot bounciness) + * + * @method LeanSmooth.spring (Color) + * @param {Color} current:float the current value + * @param {Color} target:float the value we are trying to reach + * @param {Color} currentVelocity:float the current velocity of the value + * @param {float} smoothTime:float roughly the time it takes to reach the destination + * @param {float} maxSpeed:float the top speed you want the value to move at (defaults to unlimited -1f) + * @param {float} deltaTime:float the difference in time since the method was called (defaults to Time.deltaTime) + * @param {float} [friction]:float rate at which the spring is slowed down once it reaches it's destination + * @param {float} [accelRate]:float the rate it accelerates from it's initial position + * @example + * fromColor = LeanSmooth.spring(fromColor, transform.GetComponent().material.color, ref velocityColor, 1.1f);\n + * Debug.Log("current:"+fromColor); + */ + public static Color spring(Color current, Color target, ref Color currentVelocity, float smoothTime, float maxSpeed = -1f, float deltaTime = -1f, float friction = 2f, float accelRate = 0.5f) + { + float r = spring(current.r, target.r, ref currentVelocity.r, smoothTime, maxSpeed, deltaTime, friction, accelRate); + float g = spring(current.g, target.g, ref currentVelocity.g, smoothTime, maxSpeed, deltaTime, friction, accelRate); + float b = spring(current.b, target.b, ref currentVelocity.b, smoothTime, maxSpeed, deltaTime, friction, accelRate); + float a = spring(current.a, target.a, ref currentVelocity.a, smoothTime, maxSpeed, deltaTime, friction, accelRate); + + return new Color(r, g, b, a); + } + + /** + * Moves one value towards another (at a constant speed) + * + * @method LeanSmooth.linear (float) + * @param {float} current:float the current value + * @param {float} target:float the value we are trying to reach + * @param {float} moveSpeed:float the speed at which to move towards the target + * @param {float} deltaTime:float the difference in time since the method was called (defaults to Time.deltaTime) + * @example + * followVar = LeanSmooth.linear(followVar, destinationVar, 50f);\n + * Debug.Log("current:"+followVar); + */ + public static float linear(float current, float target, float moveSpeed, float deltaTime = -1f) + { + if (deltaTime < 0f) + deltaTime = Time.deltaTime; + + bool targetGreater = (target > current); + + float currentVelocity = deltaTime * moveSpeed * (targetGreater ? 1f : -1f); + + float returned = current + currentVelocity; + + float returnPassed = returned - target; + if ((targetGreater && returnPassed > 0) || !targetGreater && returnPassed < 0) + { // Has passed point, return target + return target; + } + + return returned; + } + + /** + * Moves one value towards another (at a constant speed) + * + * @method LeanSmooth.linear (Vector3) + * @param {Vector3} current:float the current value + * @param {Vector3} target:float the value we are trying to reach + * @param {float} moveSpeed:float the speed at which to move towards the target + * @param {float} deltaTime:float the difference in time since the method was called (defaults to Time.deltaTime) + * @example + * transform.position = LeanSmooth.linear(transform.position, followTrans.position, 50f);\n + * Debug.Log("current:"+transform.position); + */ + public static Vector3 linear(Vector3 current, Vector3 target, float moveSpeed, float deltaTime = -1f) + { + float x = linear(current.x, target.x, moveSpeed, deltaTime); + float y = linear(current.y, target.y, moveSpeed, deltaTime); + float z = linear(current.z, target.z, moveSpeed, deltaTime); + + return new Vector3(x, y, z); + } + + /** + * Moves one color towards another (at a constant speed) + * + * @method LeanSmooth.linear (Color) + * @param {Color} current:float the current value + * @param {Color} target:float the value we are trying to reach + * @param {float} moveSpeed:float the speed at which to move towards the target + * @param {float} deltaTime:float the difference in time since the method was called (defaults to Time.deltaTime) + * @example + * fromColor = LeanSmooth.linear(fromColor, transform.GetComponent().material.color, 50f);\n + * Debug.Log("current:"+fromColor); + */ + public static Color linear(Color current, Color target, float moveSpeed) + { + float r = linear(current.r, target.r, moveSpeed); + float g = linear(current.g, target.g, moveSpeed); + float b = linear(current.b, target.b, moveSpeed); + float a = linear(current.a, target.a, moveSpeed); + + return new Color(r, g, b, a); + } + + /** + * Moves one value towards another (with an ease that bounces back some when it reaches it's destination) + * + * @method LeanSmooth.bounceOut (float) + * @param {float} current:float the current value + * @param {float} target:float the value we are trying to reach + * @param {float} currentVelocity:float the current velocity of the value + * @param {float} smoothTime:float roughly the time it takes to reach the destination + * @param {float} maxSpeed:float the top speed you want the value to move at (defaults to unlimited -1f) + * @param {float} deltaTime:float the difference in time since the method was called (defaults to Time.deltaTime) + * @param {float} [friction]:float rate at which the spring is slowed down once it reaches it's destination + * @param {float} [accelRate]:float the rate it accelerates from it's initial position + * @param {float} [hitDamping]:float the rate at which to dampen the bounciness of when it reaches it's destination + * @example + * followVar = LeanSmooth.bounceOut(followVar, destinationVar, ref followVelocity, 1.1f);\n + * Debug.Log("current:"+followVar); + */ + public static float bounceOut(float current, float target, ref float currentVelocity, float smoothTime, float maxSpeed = -1f, float deltaTime = -1f, float friction = 2f, float accelRate = 0.5f, float hitDamping = 0.9f) + { + if (deltaTime < 0f) + deltaTime = Time.deltaTime; + + float diff = target - current; + + currentVelocity += deltaTime / smoothTime * accelRate * diff; + + currentVelocity *= (1f - deltaTime * friction); + + if (maxSpeed > 0f && maxSpeed < Mathf.Abs(currentVelocity)) + currentVelocity = maxSpeed * Mathf.Sign(currentVelocity); + + float returned = current + currentVelocity; + + bool targetGreater = (target > current); + float returnPassed = returned - target; + if ((targetGreater && returnPassed > 0) || !targetGreater && returnPassed < 0) + { // Start a bounce + currentVelocity = -currentVelocity * hitDamping; + returned = current + currentVelocity; + } + + return returned; + } + + /** + * Moves one value towards another (with an ease that bounces back some when it reaches it's destination) + * + * @method LeanSmooth.bounceOut (Vector3) + * @param {Vector3} current:float the current value + * @param {Vector3} target:float the value we are trying to reach + * @param {Vector3} currentVelocity:float the current velocity of the value + * @param {float} smoothTime:float roughly the time it takes to reach the destination + * @param {float} maxSpeed:float the top speed you want the value to move at (defaults to unlimited -1f) + * @param {float} deltaTime:float the difference in time since the method was called (defaults to Time.deltaTime) + * @param {float} [friction]:float rate at which the spring is slowed down once it reaches it's destination + * @param {float} [accelRate]:float the rate it accelerates from it's initial position + * @param {float} [hitDamping]:float the rate at which to dampen the bounciness of when it reaches it's destination + * @example + * transform.position = LeanSmooth.bounceOut(transform.position, followTrans.position, ref followVelocity, 1.1f);\n + * Debug.Log("current:"+transform.position); + */ + public static Vector3 bounceOut(Vector3 current, Vector3 target, ref Vector3 currentVelocity, float smoothTime, float maxSpeed = -1f, float deltaTime = -1f, float friction = 2f, float accelRate = 0.5f, float hitDamping = 0.9f) + { + float x = bounceOut(current.x, target.x, ref currentVelocity.x, smoothTime, maxSpeed, deltaTime, friction, accelRate, hitDamping); + float y = bounceOut(current.y, target.y, ref currentVelocity.y, smoothTime, maxSpeed, deltaTime, friction, accelRate, hitDamping); + float z = bounceOut(current.z, target.z, ref currentVelocity.z, smoothTime, maxSpeed, deltaTime, friction, accelRate, hitDamping); + + return new Vector3(x, y, z); + } + + /** + * Moves one color towards another (with an ease that bounces back some when it reaches it's destination) + * + * @method LeanSmooth.bounceOut (Color) + * @param {Color} current:float the current value + * @param {Color} target:float the value we are trying to reach + * @param {Color} currentVelocity:float the current velocity of the value + * @param {float} smoothTime:float roughly the time it takes to reach the destination + * @param {float} maxSpeed:float the top speed you want the value to move at (defaults to unlimited -1f) + * @param {float} deltaTime:float the difference in time since the method was called (defaults to Time.deltaTime) + * @param {float} [friction]:float rate at which the spring is slowed down once it reaches it's destination + * @param {float} [accelRate]:float the rate it accelerates from it's initial position + * @param {float} [hitDamping]:float the rate at which to dampen the bounciness of when it reaches it's destination + * @example + * fromColor = LeanSmooth.bounceOut(fromColor, transform.GetComponent().material.color, ref followVelocity, 1.1f);\n + * Debug.Log("current:" + fromColor); + */ + public static Color bounceOut(Color current, Color target, ref Color currentVelocity, float smoothTime, float maxSpeed = -1f, float deltaTime = -1f, float friction = 2f, float accelRate = 0.5f, float hitDamping = 0.9f) + { + float r = bounceOut(current.r, target.r, ref currentVelocity.r, smoothTime, maxSpeed, deltaTime, friction, accelRate, hitDamping); + float g = bounceOut(current.g, target.g, ref currentVelocity.g, smoothTime, maxSpeed, deltaTime, friction, accelRate, hitDamping); + float b = bounceOut(current.b, target.b, ref currentVelocity.b, smoothTime, maxSpeed, deltaTime, friction, accelRate, hitDamping); + float a = bounceOut(current.a, target.a, ref currentVelocity.a, smoothTime, maxSpeed, deltaTime, friction, accelRate, hitDamping); + + return new Color(r, g, b, a); + } +} diff --git a/KFAttached/LeanTween/Framework/LeanSmooth.cs.meta b/KFAttached/LeanTween/Framework/LeanSmooth.cs.meta new file mode 100644 index 0000000..4494902 --- /dev/null +++ b/KFAttached/LeanTween/Framework/LeanSmooth.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1ca0f285af8dd4270bd759978223faad +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/LeanTween/Framework/LeanTest.cs b/KFAttached/LeanTween/Framework/LeanTest.cs new file mode 100644 index 0000000..2477135 --- /dev/null +++ b/KFAttached/LeanTween/Framework/LeanTest.cs @@ -0,0 +1,146 @@ +using System.Collections; +using UnityEngine; + +public class LeanTester : MonoBehaviour +{ + public float timeout = 15f; + +#if !UNITY_3_5 && !UNITY_4_0 && !UNITY_4_0_1 && !UNITY_4_1 && !UNITY_4_2 && !UNITY_4_3 && !UNITY_4_5 + public void Start() + { + StartCoroutine(timeoutCheck()); + } + + IEnumerator timeoutCheck() + { + float pauseEndTime = Time.realtimeSinceStartup + timeout; + while (Time.realtimeSinceStartup < pauseEndTime) + { + yield return 0; + } + if (LeanTest.testsFinished == false) + { + Debug.Log(LeanTest.formatB("Tests timed out!")); + LeanTest.overview(); + } + } +#endif +} + +public class LeanTest : object +{ + public static int expected = 0; + private static int tests = 0; + private static int passes = 0; + + public static float timeout = 15f; + public static bool timeoutStarted = false; + public static bool testsFinished = false; + + public static void debug(string name, bool didPass, string failExplaination = null) + { + expect(didPass, name, failExplaination); + } + + public static void expect(bool didPass, string definition, string failExplaination = null) + { + float len = printOutLength(definition); + int paddingLen = 40 - (int)(len * 1.05f); +#if UNITY_FLASH + string padding = padRight(paddingLen); +#else + string padding = "".PadRight(paddingLen, "_"[0]); +#endif + string logName = formatB(definition) + " " + padding + " [ " + (didPass ? formatC("pass", "green") : formatC("fail", "red")) + " ]"; + if (didPass == false && failExplaination != null) + logName += " - " + failExplaination; + Debug.Log(logName); + if (didPass) + passes++; + tests++; + + // Debug.Log("tests:"+tests+" expected:"+expected); + if (tests == expected && testsFinished == false) + { + overview(); + } + else if (tests > expected) + { + Debug.Log(formatB("Too many tests for a final report!") + " set LeanTest.expected = " + tests); + } + + if (timeoutStarted == false) + { + timeoutStarted = true; + GameObject tester = new GameObject(); + tester.name = "~LeanTest"; + LeanTester test = tester.AddComponent(typeof(LeanTester)) as LeanTester; + test.timeout = timeout; +#if !UNITY_EDITOR + tester.hideFlags = HideFlags.HideAndDontSave; +#endif + } + } + + public static string padRight(int len) + { + string str = ""; + for (int i = 0; i < len; i++) + { + str += "_"; + } + return str; + } + + public static float printOutLength(string str) + { + float len = 0.0f; + for (int i = 0; i < str.Length; i++) + { + if (str[i] == "I"[0]) + { + len += 0.5f; + } + else if (str[i] == "J"[0]) + { + len += 0.85f; + } + else + { + len += 1.0f; + } + } + return len; + } + + public static string formatBC(string str, string color) + { + return formatC(formatB(str), color); + } + + public static string formatB(string str) + { +#if UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_1 || UNITY_4_2 + return str; +#else + return "" + str + ""; +#endif + } + + public static string formatC(string str, string color) + { +#if UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_1 || UNITY_4_2 + return str; +#else + return "" + str + ""; +#endif + } + + public static void overview() + { + testsFinished = true; + int failedCnt = (expected - passes); + string failedStr = failedCnt > 0 ? formatBC("" + failedCnt, "red") : "" + failedCnt; + Debug.Log(formatB("Final Report:") + " _____________________ PASSED: " + formatBC("" + passes, "green") + " FAILED: " + failedStr + " "); + } +} diff --git a/KFAttached/LeanTween/Framework/LeanTest.cs.meta b/KFAttached/LeanTween/Framework/LeanTest.cs.meta new file mode 100644 index 0000000..8508b49 --- /dev/null +++ b/KFAttached/LeanTween/Framework/LeanTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 82464f26ca2ba284a8f92f51248c574a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/LeanTween/Framework/LeanTween.cs b/KFAttached/LeanTween/Framework/LeanTween.cs new file mode 100644 index 0000000..7fabca2 --- /dev/null +++ b/KFAttached/LeanTween/Framework/LeanTween.cs @@ -0,0 +1,4581 @@ +//namespace DentedPixel{ + +// LeanTween version 2.50 - http://dentedpixel.com/developer-diary/ +// +// The MIT License (MIT) +// +// Copyright (c) 2017 Russell Savage - Dented Pixel +// +// 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. + + + +/* +TERMS OF USE - EASING EQUATIONS# +Open source under the BSD License. +Copyright (c)2001 Robert Penner +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of the author nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** +* Pass this to the "ease" parameter, to get a different easing behavior

+* Example:
LeanTween.rotateX(gameObject, 270.0f, 1.5f).setEase(LeanTweenType.easeInBack); +* +* @class LeanTweenType +*/ + +/** +* @property {integer} linear +*/ +/** +* @property {integer} easeOutQuad +*/ +/** +* @property {integer} easeInQuad +*/ +/** +* @property {integer} easeInOutQuad +*/ +/** +* @property {integer} easeInCubic +*/ +/** +* @property {integer} easeOutCubic +*/ +/** +* @property {integer} easeInOutCubic +*/ +/** +* @property {integer} easeInQuart +*/ +/** +* @property {integer} easeOutQuart +*/ +/** +* @property {integer} easeInOutQuart +*/ +/** +* @property {integer} easeInQuint +*/ +/** +* @property {integer} easeOutQuint +*/ +/** +* @property {integer} easeInOutQuint +*/ +/** +* @property {integer} easeInSine +*/ +/** +* @property {integer} easeOutSine +*/ +/** +* @property {integer} easeInOutSine +*/ +/** +* @property {integer} easeInExpo +*/ +/** +* @property {integer} easeOutExpo +*/ +/** +* @property {integer} easeInOutExpo +*/ +/** +* @property {integer} easeInCirc +*/ +/** +* @property {integer} easeOutCirc +*/ +/** +* @property {integer} easeInOutCirc +*/ +/** +* @property {integer} easeInBounce +*/ +/** +* @property {integer} easeOutBounce +*/ +/** +* @property {integer} easeInOutBounce +*/ +/** +* @property {integer} easeInBack +*/ +/** +* @property {integer} easeOutBack +*/ +/** +* @property {integer} easeInOutBack +*/ +/** +* @property {integer} easeInElastic +*/ +/** +* @property {integer} easeOutElastic +*/ +/** +* @property {integer} easeInOutElastic +*/ +/** +* @property {integer} punch +*/ +using System; +using System.Collections.Generic; +using UnityEngine; + +public enum TweenAction +{ + MOVE_X, + MOVE_Y, + MOVE_Z, + MOVE_LOCAL_X, + MOVE_LOCAL_Y, + MOVE_LOCAL_Z, + MOVE_CURVED, + MOVE_CURVED_LOCAL, + MOVE_SPLINE, + MOVE_SPLINE_LOCAL, + SCALE_X, + SCALE_Y, + SCALE_Z, + ROTATE_X, + ROTATE_Y, + ROTATE_Z, + ROTATE_AROUND, + ROTATE_AROUND_LOCAL, + CANVAS_ROTATEAROUND, + CANVAS_ROTATEAROUND_LOCAL, + CANVAS_PLAYSPRITE, + ALPHA, + TEXT_ALPHA, + CANVAS_ALPHA, + CANVASGROUP_ALPHA, + ALPHA_VERTEX, + COLOR, + CALLBACK_COLOR, + TEXT_COLOR, + CANVAS_COLOR, + CANVAS_MOVE_X, + CANVAS_MOVE_Y, + CANVAS_MOVE_Z, + CALLBACK, + MOVE, + MOVE_LOCAL, + MOVE_TO_TRANSFORM, + ROTATE, + ROTATE_LOCAL, + SCALE, + VALUE3, + GUI_MOVE, + GUI_MOVE_MARGIN, + GUI_SCALE, + GUI_ALPHA, + GUI_ROTATE, + DELAYED_SOUND, + CANVAS_MOVE, + CANVAS_SCALE, + CANVAS_SIZEDELTA, + FOLLOW, + +} + +public enum LeanTweenType +{ + notUsed, linear, easeOutQuad, easeInQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic, easeInQuart, easeOutQuart, easeInOutQuart, + easeInQuint, easeOutQuint, easeInOutQuint, easeInSine, easeOutSine, easeInOutSine, easeInExpo, easeOutExpo, easeInOutExpo, easeInCirc, easeOutCirc, easeInOutCirc, + easeInBounce, easeOutBounce, easeInOutBounce, easeInBack, easeOutBack, easeInOutBack, easeInElastic, easeOutElastic, easeInOutElastic, easeSpring, easeShake, punch, once, clamp, pingPong, animationCurve +} + +public enum LeanProp +{ + position, + localPosition, + x, + y, + z, + localX, + localY, + localZ, + scale, + color +} + +/** +* LeanTween is an efficient tweening engine for Unity3d

+* Index of All Methods | Optional Paramaters that can be passed

+* Optional Parameters are passed at the end of every method
+*
+* Example:
+* LeanTween.moveX( gameObject, 1f, 1f).setEase( LeanTweenType.easeInQuad ).setDelay(1f);
+*
+* You can pass the optional parameters in any order, and chain on as many as you wish!

+* You can also modify this tween later, just save the unique id of the tween.
+*

Example:

+* int id = LeanTween.moveX(gameObject, 1f, 1f).id;
+* LTDescr d = LeanTween.descr( id );

+* if(d!=null){ // if the tween has already finished it will return null
+*    // change some parameters
+*    d.setOnComplete( onCompleteFunc ).setEase( LeanTweenType.easeInOutBack );
+* } +* +* @class LeanTween +*/ + +public class LeanTween : MonoBehaviour +{ + + public static bool throwErrors = true; + public static float tau = Mathf.PI * 2.0f; + public static float PI_DIV2 = Mathf.PI / 2.0f; + + private static LTSeq[] sequences; + + private static LTDescr[] tweens; + private static int[] tweensFinished; + private static int[] tweensFinishedIds; + private static LTDescr tween; + private static int tweenMaxSearch = -1; + private static int maxTweens = 400; + private static int maxSequences = 400; + private static int frameRendered = -1; + private static GameObject _tweenEmpty; + public static float dtEstimated = -1f; + public static float dtManual; +#if UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 + private static float previousRealTime; +#endif + public static float dtActual; + private static uint global_counter = 0; + private static int i; + private static int j; + private static int finishedCnt; + public static AnimationCurve punch = new AnimationCurve(new Keyframe(0.0f, 0.0f), new Keyframe(0.112586f, 0.9976035f), new Keyframe(0.3120486f, -0.1720615f), new Keyframe(0.4316337f, 0.07030682f), new Keyframe(0.5524869f, -0.03141804f), new Keyframe(0.6549395f, 0.003909959f), new Keyframe(0.770987f, -0.009817753f), new Keyframe(0.8838775f, 0.001939224f), new Keyframe(1.0f, 0.0f)); + public static AnimationCurve shake = new AnimationCurve(new Keyframe(0f, 0f), new Keyframe(0.25f, 1f), new Keyframe(0.75f, -1f), new Keyframe(1f, 0f)); + + public static void init() + { + init(maxTweens); + } + + public static int maxSearch + { + get + { + return tweenMaxSearch; + } + } + + public static int maxSimulataneousTweens + { + get + { + return maxTweens; + } + } + + /** + * Find out how many tweens you have animating at a given time + * + * @method LeanTween.tweensRunning + * @example + * Debug.Log("I have "+LeanTween.tweensRunning+" animating!"); + */ + public static int tweensRunning + { + get + { + int count = 0; + for (int i = 0; i <= tweenMaxSearch; i++) + { + if (tweens[i].toggle) + { + count++; + } + } + return count; + } + } + + /** + * This line is optional. Here you can specify the maximum number of tweens you will use (the default is 400). This must be called before any use of LeanTween is made for it to be effective. This line is optional. Here you can specify the maximum number of tweens you will use (the default is 400). This must be called before any use of LeanTween is made for it to be effective. + * + * @method LeanTween.init + * @param {integer} maxSimultaneousTweens:int The maximum number of tweens you will use, make sure you don't go over this limit, otherwise the code will throw an error + * @example + * LeanTween.init( 800 ); + */ + public static void init(int maxSimultaneousTweens) + { + init(maxSimultaneousTweens, maxSequences); + } + + public static void init(int maxSimultaneousTweens, int maxSimultaneousSequences) + { + if (tweens == null) + { + maxTweens = maxSimultaneousTweens; + tweens = new LTDescr[maxTweens]; + tweensFinished = new int[maxTweens]; + tweensFinishedIds = new int[maxTweens]; + _tweenEmpty = new GameObject(); + _tweenEmpty.name = "~LeanTween"; + _tweenEmpty.AddComponent(typeof(LeanTween)); + _tweenEmpty.isStatic = true; +#if !UNITY_EDITOR + _tweenEmpty.hideFlags = HideFlags.HideAndDontSave; +#endif +#if UNITY_EDITOR + if(Application.isPlaying) + DontDestroyOnLoad( _tweenEmpty ); +#else + DontDestroyOnLoad(_tweenEmpty); +#endif + for (int i = 0; i < maxTweens; i++) + { + tweens[i] = new LTDescr(); + } + +#if UNITY_5_4_OR_NEWER + UnityEngine.SceneManagement.SceneManager.sceneLoaded += onLevelWasLoaded54; +#endif + + sequences = new LTSeq[maxSimultaneousSequences]; + + for (int i = 0; i < maxSimultaneousSequences; i++) + { + sequences[i] = new LTSeq(); + } + } + } + + public static void reset() + { + if (tweens != null) + { + for (int i = 0; i <= tweenMaxSearch; i++) + { + if (tweens[i] != null) + tweens[i].toggle = false; + } + } + tweens = null; + Destroy(_tweenEmpty); + } + + public void Update() + { + LeanTween.update(); + } + +#if UNITY_5_4_OR_NEWER + private static void onLevelWasLoaded54( UnityEngine.SceneManagement.Scene scene, UnityEngine.SceneManagement.LoadSceneMode mode ){ internalOnLevelWasLoaded( scene.buildIndex ); } +#else + public void OnLevelWasLoaded(int lvl) { internalOnLevelWasLoaded(lvl); } +#endif + + private static void internalOnLevelWasLoaded(int lvl) + { + // Debug.Log("reseting gui"); + LTGUI.reset(); + } + + private static int maxTweenReached; + + public static void update() + { + if (frameRendered != Time.frameCount) + { // make sure update is only called once per frame + init(); + +#if UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 + dtEstimated = Time.realtimeSinceStartup - previousRealTime; + if(dtEstimated>0.2f) // a catch put in, when at the start sometimes this number can grow unrealistically large + dtEstimated = 0.2f; + previousRealTime = Time.realtimeSinceStartup; +#else + + dtEstimated = dtEstimated < 0f ? 0f : dtEstimated = Time.unscaledDeltaTime; + + // Debug.Log("Time.unscaledDeltaTime:"+Time.unscaledDeltaTime); +#endif + + dtActual = Time.deltaTime; + maxTweenReached = 0; + finishedCnt = 0; + // if(tweenMaxSearch>1500) + // Debug.Log("tweenMaxSearch:"+tweenMaxSearch +" maxTweens:"+maxTweens); + for (int i = 0; i <= tweenMaxSearch && i < maxTweens; i++) + { + tween = tweens[i]; + // if(i==0 && tweens[i].toggle) + // Debug.Log("tweens["+i+"]"+tweens[i]); + if (tween.toggle) + { + maxTweenReached = i; + + if (tween.updateInternal()) + { // returns true if the tween is finished with it's loop + tweensFinished[finishedCnt] = i; + tweensFinishedIds[finishedCnt] = tweens[i].id; + finishedCnt++; + } + } + } + + // Debug.Log("maxTweenReached:"+maxTweenReached); + tweenMaxSearch = maxTweenReached; + frameRendered = Time.frameCount; + + for (int i = 0; i < finishedCnt; i++) + { + j = tweensFinished[i]; + tween = tweens[j]; + + if (tween.id == tweensFinishedIds[i]) + { + // Debug.Log("removing tween:"+tween); + removeTween(j); + if (tween.hasExtraOnCompletes && tween.trans != null) + tween.callOnCompletes(); + } + } + + } + } + + + + public static void removeTween(int i, int uniqueId) + { // Only removes the tween if the unique id matches Move a GameObject to a certain location + if (tweens[i].uniqueId == uniqueId) + { + removeTween(i); + } + } + + // This method is only used internally! Do not call this from your scripts. To cancel a tween use LeanTween.cancel + public static void removeTween(int i) + { + if (tweens[i].toggle) + { + tweens[i].toggle = false; + tweens[i].counter = uint.MaxValue; + //logError("Removing tween["+i+"]:"+tweens[i]); + if (tweens[i].destroyOnComplete) + { + // Debug.Log("destroying tween.type:"+tween.type+" ltRect"+(tweens[i]._optional.ltRect==null)); + if (tweens[i]._optional.ltRect != null) + { + // Debug.Log("destroy i:"+i+" id:"+tweens[i].ltRect.id); + LTGUI.destroy(tweens[i]._optional.ltRect.id); + } + else + { // check if equal to tweenEmpty + if (tweens[i].trans != null && tweens[i].trans.gameObject != _tweenEmpty) + { + Destroy(tweens[i].trans.gameObject); + } + } + } + //tweens[i].optional = null; + startSearch = i; + //Debug.Log("start search reset:"+startSearch + " i:"+i+" tweenMaxSearch:"+tweenMaxSearch); + if (i + 1 >= tweenMaxSearch) + { + //Debug.Log("reset to zero"); + startSearch = 0; + //tweenMaxSearch--; + } + } + } + + public static Vector3[] add(Vector3[] a, Vector3 b) + { + Vector3[] c = new Vector3[a.Length]; + for (i = 0; i < a.Length; i++) + { + c[i] = a[i] + b; + } + + return c; + } + + public static float closestRot(float from, float to) + { + float minusWhole = 0 - (360 - to); + float plusWhole = 360 + to; + float toDiffAbs = Mathf.Abs(to - from); + float minusDiff = Mathf.Abs(minusWhole - from); + float plusDiff = Mathf.Abs(plusWhole - from); + if (toDiffAbs < minusDiff && toDiffAbs < plusDiff) + { + return to; + } + else + { + if (minusDiff < plusDiff) + { + return minusWhole; + } + else + { + return plusWhole; + } + } + } + + /** + * Cancels all tweens + * + * @method LeanTween.cancelAll + * @param {bool} callComplete:bool (optional) if true, then the all onCompletes will run before canceling + * @example LeanTween.cancelAll(true);
+ */ + public static void cancelAll() + { + cancelAll(false); + } + public static void cancelAll(bool callComplete) + { + init(); + for (int i = 0; i <= tweenMaxSearch; i++) + { + if (tweens[i].trans != null) + { + if (callComplete && tweens[i].optional.onComplete != null) + tweens[i].optional.onComplete(); + removeTween(i); + } + } + } + + /** + * Cancel all tweens that are currently targeting the gameObject + * + * @method LeanTween.cancel + * @param {GameObject} gameObject:GameObject gameObject whose tweens you wish to cancel + * @param {bool} callOnComplete:bool (optional) whether to call the onComplete method before canceling + * @example LeanTween.move( gameObject, new Vector3(0f,1f,2f), 1f);
+ * LeanTween.cancel( gameObject ); + */ + public static void cancel(GameObject gameObject) + { + cancel(gameObject, false); + } + public static void cancel(GameObject gameObject, bool callOnComplete) + { + init(); + Transform trans = gameObject.transform; + for (int i = 0; i <= tweenMaxSearch; i++) + { + LTDescr tween = tweens[i]; + if (tween != null && tween.toggle && tween.trans == trans) + { + if (callOnComplete && tween.optional.onComplete != null) + tween.optional.onComplete(); + removeTween(i); + } + } + } + + public static void cancel(RectTransform rect) + { + cancel(rect.gameObject, false); + } + + // public static void cancel( GameObject gameObject, int uniqueId ){ + // if(uniqueId>=0){ + // init(); + // int backId = uniqueId & 0xFFFF; + // int backCounter = uniqueId >> 16; + // // Debug.Log("uniqueId:"+uniqueId+ " id:"+backId +" counter:"+backCounter + " setCounter:"+ tweens[backId].counter + " tweens[id].type:"+tweens[backId].type); + // if(tweens[backId].trans==null || (tweens[backId].trans.gameObject == gameObject && tweens[backId].counter==backCounter)) + // removeTween((int)backId); + // } + // } + + public static void cancel(GameObject gameObject, int uniqueId, bool callOnComplete = false) + { + if (uniqueId >= 0) + { + init(); + int backId = uniqueId & 0xFFFF; + int backCounter = uniqueId >> 16; + // Debug.Log("uniqueId:"+uniqueId+ " id:"+backId +" counter:"+backCounter + " setCounter:"+ tw eens[backId].counter + " tweens[id].type:"+tweens[backId].type); + if (tweens[backId].trans == null || (tweens[backId].trans.gameObject == gameObject && tweens[backId].counter == backCounter)) + { + if (callOnComplete && tweens[backId].optional.onComplete != null) + tweens[backId].optional.onComplete(); + removeTween((int)backId); + } + } + } + + public static void cancel(LTRect ltRect, int uniqueId) + { + if (uniqueId >= 0) + { + init(); + int backId = uniqueId & 0xFFFF; + int backCounter = uniqueId >> 16; + // Debug.Log("uniqueId:"+uniqueId+ " id:"+backId +" action:"+(TweenAction)backType + " tweens[id].type:"+tweens[backId].type); + if (tweens[backId]._optional.ltRect == ltRect && tweens[backId].counter == backCounter) + removeTween((int)backId); + } + } + + /** + * Cancel a specific tween with the provided id + * + * @method LeanTween.cancel + * @param {int} id:int unique id that represents that tween + * @param {bool} callOnComplete:bool (optional) whether to call the onComplete method before canceling + * @example int id = LeanTween.move( gameObject, new Vector3(0f,1f,2f), 1f).id;
+ * LeanTween.cancel( id ); + */ + public static void cancel(int uniqueId) + { + cancel(uniqueId, false); + } + public static void cancel(int uniqueId, bool callOnComplete) + { + if (uniqueId >= 0) + { + init(); + int backId = uniqueId & 0xFFFF; + int backCounter = uniqueId >> 16; + if (backId > tweens.Length - 1) + { // sequence + int sequenceId = backId - tweens.Length; + LTSeq seq = sequences[sequenceId]; + // Debug.Log("sequenceId:" + sequenceId+" maxSequences:"+maxSequences+" prev:"+seq.previous); + + for (int i = 0; i < maxSequences; i++) + { + if (seq.current.tween != null) + { + int tweenId = seq.current.tween.uniqueId; + int tweenIndex = tweenId & 0xFFFF; + removeTween(tweenIndex); + } + if (seq.current.previous == null) + break; + seq.current = seq.current.previous; + } + } + else + { // tween + // Debug.Log("uniqueId:"+uniqueId+ " id:"+backId +" action:"+(TweenAction)backType + " tweens[id].type:"+tweens[backId].type); + if (tweens[backId].counter == backCounter) + { + if (callOnComplete && tweens[backId].optional.onComplete != null) + tweens[backId].optional.onComplete(); + removeTween((int)backId); + } + } + } + } + + /** + * Retrieve a tweens LTDescr object to modify + * + * @method LeanTween.descr + * @param {int} id:int unique id that represents that tween + * @example int id = LeanTween.move( gameObject, new Vector3(0f,1f,2f), 1f).setOnComplete( oldMethod ).id;

+ *
// later I want decide I want to change onComplete method
+ * LTDescr descr = LeanTween.descr( id );
+ * if(descr!=null) // if the tween has already finished it will come back null
+ *   descr.setOnComplete( newMethod );
+ */ + public static LTDescr descr(int uniqueId) + { + init(); + + int backId = uniqueId & 0xFFFF; + int backCounter = uniqueId >> 16; + + // Debug.Log("backId:" + backId+" backCounter:"+backCounter); + if (tweens[backId] != null && tweens[backId].uniqueId == uniqueId && tweens[backId].counter == backCounter) + { + // Debug.Log("tween count:" + tweens[backId].counter); + return tweens[backId]; + } + for (int i = 0; i <= tweenMaxSearch; i++) + { + if (tweens[i].uniqueId == uniqueId && tweens[i].counter == backCounter) + { + return tweens[i]; + } + } + return null; + } + + public static LTDescr description(int uniqueId) + { + return descr(uniqueId); + } + + /** + * Retrieve a tweens LTDescr object(s) to modify + * + * @method LeanTween.descriptions + * @param {GameObject} id:GameObject object whose tween descriptions you want to retrieve + * @example LeanTween.move( gameObject, new Vector3(0f,1f,2f), 1f).setOnComplete( oldMethod );

+ *
// later I want decide I want to change onComplete method
+ * LTDescr[] descr = LeanTween.descriptions( gameObject );
+ * if(descr.Length>0) // make sure there is a valid description for this target
+ *   descr[0].setOnComplete( newMethod );// in this case we only ever expect there to be one tween on this object
+ */ + public static LTDescr[] descriptions(GameObject gameObject = null) + { + if (gameObject == null) return null; + + List descrs = new List(); + Transform trans = gameObject.transform; + for (int i = 0; i <= tweenMaxSearch; i++) + { + if (tweens[i].toggle && tweens[i].trans == trans) + descrs.Add(tweens[i]); + } + return descrs.ToArray(); + } + + [System.Obsolete("Use 'pause( id )' instead")] + public static void pause(GameObject gameObject, int uniqueId) + { + pause(uniqueId); + } + + /** + * Pause all tweens for a GameObject + * + * @method LeanTween.pause + * @param {int} id:int Id of the tween you want to pause + * @example + * int id = LeanTween.moveX(gameObject, 5, 1.0).id
+ * LeanTween.pause( id );
+ * // Later....
+ * LeanTween.resume( id ); + */ + public static void pause(int uniqueId) + { + int backId = uniqueId & 0xFFFF; + int backCounter = uniqueId >> 16; + if (tweens[backId].counter == backCounter) + { + tweens[backId].pause(); + } + } + + /** + * Pause all tweens for a GameObject + * + * @method LeanTween.pause + * @param {GameObject} gameObject:GameObject GameObject whose tweens you want to pause + */ + public static void pause(GameObject gameObject) + { + Transform trans = gameObject.transform; + for (int i = 0; i <= tweenMaxSearch; i++) + { + if (tweens[i].trans == trans) + { + tweens[i].pause(); + } + } + } + + /** + * Pause all active tweens + * + * @method LeanTween.pauseAll + */ + public static void pauseAll() + { + init(); + for (int i = 0; i <= tweenMaxSearch; i++) + { + tweens[i].pause(); + } + } + + /** + * Resume all active tweens + * + * @method LeanTween.resumeAll + */ + public static void resumeAll() + { + init(); + for (int i = 0; i <= tweenMaxSearch; i++) + { + tweens[i].resume(); + } + } + + [System.Obsolete("Use 'resume( id )' instead")] + public static void resume(GameObject gameObject, int uniqueId) + { + resume(uniqueId); + } + + /** + * Resume a specific tween + * + * @method LeanTween.resume + * @param {int} id:int Id of the tween you want to resume + * @example + * int id = LeanTween.moveX(gameObject, 5, 1.0).id
+ * LeanTween.pause( id );
+ * // Later....
+ * LeanTween.resume( id ); + */ + public static void resume(int uniqueId) + { + int backId = uniqueId & 0xFFFF; + int backCounter = uniqueId >> 16; + if (tweens[backId].counter == backCounter) + { + tweens[backId].resume(); + } + } + + /** + * Resume all the tweens on a GameObject + * + * @method LeanTween.resume + * @param {GameObject} gameObject:GameObject GameObject whose tweens you want to resume + */ + public static void resume(GameObject gameObject) + { + Transform trans = gameObject.transform; + for (int i = 0; i <= tweenMaxSearch; i++) + { + if (tweens[i].trans == trans) + tweens[i].resume(); + } + } + + /** + * Test whether or not a tween is paused on a GameObject + * + * @method LeanTween.isPaused + * @param {GameObject} gameObject:GameObject GameObject that you want to test if it is paused + */ + public static bool isPaused(GameObject gameObject = null) + { + if (gameObject == null) + { + for (int i = 0; i <= tweenMaxSearch; i++) + { + if (Mathf.Equals(tweens[i].direction, 0f)) + return true; + } + return false; + } + Transform trans = gameObject.transform; + for (int i = 0; i <= tweenMaxSearch; i++) + { + if (Mathf.Equals(tweens[i].direction, 0f) && tweens[i].trans == trans) + return true; + } + return false; + } + + public static bool isPaused(RectTransform rect) + { + return isTweening(rect.gameObject); + } + + /** + * Test whether or not a tween is paused or not + * + * @method LeanTween.isPaused + * @param {GameObject} id:int id of the tween that you want to test if it is paused + * @example + * int id = LeanTween.moveX(gameObject, 1f, 3f).id;
+ * LeanTween.pause(gameObject);
+ * if(LeanTween.isPaused( id ))
+ *      Debug.Log("I am paused!");
+ */ + public static bool isPaused(int uniqueId) + { + int backId = uniqueId & 0xFFFF; + int backCounter = uniqueId >> 16; + if (backId < 0 || backId >= maxTweens) return false; + // Debug.Log("tweens[backId].counter:"+tweens[backId].counter+" backCounter:"+backCounter +" toggle:"+tweens[backId].toggle); + if (tweens[backId].counter == backCounter && Mathf.Equals(tweens[i].direction, 0f)) + { + return true; + } + return false; + } + + /** + * Test whether or not a tween is active on a GameObject + * + * @method LeanTween.isTweening + * @param {GameObject} gameObject:GameObject GameObject that you want to test if it is tweening + */ + public static bool isTweening(GameObject gameObject = null) + { + if (gameObject == null) + { + for (int i = 0; i <= tweenMaxSearch; i++) + { + if (tweens[i].toggle) + return true; + } + return false; + } + Transform trans = gameObject.transform; + for (int i = 0; i <= tweenMaxSearch; i++) + { + if (tweens[i].toggle && tweens[i].trans == trans) + return true; + } + return false; + } + + public static bool isTweening(RectTransform rect) + { + return isTweening(rect.gameObject); + } + + /** + * Test whether or not a tween is active or not + * + * @method LeanTween.isTweening + * @param {GameObject} id:int id of the tween that you want to test if it is tweening + * @example + * int id = LeanTween.moveX(gameObject, 1f, 3f).id;
+ * if(LeanTween.isTweening( id ))
+ *      Debug.Log("I am tweening!");
+ */ + public static bool isTweening(int uniqueId) + { + int backId = uniqueId & 0xFFFF; + int backCounter = uniqueId >> 16; + if (backId < 0 || backId >= maxTweens) return false; + // Debug.Log("tweens[backId].counter:"+tweens[backId].counter+" backCounter:"+backCounter +" toggle:"+tweens[backId].toggle); + if (tweens[backId].counter == backCounter && tweens[backId].toggle) + { + return true; + } + return false; + } + + public static bool isTweening(LTRect ltRect) + { + for (int i = 0; i <= tweenMaxSearch; i++) + { + if (tweens[i].toggle && tweens[i]._optional.ltRect == ltRect) + return true; + } + return false; + } + + public static void drawBezierPath(Vector3 a, Vector3 b, Vector3 c, Vector3 d, float arrowSize = 0.0f, Transform arrowTransform = null) + { + Vector3 last = a; + Vector3 p; + Vector3 aa = (-a + 3 * (b - c) + d); + Vector3 bb = 3 * (a + c) - 6 * b; + Vector3 cc = 3 * (b - a); + + float t; + + if (arrowSize > 0.0f) + { + Vector3 beforePos = arrowTransform.position; + Quaternion beforeQ = arrowTransform.rotation; + float distanceTravelled = 0f; + + for (float k = 1.0f; k <= 120.0f; k++) + { + t = k / 120.0f; + p = ((aa * t + (bb)) * t + cc) * t + a; + Gizmos.DrawLine(last, p); + distanceTravelled += (p - last).magnitude; + if (distanceTravelled > 1f) + { + distanceTravelled = distanceTravelled - 1f; + /*float deltaY = p.y - last.y; + float deltaX = p.x - last.x; + float ang = Mathf.Atan(deltaY / deltaX); + Vector3 arrow = p + new Vector3( Mathf.Cos(ang+2.5f), Mathf.Sin(ang+2.5f), 0f)*0.5f; + Gizmos.DrawLine(p, arrow); + arrow = p + new Vector3( Mathf.Cos(ang+-2.5f), Mathf.Sin(ang+-2.5f), 0f)*0.5f; + Gizmos.DrawLine(p, arrow);*/ + + arrowTransform.position = p; + arrowTransform.LookAt(last, Vector3.forward); + Vector3 to = arrowTransform.TransformDirection(Vector3.right); + // Debug.Log("to:"+to+" tweenEmpty.transform.position:"+arrowTransform.position); + Vector3 back = (last - p); + back = back.normalized; + Gizmos.DrawLine(p, p + (to + back) * arrowSize); + to = arrowTransform.TransformDirection(-Vector3.right); + Gizmos.DrawLine(p, p + (to + back) * arrowSize); + } + last = p; + } + + arrowTransform.position = beforePos; + arrowTransform.rotation = beforeQ; + } + else + { + for (float k = 1.0f; k <= 30.0f; k++) + { + t = k / 30.0f; + p = ((aa * t + (bb)) * t + cc) * t + a; + Gizmos.DrawLine(last, p); + last = p; + } + } + } + + public static object logError(string error) + { + if (throwErrors) Debug.LogError(error); else Debug.Log(error); + return null; + } + + public static LTDescr options(LTDescr seed) { Debug.LogError("error this function is no longer used"); return null; } + public static LTDescr options() + { + init(); + + bool found = false; + // Debug.Log("Search start"); + for (j = 0, i = startSearch; j <= maxTweens; i++) + { + if (j >= maxTweens) + return logError("LeanTween - You have run out of available spaces for tweening. To avoid this error increase the number of spaces to available for tweening when you initialize the LeanTween class ex: LeanTween.init( " + (maxTweens * 2) + " );") as LTDescr; + if (i >= maxTweens) + i = 0; + // Debug.Log("searching i:"+i); + if (tweens[i].toggle == false) + { + if (i + 1 > tweenMaxSearch && i + 1 < maxTweens) + tweenMaxSearch = i + 1; + startSearch = i + 1; + found = true; + break; + } + + j++; + } + if (found == false) + logError("no available tween found!"); + + // Debug.Log("new tween with i:"+i+" counter:"+tweens[i].counter+" tweenMaxSearch:"+tweenMaxSearch+" tween:"+tweens[i]); + tweens[i].reset(); + + global_counter++; + if (global_counter > 0x8000) + global_counter = 0; + + tweens[i].setId((uint)i, global_counter); + + return tweens[i]; + } + + + public static GameObject tweenEmpty + { + get + { + init(maxTweens); + return _tweenEmpty; + } + } + + public static int startSearch = 0; + public static LTDescr d; + + private static LTDescr pushNewTween(GameObject gameObject, Vector3 to, float time, LTDescr tween) + { + init(maxTweens); + if (gameObject == null || tween == null) + return null; + + tween.trans = gameObject.transform; + tween.to = to; + tween.time = time; + + if (tween.time <= 0f) + tween.updateInternal(); + //tween.hasPhysics = gameObject.rigidbody!=null; + + return tween; + } + +#if !UNITY_3_5 && !UNITY_4_0 && !UNITY_4_0_1 && !UNITY_4_1 && !UNITY_4_2 && !UNITY_4_3 && !UNITY_4_5 + /** + * Play a sequence of images on a Unity UI Object + * + * @method LeanTween.play + * @param {RectTransform} rectTransform:RectTransform RectTransform that you want to play the sequence of sprites on + * @param {Sprite[]} sprites:Sprite[] Sequence of sprites to be played + * @return {LTDescr} LTDescr an object that distinguishes the tween
+ * @example + * LeanTween.play(gameObject.GetComponent<RectTransform>(), sprites).setLoopPingPong(); + */ + public static LTDescr play(RectTransform rectTransform, UnityEngine.Sprite[] sprites) + { + float defaultFrameRate = 0.25f; + float time = defaultFrameRate * sprites.Length; + return pushNewTween(rectTransform.gameObject, new Vector3((float)sprites.Length - 1.0f, 0, 0), time, options().setCanvasPlaySprite().setSprites(sprites).setRepeat(-1)); + } +#endif + + + /** + * Retrieve a sequencer object where you can easily chain together tweens and methods one after another + * + * @method LeanTween.sequence + * @return {LTSeq} LTSeq an object that you can add tweens, methods and time on to + * @example + * var seq = LeanTween.sequence();
+ * seq.add(1f); // delay everything one second
+ * seq.add( () => { // fire an event before start
+ *  Debug.Log("I have started");
+ * });
+ * seq.add( LeanTween.move(cube1, Vector3.one * 10f, 1f) ); // do a tween
+ * seq.add( () => { // fire event after tween
+ *  Debug.Log("We are done now");
+ * });;
+ */ + public static LTSeq sequence(bool initSequence = true) + { + init(maxTweens); + // Loop through and find available sequence + for (int i = 0; i < sequences.Length; i++) + { + // Debug.Log("i:" + i + " sequences[i]:" + sequences[i]); + if (sequences[i].tween == null || sequences[i].tween.toggle == false) + { + if (sequences[i].toggle == false) + { + LTSeq seq = sequences[i]; + if (initSequence) + { + seq.init((uint)(i + tweens.Length), global_counter); + + global_counter++; + if (global_counter > 0x8000) + global_counter = 0; + } + else + { + seq.reset(); + } + + return seq; + } + } + } + + return null; + } + + /** + * Fade a gameobject's material to a certain alpha value. + * + * @method LeanTween.alpha + * @param {GameObject} gameObject:GameObject Gameobject that you wish to fade + * @param {float} to:float the final alpha value (0-1) + * @param {float} time:float The time with which to fade the object + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.alpha(gameObject, 1f, 1f) .setDelay(1f); + */ + public static LTDescr alpha(GameObject gameObject, float to, float time) + { + LTDescr lt = pushNewTween(gameObject, new Vector3(to, 0, 0), time, options().setAlpha()); + +#if !UNITY_3_5 && !UNITY_4_0 && !UNITY_4_0_1 && !UNITY_4_1 && !UNITY_4_2 + SpriteRenderer ren = gameObject.GetComponent(); + lt.spriteRen = ren; +#endif + return lt; + } + + /** + * Fade a GUI Object + * + * @method LeanTween.alpha + * @param {LTRect} ltRect:LTRect LTRect that you wish to fade + * @param {float} to:float the final alpha value (0-1) + * @param {float} time:float The time with which to fade the object + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.alpha(ltRect, 1f, 1f) .setEase(LeanTweenType.easeInCirc); + */ + public static LTDescr alpha(LTRect ltRect, float to, float time) + { + ltRect.alphaEnabled = true; + return pushNewTween(tweenEmpty, new Vector3(to, 0f, 0f), time, options().setGUIAlpha().setRect(ltRect)); + } + + +#if !UNITY_3_5 && !UNITY_4_0 && !UNITY_4_0_1 && !UNITY_4_1 && !UNITY_4_2 && !UNITY_4_3 && !UNITY_4_5 + /** + * Fade a Unity UI Object + * + * @method LeanTween.alphaText + * @param {RectTransform} rectTransform:RectTransform RectTransform associated with the Text Component you wish to fade + * @param {float} to:float the final alpha value (0-1) + * @param {float} time:float The time with which to fade the object + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.alphaText(gameObject.GetComponent<RectTransform>(), 1f, 1f) .setEase(LeanTweenType.easeInCirc); + */ + public static LTDescr textAlpha(RectTransform rectTransform, float to, float time) + { + return pushNewTween(rectTransform.gameObject, new Vector3(to, 0, 0), time, options().setTextAlpha()); + } + public static LTDescr alphaText(RectTransform rectTransform, float to, float time) + { + return pushNewTween(rectTransform.gameObject, new Vector3(to, 0, 0), time, options().setTextAlpha()); + } + + /** + * Fade a Unity UI Canvas Group + * + * @method LeanTween.alphaCanvas + * @param {RectTransform} rectTransform:RectTransform RectTransform that the CanvasGroup is attached to + * @param {float} to:float the final alpha value (0-1) + * @param {float} time:float The time with which to fade the object + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.alphaCanvas(gameObject.GetComponent<RectTransform>(), 0f, 1f) .setLoopPingPong(); + */ + public static LTDescr alphaCanvas(CanvasGroup canvasGroup, float to, float time) + { + return pushNewTween(canvasGroup.gameObject, new Vector3(to, 0, 0), time, options().setCanvasGroupAlpha()); + } +#endif + + /** + * This works by tweening the vertex colors directly +
+ Vertex-based coloring is useful because you avoid making a copy of your + object's material for each instance that needs a different color.
+
+ A shader that supports vertex colors is required for it to work + (for example the shaders in Mobile/Particles/) + * + * @method LeanTween.alphaVertex + * @param {GameObject} gameObject:GameObject Gameobject that you wish to alpha + * @param {float} to:float The alpha value you wish to tween to + * @param {float} time:float The time with which to delay before calling the function + * @return {LTDescr} LTDescr an object that distinguishes the tween + */ + public static LTDescr alphaVertex(GameObject gameObject, float to, float time) + { + return pushNewTween(gameObject, new Vector3(to, 0f, 0f), time, options().setAlphaVertex()); + } + + /** + * Change a gameobject's material to a certain color value + * + * @method LeanTween.color + * @param {GameObject} gameObject:GameObject Gameobject that you wish to change the color + * @param {Color} to:Color the final color value ex: Color.Red, new Color(1.0f,1.0f,0.0f,0.8f) + * @param {float} time:float The time with which to fade the object + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.color(gameObject, Color.yellow, 1f) .setDelay(1f); + */ + public static LTDescr color(GameObject gameObject, Color to, float time) + { + LTDescr lt = pushNewTween(gameObject, new Vector3(1.0f, to.a, 0.0f), time, options().setColor().setPoint(new Vector3(to.r, to.g, to.b))); +#if !UNITY_3_5 && !UNITY_4_0 && !UNITY_4_0_1 && !UNITY_4_1 && !UNITY_4_2 + SpriteRenderer ren = gameObject.GetComponent(); + lt.spriteRen = ren; +#endif + return lt; + } + +#if !UNITY_3_5 && !UNITY_4_0 && !UNITY_4_0_1 && !UNITY_4_1 && !UNITY_4_2 && !UNITY_4_3 && !UNITY_4_5 + /** + * Change the color a Unity UI Object + * + * @method LeanTween.colorText + * @param {RectTransform} rectTransform:RectTransform RectTransform attached to the Text Component whose color you want to change + * @param {Color} to:Color the final alpha value ex: Color.Red, new Color(1.0f,1.0f,0.0f,0.8f) + * @param {float} time:float The time with which to fade the object + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * LeanTween.colorText(gameObject.GetComponent<RectTransform>(), Color.yellow, 1f) .setDelay(1f); + */ + public static LTDescr textColor(RectTransform rectTransform, Color to, float time) + { + return pushNewTween(rectTransform.gameObject, new Vector3(1.0f, to.a, 0.0f), time, options().setTextColor().setPoint(new Vector3(to.r, to.g, to.b))); + } + public static LTDescr colorText(RectTransform rectTransform, Color to, float time) + { + return pushNewTween(rectTransform.gameObject, new Vector3(1.0f, to.a, 0.0f), time, options().setTextColor().setPoint(new Vector3(to.r, to.g, to.b))); + } +#endif + + /** + * Call a method after a specified amount of time + * + * @method LeanTween.delayedCall + * @param {GameObject} gameObject:GameObject Gameobject that you wish to associate with this delayed call + * @param {float} time:float delay The time you wish to pass before the method is called + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example LeanTween.delayedCall(gameObject, 1f, ()=>{
Debug.Log("I am called one second later!");
})); + */ + public static LTDescr delayedCall(float delayTime, Action callback) + { + return pushNewTween(tweenEmpty, Vector3.zero, delayTime, options().setCallback().setOnComplete(callback)); + } + + public static LTDescr delayedCall(float delayTime, Action callback) + { + return pushNewTween(tweenEmpty, Vector3.zero, delayTime, options().setCallback().setOnComplete(callback)); + } + + public static LTDescr delayedCall(GameObject gameObject, float delayTime, Action callback) + { + return pushNewTween(gameObject, Vector3.zero, delayTime, options().setCallback().setOnComplete(callback)); + } + + public static LTDescr delayedCall(GameObject gameObject, float delayTime, Action callback) + { + return pushNewTween(gameObject, Vector3.zero, delayTime, options().setCallback().setOnComplete(callback)); + } + + public static LTDescr destroyAfter(LTRect rect, float delayTime) + { + return pushNewTween(tweenEmpty, Vector3.zero, delayTime, options().setCallback().setRect(rect).setDestroyOnComplete(true)); + } + + /*public static LTDescr delayedCall(GameObject gameObject, float delayTime, string callback){ + return pushNewTween( gameObject, Vector3.zero, delayTime, TweenAction.CALLBACK, options().setOnComplete( callback ) ); + }*/ + + + /** + * Move a GameObject to a certain location + * + * @method LeanTween.move + * @param {GameObject} gameObject:GameObject Gameobject that you wish to move + * @param {Vector3} vec:Vector3 to The final positin with which to move to + * @param {float} time:float time The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example LeanTween.move(gameObject, new Vector3(0f,-3f,5f), 2.0f) .setEase( LeanTweenType.easeOutQuad ); + */ + public static LTDescr move(GameObject gameObject, Vector3 to, float time) + { + return pushNewTween(gameObject, to, time, options().setMove()); + } + public static LTDescr move(GameObject gameObject, Vector2 to, float time) + { + return pushNewTween(gameObject, new Vector3(to.x, to.y, gameObject.transform.position.z), time, options().setMove()); + } + + + /** + * Move a GameObject along a set of bezier curves + * + * @method LeanTween.move + * @param {GameObject} gameObject:GameObject Gameobject that you wish to move + * @param {Vector3[]} path:Vector3[] A set of points that define the curve(s) ex: Point1,Handle2,Handle1,Point2,... + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * Javascript:
+ * LeanTween.move(gameObject, [Vector3(0,0,0),Vector3(1,0,0),Vector3(1,0,0),Vector3(1,0,1)], 2.0) .setEase(LeanTweenType.easeOutQuad).setOrientToPath(true);

+ * C#:
+ * LeanTween.move(gameObject, new Vector3[]{new Vector3(0f,0f,0f),new Vector3(1f,0f,0f),new Vector3(1f,0f,0f),new Vector3(1f,0f,1f)}, 1.5f).setEase(LeanTweenType.easeOutQuad).setOrientToPath(true);;
+ */ + public static LTDescr move(GameObject gameObject, Vector3[] to, float time) + { + d = options().setMoveCurved(); + if (d.optional.path == null) + d.optional.path = new LTBezierPath(to); + else + d.optional.path.setPoints(to); + + return pushNewTween(gameObject, new Vector3(1.0f, 0.0f, 0.0f), time, d); + } + + public static LTDescr move(GameObject gameObject, LTBezierPath to, float time) + { + d = options().setMoveCurved(); + d.optional.path = to; + + return pushNewTween(gameObject, new Vector3(1.0f, 0.0f, 0.0f), time, d); + } + + public static LTDescr move(GameObject gameObject, LTSpline to, float time) + { + d = options().setMoveSpline(); + d.optional.spline = to; + + return pushNewTween(gameObject, new Vector3(1.0f, 0.0f, 0.0f), time, d); + } + + /** + * Move a GameObject through a set of points + * + * @method LeanTween.moveSpline + * @param {GameObject} gameObject:GameObject Gameobject that you wish to move + * @param {Vector3[]} path:Vector3[] A set of points that define the curve(s) ex: ControlStart,Pt1,Pt2,Pt3,.. ..ControlEnd
Note: The first and last item just define the angle of the end points, they are not actually used in the spline path itself. If you do not care about the angle you can jus set the first two items and last two items as the same value. + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * Javascript:
+ * LeanTween.moveSpline(gameObject, [Vector3(0,0,0),Vector3(1,0,0),Vector3(1,0,0),Vector3(1,0,1)], 2.0) .setEase(LeanTweenType.easeOutQuad).setOrientToPath(true);

+ * C#:
+ * LeanTween.moveSpline(gameObject, new Vector3[]{new Vector3(0f,0f,0f),new Vector3(1f,0f,0f),new Vector3(1f,0f,0f),new Vector3(1f,0f,1f)}, 1.5f).setEase(LeanTweenType.easeOutQuad).setOrientToPath(true);
+ */ + public static LTDescr moveSpline(GameObject gameObject, Vector3[] to, float time) + { + d = options().setMoveSpline(); + d.optional.spline = new LTSpline(to); + + return pushNewTween(gameObject, new Vector3(1.0f, 0.0f, 0.0f), time, d); + } + + /** + * Move a GameObject through a set of points + * + * @method LeanTween.moveSpline + * @param {GameObject} gameObject:GameObject Gameobject that you wish to move + * @param {LTSpline} spline:LTSpline pass a pre-existing LTSpline for the object to move along + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * Javascript:
+ * LeanTween.moveSpline(gameObject, ltSpline, 2.0) .setEase(LeanTweenType.easeOutQuad).setOrientToPath(true);

+ * C#:
+ * LeanTween.moveSpline(gameObject, ltSpline, 1.5f).setEase(LeanTweenType.easeOutQuad).setOrientToPath(true);
+ */ + public static LTDescr moveSpline(GameObject gameObject, LTSpline to, float time) + { + d = options().setMoveSpline(); + d.optional.spline = to; + + return pushNewTween(gameObject, new Vector3(1.0f, 0.0f, 0.0f), time, d); + } + + /** + * Move a GameObject through a set of points, in local space + * + * @method LeanTween.moveSplineLocal + * @param {GameObject} gameObject:GameObject Gameobject that you wish to move + * @param {Vector3[]} path:Vector3[] A set of points that define the curve(s) ex: ControlStart,Pt1,Pt2,Pt3,.. ..ControlEnd + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * Javascript:
+ * LeanTween.moveSpline(gameObject, [Vector3(0,0,0),Vector3(1,0,0),Vector3(1,0,0),Vector3(1,0,1)], 2.0) .setEase(LeanTweenType.easeOutQuad).setOrientToPath(true);

+ * C#:
+ * LeanTween.moveSpline(gameObject, new Vector3[]{new Vector3(0f,0f,0f),new Vector3(1f,0f,0f),new Vector3(1f,0f,0f),new Vector3(1f,0f,1f)}, 1.5f).setEase(LeanTweenType.easeOutQuad).setOrientToPath(true);
+ */ + public static LTDescr moveSplineLocal(GameObject gameObject, Vector3[] to, float time) + { + d = options().setMoveSplineLocal(); + d.optional.spline = new LTSpline(to); + + return pushNewTween(gameObject, new Vector3(1.0f, 0.0f, 0.0f), time, d); + } + + /** + * Move a GUI Element to a certain location + * + * @method LeanTween.move (GUI) + * @param {LTRect} ltRect:LTRect ltRect LTRect object that you wish to move + * @param {Vector2} vec:Vector2 to The final position with which to move to (pixel coordinates) + * @param {float} time:float time The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + */ + public static LTDescr move(LTRect ltRect, Vector2 to, float time) + { + return pushNewTween(tweenEmpty, to, time, options().setGUIMove().setRect(ltRect)); + } + + public static LTDescr moveMargin(LTRect ltRect, Vector2 to, float time) + { + return pushNewTween(tweenEmpty, to, time, options().setGUIMoveMargin().setRect(ltRect)); + } + + /** + * Move a GameObject along the x-axis + * + * @method LeanTween.moveX + * @param {GameObject} gameObject:GameObject gameObject Gameobject that you wish to move + * @param {float} to:float to The final position with which to move to + * @param {float} time:float time The time to complete the move in + * @return {LTDescr} LTDescr an object that distinguishes the tween + */ + public static LTDescr moveX(GameObject gameObject, float to, float time) + { + return pushNewTween(gameObject, new Vector3(to, 0, 0), time, options().setMoveX()); + } + + /** + * Move a GameObject along the y-axis + * + * @method LeanTween.moveY + * @param {GameObject} GameObject gameObject Gameobject that you wish to move + * @param {float} float to The final position with which to move to + * @param {float} float time The time to complete the move in + * @return {LTDescr} LTDescr an object that distinguishes the tween + */ + public static LTDescr moveY(GameObject gameObject, float to, float time) + { + return pushNewTween(gameObject, new Vector3(to, 0, 0), time, options().setMoveY()); + } + + /** + * Move a GameObject along the z-axis + * + * @method LeanTween.moveZ + * @param {GameObject} GameObject gameObject Gameobject that you wish to move + * @param {float} float to The final position with which to move to + * @param {float} float time The time to complete the move in + * @return {LTDescr} LTDescr an object that distinguishes the tween + */ + public static LTDescr moveZ(GameObject gameObject, float to, float time) + { + return pushNewTween(gameObject, new Vector3(to, 0, 0), time, options().setMoveZ()); + } + + /** + * Move a GameObject to a certain location relative to the parent transform. + * + * @method LeanTween.moveLocal + * @param {GameObject} GameObject gameObject Gameobject that you wish to rotate + * @param {Vector3} Vector3 to The final positin with which to move to + * @param {float} float time The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + */ + public static LTDescr moveLocal(GameObject gameObject, Vector3 to, float time) + { + return pushNewTween(gameObject, to, time, options().setMoveLocal()); + } + + /** + * Move a GameObject along a set of bezier curves, in local space + * + * @method LeanTween.moveLocal + * @param {GameObject} gameObject:GameObject Gameobject that you wish to move + * @param {Vector3[]} path:Vector3[] A set of points that define the curve(s) ex: Point1,Handle1,Handle2,Point2,... + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * Javascript:
+ * LeanTween.moveLocal(gameObject, [Vector3(0,0,0),Vector3(1,0,0),Vector3(1,0,0),Vector3(1,0,1)], 2.0).setEase(LeanTweenType.easeOutQuad).setOrientToPath(true);

+ * C#:
+ * LeanTween.moveLocal(gameObject, new Vector3[]{Vector3(0f,0f,0f),Vector3(1f,0f,0f),Vector3(1f,0f,0f),Vector3(1f,0f,1f)}).setEase(LeanTweenType.easeOutQuad).setOrientToPath(true);
+ */ + public static LTDescr moveLocal(GameObject gameObject, Vector3[] to, float time) + { + d = options().setMoveCurvedLocal(); + if (d.optional.path == null) + d.optional.path = new LTBezierPath(to); + else + d.optional.path.setPoints(to); + + return pushNewTween(gameObject, new Vector3(1.0f, 0.0f, 0.0f), time, d); + } + + public static LTDescr moveLocalX(GameObject gameObject, float to, float time) + { + return pushNewTween(gameObject, new Vector3(to, 0, 0), time, options().setMoveLocalX()); + } + + public static LTDescr moveLocalY(GameObject gameObject, float to, float time) + { + return pushNewTween(gameObject, new Vector3(to, 0, 0), time, options().setMoveLocalY()); + } + + public static LTDescr moveLocalZ(GameObject gameObject, float to, float time) + { + return pushNewTween(gameObject, new Vector3(to, 0, 0), time, options().setMoveLocalZ()); + } + + public static LTDescr moveLocal(GameObject gameObject, LTBezierPath to, float time) + { + d = options().setMoveCurvedLocal(); + d.optional.path = to; + + return pushNewTween(gameObject, new Vector3(1.0f, 0.0f, 0.0f), time, d); + } + public static LTDescr moveLocal(GameObject gameObject, LTSpline to, float time) + { + d = options().setMoveSplineLocal(); + d.optional.spline = to; + + return pushNewTween(gameObject, new Vector3(1.0f, 0.0f, 0.0f), time, d); + } + + /** + * Move a GameObject to another transform + * + * @method LeanTween.move + * @param {GameObject} gameObject:GameObject Gameobject that you wish to move + * @param {Transform} destination:Transform Transform whose position the tween will finally end on + * @param {float} time:float time The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example LeanTween.move(gameObject, anotherTransform, 2.0f) .setEase( LeanTweenType.easeOutQuad ); + */ + public static LTDescr move(GameObject gameObject, Transform to, float time) + { + return pushNewTween(gameObject, Vector3.zero, time, options().setTo(to).setMoveToTransform()); + } + + /** + * Rotate a GameObject, to values are in passed in degrees + * + * @method LeanTween.rotate + * @param {GameObject} GameObject gameObject Gameobject that you wish to rotate + * @param {Vector3} Vector3 to The final rotation with which to rotate to + * @param {float} float time The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example LeanTween.rotate(cube, new Vector3(180f,30f,0f), 1.5f); + */ + + public static LTDescr rotate(GameObject gameObject, Vector3 to, float time) + { + return pushNewTween(gameObject, to, time, options().setRotate()); + } + + /** + * Rotate a GUI element (using an LTRect object), to a value that is in degrees + * + * @method LeanTween.rotate + * @param {LTRect} ltRect:LTRect LTRect that you wish to rotate + * @param {float} to:float The final rotation with which to rotate to + * @param {float} time:float The time to complete the tween in + * @param {Array} optional:Array Object Array where you can pass optional items. + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * if(GUI.Button(buttonRect.rect, "Rotate"))
+ * LeanTween.rotate( buttonRect4, 150.0f, 1.0f).setEase(LeanTweenType.easeOutElastic);
+ * GUI.matrix = Matrix4x4.identity;
+ */ + public static LTDescr rotate(LTRect ltRect, float to, float time) + { + return pushNewTween(tweenEmpty, new Vector3(to, 0f, 0f), time, options().setGUIRotate().setRect(ltRect)); + } + + /** + * Rotate a GameObject in the objects local space (on the transforms localEulerAngles object) + * + * @method LeanTween.rotateLocal + * @param {GameObject} gameObject:GameObject Gameobject that you wish to rotate + * @param {Vector3} to:Vector3 The final rotation with which to rotate to + * @param {float} time:float The time to complete the rotation in + * @return {LTDescr} LTDescr an object that distinguishes the tween + */ + public static LTDescr rotateLocal(GameObject gameObject, Vector3 to, float time) + { + return pushNewTween(gameObject, to, time, options().setRotateLocal()); + } + + /** + * Rotate a GameObject only on the X axis Rotate a GameObject only on the X axis + * + * @method LeanTween.rotateX + * @param {GameObject} GameObject Gameobject that you wish to rotate + * @param {float} to:float The final x-axis rotation with which to rotate + * @param {float} time:float The time to complete the rotation in + * @return {LTDescr} LTDescr an object that distinguishes the tween + */ + public static LTDescr rotateX(GameObject gameObject, float to, float time) + { + return pushNewTween(gameObject, new Vector3(to, 0, 0), time, options().setRotateX()); + } + + /** + * Rotate a GameObject only on the Y axis + * + * @method LeanTween.rotateY + * @param {GameObject} GameObject Gameobject that you wish to rotate + * @param {float} to:float The final y-axis rotation with which to rotate + * @param {float} time:float The time to complete the rotation in + * @return {LTDescr} LTDescr an object that distinguishes the tween + */ + public static LTDescr rotateY(GameObject gameObject, float to, float time) + { + return pushNewTween(gameObject, new Vector3(to, 0, 0), time, options().setRotateY()); + } + + /** + * Rotate a GameObject only on the Z axis + * + * @method LeanTween.rotateZ + * @param {GameObject} GameObject Gameobject that you wish to rotate + * @param {float} to:float The final z-axis rotation with which to rotate + * @param {float} time:float The time to complete the rotation in + * @return {LTDescr} LTDescr an object that distinguishes the tween + */ + public static LTDescr rotateZ(GameObject gameObject, float to, float time) + { + return pushNewTween(gameObject, new Vector3(to, 0, 0), time, options().setRotateZ()); + } + + /** + * Rotate a GameObject around a certain Axis (the best method to use when you want to rotate beyond 180 degrees) + * + * @method LeanTween.rotateAround + * @param {GameObject} gameObject:GameObject Gameobject that you wish to rotate + * @param {Vector3} vec:Vector3 axis in which to rotate around ex: Vector3.up + * @param {float} degrees:float the degrees in which to rotate + * @param {float} time:float time The time to complete the rotation in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * Example:
+ * LeanTween.rotateAround ( gameObject, Vector3.left, 90f, 1f ); + */ + public static LTDescr rotateAround(GameObject gameObject, Vector3 axis, float add, float time) + { + return pushNewTween(gameObject, new Vector3(add, 0f, 0f), time, options().setAxis(axis).setRotateAround()); + } + + /** + * Rotate a GameObject around a certain Axis in Local Space (the best method to use when you want to rotate beyond 180 degrees) + * + * @method LeanTween.rotateAroundLocal + * @param {GameObject} gameObject:GameObject Gameobject that you wish to rotate + * @param {Vector3} vec:Vector3 axis in which to rotate around ex: Vector3.up + * @param {float} degrees:float the degrees in which to rotate + * @param {float} time:float time The time to complete the rotation in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * Example:
+ * LeanTween.rotateAround ( gameObject, Vector3.left, 90f, 1f ); + */ + public static LTDescr rotateAroundLocal(GameObject gameObject, Vector3 axis, float add, float time) + { + return pushNewTween(gameObject, new Vector3(add, 0f, 0f), time, options().setRotateAroundLocal().setAxis(axis)); + } + + /** + * Scale a GameObject to a certain size + * + * @method LeanTween.scale + * @param {GameObject} gameObject:GameObject gameObject Gameobject that you wish to scale + * @param {Vector3} vec:Vector3 to The size with which to tween to + * @param {float} time:float time The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + */ + public static LTDescr scale(GameObject gameObject, Vector3 to, float time) + { + return pushNewTween(gameObject, to, time, options().setScale()); + } + + /** + * Scale a GUI Element to a certain width and height + * + * @method LeanTween.scale (GUI) + * @param {LTRect} LTRect ltRect LTRect object that you wish to move + * @param {Vector2} Vector2 to The final width and height to scale to (pixel based) + * @param {float} float time The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * Example Javascript:
+ * var bRect:LTRect = new LTRect( 0, 0, 100, 50 );
+ * LeanTween.scale( bRect, Vector2(bRect.rect.width, bRect.rect.height) * 1.3, 0.25 ).setEase(LeanTweenType.easeOutBounce);
+ * function OnGUI(){
+ *   if(GUI.Button(bRect.rect, "Scale")){ }
+ * }
+ *
+ * Example C#:
+ * LTRect bRect = new LTRect( 0f, 0f, 100f, 50f );
+ * LeanTween.scale( bRect, new Vector2(150f,75f), 0.25f ).setEase(LeanTweenType.easeOutBounce);
+ * void OnGUI(){
+ *   if(GUI.Button(bRect.rect, "Scale")){ }
+ * }
+ */ + public static LTDescr scale(LTRect ltRect, Vector2 to, float time) + { + return pushNewTween(tweenEmpty, to, time, options().setGUIScale().setRect(ltRect)); + } + + /** + * Scale a GameObject to a certain size along the x-axis only + * + * @method LeanTween.scaleX + * @param {GameObject} gameObject:GameObject Gameobject that you wish to scale + * @param {float} scaleTo:float the size with which to scale to + * @param {float} time:float the time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + */ + public static LTDescr scaleX(GameObject gameObject, float to, float time) + { + return pushNewTween(gameObject, new Vector3(to, 0, 0), time, options().setScaleX()); + } + + /** + * Scale a GameObject to a certain size along the y-axis only + * + * @method LeanTween.scaleY + * @param {GameObject} gameObject:GameObject Gameobject that you wish to scale + * @param {float} scaleTo:float the size with which to scale to + * @param {float} time:float the time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + */ + public static LTDescr scaleY(GameObject gameObject, float to, float time) + { + return pushNewTween(gameObject, new Vector3(to, 0, 0), time, options().setScaleY()); + } + + /** + * Scale a GameObject to a certain size along the z-axis only + * + * @method LeanTween.scaleZ + * @param {GameObject} gameObject:GameObject Gameobject that you wish to scale + * @param {float} scaleTo:float the size with which to scale to + * @param {float} time:float the time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + */ + public static LTDescr scaleZ(GameObject gameObject, float to, float time) + { + return pushNewTween(gameObject, new Vector3(to, 0, 0), time, options().setScaleZ()); + } + + /** + * Tween any particular value (float) + * + * @method LeanTween.value (float) + * @param {GameObject} gameObject:GameObject Gameobject that you wish to attach the tween to + * @param {float} from:float The original value to start the tween from + * @param {Vector3} to:float The final float with which to tween to + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * Example Javascript:
+ * LeanTween.value( gameObject, 1f, 5f, 5f).setOnUpdate( function( val:float ){
+ *  Debug.Log("tweened val:"+val);
+ * } );
+ *
+ * Example C#:
+ * LeanTween.value( gameObject, 1f, 5f, 5f).setOnUpdate( (float val)=>{
+ *  Debug.Log("tweened val:"+val);
+ * } );
+ */ + public static LTDescr value(GameObject gameObject, float from, float to, float time) + { + return pushNewTween(gameObject, new Vector3(to, 0, 0), time, options().setCallback().setFrom(new Vector3(from, 0, 0))); + } + public static LTDescr value(float from, float to, float time) + { + return pushNewTween(tweenEmpty, new Vector3(to, 0, 0), time, options().setCallback().setFrom(new Vector3(from, 0, 0))); + } + + /** + * Tween any particular value (Vector2) + * + * @method LeanTween.value (Vector2) + * @param {GameObject} gameObject:GameObject Gameobject that you wish to attach the tween to + * @param {Vector2} from:Vector2 The original value to start the tween from + * @param {Vector3} to:Vector2 The final Vector2 with which to tween to + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * Example Javascript:
+ * LeanTween.value( gameObject, new Vector2(1f,0f), new Vector3(5f,0f), 5f).setOnUpdate( function( val:Vector2 ){
+ *  Debug.Log("tweened val:"+val);
+ * } );
+ *
+ * Example C#:
+ * LeanTween.value( gameObject, new Vector3(1f,0f), new Vector3(5f,0f), 5f).setOnUpdate( (Vector2 val)=>{
+ *  Debug.Log("tweened val:"+val);
+ * } );
+ */ + public static LTDescr value(GameObject gameObject, Vector2 from, Vector2 to, float time) + { + return pushNewTween(gameObject, new Vector3(to.x, to.y, 0), time, options().setValue3().setTo(new Vector3(to.x, to.y, 0f)).setFrom(new Vector3(from.x, from.y, 0))); + } + + /** + * Tween any particular value (Vector3) + * + * @method LeanTween.value (Vector3) + * @param {GameObject} gameObject:GameObject Gameobject that you wish to attach the tween to + * @param {Vector3} from:Vector3 The original value to start the tween from + * @param {Vector3} to:Vector3 The final Vector3 with which to tween to + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * Example Javascript:
+ * LeanTween.value( gameObject, new Vector3(1f,0f,0f), new Vector3(5f,0f,0f), 5f).setOnUpdate( function( val:Vector3 ){
+ *  Debug.Log("tweened val:"+val);
+ * } );
+ *
+ * Example C#:
+ * LeanTween.value( gameObject, new Vector3(1f,0f,0f), new Vector3(5f,0f,0f), 5f).setOnUpdate( (Vector3 val)=>{
+ *  Debug.Log("tweened val:"+val);
+ * } );
+ */ + public static LTDescr value(GameObject gameObject, Vector3 from, Vector3 to, float time) + { + return pushNewTween(gameObject, to, time, options().setValue3().setFrom(from)); + } + + /** + * Tween any particular value (Color) + * + * @method LeanTween.value (Color) + * @param {GameObject} gameObject:GameObject Gameobject that you wish to attach the tween to + * @param {Color} from:Color The original value to start the tween from + * @param {Color} to:Color The final Color with which to tween to + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * Example Javascript:
+ * LeanTween.value( gameObject, Color.red, Color.yellow, 5f).setOnUpdate( function( val:Color ){
+ *  Debug.Log("tweened val:"+val);
+ * } );
+ *
+ * Example C#:
+ * LeanTween.value( gameObject, Color.red, Color.yellow, 5f).setOnUpdate( (Color val)=>{
+ *  Debug.Log("tweened val:"+val);
+ * } );
+ */ + public static LTDescr value(GameObject gameObject, Color from, Color to, float time) + { + LTDescr lt = pushNewTween(gameObject, new Vector3(1f, to.a, 0f), time, options().setCallbackColor().setPoint(new Vector3(to.r, to.g, to.b)) + .setFromColor(from).setHasInitialized(false)); + +#if !UNITY_3_5 && !UNITY_4_0 && !UNITY_4_0_1 && !UNITY_4_1 && !UNITY_4_2 + SpriteRenderer ren = gameObject.GetComponent(); + lt.spriteRen = ren; +#endif + return lt; + } + + /** + * Tween any particular value, it does not need to be tied to any particular type or GameObject + * + * @method LeanTween.value (float) + * @param {GameObject} GameObject gameObject GameObject with which to tie the tweening with. This is only used when you need to cancel this tween, it does not actually perform any operations on this gameObject + * @param {Action} callOnUpdate:Action The function that is called on every Update frame, this function needs to accept a float value ex: function updateValue( float val ){ } + * @param {float} float from The original value to start the tween from + * @param {float} float to The value to end the tween on + * @param {float} float time The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * Example Javascript:
+ * LeanTween.value( gameObject, updateValueExampleCallback, 180f, 270f, 1f).setEase(LeanTweenType.easeOutElastic);
+ * function updateValueExampleCallback( val:float ){
+ *   Debug.Log("tweened value:"+val+" set this to whatever variable you are tweening...");
+ * }
+ *
+ * Example C#:
+ * LeanTween.value( gameObject, updateValueExampleCallback, 180f, 270f, 1f).setEase(LeanTweenType.easeOutElastic);
+ * void updateValueExampleCallback( float val ){
+ *   Debug.Log("tweened value:"+val+" set this to whatever variable you are tweening...");
+ * }
+ */ + + public static LTDescr value(GameObject gameObject, Action callOnUpdate, float from, float to, float time) + { + return pushNewTween(gameObject, new Vector3(to, 0, 0), time, options().setCallback().setTo(new Vector3(to, 0, 0)).setFrom(new Vector3(from, 0, 0)).setOnUpdate(callOnUpdate)); + } + + /** + * Tweens any float value, it does not need to be tied to any particular type or GameObject + * + * @method LeanTween.value (float) + * @param {GameObject} GameObject gameObject GameObject with which to tie the tweening with. This is only used when you need to cancel this tween, it does not actually perform any operations on this gameObject + * @param {Action} callOnUpdateRatio:Action Function that's called every Update frame. It must accept two float values ex: function updateValue( float val, float ratio){ } + * @param {float} float from The original value to start the tween from + * @param {float} float to The value to end the tween on + * @param {float} float time The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * Example Javascript:
+ * LeanTween.value( gameObject, updateValueExampleCallback, 180f, 270f, 1f).setEase(LeanTweenType.easeOutElastic);
+ * function updateValueExampleCallback( val:float, ratio:float ){
+ *   Debug.Log("tweened value:"+val+" percent complete:"+ratio*100);
+ * }
+ *
+ * Example C#:
+ * LeanTween.value( gameObject, updateValueExampleCallback, 180f, 270f, 1f).setEase(LeanTweenType.easeOutElastic);
+ * void updateValueExampleCallback( float val, float ratio ){
+ *   Debug.Log("tweened value:"+val+" percent complete:"+ratio*100);
+ * }
+ */ + + public static LTDescr value(GameObject gameObject, Action callOnUpdateRatio, float from, float to, float time) + { + return pushNewTween(gameObject, new Vector3(to, 0, 0), time, options().setCallback().setTo(new Vector3(to, 0, 0)).setFrom(new Vector3(from, 0, 0)).setOnUpdateRatio(callOnUpdateRatio)); + } + + /** + * Tween from one color to another + * + * @method LeanTween.value (Color) + * @param {GameObject} GameObject gameObject GameObject with which to tie the tweening with. This is only used when you need to cancel this tween, it does not actually perform any operations on this gameObject + * @param {Action} callOnUpdate:Action The function that is called on every Update frame, this function needs to accept a color value ex: function updateValue( Color val ){ } + * @param {Color} Color from The original value to start the tween from + * @param {Color} Color to The value to end the tween on + * @param {Color} Color time The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example + * Example Javascript:
+ * LeanTween.value( gameObject, updateValueExampleCallback, Color.red, Color.green, 1f).setEase(LeanTweenType.easeOutElastic);
+ * function updateValueExampleCallback( val:Color ){
+ *   Debug.Log("tweened color:"+val+" set this to whatever variable you are tweening...");
+ * }
+ *
+ * Example C#:
+ * LeanTween.value( gameObject, updateValueExampleCallback, Color.red, Color.green, 1f).setEase(LeanTweenType.easeOutElastic);
+ * void updateValueExampleCallback( Color val ){
+ *   Debug.Log("tweened color:"+val+" set this to whatever variable you are tweening...");
+ * }
+ */ + + public static LTDescr value(GameObject gameObject, Action callOnUpdate, Color from, Color to, float time) + { + return pushNewTween(gameObject, new Vector3(1.0f, to.a, 0.0f), time, options().setCallbackColor().setPoint(new Vector3(to.r, to.g, to.b)) + .setAxis(new Vector3(from.r, from.g, from.b)).setFrom(new Vector3(0.0f, from.a, 0.0f)).setHasInitialized(false).setOnUpdateColor(callOnUpdate)); + } + public static LTDescr value(GameObject gameObject, Action callOnUpdate, Color from, Color to, float time) + { + return pushNewTween(gameObject, new Vector3(1.0f, to.a, 0.0f), time, options().setCallbackColor().setPoint(new Vector3(to.r, to.g, to.b)) + .setAxis(new Vector3(from.r, from.g, from.b)).setFrom(new Vector3(0.0f, from.a, 0.0f)).setHasInitialized(false).setOnUpdateColor(callOnUpdate)); + } + + /** + * Tween any particular value (Vector2), this could be used to tween an arbitrary value like offset property + * + * @method LeanTween.value (Vector2) + * @param {GameObject} gameObject:GameObject Gameobject that you wish to attach the tween to + * @param {Action} callOnUpdate:Action The function that is called on every Update frame, this function needs to accept a float value ex: function updateValue( Vector3 val ){ } + * @param {float} from:Vector2 The original value to start the tween from + * @param {Vector2} to:Vector2 The final Vector3 with which to tween to + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + */ + public static LTDescr value(GameObject gameObject, Action callOnUpdate, Vector2 from, Vector2 to, float time) + { + return pushNewTween(gameObject, new Vector3(to.x, to.y, 0f), time, options().setValue3().setTo(new Vector3(to.x, to.y, 0f)).setFrom(new Vector3(from.x, from.y, 0f)).setOnUpdateVector2(callOnUpdate)); + } + + /** + * Tween any particular value (Vector3), this could be used to tween an arbitrary property that uses a Vector + * + * @method LeanTween.value (Vector3) + * @param {GameObject} gameObject:GameObject Gameobject that you wish to attach the tween to + * @param {Action} callOnUpdate:Action The function that is called on every Update frame, this function needs to accept a float value ex: function updateValue( Vector3 val ){ } + * @param {float} from:Vector3 The original value to start the tween from + * @param {Vector3} to:Vector3 The final Vector3 with which to tween to + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + */ + public static LTDescr value(GameObject gameObject, Action callOnUpdate, Vector3 from, Vector3 to, float time) + { + return pushNewTween(gameObject, to, time, options().setValue3().setTo(to).setFrom(from).setOnUpdateVector3(callOnUpdate)); + } + + /** + * Tween any particular value (float) + * + * @method LeanTween.value (float,object) + * @param {GameObject} gameObject:GameObject Gameobject that you wish to attach the tween to + * @param {Action} callOnUpdate:Action The function that is called on every Update frame, this function needs to accept a float value ex: function updateValue( Vector3 val, object obj ){ } + * @param {float} from:float The original value to start the tween from + * @param {Vector3} to:float The final Vector3 with which to tween to + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + */ + public static LTDescr value(GameObject gameObject, Action callOnUpdate, float from, float to, float time) + { + return pushNewTween(gameObject, new Vector3(to, 0, 0), time, options().setCallback().setTo(new Vector3(to, 0, 0)).setFrom(new Vector3(from, 0, 0)).setOnUpdate(callOnUpdate, gameObject)); + } + + public static LTDescr delayedSound(AudioClip audio, Vector3 pos, float volume) + { + //Debug.LogError("Delay sound??"); + return pushNewTween(tweenEmpty, pos, 0f, options().setDelayedSound().setTo(pos).setFrom(new Vector3(volume, 0, 0)).setAudio(audio)); + } + + public static LTDescr delayedSound(GameObject gameObject, AudioClip audio, Vector3 pos, float volume) + { + //Debug.LogError("Delay sound??"); + return pushNewTween(gameObject, pos, 0f, options().setDelayedSound().setTo(pos).setFrom(new Vector3(volume, 0, 0)).setAudio(audio)); + } + +#if !UNITY_3_5 && !UNITY_4_0 && !UNITY_4_0_1 && !UNITY_4_1 && !UNITY_4_2 && !UNITY_4_3 && !UNITY_4_5 + + /** + * Move a RectTransform object (used in Unity GUI in 4.6+, for Buttons, Panel, Scrollbar, etc...) + * + * @method LeanTween.move (RectTransform) + * @param {RectTransform} rectTrans:RectTransform RectTransform that you wish to attach the tween to + * @param {Vector3} to:Vector3 The final Vector3 with which to tween to + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example LeanTween.move(gameObject.GetComponent<RectTransform>(), new Vector3(200f,-100f,0f), 1f).setDelay(1f); + */ + public static LTDescr move(RectTransform rectTrans, Vector3 to, float time) + { + return pushNewTween(rectTrans.gameObject, to, time, options().setCanvasMove().setRect(rectTrans)); + } + + /** + * Move a RectTransform object affecting x-axis only (used in Unity GUI in 4.6+, for Buttons, Panel, Scrollbar, etc...) + * + * @method LeanTween.moveX (RectTransform) + * @param {RectTransform} rectTrans:RectTransform RectTransform that you wish to attach the tween to + * @param {float} to:float The final x location with which to tween to + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example LeanTween.moveX(gameObject.GetComponent<RectTransform>(), 200f, 1f).setDelay(1f); + */ + public static LTDescr moveX(RectTransform rectTrans, float to, float time) + { + return pushNewTween(rectTrans.gameObject, new Vector3(to, 0f, 0f), time, options().setCanvasMoveX().setRect(rectTrans)); + } + + /** + * Move a RectTransform object affecting y-axis only (used in Unity GUI in 4.6+, for Buttons, Panel, Scrollbar, etc...) + * + * @method LeanTween.moveY (RectTransform) + * @param {RectTransform} rectTrans:RectTransform RectTransform that you wish to attach the tween to + * @param {float} to:float The final y location with which to tween to + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example LeanTween.moveY(gameObject.GetComponent<RectTransform>(), 200f, 1f).setDelay(1f); + */ + public static LTDescr moveY(RectTransform rectTrans, float to, float time) + { + return pushNewTween(rectTrans.gameObject, new Vector3(to, 0f, 0f), time, options().setCanvasMoveY().setRect(rectTrans)); + } + + /** + * Move a RectTransform object affecting z-axis only (used in Unity GUI in 4.6+, for Buttons, Panel, Scrollbar, etc...)n + * + * @method LeanTween.moveZ (RectTransform) + * @param {RectTransform} rectTrans:RectTransform RectTransform that you wish to attach the tween to + * @param {float} to:float The final x location with which to tween to + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example LeanTween.moveZ(gameObject.GetComponent<RectTransform>(), 200f, 1f).setDelay(1f); + */ + public static LTDescr moveZ(RectTransform rectTrans, float to, float time) + { + return pushNewTween(rectTrans.gameObject, new Vector3(to, 0f, 0f), time, options().setCanvasMoveZ().setRect(rectTrans)); + } + + /** + * Rotate a RectTransform object (used in Unity GUI in 4.6+, for Buttons, Panel, Scrollbar, etc...) + * + * @method LeanTween.rotate (RectTransform) + * @param {RectTransform} rectTrans:RectTransform RectTransform that you wish to attach the tween to + * @param {float} to:float The degree with which to rotate the RectTransform + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example LeanTween.rotate(gameObject.GetComponent<RectTransform>(), 90f, 1f).setDelay(1f); + */ + public static LTDescr rotate(RectTransform rectTrans, float to, float time) + { + return pushNewTween(rectTrans.gameObject, new Vector3(to, 0f, 0f), time, options().setCanvasRotateAround().setRect(rectTrans).setAxis(Vector3.forward)); + } + + public static LTDescr rotate(RectTransform rectTrans, Vector3 to, float time) + { + return pushNewTween(rectTrans.gameObject, to, time, options().setCanvasRotateAround().setRect(rectTrans).setAxis(Vector3.forward)); + } + + /** + * Rotate a RectTransform object (used in Unity GUI in 4.6+, for Buttons, Panel, Scrollbar, etc...) + * + * @method LeanTween.rotateAround (RectTransform) + * @param {RectTransform} rectTrans:RectTransform RectTransform that you wish to attach the tween to + * @param {Vector3} axis:Vector3 The axis in which to rotate the RectTransform (Vector3.forward is most commonly used) + * @param {float} to:float The degree with which to rotate the RectTransform + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example LeanTween.rotateAround(gameObject.GetComponent<RectTransform>(), Vector3.forward, 90f, 1f).setDelay(1f); + */ + public static LTDescr rotateAround(RectTransform rectTrans, Vector3 axis, float to, float time) + { + return pushNewTween(rectTrans.gameObject, new Vector3(to, 0f, 0f), time, options().setCanvasRotateAround().setRect(rectTrans).setAxis(axis)); + } + + /** + * Rotate a RectTransform object around it's local axis (used in Unity GUI in 4.6+, for Buttons, Panel, Scrollbar, etc...) + * + * @method LeanTween.rotateAroundLocal (RectTransform) + * @param {RectTransform} rectTrans:RectTransform RectTransform that you wish to attach the tween to + * @param {Vector3} axis:Vector3 The local axis in which to rotate the RectTransform (Vector3.forward is most commonly used) + * @param {float} to:float The degree with which to rotate the RectTransform + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example LeanTween.rotateAroundLocal(gameObject.GetComponent<RectTransform>(), Vector3.forward, 90f, 1f).setDelay(1f); + */ + public static LTDescr rotateAroundLocal(RectTransform rectTrans, Vector3 axis, float to, float time) + { + return pushNewTween(rectTrans.gameObject, new Vector3(to, 0f, 0f), time, options().setCanvasRotateAroundLocal().setRect(rectTrans).setAxis(axis)); + } + + /** + * Scale a RectTransform object (used in Unity GUI in 4.6+, for Buttons, Panel, Scrollbar, etc...) + * + * @method LeanTween.scale (RectTransform) + * @param {RectTransform} rectTrans:RectTransform RectTransform that you wish to attach the tween to + * @param {Vector3} to:Vector3 The final Vector3 with which to tween to (localScale) + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example LeanTween.scale(gameObject.GetComponent<RectTransform>(), gameObject.GetComponent<RectTransform>().localScale*2f, 1f).setDelay(1f); + */ + public static LTDescr scale(RectTransform rectTrans, Vector3 to, float time) + { + return pushNewTween(rectTrans.gameObject, to, time, options().setCanvasScale().setRect(rectTrans)); + } + + /** + * Change the sizeDelta of a RectTransform object (used in Unity Canvas, for Buttons, Panel, Scrollbar, etc...) + * + * @method LeanTween.size (RectTransform) + * @param {RectTransform} rectTrans:RectTransform RectTransform that you wish to attach the tween to + * @param {Vector2} to:Vector2 The final Vector2 the tween will end at for sizeDelta property + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example LeanTween.size(gameObject.GetComponent<RectTransform>(), gameObject.GetComponent<RectTransform>().sizeDelta*2f, 1f).setDelay(1f); + */ + public static LTDescr size(RectTransform rectTrans, Vector2 to, float time) + { + return pushNewTween(rectTrans.gameObject, to, time, options().setCanvasSizeDelta().setRect(rectTrans)); + } + + /** + * Alpha an Image Component attached to a RectTransform (used in Unity GUI in 4.6+, for Buttons, Panel, Scrollbar, etc...) + * + * @method LeanTween.alpha (RectTransform) + * @param {RectTransform} rectTrans:RectTransform RectTransform that you wish to attach the tween to + * @param {float} to:float The final Vector3 with which to tween to (localScale) + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example LeanTween.alpha(gameObject.GetComponent<RectTransform>(), 0.5f, 1f).setDelay(1f); + */ + public static LTDescr alpha(RectTransform rectTrans, float to, float time) + { + return pushNewTween(rectTrans.gameObject, new Vector3(to, 0f, 0f), time, options().setCanvasAlpha().setRect(rectTrans)); + } + + /** + * Change the Color of an Image Component attached to a RectTransform (used in Unity GUI in 4.6+, for Buttons, Panel, Scrollbar, etc...) + * + * @method LeanTween.alpha (RectTransform) + * @param {RectTransform} rectTrans:RectTransform RectTransform that you wish to attach the tween to + * @param {float} to:float The final Vector3 with which to tween to (localScale) + * @param {float} time:float The time to complete the tween in + * @return {LTDescr} LTDescr an object that distinguishes the tween + * @example LeanTween.color(gameObject.GetComponent<RectTransform>(), 0.5f, 1f).setDelay(1f); + */ + public static LTDescr color(RectTransform rectTrans, Color to, float time) + { + return pushNewTween(rectTrans.gameObject, new Vector3(1.0f, to.a, 0.0f), time, options().setCanvasColor().setRect(rectTrans).setPoint(new Vector3(to.r, to.g, to.b))); + } + +#endif + + // Tweening Functions - Thanks to Robert Penner and GFX47 + + public static float tweenOnCurve(LTDescr tweenDescr, float ratioPassed) + { + // Debug.Log("single ratio:"+ratioPassed+" tweenDescr.animationCurve.Evaluate(ratioPassed):"+tweenDescr.animationCurve.Evaluate(ratioPassed)); + return tweenDescr.from.x + (tweenDescr.diff.x) * tweenDescr.optional.animationCurve.Evaluate(ratioPassed); + } + + public static Vector3 tweenOnCurveVector(LTDescr tweenDescr, float ratioPassed) + { + return new Vector3(tweenDescr.from.x + (tweenDescr.diff.x) * tweenDescr.optional.animationCurve.Evaluate(ratioPassed), + tweenDescr.from.y + (tweenDescr.diff.y) * tweenDescr.optional.animationCurve.Evaluate(ratioPassed), + tweenDescr.from.z + (tweenDescr.diff.z) * tweenDescr.optional.animationCurve.Evaluate(ratioPassed)); + } + + public static float easeOutQuadOpt(float start, float diff, float ratioPassed) + { + return -diff * ratioPassed * (ratioPassed - 2) + start; + } + + public static float easeInQuadOpt(float start, float diff, float ratioPassed) + { + return diff * ratioPassed * ratioPassed + start; + } + + public static float easeInOutQuadOpt(float start, float diff, float ratioPassed) + { + ratioPassed /= .5f; + if (ratioPassed < 1) return diff / 2 * ratioPassed * ratioPassed + start; + ratioPassed--; + return -diff / 2 * (ratioPassed * (ratioPassed - 2) - 1) + start; + } + + public static Vector3 easeInOutQuadOpt(Vector3 start, Vector3 diff, float ratioPassed) + { + ratioPassed /= .5f; + if (ratioPassed < 1) return diff / 2 * ratioPassed * ratioPassed + start; + ratioPassed--; + return -diff / 2 * (ratioPassed * (ratioPassed - 2) - 1) + start; + } + + public static float linear(float start, float end, float val) + { + return Mathf.Lerp(start, end, val); + } + + public static float clerp(float start, float end, float val) + { + float min = 0.0f; + float max = 360.0f; + float half = Mathf.Abs((max - min) / 2.0f); + float retval = 0.0f; + float diff = 0.0f; + if ((end - start) < -half) + { + diff = ((max - start) + end) * val; + retval = start + diff; + } + else if ((end - start) > half) + { + diff = -((max - end) + start) * val; + retval = start + diff; + } + else retval = start + (end - start) * val; + return retval; + } + + public static float spring(float start, float end, float val) + { + val = Mathf.Clamp01(val); + val = (Mathf.Sin(val * Mathf.PI * (0.2f + 2.5f * val * val * val)) * Mathf.Pow(1f - val, 2.2f) + val) * (1f + (1.2f * (1f - val))); + return start + (end - start) * val; + } + + public static float easeInQuad(float start, float end, float val) + { + end -= start; + return end * val * val + start; + } + + public static float easeOutQuad(float start, float end, float val) + { + end -= start; + return -end * val * (val - 2) + start; + } + + public static float easeInOutQuad(float start, float end, float val) + { + val /= .5f; + end -= start; + if (val < 1) return end / 2 * val * val + start; + val--; + return -end / 2 * (val * (val - 2) - 1) + start; + } + + + public static float easeInOutQuadOpt2(float start, float diffBy2, float val, float val2) + { + val /= .5f; + if (val < 1) return diffBy2 * val2 + start; + val--; + return -diffBy2 * ((val2 - 2) - 1f) + start; + } + + public static float easeInCubic(float start, float end, float val) + { + end -= start; + return end * val * val * val + start; + } + + public static float easeOutCubic(float start, float end, float val) + { + val--; + end -= start; + return end * (val * val * val + 1) + start; + } + + public static float easeInOutCubic(float start, float end, float val) + { + val /= .5f; + end -= start; + if (val < 1) return end / 2 * val * val * val + start; + val -= 2; + return end / 2 * (val * val * val + 2) + start; + } + + public static float easeInQuart(float start, float end, float val) + { + end -= start; + return end * val * val * val * val + start; + } + + public static float easeOutQuart(float start, float end, float val) + { + val--; + end -= start; + return -end * (val * val * val * val - 1) + start; + } + + public static float easeInOutQuart(float start, float end, float val) + { + val /= .5f; + end -= start; + if (val < 1) return end / 2 * val * val * val * val + start; + val -= 2; + return -end / 2 * (val * val * val * val - 2) + start; + } + + public static float easeInQuint(float start, float end, float val) + { + end -= start; + return end * val * val * val * val * val + start; + } + + public static float easeOutQuint(float start, float end, float val) + { + val--; + end -= start; + return end * (val * val * val * val * val + 1) + start; + } + + public static float easeInOutQuint(float start, float end, float val) + { + val /= .5f; + end -= start; + if (val < 1) return end / 2 * val * val * val * val * val + start; + val -= 2; + return end / 2 * (val * val * val * val * val + 2) + start; + } + + public static float easeInSine(float start, float end, float val) + { + end -= start; + return -end * Mathf.Cos(val / 1 * (Mathf.PI / 2)) + end + start; + } + + public static float easeOutSine(float start, float end, float val) + { + end -= start; + return end * Mathf.Sin(val / 1 * (Mathf.PI / 2)) + start; + } + + public static float easeInOutSine(float start, float end, float val) + { + end -= start; + return -end / 2 * (Mathf.Cos(Mathf.PI * val / 1) - 1) + start; + } + + public static float easeInExpo(float start, float end, float val) + { + end -= start; + return end * Mathf.Pow(2, 10 * (val / 1 - 1)) + start; + } + + public static float easeOutExpo(float start, float end, float val) + { + end -= start; + return end * (-Mathf.Pow(2, -10 * val / 1) + 1) + start; + } + + public static float easeInOutExpo(float start, float end, float val) + { + val /= .5f; + end -= start; + if (val < 1) return end / 2 * Mathf.Pow(2, 10 * (val - 1)) + start; + val--; + return end / 2 * (-Mathf.Pow(2, -10 * val) + 2) + start; + } + + public static float easeInCirc(float start, float end, float val) + { + end -= start; + return -end * (Mathf.Sqrt(1 - val * val) - 1) + start; + } + + public static float easeOutCirc(float start, float end, float val) + { + val--; + end -= start; + return end * Mathf.Sqrt(1 - val * val) + start; + } + + public static float easeInOutCirc(float start, float end, float val) + { + val /= .5f; + end -= start; + if (val < 1) return -end / 2 * (Mathf.Sqrt(1 - val * val) - 1) + start; + val -= 2; + return end / 2 * (Mathf.Sqrt(1 - val * val) + 1) + start; + } + + public static float easeInBounce(float start, float end, float val) + { + end -= start; + float d = 1f; + return end - easeOutBounce(0, end, d - val) + start; + } + + public static float easeOutBounce(float start, float end, float val) + { + val /= 1f; + end -= start; + if (val < (1 / 2.75f)) + { + return end * (7.5625f * val * val) + start; + } + else if (val < (2 / 2.75f)) + { + val -= (1.5f / 2.75f); + return end * (7.5625f * (val) * val + .75f) + start; + } + else if (val < (2.5 / 2.75)) + { + val -= (2.25f / 2.75f); + return end * (7.5625f * (val) * val + .9375f) + start; + } + else + { + val -= (2.625f / 2.75f); + return end * (7.5625f * (val) * val + .984375f) + start; + } + } + + public static float easeInOutBounce(float start, float end, float val) + { + end -= start; + float d = 1f; + if (val < d / 2) return easeInBounce(0, end, val * 2) * 0.5f + start; + else return easeOutBounce(0, end, val * 2 - d) * 0.5f + end * 0.5f + start; + } + + public static float easeInBack(float start, float end, float val, float overshoot = 1.0f) + { + end -= start; + val /= 1; + float s = 1.70158f * overshoot; + return end * (val) * val * ((s + 1) * val - s) + start; + } + + public static float easeOutBack(float start, float end, float val, float overshoot = 1.0f) + { + float s = 1.70158f * overshoot; + end -= start; + val = (val / 1) - 1; + return end * ((val) * val * ((s + 1) * val + s) + 1) + start; + } + + public static float easeInOutBack(float start, float end, float val, float overshoot = 1.0f) + { + float s = 1.70158f * overshoot; + end -= start; + val /= .5f; + if ((val) < 1) + { + s *= (1.525f) * overshoot; + return end / 2 * (val * val * (((s) + 1) * val - s)) + start; + } + val -= 2; + s *= (1.525f) * overshoot; + return end / 2 * ((val) * val * (((s) + 1) * val + s) + 2) + start; + } + + public static float easeInElastic(float start, float end, float val, float overshoot = 1.0f, float period = 0.3f) + { + end -= start; + + float p = period; + float s = 0f; + float a = 0f; + + if (val == 0f) return start; + + if (val == 1f) return start + end; + + if (a == 0f || a < Mathf.Abs(end)) + { + a = end; + s = p / 4f; + } + else + { + s = p / (2f * Mathf.PI) * Mathf.Asin(end / a); + } + + if (overshoot > 1f && val > 0.6f) + overshoot = 1f + ((1f - val) / 0.4f * (overshoot - 1f)); + // Debug.Log("ease in elastic val:"+val+" a:"+a+" overshoot:"+overshoot); + + val = val - 1f; + return start - (a * Mathf.Pow(2f, 10f * val) * Mathf.Sin((val - s) * (2f * Mathf.PI) / p)) * overshoot; + } + + public static float easeOutElastic(float start, float end, float val, float overshoot = 1.0f, float period = 0.3f) + { + end -= start; + + float p = period; + float s = 0f; + float a = 0f; + + if (val == 0f) return start; + + // Debug.Log("ease out elastic val:"+val+" a:"+a); + if (val == 1f) return start + end; + + if (a == 0f || a < Mathf.Abs(end)) + { + a = end; + s = p / 4f; + } + else + { + s = p / (2f * Mathf.PI) * Mathf.Asin(end / a); + } + if (overshoot > 1f && val < 0.4f) + overshoot = 1f + (val / 0.4f * (overshoot - 1f)); + // Debug.Log("ease out elastic val:"+val+" a:"+a+" overshoot:"+overshoot); + + return start + end + a * Mathf.Pow(2f, -10f * val) * Mathf.Sin((val - s) * (2f * Mathf.PI) / p) * overshoot; + } + + public static float easeInOutElastic(float start, float end, float val, float overshoot = 1.0f, float period = 0.3f) + { + end -= start; + + float p = period; + float s = 0f; + float a = 0f; + + if (val == 0f) return start; + + val = val / (1f / 2f); + if (val == 2f) return start + end; + + if (a == 0f || a < Mathf.Abs(end)) + { + a = end; + s = p / 4f; + } + else + { + s = p / (2f * Mathf.PI) * Mathf.Asin(end / a); + } + + if (overshoot > 1f) + { + if (val < 0.2f) + { + overshoot = 1f + (val / 0.2f * (overshoot - 1f)); + } + else if (val > 0.8f) + { + overshoot = 1f + ((1f - val) / 0.2f * (overshoot - 1f)); + } + } + + if (val < 1f) + { + val = val - 1f; + return start - 0.5f * (a * Mathf.Pow(2f, 10f * val) * Mathf.Sin((val - s) * (2f * Mathf.PI) / p)) * overshoot; + } + val = val - 1f; + return end + start + a * Mathf.Pow(2f, -10f * val) * Mathf.Sin((val - s) * (2f * Mathf.PI) / p) * 0.5f * overshoot; + } + + // Mark: LeanTween Following + + /** + * Follow another transforms position/scale/color with a damp transition (eases in and out to destination with no overshoot) + * + * @method LeanTween.followDamp + * @param {Transform} transform:Transform the transform you wish to be the follower + * @param {Transform} transform:Transform the transform you wish to follow + * @param {LeanProp} leanProp:LeanProp enum of the type of following you wish to do position, scale, color, etc. + * @param {float} smoothTime:float roughly the time it takes to reach the destination + * @param {float} [maxSpeed]:float maximum speed at which it moves towards the destination + * @example + * LeanTween.followDamp(transform, followTransform, LeanProp.localY, 1.1f); + */ + public static LTDescr followDamp(Transform trans, Transform target, LeanProp prop, float smoothTime, float maxSpeed = -1f) + { + var d = pushNewTween(trans.gameObject, Vector3.zero, float.MaxValue, options().setFollow().setTarget(target)); + + switch (prop) + { + case LeanProp.localPosition: + d.optional.axis = d.trans.localPosition; + d.easeInternal = () => + { + d.optional.axis = LeanSmooth.damp(d.optional.axis, d.toTrans.localPosition, ref d.fromInternal, smoothTime, maxSpeed, Time.deltaTime); + d.trans.localPosition = d.optional.axis + d.toInternal; + }; break; + case LeanProp.position: + d.diff = d.trans.position; + d.easeInternal = () => + { + d.optional.axis = LeanSmooth.damp(d.optional.axis, d.toTrans.position, ref d.fromInternal, smoothTime, maxSpeed, Time.deltaTime); + d.trans.position = d.optional.axis + d.toInternal; + }; break; + case LeanProp.localX: + d.easeInternal = () => + { + d.trans.LeanSetLocalPosX(LeanSmooth.damp(d.trans.localPosition.x, d.toTrans.localPosition.x, ref d.fromInternal.x, smoothTime, maxSpeed, Time.deltaTime)); + }; break; + case LeanProp.localY: + d.easeInternal = () => + { + d.trans.LeanSetLocalPosY(LeanSmooth.damp(d.trans.localPosition.y, d.toTrans.localPosition.y, ref d.fromInternal.y, smoothTime, maxSpeed, Time.deltaTime)); + }; break; + case LeanProp.localZ: + d.easeInternal = () => + { + d.trans.LeanSetLocalPosZ(LeanSmooth.damp(d.trans.localPosition.z, d.toTrans.localPosition.z, ref d.fromInternal.z, smoothTime, maxSpeed, Time.deltaTime)); + }; break; + case LeanProp.x: + d.easeInternal = () => + { + d.trans.LeanSetPosX(LeanSmooth.damp(d.trans.position.x, d.toTrans.position.x, ref d.fromInternal.x, smoothTime, maxSpeed, Time.deltaTime)); + }; break; + case LeanProp.y: + d.easeInternal = () => + { + d.trans.LeanSetPosY(LeanSmooth.damp(d.trans.position.y, d.toTrans.position.y, ref d.fromInternal.y, smoothTime, maxSpeed, Time.deltaTime)); + }; break; + case LeanProp.z: + d.easeInternal = () => + { + d.trans.LeanSetPosZ(LeanSmooth.damp(d.trans.position.z, d.toTrans.position.z, ref d.fromInternal.z, smoothTime, maxSpeed, Time.deltaTime)); + }; break; + case LeanProp.scale: + d.easeInternal = () => + { + d.trans.localScale = LeanSmooth.damp(d.trans.localScale, d.toTrans.localScale, ref d.fromInternal, smoothTime, maxSpeed, Time.deltaTime); + }; break; + case LeanProp.color: + d.easeInternal = () => + { + var col = LeanSmooth.damp(d.trans.LeanColor(), d.toTrans.LeanColor(), ref d.optional.color, smoothTime, maxSpeed, Time.deltaTime); + d.trans.GetComponent().material.color = col; + }; break; + } + + return d; + } + + /** + * Follow another transforms position/scale/color with a springy transition (eases in and out to destination with possible overshoot bounciness) + * + * @method LeanTween.followSpring + * @param {Transform} transform:Transform the transform you wish to be the follower + * @param {Transform} transform:Transform the transform you wish to follow + * @param {LeanProp} leanProp:LeanProp enum of the type of following you wish to do position, scale, color, etc. + * @param {float} smoothTime:float roughly the time it takes to reach the destination + * @param {float} [maxSpeed]:float maximum speed at which it moves towards the destination + * @param {float} [friction]:float rate at which the spring is slowed down once it reaches it's destination + * @param {float} [accelRate]:float the rate it accelerates from it's initial position + * @example + * LeanTween.followSpring(transform, followTransform, LeanProp.localY); + */ + public static LTDescr followSpring(Transform trans, Transform target, LeanProp prop, float smoothTime, float maxSpeed = -1f, float friction = 2f, float accelRate = 0.5f) + { + var d = pushNewTween(trans.gameObject, Vector3.zero, float.MaxValue, options().setFollow().setTarget(target)); + switch (prop) + { + case LeanProp.localPosition: + d.optional.axis = d.trans.localPosition; + d.easeInternal = () => + { + d.optional.axis = LeanSmooth.spring(d.optional.axis, d.toTrans.localPosition, ref d.fromInternal, smoothTime, maxSpeed, Time.deltaTime, friction, accelRate); + d.trans.localPosition = d.optional.axis + d.toInternal; + }; break; + case LeanProp.position: + d.diff = d.trans.position; + d.easeInternal = () => + { + d.diff = LeanSmooth.spring(d.diff, d.toTrans.position, ref d.fromInternal, smoothTime, maxSpeed, Time.deltaTime, friction, accelRate); + d.trans.position = d.diff;// + d.toInternal; + }; break; + case LeanProp.localX: + d.easeInternal = () => + { + d.trans.LeanSetLocalPosX(LeanSmooth.spring(d.trans.localPosition.x, d.toTrans.localPosition.x, ref d.fromInternal.x, smoothTime, maxSpeed, Time.deltaTime, friction, accelRate)); + }; break; + case LeanProp.localY: + d.easeInternal = () => + { + d.trans.LeanSetLocalPosY(LeanSmooth.spring(d.trans.localPosition.y, d.toTrans.localPosition.y, ref d.fromInternal.y, smoothTime, maxSpeed, Time.deltaTime, friction, accelRate)); + }; break; + case LeanProp.localZ: + d.easeInternal = () => + { + d.trans.LeanSetLocalPosZ(LeanSmooth.spring(d.trans.localPosition.z, d.toTrans.localPosition.z, ref d.fromInternal.z, smoothTime, maxSpeed, Time.deltaTime, friction, accelRate)); + }; break; + case LeanProp.x: + d.easeInternal = () => + { + d.trans.LeanSetPosX(LeanSmooth.spring(d.trans.position.x, d.toTrans.position.x, ref d.fromInternal.x, smoothTime, maxSpeed, Time.deltaTime, friction, accelRate)); + }; break; + case LeanProp.y: + d.easeInternal = () => + { + d.trans.LeanSetPosY(LeanSmooth.spring(d.trans.position.y, d.toTrans.position.y, ref d.fromInternal.y, smoothTime, maxSpeed, Time.deltaTime, friction, accelRate)); + }; break; + case LeanProp.z: + d.easeInternal = () => + { + d.trans.LeanSetPosZ(LeanSmooth.spring(d.trans.position.z, d.toTrans.position.z, ref d.fromInternal.z, smoothTime, maxSpeed, Time.deltaTime, friction, accelRate)); + }; break; + case LeanProp.scale: + d.easeInternal = () => + { + d.trans.localScale = LeanSmooth.spring(d.trans.localScale, d.toTrans.localScale, ref d.fromInternal, smoothTime, maxSpeed, Time.deltaTime, friction, accelRate); + }; break; + case LeanProp.color: + d.easeInternal = () => + { + var col = LeanSmooth.spring(d.trans.LeanColor(), d.toTrans.LeanColor(), ref d.optional.color, smoothTime, maxSpeed, Time.deltaTime, friction, accelRate); + d.trans.GetComponent().material.color = col; + }; break; + } + + return d; + } + + /** + * Follow another transforms position/scale/color (with an ease that bounces back some when it reaches it's destination) + * + * @method LeanTween.followBounceOut + * @param {Transform} transform:Transform the transform you wish to be the follower + * @param {Transform} transform:Transform the transform you wish to follow + * @param {LeanProp} leanProp:LeanProp enum of the type of following you wish to do position, scale, color, etc. + * @param {float} smoothTime:float roughly the time it takes to reach the destination + * @param {float} [maxSpeed]:float maximum speed at which it moves towards the destination + * @param {float} [friction]:float rate at which the spring is slowed down once it reaches it's destination + * @param {float} [accelRate]:float the rate it accelerates from it's initial position + * @param {float} [hitDamp]:float the rate at which to dampen the bounciness of when it reaches it's destination + * @example + * LeanTween.followBounceOut(transform, followTransform, LeanProp.localY, 1.1f); + */ + public static LTDescr followBounceOut(Transform trans, Transform target, LeanProp prop, float smoothTime, float maxSpeed = -1f, float friction = 2f, float accelRate = 0.5f, float hitDamping = 0.9f) + { + var d = pushNewTween(trans.gameObject, Vector3.zero, float.MaxValue, options().setFollow().setTarget(target)); + switch (prop) + { + case LeanProp.localPosition: + d.optional.axis = d.trans.localPosition; + d.easeInternal = () => + { + d.optional.axis = LeanSmooth.bounceOut(d.optional.axis, d.toTrans.localPosition, ref d.fromInternal, smoothTime, maxSpeed, Time.deltaTime, friction, accelRate, hitDamping); + d.trans.localPosition = d.optional.axis + d.toInternal; + }; break; + case LeanProp.position: + d.easeInternal = () => + { + d.optional.axis = LeanSmooth.bounceOut(d.optional.axis, d.toTrans.position, ref d.fromInternal, smoothTime, maxSpeed, Time.deltaTime, friction, accelRate, hitDamping); + d.trans.position = d.optional.axis + d.toInternal; + }; break; + case LeanProp.localX: + d.easeInternal = () => + { + d.trans.LeanSetLocalPosX(LeanSmooth.bounceOut(d.trans.localPosition.x, d.toTrans.localPosition.x, ref d.fromInternal.x, smoothTime, maxSpeed, Time.deltaTime, friction, accelRate, hitDamping)); + }; break; + case LeanProp.localY: + d.easeInternal = () => + { + d.trans.LeanSetLocalPosY(LeanSmooth.bounceOut(d.trans.localPosition.y, d.toTrans.localPosition.y, ref d.fromInternal.y, smoothTime, maxSpeed, Time.deltaTime, friction, accelRate, hitDamping)); + }; break; + case LeanProp.localZ: + d.easeInternal = () => + { + d.trans.LeanSetLocalPosZ(LeanSmooth.bounceOut(d.trans.localPosition.z, d.toTrans.localPosition.z, ref d.fromInternal.z, smoothTime, maxSpeed, Time.deltaTime, friction, accelRate, hitDamping)); + }; break; + case LeanProp.x: + d.easeInternal = () => + { + d.trans.LeanSetPosX(LeanSmooth.bounceOut(d.trans.position.x, d.toTrans.position.x, ref d.fromInternal.x, smoothTime, maxSpeed, Time.deltaTime, friction, accelRate, hitDamping)); + }; break; + case LeanProp.y: + d.easeInternal = () => + { + d.trans.LeanSetPosY(LeanSmooth.bounceOut(d.trans.position.y, d.toTrans.position.y, ref d.fromInternal.y, smoothTime, maxSpeed, Time.deltaTime, friction, accelRate, hitDamping)); + }; break; + case LeanProp.z: + d.easeInternal = () => + { + d.trans.LeanSetPosZ(LeanSmooth.bounceOut(d.trans.position.z, d.toTrans.position.z, ref d.fromInternal.z, smoothTime, maxSpeed, Time.deltaTime, friction, accelRate, hitDamping)); + }; break; + case LeanProp.scale: + d.easeInternal = () => + { + d.trans.localScale = LeanSmooth.bounceOut(d.trans.localScale, d.toTrans.localScale, ref d.fromInternal, smoothTime, maxSpeed, Time.deltaTime, friction, accelRate, hitDamping); + }; break; + case LeanProp.color: + d.easeInternal = () => + { + var col = LeanSmooth.bounceOut(d.trans.LeanColor(), d.toTrans.LeanColor(), ref d.optional.color, smoothTime, maxSpeed, Time.deltaTime, friction, accelRate, hitDamping); + d.trans.GetComponent().material.color = col; + }; break; + } + + return d; + } + + /** + * Follow another transforms position/scale/color with a constant speed + * + * @method LeanTween.followLinear + * @param {Transform} transform:Transform the transform you wish to be the follower + * @param {Transform} transform:Transform the transform you wish to follow + * @param {LeanProp} leanProp:LeanProp enum of the type of following you wish to do position, scale, color, etc. + * @param {float} moveSpeed:float roughly the time it takes to reach the destination + * @example + * LeanTween.followLinear(transform, followTransform, LeanProp.localY, 50f); + */ + public static LTDescr followLinear(Transform trans, Transform target, LeanProp prop, float moveSpeed) + { + var d = pushNewTween(trans.gameObject, Vector3.zero, float.MaxValue, options().setFollow().setTarget(target)); + switch (prop) + { + case LeanProp.localPosition: + d.optional.axis = d.trans.localPosition; + d.easeInternal = () => + { + d.optional.axis = LeanSmooth.linear(d.optional.axis, d.toTrans.localPosition, moveSpeed); + d.trans.localPosition = d.optional.axis + d.toInternal; + }; break; + case LeanProp.position: + d.easeInternal = () => + { + d.trans.position = LeanSmooth.linear(d.trans.position, d.toTrans.position, moveSpeed); + }; break; + case LeanProp.localX: + d.easeInternal = () => + { + d.trans.LeanSetLocalPosX(LeanSmooth.linear(d.trans.localPosition.x, d.toTrans.localPosition.x, moveSpeed)); + }; break; + case LeanProp.localY: + d.easeInternal = () => + { + d.trans.LeanSetLocalPosY(LeanSmooth.linear(d.trans.localPosition.y, d.toTrans.localPosition.y, moveSpeed)); + }; break; + case LeanProp.localZ: + d.easeInternal = () => + { + d.trans.LeanSetLocalPosZ(LeanSmooth.linear(d.trans.localPosition.z, d.toTrans.localPosition.z, moveSpeed)); + }; break; + case LeanProp.x: + d.easeInternal = () => + { + d.trans.LeanSetPosX(LeanSmooth.linear(d.trans.position.x, d.toTrans.position.x, moveSpeed)); + }; break; + case LeanProp.y: + d.easeInternal = () => + { + d.trans.LeanSetPosY(LeanSmooth.linear(d.trans.position.y, d.toTrans.position.y, moveSpeed)); + }; break; + case LeanProp.z: + d.easeInternal = () => + { + d.trans.LeanSetPosZ(LeanSmooth.linear(d.trans.position.z, d.toTrans.position.z, moveSpeed)); + }; break; + case LeanProp.scale: + d.easeInternal = () => + { + d.trans.localScale = LeanSmooth.linear(d.trans.localScale, d.toTrans.localScale, moveSpeed); + }; break; + case LeanProp.color: + d.easeInternal = () => + { + var col = LeanSmooth.linear(d.trans.LeanColor(), d.toTrans.LeanColor(), moveSpeed); + d.trans.GetComponent().material.color = col; + }; break; + } + + return d; + } + + // LeanTween Listening/Dispatch + + private static System.Action[] eventListeners; + private static GameObject[] goListeners; + private static int eventsMaxSearch = 0; + public static int EVENTS_MAX = 10; + public static int LISTENERS_MAX = 10; + private static int INIT_LISTENERS_MAX = LISTENERS_MAX; + + public static void addListener(int eventId, System.Action callback) + { + addListener(tweenEmpty, eventId, callback); + } + + /** + * Add a listener method to be called when the appropriate LeanTween.dispatchEvent is called + * + * @method LeanTween.addListener + * @param {GameObject} caller:GameObject the gameObject the listener is attached to + * @param {int} eventId:int a unique int that describes the event (best to use an enum) + * @param {System.Action} callback:System.Action the method to call when the event has been dispatched + * @example + * LeanTween.addListener(gameObject, (int)MyEvents.JUMP, jumpUp);
+ *
+ * void jumpUp( LTEvent e ){ Debug.Log("jump!"); }
+ */ + public static void addListener(GameObject caller, int eventId, System.Action callback) + { + if (eventListeners == null) + { + INIT_LISTENERS_MAX = LISTENERS_MAX; + eventListeners = new System.Action[EVENTS_MAX * LISTENERS_MAX]; + goListeners = new GameObject[EVENTS_MAX * LISTENERS_MAX]; + } + // Debug.Log("searching for an empty space for:"+caller + " eventid:"+event); + for (i = 0; i < INIT_LISTENERS_MAX; i++) + { + int point = eventId * INIT_LISTENERS_MAX + i; + if (goListeners[point] == null || eventListeners[point] == null) + { + eventListeners[point] = callback; + goListeners[point] = caller; + if (i >= eventsMaxSearch) + eventsMaxSearch = i + 1; + // Debug.Log("adding event for:"+caller.name); + + return; + } +#if UNITY_FLASH + if(goListeners[ point ] == caller && System.Object.ReferenceEquals( eventListeners[ point ], callback)){ + // Debug.Log("This event is already being listened for."); + return; + } +#else + if (goListeners[point] == caller && System.Object.Equals(eventListeners[point], callback)) + { + // Debug.Log("This event is already being listened for."); + return; + } +#endif + } + Debug.LogError("You ran out of areas to add listeners, consider increasing LISTENERS_MAX, ex: LeanTween.LISTENERS_MAX = " + (LISTENERS_MAX * 2)); + } + + public static bool removeListener(int eventId, System.Action callback) + { + return removeListener(tweenEmpty, eventId, callback); + } + + public static bool removeListener(int eventId) + { + int point = eventId * INIT_LISTENERS_MAX + i; + eventListeners[point] = null; + goListeners[point] = null; + return true; + } + + + /** + * Remove an event listener you have added + * @method LeanTween.removeListener + * @param {GameObject} caller:GameObject the gameObject the listener is attached to + * @param {int} eventId:int a unique int that describes the event (best to use an enum) + * @param {System.Action} callback:System.Action the method that was specified to call when the event has been dispatched + * @example + * LeanTween.removeListener(gameObject, (int)MyEvents.JUMP, jumpUp);
+ *
+ * void jumpUp( LTEvent e ){ }
+ */ + public static bool removeListener(GameObject caller, int eventId, System.Action callback) + { + for (i = 0; i < eventsMaxSearch; i++) + { + int point = eventId * INIT_LISTENERS_MAX + i; +#if UNITY_FLASH + if(goListeners[ point ] == caller && System.Object.ReferenceEquals( eventListeners[ point ], callback) ){ +#else + if (goListeners[point] == caller && System.Object.Equals(eventListeners[point], callback)) + { +#endif + eventListeners[point] = null; + goListeners[point] = null; + return true; + } + } + return false; + } + + /** + * Tell the added listeners that you are dispatching the event + * @method LeanTween.dispatchEvent + * @param {int} eventId:int a unique int that describes the event (best to use an enum) + * @example + * LeanTween.dispatchEvent( (int)MyEvents.JUMP );
+ */ + public static void dispatchEvent(int eventId) + { + dispatchEvent(eventId, null); + } + + /** + * Tell the added listeners that you are dispatching the event + * @method LeanTween.dispatchEvent + * @param {int} eventId:int a unique int that describes the event (best to use an enum) + * @param {object} data:object Pass data to the listener, access it from the listener with *.data on the LTEvent object + * @example + * LeanTween.dispatchEvent( (int)MyEvents.JUMP, transform );
+ *
+ * void jumpUp( LTEvent e ){
+ *   Transform tran = (Transform)e.data;
+ * }
+ */ + public static void dispatchEvent(int eventId, object data) + { + for (int k = 0; k < eventsMaxSearch; k++) + { + int point = eventId * INIT_LISTENERS_MAX + k; + if (eventListeners[point] != null) + { + if (goListeners[point]) + { + eventListeners[point](new LTEvent(eventId, data)); + } + else + { + eventListeners[point] = null; + } + } + } + } + + +} // End LeanTween class + +public class LTUtility +{ + + public static Vector3[] reverse(Vector3[] arr) + { + int length = arr.Length; + int left = 0; + int right = length - 1; + + for (; left < right; left += 1, right -= 1) + { + Vector3 temporary = arr[left]; + arr[left] = arr[right]; + arr[right] = temporary; + } + return arr; + } +} + +public class LTBezier +{ + public float length; + + private Vector3 a; + private Vector3 aa; + private Vector3 bb; + private Vector3 cc; + private float len; + private float[] arcLengths; + + public LTBezier(Vector3 a, Vector3 b, Vector3 c, Vector3 d, float precision) + { + this.a = a; + aa = (-a + 3 * (b - c) + d); + bb = 3 * (a + c) - 6 * b; + cc = 3 * (b - a); + + this.len = 1.0f / precision; + arcLengths = new float[(int)this.len + (int)1]; + arcLengths[0] = 0; + + Vector3 ov = a; + Vector3 v; + float clen = 0.0f; + for (int i = 1; i <= this.len; i++) + { + v = bezierPoint(i * precision); + clen += (ov - v).magnitude; + this.arcLengths[i] = clen; + ov = v; + } + this.length = clen; + } + + private float map(float u) + { + float targetLength = u * this.arcLengths[(int)this.len]; + int low = 0; + int high = (int)this.len; + int index = 0; + while (low < high) + { + index = low + ((int)((high - low) / 2.0f) | 0); + if (this.arcLengths[index] < targetLength) + { + low = index + 1; + } + else + { + high = index; + } + } + if (this.arcLengths[index] > targetLength) + index--; + if (index < 0) + index = 0; + + return (index + (targetLength - arcLengths[index]) / (arcLengths[index + 1] - arcLengths[index])) / this.len; + } + + private Vector3 bezierPoint(float t) + { + return ((aa * t + (bb)) * t + cc) * t + a; + } + + public Vector3 point(float t) + { + return bezierPoint(map(t)); + } +} + +/** +* Manually animate along a bezier path with this class +* @class LTBezierPath +* @constructor +* @param {Vector3 Array} pts A set of points that define one or many bezier paths (the paths should be passed in multiples of 4, which correspond to each individual bezier curve)
+* It goes in the order: startPoint,endControl,startControl,endPoint - Note: the control for the end and start are reversed! This is just a *quirk* of the API.
+* +* @example +* LTBezierPath ltPath = new LTBezierPath( new Vector3[] { new Vector3(0f,0f,0f),new Vector3(1f,0f,0f), new Vector3(1f,0f,0f), new Vector3(1f,1f,0f)} );

+* LeanTween.move(lt, ltPath.vec3, 4.0f).setOrientToPath(true).setDelay(1f).setEase(LeanTweenType.easeInOutQuad); // animate
+* Vector3 pt = ltPath.point( 0.6f ); // retrieve a point along the path +*/ +public class LTBezierPath +{ + public Vector3[] pts; + public float length; + public bool orientToPath; + public bool orientToPath2d; + + private LTBezier[] beziers; + private float[] lengthRatio; + private int currentBezier = 0, previousBezier = 0; + + public LTBezierPath() { } + public LTBezierPath(Vector3[] pts_) + { + setPoints(pts_); + } + + public void setPoints(Vector3[] pts_) + { + if (pts_.Length < 4) + LeanTween.logError("LeanTween - When passing values for a vector path, you must pass four or more values!"); + if (pts_.Length % 4 != 0) + LeanTween.logError("LeanTween - When passing values for a vector path, they must be in sets of four: controlPoint1, controlPoint2, endPoint2, controlPoint2, controlPoint2..."); + + pts = pts_; + + int k = 0; + beziers = new LTBezier[pts.Length / 4]; + lengthRatio = new float[beziers.Length]; + int i; + length = 0; + for (i = 0; i < pts.Length; i += 4) + { + beziers[k] = new LTBezier(pts[i + 0], pts[i + 2], pts[i + 1], pts[i + 3], 0.05f); + length += beziers[k].length; + k++; + } + // Debug.Log("beziers.Length:"+beziers.Length + " beziers:"+beziers); + for (i = 0; i < beziers.Length; i++) + { + lengthRatio[i] = beziers[i].length / length; + } + } + + /** + * @property {float} distance distance of the path (in unity units) + */ + public float distance + { + get + { + return length; + } + } + + /** + * Retrieve a point along a path + * + * @method point + * @param {float} ratio:float ratio of the point along the path you wish to receive (0-1) + * @return {Vector3} Vector3 position of the point along the path + * @example + * transform.position = ltPath.point( 0.6f ); + */ + public Vector3 point(float ratio) + { + float added = 0.0f; + for (int i = 0; i < lengthRatio.Length; i++) + { + added += lengthRatio[i]; + if (added >= ratio) + return beziers[i].point((ratio - (added - lengthRatio[i])) / lengthRatio[i]); + } + return beziers[lengthRatio.Length - 1].point(1.0f); + } + + public void place2d(Transform transform, float ratio) + { + transform.position = point(ratio); + ratio += 0.001f; + if (ratio <= 1.0f) + { + Vector3 v3Dir = point(ratio) - transform.position; + float angle = Mathf.Atan2(v3Dir.y, v3Dir.x) * Mathf.Rad2Deg; + transform.eulerAngles = new Vector3(0, 0, angle); + } + } + + public void placeLocal2d(Transform transform, float ratio) + { + transform.localPosition = point(ratio); + ratio += 0.001f; + if (ratio <= 1.0f) + { + Vector3 v3Dir = point(ratio) - transform.localPosition; + float angle = Mathf.Atan2(v3Dir.y, v3Dir.x) * Mathf.Rad2Deg; + transform.localEulerAngles = new Vector3(0, 0, angle); + } + } + + /** + * Place an object along a certain point on the path (facing the direction perpendicular to the path) + * + * @method place + * @param {Transform} transform:Transform the transform of the object you wish to place along the path + * @param {float} ratio:float ratio of the point along the path you wish to receive (0-1) + * @example + * ltPath.place( transform, 0.6f ); + */ + public void place(Transform transform, float ratio) + { + place(transform, ratio, Vector3.up); + + } + + /** + * Place an object along a certain point on the path, with it facing a certain direction perpendicular to the path + * + * @method place + * @param {Transform} transform:Transform the transform of the object you wish to place along the path + * @param {float} ratio:float ratio of the point along the path you wish to receive (0-1) + * @param {Vector3} rotation:Vector3 the direction in which to place the transform ex: Vector3.up + * @example + * ltPath.place( transform, 0.6f, Vector3.left ); + */ + public void place(Transform transform, float ratio, Vector3 worldUp) + { + transform.position = point(ratio); + ratio += 0.001f; + if (ratio <= 1.0f) + transform.LookAt(point(ratio), worldUp); + + } + + /** + * Place an object along a certain point on the path (facing the direction perpendicular to the path) - Local Space, not world-space + * + * @method placeLocal + * @param {Transform} transform:Transform the transform of the object you wish to place along the path + * @param {float} ratio:float ratio of the point along the path you wish to receive (0-1) + * @example + * ltPath.placeLocal( transform, 0.6f ); + */ + public void placeLocal(Transform transform, float ratio) + { + placeLocal(transform, ratio, Vector3.up); + } + + /** + * Place an object along a certain point on the path, with it facing a certain direction perpendicular to the path - Local Space, not world-space + * + * @method placeLocal + * @param {Transform} transform:Transform the transform of the object you wish to place along the path + * @param {float} ratio:float ratio of the point along the path you wish to receive (0-1) + * @param {Vector3} rotation:Vector3 the direction in which to place the transform ex: Vector3.up + * @example + * ltPath.placeLocal( transform, 0.6f, Vector3.left ); + */ + public void placeLocal(Transform transform, float ratio, Vector3 worldUp) + { + // Debug.Log("place ratio:" + ratio + " greater:"+(ratio>1f)); + ratio = Mathf.Clamp01(ratio); + transform.localPosition = point(ratio); + // Debug.Log("ratio:" + ratio + " +:" + (ratio + 0.001f)); + ratio = Mathf.Clamp01(ratio + 0.001f); + + if (ratio <= 1.0f) + transform.LookAt(transform.parent.TransformPoint(point(ratio)), worldUp); + } + + public void gizmoDraw(float t = -1.0f) + { + Vector3 prevPt = point(0); + + for (int i = 1; i <= 120; i++) + { + float pm = (float)i / 120f; + Vector3 currPt2 = point(pm); + //Gizmos.color = new Color(UnityEngine.Random.Range(0f,1f),UnityEngine.Random.Range(0f,1f),UnityEngine.Random.Range(0f,1f),1); + Gizmos.color = (previousBezier == currentBezier) ? Color.magenta : Color.grey; + Gizmos.DrawLine(currPt2, prevPt); + prevPt = currPt2; + previousBezier = currentBezier; + } + } + + /** + * Retrieve the closest ratio near the point + * + * @method ratioAtPoint + * @param {Vector3} point:Vector3 given a current location it makes the best approximiation of where it is along the path ratio-wise (0-1) + * @return {float} float of ratio along the path + * @example + * ratioIter = ltBezier.ratioAtPoint( transform.position ); + */ + public float ratioAtPoint(Vector3 pt, float precision = 0.01f) + { + float closestDist = float.MaxValue; + int closestI = 0; + int maxIndex = Mathf.RoundToInt(1f / precision); + for (int i = 0; i < maxIndex; i++) + { + float ratio = (float)i / (float)maxIndex; + float dist = Vector3.Distance(pt, point(ratio)); + // Debug.Log("i:"+i+" dist:"+dist); + if (dist < closestDist) + { + closestDist = dist; + closestI = i; + } + } + //Debug.Log("closestI:"+closestI+" maxIndex:"+maxIndex); + return (float)closestI / (float)(maxIndex); + } +} + +/** +* Animate along a set of points that need to be in the format: controlPoint, point1, point2.... pointLast, endControlPoint Move a GameObject to a certain location +* @class LTSpline +* @constructor +* @param {Vector3 Array} pts A set of points that define the points the path will pass through (starting with starting control point, and ending with a control point)
+Note: The first and last item just define the angle of the end points, they are not actually used in the spline path itself. If you do not care about the angle you can jus set the first two items and last two items as the same value. +* @example +* LTSpline ltSpline = new LTSpline( new Vector3[] { new Vector3(0f,0f,0f),new Vector3(0f,0f,0f), new Vector3(0f,0.5f,0f), new Vector3(1f,1f,0f), new Vector3(1f,1f,0f)} );

+* LeanTween.moveSpline(lt, ltSpline.vec3, 4.0f).setOrientToPath(true).setDelay(1f).setEase(LeanTweenType.easeInOutQuad); // animate
+* Vector3 pt = ltSpline.point( 0.6f ); // retrieve a point along the path +*/ +[System.Serializable] +public class LTSpline +{ + public static int DISTANCE_COUNT = 3; // increase for a more accurate constant speed + public static int SUBLINE_COUNT = 20; // increase for a more accurate smoothing of the curves into lines + + /** + * @property {float} distance distance of the spline (in unity units) + */ + public float distance = 0f; + + public bool constantSpeed = true; + + public Vector3[] pts; + [System.NonSerialized] + public Vector3[] ptsAdj; + public int ptsAdjLength; + public bool orientToPath; + public bool orientToPath2d; + private int numSections; + private int currPt; + + public LTSpline(Vector3[] pts) + { + init(pts, true); + } + + public LTSpline(Vector3[] pts, bool constantSpeed) + { + this.constantSpeed = constantSpeed; + init(pts, constantSpeed); + } + + private void init(Vector3[] pts, bool constantSpeed) + { + if (pts.Length < 4) + { + LeanTween.logError("LeanTween - When passing values for a spline path, you must pass four or more values!"); + return; + } + + this.pts = new Vector3[pts.Length]; + System.Array.Copy(pts, this.pts, pts.Length); + + numSections = pts.Length - 3; + + float minSegment = float.PositiveInfinity; + Vector3 earlierPoint = this.pts[1]; + float totalDistance = 0f; + for (int i = 1; i < this.pts.Length - 1; i++) + { + // float pointDistance = (this.pts[i]-earlierPoint).sqrMagnitude; + float pointDistance = Vector3.Distance(this.pts[i], earlierPoint); + //Debug.Log("pointDist:"+pointDistance); + if (pointDistance < minSegment) + { + minSegment = pointDistance; + } + + totalDistance += pointDistance; + } + + if (constantSpeed) + { + minSegment = totalDistance / (numSections * SUBLINE_COUNT); + //Debug.Log("minSegment:"+minSegment+" numSections:"+numSections); + + float minPrecision = minSegment / SUBLINE_COUNT; // number of subdivisions in each segment + int precision = (int)Mathf.Ceil(totalDistance / minPrecision) * DISTANCE_COUNT; + // Debug.Log("precision:"+precision); + if (precision <= 1) // precision has to be greater than one + precision = 2; + + ptsAdj = new Vector3[precision]; + earlierPoint = interp(0f); + int num = 1; + ptsAdj[0] = earlierPoint; + distance = 0f; + for (int i = 0; i < precision + 1; i++) + { + float fract = ((float)(i)) / precision; + // Debug.Log("fract:"+fract); + Vector3 point = interp(fract); + float dist = Vector3.Distance(point, earlierPoint); + + // float dist = (point-earlierPoint).sqrMagnitude; + if (dist >= minPrecision || fract >= 1.0f) + { + ptsAdj[num] = point; + distance += dist; // only add it to the total distance once we know we are adding it as an adjusted point + + earlierPoint = point; + // Debug.Log("fract:"+fract+" point:"+point); + num++; + } + } + // make sure there is a point at the very end + /*num++; + Vector3 endPoint = interp( 1f ); + ptsAdj[num] = endPoint;*/ + // Debug.Log("fract 1f endPoint:"+endPoint); + + ptsAdjLength = num; + } + // Debug.Log("map 1f:"+map(1f)+" end:"+ptsAdj[ ptsAdjLength-1 ]); + + // Debug.Log("ptsAdjLength:"+ptsAdjLength+" minPrecision:"+minPrecision+" precision:"+precision); + } + + public Vector3 map(float u) + { + if (u >= 1f) + return pts[pts.Length - 2]; + float t = u * (ptsAdjLength - 1); + int first = (int)Mathf.Floor(t); + int next = (int)Mathf.Ceil(t); + + if (first < 0) + first = 0; + + Vector3 val = ptsAdj[first]; + + + Vector3 nextVal = ptsAdj[next]; + float diff = t - first; + + // Debug.Log("u:"+u+" val:"+val +" nextVal:"+nextVal+" diff:"+diff+" first:"+first+" next:"+next); + + val = val + (nextVal - val) * diff; + + return val; + } + + public Vector3 interp(float t) + { + currPt = Mathf.Min(Mathf.FloorToInt(t * (float)numSections), numSections - 1); + float u = t * (float)numSections - (float)currPt; + + //Debug.Log("currPt:"+currPt+" numSections:"+numSections+" pts.Length :"+pts.Length ); + Vector3 a = pts[currPt]; + Vector3 b = pts[currPt + 1]; + Vector3 c = pts[currPt + 2]; + Vector3 d = pts[currPt + 3]; + + Vector3 val = (.5f * ( + (-a + 3f * b - 3f * c + d) * (u * u * u) + + (2f * a - 5f * b + 4f * c - d) * (u * u) + + (-a + c) * u + + 2f * b)); + // Debug.Log("currPt:"+currPt+" t:"+t+" val.x"+val.x+" y:"+val.y+" z:"+val.z); + + return val; + } + + /** + * Retrieve a point along a path Move a GameObject to a certain location + * + * @method ratioAtPoint + * @param {Vector3} point:Vector3 given a current location it makes the best approximiation of where it is along the path ratio-wise (0-1) + * @return {float} float of ratio along the path + * @example + * ratioIter = ltSpline.ratioAtPoint( transform.position ); + */ + public float ratioAtPoint(Vector3 pt) + { + float closestDist = float.MaxValue; + int closestI = 0; + for (int i = 0; i < ptsAdjLength; i++) + { + float dist = Vector3.Distance(pt, ptsAdj[i]); + // Debug.Log("i:"+i+" dist:"+dist); + if (dist < closestDist) + { + closestDist = dist; + closestI = i; + } + } + // Debug.Log("closestI:"+closestI+" ptsAdjLength:"+ptsAdjLength); + return (float)closestI / (float)(ptsAdjLength - 1); + } + + /** + * Retrieve a point along a path Move a GameObject to a certain location + * + * @method point + * @param {float} ratio:float ratio of the point along the path you wish to receive (0-1) + * @return {Vector3} Vector3 position of the point along the path + * @example + * transform.position = ltSpline.point( 0.6f ); + */ + public Vector3 point(float ratio) + { + float t = ratio > 1f ? 1f : ratio; + return constantSpeed ? map(t) : interp(t); + } + + public void place2d(Transform transform, float ratio) + { + transform.position = point(ratio); + ratio += 0.001f; + if (ratio <= 1.0f) + { + Vector3 v3Dir = point(ratio) - transform.position; + float angle = Mathf.Atan2(v3Dir.y, v3Dir.x) * Mathf.Rad2Deg; + transform.eulerAngles = new Vector3(0, 0, angle); + } + } + + public void placeLocal2d(Transform transform, float ratio) + { + Transform trans = transform.parent; + if (trans == null) + { // this has no parent, just do a regular transform + place2d(transform, ratio); + return; + } + transform.localPosition = point(ratio); + ratio += 0.001f; + if (ratio <= 1.0f) + { + Vector3 ptAhead = point(ratio);//trans.TransformPoint( ); + Vector3 v3Dir = ptAhead - transform.localPosition; + float angle = Mathf.Atan2(v3Dir.y, v3Dir.x) * Mathf.Rad2Deg; + transform.localEulerAngles = new Vector3(0, 0, angle); + } + } + + + /** + * Place an object along a certain point on the path (facing the direction perpendicular to the path) Move a GameObject to a certain location + * + * @method place + * @param {Transform} transform:Transform the transform of the object you wish to place along the path + * @param {float} ratio:float ratio of the point along the path you wish to receive (0-1) + * @example + * ltPath.place( transform, 0.6f ); + */ + public void place(Transform transform, float ratio) + { + place(transform, ratio, Vector3.up); + } + + /** + * Place an object along a certain point on the path, with it facing a certain direction perpendicular to the path Move a GameObject to a certain location + * + * @method place + * @param {Transform} transform:Transform the transform of the object you wish to place along the path + * @param {float} ratio:float ratio of the point along the path you wish to receive (0-1) + * @param {Vector3} rotation:Vector3 the direction in which to place the transform ex: Vector3.up + * @example + * ltPath.place( transform, 0.6f, Vector3.left ); + */ + public void place(Transform transform, float ratio, Vector3 worldUp) + { + // ratio = Mathf.Repeat(ratio, 1.0f); // make sure ratio is always between 0-1 + transform.position = point(ratio); + ratio += 0.001f; + if (ratio <= 1.0f) + transform.LookAt(point(ratio), worldUp); + + } + + /** + * Place an object along a certain point on the path (facing the direction perpendicular to the path) - Local Space, not world-space Move a GameObject to a certain location + * + * @method placeLocal + * @param {Transform} transform:Transform the transform of the object you wish to place along the path + * @param {float} ratio:float ratio of the point along the path you wish to receive (0-1) + * @example + * ltPath.placeLocal( transform, 0.6f ); + */ + public void placeLocal(Transform transform, float ratio) + { + placeLocal(transform, ratio, Vector3.up); + } + + /** + * Place an object along a certain point on the path, with it facing a certain direction perpendicular to the path - Local Space, not world-space Move a GameObject to a certain location + * + * @method placeLocal + * @param {Transform} transform:Transform the transform of the object you wish to place along the path + * @param {float} ratio:float ratio of the point along the path you wish to receive (0-1) + * @param {Vector3} rotation:Vector3 the direction in which to place the transform ex: Vector3.up + * @example + * ltPath.placeLocal( transform, 0.6f, Vector3.left ); + */ + public void placeLocal(Transform transform, float ratio, Vector3 worldUp) + { + transform.localPosition = point(ratio); + ratio += 0.001f; + if (ratio <= 1.0f) + transform.LookAt(transform.parent.TransformPoint(point(ratio)), worldUp); + } + + public void gizmoDraw(float t = -1.0f) + { + if (ptsAdj == null || ptsAdj.Length <= 0) + return; + + Vector3 prevPt = ptsAdj[0]; + + for (int i = 0; i < ptsAdjLength; i++) + { + Vector3 currPt2 = ptsAdj[i]; + // Debug.Log("currPt2:"+currPt2); + //Gizmos.color = new Color(UnityEngine.Random.Range(0f,1f),UnityEngine.Random.Range(0f,1f),UnityEngine.Random.Range(0f,1f),1); + Gizmos.DrawLine(prevPt, currPt2); + prevPt = currPt2; + } + } + + public void drawGizmo(Color color) + { + if (this.ptsAdjLength >= 4) + { + + Vector3 prevPt = this.ptsAdj[0]; + + Color colorBefore = Gizmos.color; + Gizmos.color = color; + for (int i = 0; i < this.ptsAdjLength; i++) + { + Vector3 currPt2 = this.ptsAdj[i]; + // Debug.Log("currPt2:"+currPt2); + + Gizmos.DrawLine(prevPt, currPt2); + prevPt = currPt2; + } + Gizmos.color = colorBefore; + } + } + + public static void drawGizmo(Transform[] arr, Color color) + { + if (arr.Length >= 4) + { + Vector3[] vec3s = new Vector3[arr.Length]; + for (int i = 0; i < arr.Length; i++) + { + vec3s[i] = arr[i].position; + } + LTSpline spline = new LTSpline(vec3s); + Vector3 prevPt = spline.ptsAdj[0]; + + Color colorBefore = Gizmos.color; + Gizmos.color = color; + for (int i = 0; i < spline.ptsAdjLength; i++) + { + Vector3 currPt2 = spline.ptsAdj[i]; + // Debug.Log("currPt2:"+currPt2); + + Gizmos.DrawLine(prevPt, currPt2); + prevPt = currPt2; + } + Gizmos.color = colorBefore; + } + } + + + public static void drawLine(Transform[] arr, float width, Color color) + { + if (arr.Length >= 4) + { + + } + } + + /*public Vector3 Velocity(float t) { + t = map( t ); + + int numSections = pts.Length - 3; + int currPt = Mathf.Min(Mathf.FloorToInt(t * (float) numSections), numSections - 1); + float u = t * (float) numSections - (float) currPt; + + Vector3 a = pts[currPt]; + Vector3 b = pts[currPt + 1]; + Vector3 c = pts[currPt + 2]; + Vector3 d = pts[currPt + 3]; + + return 1.5f * (-a + 3f * b - 3f * c + d) * (u * u) + + (2f * a -5f * b + 4f * c - d) * u + + .5f * c - .5f * a; + }*/ + + public void drawLinesGLLines(Material outlineMaterial, Color color, float width) + { + GL.PushMatrix(); + outlineMaterial.SetPass(0); + GL.LoadPixelMatrix(); + GL.Begin(GL.LINES); + GL.Color(color); + + if (constantSpeed) + { + if (this.ptsAdjLength >= 4) + { + + Vector3 prevPt = this.ptsAdj[0]; + + for (int i = 0; i < this.ptsAdjLength; i++) + { + Vector3 currPt2 = this.ptsAdj[i]; + GL.Vertex(prevPt); + GL.Vertex(currPt2); + + prevPt = currPt2; + } + } + + } + else + { + if (this.pts.Length >= 4) + { + + Vector3 prevPt = this.pts[0]; + + float split = 1f / ((float)this.pts.Length * 10f); + + float iter = 0f; + while (iter < 1f) + { + float at = iter / 1f; + Vector3 currPt2 = interp(at); + // Debug.Log("currPt2:"+currPt2); + + GL.Vertex(prevPt); + GL.Vertex(currPt2); + + prevPt = currPt2; + + iter += split; + } + } + } + + + GL.End(); + GL.PopMatrix(); + + } + + public Vector3[] generateVectors() + { + if (this.pts.Length >= 4) + { + List meshPoints = new List(); + Vector3 prevPt = this.pts[0]; + meshPoints.Add(prevPt); + + float split = 1f / ((float)this.pts.Length * 10f); + + float iter = 0f; + while (iter < 1f) + { + float at = iter / 1f; + Vector3 currPt2 = interp(at); + // Debug.Log("currPt2:"+currPt2); + + // GL.Vertex(prevPt); + // GL.Vertex(currPt2); + meshPoints.Add(currPt2); + + // prevPt = currPt2; + + iter += split; + } + + meshPoints.ToArray(); + } + return null; + } +} + +/** +* Animate GUI Elements by creating this object and passing the *.rect variable to the GUI method

+* Example Javascript:
var bRect:LTRect = new LTRect( 0, 0, 100, 50 );
+* LeanTween.scale( bRect, Vector2(bRect.rect.width, bRect.rect.height) * 1.3, 0.25 );
+* function OnGUI(){
+*   if(GUI.Button(bRect.rect, "Scale")){ }
+* }
+*
+* Example C#:
+* LTRect bRect = new LTRect( 0f, 0f, 100f, 50f );
+* LeanTween.scale( bRect, new Vector2(150f,75f), 0.25f );
+* void OnGUI(){
+*   if(GUI.Button(bRect.rect, "Scale")){ }
+* }
+* +* @class LTRect +* @constructor +* @param {float} x:float X location +* @param {float} y:float Y location +* @param {float} width:float Width +* @param {float} height:float Height +* @param {float} alpha:float (Optional) initial alpha amount (0-1) +* @param {float} rotation:float (Optional) initial rotation in degrees (0-360) +*/ + +[System.Serializable] +public class LTRect : System.Object +{ + /** + * Pass this value to the GUI Methods + * + * @property rect + * @type {Rect} rect:Rect Rect object that controls the positioning and size + */ + public Rect _rect; + public float alpha = 1f; + public float rotation; + public Vector2 pivot; + public Vector2 margin; + public Rect relativeRect = new Rect(0f, 0f, float.PositiveInfinity, float.PositiveInfinity); + + public bool rotateEnabled; + [HideInInspector] + public bool rotateFinished; + public bool alphaEnabled; + public string labelStr; + public LTGUI.Element_Type type; + public GUIStyle style; + public bool useColor = false; + public Color color = Color.white; + public bool fontScaleToFit; + public bool useSimpleScale; + public bool sizeByHeight; + + public Texture texture; + + private int _id = -1; + [HideInInspector] + public int counter; + + public static bool colorTouched; + + public LTRect() + { + reset(); + this.rotateEnabled = this.alphaEnabled = true; + _rect = new Rect(0f, 0f, 1f, 1f); + } + + public LTRect(Rect rect) + { + _rect = rect; + reset(); + } + + public LTRect(float x, float y, float width, float height) + { + _rect = new Rect(x, y, width, height); + this.alpha = 1.0f; + this.rotation = 0.0f; + this.rotateEnabled = this.alphaEnabled = false; + } + + public LTRect(float x, float y, float width, float height, float alpha) + { + _rect = new Rect(x, y, width, height); + this.alpha = alpha; + this.rotation = 0.0f; + this.rotateEnabled = this.alphaEnabled = false; + } + + public LTRect(float x, float y, float width, float height, float alpha, float rotation) + { + _rect = new Rect(x, y, width, height); + this.alpha = alpha; + this.rotation = rotation; + this.rotateEnabled = this.alphaEnabled = false; + if (rotation != 0.0f) + { + this.rotateEnabled = true; + resetForRotation(); + } + } + + public bool hasInitiliazed + { + get + { + return _id != -1; + } + } + + public int id + { + get + { + int toId = _id | counter << 16; + + /*uint backId = toId & 0xFFFF; + uint backCounter = toId >> 16; + if(_id!=backId || backCounter!=counter){ + Debug.LogError("BAD CONVERSION toId:"+_id); + }*/ + + return toId; + } + } + + public void setId(int id, int counter) + { + this._id = id; + this.counter = counter; + } + + public void reset() + { + this.alpha = 1.0f; + this.rotation = 0.0f; + this.rotateEnabled = this.alphaEnabled = false; + this.margin = Vector2.zero; + this.sizeByHeight = false; + this.useColor = false; + } + + public void resetForRotation() + { + Vector3 scale = new Vector3(GUI.matrix[0, 0], GUI.matrix[1, 1], GUI.matrix[2, 2]); + if (pivot == Vector2.zero) + { + pivot = new Vector2((_rect.x + ((_rect.width) * 0.5f)) * scale.x + GUI.matrix[0, 3], (_rect.y + ((_rect.height) * 0.5f)) * scale.y + GUI.matrix[1, 3]); + } + } + + public float x + { + get { return _rect.x; } + set { _rect.x = value; } + } + + public float y + { + get { return _rect.y; } + set { _rect.y = value; } + } + + public float width + { + get { return _rect.width; } + set { _rect.width = value; } + } + + public float height + { + get { return _rect.height; } + set { _rect.height = value; } + } + + public Rect rect + { + + get + { + if (colorTouched) + { + colorTouched = false; + GUI.color = new Color(GUI.color.r, GUI.color.g, GUI.color.b, 1.0f); + } + if (rotateEnabled) + { + if (rotateFinished) + { + rotateFinished = false; + rotateEnabled = false; + //this.rotation = 0.0f; + pivot = Vector2.zero; + } + else + { + GUIUtility.RotateAroundPivot(rotation, pivot); + } + } + if (alphaEnabled) + { + GUI.color = new Color(GUI.color.r, GUI.color.g, GUI.color.b, alpha); + colorTouched = true; + } + if (fontScaleToFit) + { + if (this.useSimpleScale) + { + style.fontSize = (int)(_rect.height * this.relativeRect.height); + } + else + { + style.fontSize = (int)_rect.height; + } + } + return _rect; + } + + set + { + _rect = value; + } + } + + public LTRect setStyle(GUIStyle style) + { + this.style = style; + return this; + } + + public LTRect setFontScaleToFit(bool fontScaleToFit) + { + this.fontScaleToFit = fontScaleToFit; + return this; + } + + public LTRect setColor(Color color) + { + this.color = color; + this.useColor = true; + return this; + } + + public LTRect setAlpha(float alpha) + { + this.alpha = alpha; + return this; + } + + public LTRect setLabel(String str) + { + this.labelStr = str; + return this; + } + + public LTRect setUseSimpleScale(bool useSimpleScale, Rect relativeRect) + { + this.useSimpleScale = useSimpleScale; + this.relativeRect = relativeRect; + return this; + } + + public LTRect setUseSimpleScale(bool useSimpleScale) + { + this.useSimpleScale = useSimpleScale; + this.relativeRect = new Rect(0f, 0f, Screen.width, Screen.height); + return this; + } + + public LTRect setSizeByHeight(bool sizeByHeight) + { + this.sizeByHeight = sizeByHeight; + return this; + } + + public override string ToString() + { + return "x:" + _rect.x + " y:" + _rect.y + " width:" + _rect.width + " height:" + _rect.height; + } +} + +/** +* Object that describes the event to an event listener +* @class LTEvent +* @constructor +* @param {object} data:object Data that has been passed from the dispatchEvent method +*/ +public class LTEvent +{ + public int id; + public object data; + + public LTEvent(int id, object data) + { + this.id = id; + this.data = data; + } +} + +public class LTGUI +{ + public static int RECT_LEVELS = 5; + public static int RECTS_PER_LEVEL = 10; + public static int BUTTONS_MAX = 24; + + private static LTRect[] levels; + private static int[] levelDepths; + private static Rect[] buttons; + private static int[] buttonLevels; + private static int[] buttonLastFrame; + private static LTRect r; + private static Color color = Color.white; + private static bool isGUIEnabled = false; + private static int global_counter = 0; + + public enum Element_Type + { + Texture, + Label + } + + public static void init() + { + if (levels == null) + { + levels = new LTRect[RECT_LEVELS * RECTS_PER_LEVEL]; + levelDepths = new int[RECT_LEVELS]; + } + } + + public static void initRectCheck() + { + if (buttons == null) + { + buttons = new Rect[BUTTONS_MAX]; + buttonLevels = new int[BUTTONS_MAX]; + buttonLastFrame = new int[BUTTONS_MAX]; + for (int i = 0; i < buttonLevels.Length; i++) + { + buttonLevels[i] = -1; + } + } + } + + public static void reset() + { + if (isGUIEnabled) + { + isGUIEnabled = false; + for (int i = 0; i < levels.Length; i++) + { + levels[i] = null; + } + + for (int i = 0; i < levelDepths.Length; i++) + { + levelDepths[i] = 0; + } + } + } + + public static void update(int updateLevel) + { + if (isGUIEnabled) + { + init(); + if (levelDepths[updateLevel] > 0) + { + color = GUI.color; + int baseI = updateLevel * RECTS_PER_LEVEL; + int maxLoop = baseI + levelDepths[updateLevel];// RECTS_PER_LEVEL;//; + + for (int i = baseI; i < maxLoop; i++) + { + r = levels[i]; + // Debug.Log("r:"+r+" i:"+i); + if (r != null /*&& checkOnScreen(r.rect)*/) + { + //Debug.Log("label:"+r.labelStr+" textColor:"+r.style.normal.textColor); + if (r.useColor) + GUI.color = r.color; + if (r.type == Element_Type.Label) + { + if (r.style != null) + GUI.skin.label = r.style; + if (r.useSimpleScale) + { + GUI.Label(new Rect((r.rect.x + r.margin.x + r.relativeRect.x) * r.relativeRect.width, (r.rect.y + r.margin.y + r.relativeRect.y) * r.relativeRect.height, r.rect.width * r.relativeRect.width, r.rect.height * r.relativeRect.height), r.labelStr); + } + else + { + GUI.Label(new Rect(r.rect.x + r.margin.x, r.rect.y + r.margin.y, r.rect.width, r.rect.height), r.labelStr); + } + } + else if (r.type == Element_Type.Texture && r.texture != null) + { + Vector2 size = r.useSimpleScale ? new Vector2(0f, r.rect.height * r.relativeRect.height) : new Vector2(r.rect.width, r.rect.height); + if (r.sizeByHeight) + { + size.x = (float)r.texture.width / (float)r.texture.height * size.y; + } + if (r.useSimpleScale) + { + GUI.DrawTexture(new Rect((r.rect.x + r.margin.x + r.relativeRect.x) * r.relativeRect.width, (r.rect.y + r.margin.y + r.relativeRect.y) * r.relativeRect.height, size.x, size.y), r.texture); + } + else + { + GUI.DrawTexture(new Rect(r.rect.x + r.margin.x, r.rect.y + r.margin.y, size.x, size.y), r.texture); + } + } + } + } + GUI.color = color; + } + } + } + + public static bool checkOnScreen(Rect rect) + { + bool offLeft = rect.x + rect.width < 0f; + bool offRight = rect.x > Screen.width; + bool offBottom = rect.y > Screen.height; + bool offTop = rect.y + rect.height < 0f; + + return !(offLeft || offRight || offBottom || offTop); + } + + public static void destroy(int id) + { + int backId = id & 0xFFFF; + int backCounter = id >> 16; + if (id >= 0 && levels[backId] != null && levels[backId].hasInitiliazed && levels[backId].counter == backCounter) + levels[backId] = null; + } + + public static void destroyAll(int depth) + { // clears all gui elements on depth + int maxLoop = depth * RECTS_PER_LEVEL + RECTS_PER_LEVEL; + for (int i = depth * RECTS_PER_LEVEL; levels != null && i < maxLoop; i++) + { + levels[i] = null; + } + } + + public static LTRect label(Rect rect, string label, int depth) + { + return LTGUI.label(new LTRect(rect), label, depth); + } + + public static LTRect label(LTRect rect, string label, int depth) + { + rect.type = Element_Type.Label; + rect.labelStr = label; + return element(rect, depth); + } + + public static LTRect texture(Rect rect, Texture texture, int depth) + { + return LTGUI.texture(new LTRect(rect), texture, depth); + } + + public static LTRect texture(LTRect rect, Texture texture, int depth) + { + rect.type = Element_Type.Texture; + rect.texture = texture; + return element(rect, depth); + } + + public static LTRect element(LTRect rect, int depth) + { + isGUIEnabled = true; + init(); + int maxLoop = depth * RECTS_PER_LEVEL + RECTS_PER_LEVEL; + int k = 0; + if (rect != null) + { + destroy(rect.id); + } + if (rect.type == LTGUI.Element_Type.Label && rect.style != null) + { + if (rect.style.normal.textColor.a <= 0f) + { + Debug.LogWarning("Your GUI normal color has an alpha of zero, and will not be rendered."); + } + } + if (rect.relativeRect.width == float.PositiveInfinity) + { + rect.relativeRect = new Rect(0f, 0f, Screen.width, Screen.height); + } + for (int i = depth * RECTS_PER_LEVEL; i < maxLoop; i++) + { + r = levels[i]; + if (r == null) + { + r = rect; + r.rotateEnabled = true; + r.alphaEnabled = true; + r.setId(i, global_counter); + levels[i] = r; + // Debug.Log("k:"+k+ " maxDepth:"+levelDepths[depth]); + if (k >= levelDepths[depth]) + { + levelDepths[depth] = k + 1; + } + global_counter++; + return r; + } + k++; + } + + Debug.LogError("You ran out of GUI Element spaces"); + + return null; + } + + public static bool hasNoOverlap(Rect rect, int depth) + { + initRectCheck(); + bool hasNoOverlap = true; + bool wasAddedToList = false; + for (int i = 0; i < buttonLevels.Length; i++) + { + // Debug.Log("buttonLastFrame["+i+"]:"+buttonLastFrame[i]); + //Debug.Log("buttonLevels["+i+"]:"+buttonLevels[i]); + if (buttonLevels[i] >= 0) + { + //Debug.Log("buttonLastFrame["+i+"]:"+buttonLastFrame[i]+" Time.frameCount:"+Time.frameCount); + if (buttonLastFrame[i] + 1 < Time.frameCount) + { // It has to have been visible within the current, or + buttonLevels[i] = -1; + // Debug.Log("resetting i:"+i); + } + else + { + //if(buttonLevels[i]>=0) + // Debug.Log("buttonLevels["+i+"]:"+buttonLevels[i]); + if (buttonLevels[i] > depth) + { + /*if(firstTouch().x > 0){ + Debug.Log("buttons["+i+"]:"+buttons[i] + " firstTouch:"); + Debug.Log(firstTouch()); + Debug.Log(buttonLevels[i]); + }*/ + if (pressedWithinRect(buttons[i])) + { + hasNoOverlap = false; // there is an overlapping button that is higher + } + } + } + } + + if (wasAddedToList == false && buttonLevels[i] < 0) + { + wasAddedToList = true; + buttonLevels[i] = depth; + buttons[i] = rect; + buttonLastFrame[i] = Time.frameCount; + } + } + + return hasNoOverlap; + } + + public static bool pressedWithinRect(Rect rect) + { + Vector2 vec2 = firstTouch(); + if (vec2.x < 0f) + return false; + float vecY = Screen.height - vec2.y; + return (vec2.x > rect.x && vec2.x < rect.x + rect.width && vecY > rect.y && vecY < rect.y + rect.height); + } + + public static bool checkWithinRect(Vector2 vec2, Rect rect) + { + vec2.y = Screen.height - vec2.y; + return (vec2.x > rect.x && vec2.x < rect.x + rect.width && vec2.y > rect.y && vec2.y < rect.y + rect.height); + } + + public static Vector2 firstTouch() + { + if (Input.touchCount > 0) + { + return Input.touches[0].position; + } + else if (Input.GetMouseButton(0)) + { + return Input.mousePosition; + } + + return new Vector2(Mathf.NegativeInfinity, Mathf.NegativeInfinity); + } + +} + +namespace DentedPixel { public class LeanDummy { } } +//} diff --git a/KFAttached/LeanTween/Framework/LeanTween.cs.meta b/KFAttached/LeanTween/Framework/LeanTween.cs.meta new file mode 100644 index 0000000..33c8c76 --- /dev/null +++ b/KFAttached/LeanTween/Framework/LeanTween.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9c2f4b27196f84954b44753aaac214bb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/LeanTween/Framework/LeanTweenExt.cs b/KFAttached/LeanTween/Framework/LeanTweenExt.cs new file mode 100644 index 0000000..6c14ba2 --- /dev/null +++ b/KFAttached/LeanTween/Framework/LeanTweenExt.cs @@ -0,0 +1,188 @@ +using System; +using UnityEngine; + +public static class LeanTweenExt +{ + //LeanTween.addListener + //LeanTween.alpha + public static LTDescr LeanAlpha(this GameObject gameObject, float to, float time) { return LeanTween.alpha(gameObject, to, time); } + //LeanTween.alphaCanvas + public static LTDescr LeanAlphaVertex(this GameObject gameObject, float to, float time) { return LeanTween.alphaVertex(gameObject, to, time); } + //LeanTween.alpha (RectTransform) + public static LTDescr LeanAlpha(this RectTransform rectTransform, float to, float time) { return LeanTween.alpha(rectTransform, to, time); } + //LeanTween.alphaCanvas + public static LTDescr LeanAlpha(this CanvasGroup canvas, float to, float time) { return LeanTween.alphaCanvas(canvas, to, time); } + //LeanTween.alphaText + public static LTDescr LeanAlphaText(this RectTransform rectTransform, float to, float time) { return LeanTween.alphaText(rectTransform, to, time); } + //LeanTween.cancel + public static void LeanCancel(this GameObject gameObject) { LeanTween.cancel(gameObject); } + public static void LeanCancel(this GameObject gameObject, bool callOnComplete) { LeanTween.cancel(gameObject, callOnComplete); } + public static void LeanCancel(this GameObject gameObject, int uniqueId, bool callOnComplete = false) { LeanTween.cancel(gameObject, uniqueId, callOnComplete); } + //LeanTween.cancel + public static void LeanCancel(this RectTransform rectTransform) { LeanTween.cancel(rectTransform); } + //LeanTween.cancelAll + //LeanTween.color + public static LTDescr LeanColor(this GameObject gameObject, Color to, float time) { return LeanTween.color(gameObject, to, time); } + //LeanTween.colorText + public static LTDescr LeanColorText(this RectTransform rectTransform, Color to, float time) { return LeanTween.colorText(rectTransform, to, time); } + //LeanTween.delayedCall + public static LTDescr LeanDelayedCall(this GameObject gameObject, float delayTime, System.Action callback) { return LeanTween.delayedCall(gameObject, delayTime, callback); } + public static LTDescr LeanDelayedCall(this GameObject gameObject, float delayTime, System.Action callback) { return LeanTween.delayedCall(gameObject, delayTime, callback); } + + //LeanTween.isPaused + public static bool LeanIsPaused(this GameObject gameObject) { return LeanTween.isPaused(gameObject); } + public static bool LeanIsPaused(this RectTransform rectTransform) { return LeanTween.isPaused(rectTransform); } + + //LeanTween.isTweening + public static bool LeanIsTweening(this GameObject gameObject) { return LeanTween.isTweening(gameObject); } + //LeanTween.isTweening + //LeanTween.move + public static LTDescr LeanMove(this GameObject gameObject, Vector3 to, float time) { return LeanTween.move(gameObject, to, time); } + public static LTDescr LeanMove(this Transform transform, Vector3 to, float time) { return LeanTween.move(transform.gameObject, to, time); } + public static LTDescr LeanMove(this RectTransform rectTransform, Vector3 to, float time) { return LeanTween.move(rectTransform, to, time); } + //LeanTween.move + public static LTDescr LeanMove(this GameObject gameObject, Vector2 to, float time) { return LeanTween.move(gameObject, to, time); } + public static LTDescr LeanMove(this Transform transform, Vector2 to, float time) { return LeanTween.move(transform.gameObject, to, time); } + //LeanTween.move + public static LTDescr LeanMove(this GameObject gameObject, Vector3[] to, float time) { return LeanTween.move(gameObject, to, time); } + public static LTDescr LeanMove(this GameObject gameObject, LTBezierPath to, float time) { return LeanTween.move(gameObject, to, time); } + public static LTDescr LeanMove(this GameObject gameObject, LTSpline to, float time) { return LeanTween.move(gameObject, to, time); } + public static LTDescr LeanMove(this Transform transform, Vector3[] to, float time) { return LeanTween.move(transform.gameObject, to, time); } + public static LTDescr LeanMove(this Transform transform, LTBezierPath to, float time) { return LeanTween.move(transform.gameObject, to, time); } + public static LTDescr LeanMove(this Transform transform, LTSpline to, float time) { return LeanTween.move(transform.gameObject, to, time); } + //LeanTween.moveLocal + public static LTDescr LeanMoveLocal(this GameObject gameObject, Vector3 to, float time) { return LeanTween.moveLocal(gameObject, to, time); } + public static LTDescr LeanMoveLocal(this GameObject gameObject, LTBezierPath to, float time) { return LeanTween.moveLocal(gameObject, to, time); } + public static LTDescr LeanMoveLocal(this GameObject gameObject, LTSpline to, float time) { return LeanTween.moveLocal(gameObject, to, time); } + public static LTDescr LeanMoveLocal(this Transform transform, Vector3 to, float time) { return LeanTween.moveLocal(transform.gameObject, to, time); } + public static LTDescr LeanMoveLocal(this Transform transform, LTBezierPath to, float time) { return LeanTween.moveLocal(transform.gameObject, to, time); } + public static LTDescr LeanMoveLocal(this Transform transform, LTSpline to, float time) { return LeanTween.moveLocal(transform.gameObject, to, time); } + //LeanTween.moveLocal + public static LTDescr LeanMoveLocalX(this GameObject gameObject, float to, float time) { return LeanTween.moveLocalX(gameObject, to, time); } + public static LTDescr LeanMoveLocalY(this GameObject gameObject, float to, float time) { return LeanTween.moveLocalY(gameObject, to, time); } + public static LTDescr LeanMoveLocalZ(this GameObject gameObject, float to, float time) { return LeanTween.moveLocalZ(gameObject, to, time); } + public static LTDescr LeanMoveLocalX(this Transform transform, float to, float time) { return LeanTween.moveLocalX(transform.gameObject, to, time); } + public static LTDescr LeanMoveLocalY(this Transform transform, float to, float time) { return LeanTween.moveLocalY(transform.gameObject, to, time); } + public static LTDescr LeanMoveLocalZ(this Transform transform, float to, float time) { return LeanTween.moveLocalZ(transform.gameObject, to, time); } + //LeanTween.moveSpline + public static LTDescr LeanMoveSpline(this GameObject gameObject, Vector3[] to, float time) { return LeanTween.moveSpline(gameObject, to, time); } + public static LTDescr LeanMoveSpline(this GameObject gameObject, LTSpline to, float time) { return LeanTween.moveSpline(gameObject, to, time); } + public static LTDescr LeanMoveSpline(this Transform transform, Vector3[] to, float time) { return LeanTween.moveSpline(transform.gameObject, to, time); } + public static LTDescr LeanMoveSpline(this Transform transform, LTSpline to, float time) { return LeanTween.moveSpline(transform.gameObject, to, time); } + //LeanTween.moveSplineLocal + public static LTDescr LeanMoveSplineLocal(this GameObject gameObject, Vector3[] to, float time) { return LeanTween.moveSplineLocal(gameObject, to, time); } + public static LTDescr LeanMoveSplineLocal(this Transform transform, Vector3[] to, float time) { return LeanTween.moveSplineLocal(transform.gameObject, to, time); } + //LeanTween.moveX + public static LTDescr LeanMoveX(this GameObject gameObject, float to, float time) { return LeanTween.moveX(gameObject, to, time); } + public static LTDescr LeanMoveX(this Transform transform, float to, float time) { return LeanTween.moveX(transform.gameObject, to, time); } + //LeanTween.moveX (RectTransform) + public static LTDescr LeanMoveX(this RectTransform rectTransform, float to, float time) { return LeanTween.moveX(rectTransform, to, time); } + //LeanTween.moveY + public static LTDescr LeanMoveY(this GameObject gameObject, float to, float time) { return LeanTween.moveY(gameObject, to, time); } + public static LTDescr LeanMoveY(this Transform transform, float to, float time) { return LeanTween.moveY(transform.gameObject, to, time); } + //LeanTween.moveY (RectTransform) + public static LTDescr LeanMoveY(this RectTransform rectTransform, float to, float time) { return LeanTween.moveY(rectTransform, to, time); } + //LeanTween.moveZ + public static LTDescr LeanMoveZ(this GameObject gameObject, float to, float time) { return LeanTween.moveZ(gameObject, to, time); } + public static LTDescr LeanMoveZ(this Transform transform, float to, float time) { return LeanTween.moveZ(transform.gameObject, to, time); } + //LeanTween.moveZ (RectTransform) + public static LTDescr LeanMoveZ(this RectTransform rectTransform, float to, float time) { return LeanTween.moveZ(rectTransform, to, time); } + //LeanTween.pause + public static void LeanPause(this GameObject gameObject) { LeanTween.pause(gameObject); } + //LeanTween.play + public static LTDescr LeanPlay(this RectTransform rectTransform, UnityEngine.Sprite[] sprites) { return LeanTween.play(rectTransform, sprites); } + //LeanTween.removeListener + //LeanTween.resume + public static void LeanResume(this GameObject gameObject) { LeanTween.resume(gameObject); } + //LeanTween.resumeAll + //LeanTween.rotate + public static LTDescr LeanRotate(this GameObject gameObject, Vector3 to, float time) { return LeanTween.rotate(gameObject, to, time); } + public static LTDescr LeanRotate(this Transform transform, Vector3 to, float time) { return LeanTween.rotate(transform.gameObject, to, time); } + //LeanTween.rotate + //LeanTween.rotate (RectTransform) + public static LTDescr LeanRotate(this RectTransform rectTransform, Vector3 to, float time) { return LeanTween.rotate(rectTransform, to, time); } + //LeanTween.rotateAround + public static LTDescr LeanRotateAround(this GameObject gameObject, Vector3 axis, float add, float time) { return LeanTween.rotateAround(gameObject, axis, add, time); } + public static LTDescr LeanRotateAround(this Transform transform, Vector3 axis, float add, float time) { return LeanTween.rotateAround(transform.gameObject, axis, add, time); } + //LeanTween.rotateAround (RectTransform) + public static LTDescr LeanRotateAround(this RectTransform rectTransform, Vector3 axis, float add, float time) { return LeanTween.rotateAround(rectTransform, axis, add, time); } + //LeanTween.rotateAroundLocal + public static LTDescr LeanRotateAroundLocal(this GameObject gameObject, Vector3 axis, float add, float time) { return LeanTween.rotateAroundLocal(gameObject, axis, add, time); } + public static LTDescr LeanRotateAroundLocal(this Transform transform, Vector3 axis, float add, float time) { return LeanTween.rotateAroundLocal(transform.gameObject, axis, add, time); } + //LeanTween.rotateAround (RectTransform) + public static LTDescr LeanRotateAroundLocal(this RectTransform rectTransform, Vector3 axis, float add, float time) { return LeanTween.rotateAroundLocal(rectTransform, axis, add, time); } + //LeanTween.rotateLocal + //LeanTween.rotateX + public static LTDescr LeanRotateX(this GameObject gameObject, float to, float time) { return LeanTween.rotateX(gameObject, to, time); } + public static LTDescr LeanRotateX(this Transform transform, float to, float time) { return LeanTween.rotateX(transform.gameObject, to, time); } + //LeanTween.rotateY + public static LTDescr LeanRotateY(this GameObject gameObject, float to, float time) { return LeanTween.rotateY(gameObject, to, time); } + public static LTDescr LeanRotateY(this Transform transform, float to, float time) { return LeanTween.rotateY(transform.gameObject, to, time); } + //LeanTween.rotateZ + public static LTDescr LeanRotateZ(this GameObject gameObject, float to, float time) { return LeanTween.rotateZ(gameObject, to, time); } + public static LTDescr LeanRotateZ(this Transform transform, float to, float time) { return LeanTween.rotateZ(transform.gameObject, to, time); } + //LeanTween.scale + public static LTDescr LeanScale(this GameObject gameObject, Vector3 to, float time) { return LeanTween.scale(gameObject, to, time); } + public static LTDescr LeanScale(this Transform transform, Vector3 to, float time) { return LeanTween.scale(transform.gameObject, to, time); } + //LeanTween.scale (GUI) + //LeanTween.scale (RectTransform) + public static LTDescr LeanScale(this RectTransform rectTransform, Vector3 to, float time) { return LeanTween.scale(rectTransform, to, time); } + //LeanTween.scaleX + public static LTDescr LeanScaleX(this GameObject gameObject, float to, float time) { return LeanTween.scaleX(gameObject, to, time); } + public static LTDescr LeanScaleX(this Transform transform, float to, float time) { return LeanTween.scaleX(transform.gameObject, to, time); } + //LeanTween.scaleY + public static LTDescr LeanScaleY(this GameObject gameObject, float to, float time) { return LeanTween.scaleY(gameObject, to, time); } + public static LTDescr LeanScaleY(this Transform transform, float to, float time) { return LeanTween.scaleY(transform.gameObject, to, time); } + //LeanTween.scaleZ + public static LTDescr LeanScaleZ(this GameObject gameObject, float to, float time) { return LeanTween.scaleZ(gameObject, to, time); } + public static LTDescr LeanScaleZ(this Transform transform, float to, float time) { return LeanTween.scaleZ(transform.gameObject, to, time); } + //LeanTween.sequence + //LeanTween.size (RectTransform) + public static LTDescr LeanSize(this RectTransform rectTransform, Vector2 to, float time) { return LeanTween.size(rectTransform, to, time); } + //LeanTween.tweensRunning + //LeanTween.value (Color) + public static LTDescr LeanValue(this GameObject gameObject, Color from, Color to, float time) { return LeanTween.value(gameObject, from, to, time); } + //LeanTween.value (Color) + //LeanTween.value (float) + public static LTDescr LeanValue(this GameObject gameObject, float from, float to, float time) { return LeanTween.value(gameObject, from, to, time); } + public static LTDescr LeanValue(this GameObject gameObject, Vector2 from, Vector2 to, float time) { return LeanTween.value(gameObject, from, to, time); } + public static LTDescr LeanValue(this GameObject gameObject, Vector3 from, Vector3 to, float time) { return LeanTween.value(gameObject, from, to, time); } + //LeanTween.value (float) + public static LTDescr LeanValue(this GameObject gameObject, Action callOnUpdate, float from, float to, float time) { return LeanTween.value(gameObject, callOnUpdate, from, to, time); } + public static LTDescr LeanValue(this GameObject gameObject, Action callOnUpdate, float from, float to, float time) { return LeanTween.value(gameObject, callOnUpdate, from, to, time); } + public static LTDescr LeanValue(this GameObject gameObject, Action callOnUpdate, float from, float to, float time) { return LeanTween.value(gameObject, callOnUpdate, from, to, time); } + public static LTDescr LeanValue(this GameObject gameObject, Action callOnUpdate, Color from, Color to, float time) { return LeanTween.value(gameObject, callOnUpdate, from, to, time); } + public static LTDescr LeanValue(this GameObject gameObject, Action callOnUpdate, Vector2 from, Vector2 to, float time) { return LeanTween.value(gameObject, callOnUpdate, from, to, time); } + public static LTDescr LeanValue(this GameObject gameObject, Action callOnUpdate, Vector3 from, Vector3 to, float time) { return LeanTween.value(gameObject, callOnUpdate, from, to, time); } + + public static void LeanSetPosX(this Transform transform, float val) + { + transform.position = new Vector3(val, transform.position.y, transform.position.z); + } + public static void LeanSetPosY(this Transform transform, float val) + { + transform.position = new Vector3(transform.position.x, val, transform.position.z); + } + public static void LeanSetPosZ(this Transform transform, float val) + { + transform.position = new Vector3(transform.position.x, transform.position.y, val); + } + + public static void LeanSetLocalPosX(this Transform transform, float val) + { + transform.localPosition = new Vector3(val, transform.localPosition.y, transform.localPosition.z); + } + public static void LeanSetLocalPosY(this Transform transform, float val) + { + transform.localPosition = new Vector3(transform.localPosition.x, val, transform.localPosition.z); + } + public static void LeanSetLocalPosZ(this Transform transform, float val) + { + transform.localPosition = new Vector3(transform.localPosition.x, transform.localPosition.y, val); + } + + public static Color LeanColor(this Transform transform) + { + return transform.GetComponent().material.color; + } +} diff --git a/KFAttached/LeanTween/Framework/LeanTweenExt.cs.meta b/KFAttached/LeanTween/Framework/LeanTweenExt.cs.meta new file mode 100644 index 0000000..2283703 --- /dev/null +++ b/KFAttached/LeanTween/Framework/LeanTweenExt.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5dbe851e9c0814f1d8f514ecf70f675d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/LeanTween/License.txt b/KFAttached/LeanTween/License.txt new file mode 100644 index 0000000..4f43b9a --- /dev/null +++ b/KFAttached/LeanTween/License.txt @@ -0,0 +1,31 @@ +The MIT License (MIT) + +Copyright (c) 2017 Russell Savage - Dented Pixel + +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. + +TERMS OF USE - EASING EQUATIONS +Open source under the BSD License. +Copyright (c)2001 Robert Penner +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of the author nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/KFAttached/LeanTween/License.txt.meta b/KFAttached/LeanTween/License.txt.meta new file mode 100644 index 0000000..b10e33d --- /dev/null +++ b/KFAttached/LeanTween/License.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e389e9bcd4f944c338327697bd209cad +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Misc.meta b/KFAttached/Misc.meta new file mode 100644 index 0000000..d8ab251 --- /dev/null +++ b/KFAttached/Misc.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e90139dce980d4840bf4b20c1114b015 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Misc/AimReference.cs b/KFAttached/Misc/AimReference.cs new file mode 100644 index 0000000..d4789d0 --- /dev/null +++ b/KFAttached/Misc/AimReference.cs @@ -0,0 +1,49 @@ +using System; +using UnityEngine; + +public class AimReference : MonoBehaviour +{ + [NonSerialized] + public Vector3 positionOffset; + [NonSerialized] + public Quaternion rotationOffset; + [NonSerialized] + public AimReferenceGroup group; + [NonSerialized] + public int index = -1; + [NonSerialized] + public ScopeBase scopeBase; + [SerializeField] + private GameObject scopeBindingObject; + public bool asReference; + + private void OnEnable() + { + if (!group) + { + return; + } + scopeBase = GetComponentInParent(); + Transform refTrans = scopeBase ? scopeBase.transform : transform.parent; + rotationOffset = Quaternion.Inverse(refTrans.rotation) * transform.rotation; + positionOffset = refTrans.InverseTransformDirection(transform.position - refTrans.position); + group.UpdateEnableStates(); + } + + private void OnDisable() + { + if (!group) + { + return; + } + group.UpdateEnableStates(); + } + + public void UpdateEnableState(bool state) + { + if (scopeBindingObject) + { + scopeBindingObject.SetActive(state); + } + } +} \ No newline at end of file diff --git a/KFAttached/Misc/AimReference.cs.meta b/KFAttached/Misc/AimReference.cs.meta new file mode 100644 index 0000000..b7cd99c --- /dev/null +++ b/KFAttached/Misc/AimReference.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 53a21d5a88ab0034c8b0db6ea40e7d85 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Misc/AimReferenceGroup.cs b/KFAttached/Misc/AimReferenceGroup.cs new file mode 100644 index 0000000..1433bd9 --- /dev/null +++ b/KFAttached/Misc/AimReferenceGroup.cs @@ -0,0 +1,62 @@ +#if NotEditor +using KFCommonUtilityLib; +#endif +using UnityEngine; + +public class AimReferenceGroup : MonoBehaviour +{ + [SerializeField] + public AimReference[] aimReferences; + private bool registered; + +#if NotEditor + private ActionModuleProceduralAiming.ProceduralAimingData data; + private void Awake() + { + if (aimReferences != null) + { + foreach (var reference in aimReferences) + { + reference.group = this; + } + } + + EntityPlayerLocal player = GetComponentInParent(); + if (aimReferences == null || aimReferences.Length == 0 || !player || player.inventory?.holdingItemData?.actionData?[1] is not IModuleContainerFor module) + { + Destroy(this); + if (aimReferences != null) + { + foreach (var reference in aimReferences) + { + Destroy(reference); + } + aimReferences = null; + } + return; + } + data = module.Instance; + } + + private void OnEnable() + { + if (registered) + { + return; + } + registered = data.RegisterGroup(aimReferences, gameObject.name); + } +#endif + + internal void UpdateEnableStates() + { +#if NotEditor + if (!registered) + { + return; + } + data.UpdateCurrentReference(); +#endif + } +} + diff --git a/KFAttached/Misc/AimReferenceGroup.cs.meta b/KFAttached/Misc/AimReferenceGroup.cs.meta new file mode 100644 index 0000000..468e69f --- /dev/null +++ b/KFAttached/Misc/AimReferenceGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 93cdda222d3f0e24aa2b48c9b9fd7b81 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Misc/AttachmentReference.cs b/KFAttached/Misc/AttachmentReference.cs new file mode 100644 index 0000000..0564261 --- /dev/null +++ b/KFAttached/Misc/AttachmentReference.cs @@ -0,0 +1,7 @@ +using UnityEngine; + +[AddComponentMenu("")] +public class AttachmentReference : MonoBehaviour +{ + public Transform attachmentReference; +} \ No newline at end of file diff --git a/KFAttached/Misc/AttachmentReference.cs.meta b/KFAttached/Misc/AttachmentReference.cs.meta new file mode 100644 index 0000000..13024ae --- /dev/null +++ b/KFAttached/Misc/AttachmentReference.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3ade2782c6b27f6448984f3d9d0ef550 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Misc/AttachmentReferenceAppended.cs b/KFAttached/Misc/AttachmentReferenceAppended.cs new file mode 100644 index 0000000..990602b --- /dev/null +++ b/KFAttached/Misc/AttachmentReferenceAppended.cs @@ -0,0 +1,45 @@ +using UnityEngine; + +public class AttachmentReferenceAppended : AttachmentReference +{ + private Transform[] bindings; + public void Merge(AnimationTargetsAbs targets) + { + if (attachmentReference && targets) + { + foreach (var bindings in attachmentReference.GetComponentsInChildren(true)) + { + bindings.targets = targets; + } + bindings = new Transform[attachmentReference.childCount]; + for (int i = 0; i < attachmentReference.childCount; i++) + { + bindings[i] = attachmentReference.GetChild(i); + bindings[i].SetParent(targets.AttachmentRef, false); + } + Destroy(attachmentReference.gameObject); + attachmentReference = null; + } + } + + public void Remove() + { + if (bindings != null) + { + foreach (var binding in bindings) + { + if (binding) + { + binding.SetParent(null, false); + Destroy(binding.gameObject); + } + } + bindings = null; + } + if (attachmentReference) + { + Destroy(attachmentReference.gameObject); + attachmentReference = null; + } + } +} \ No newline at end of file diff --git a/KFAttached/Misc/AttachmentReferenceAppended.cs.meta b/KFAttached/Misc/AttachmentReferenceAppended.cs.meta new file mode 100644 index 0000000..fc84559 --- /dev/null +++ b/KFAttached/Misc/AttachmentReferenceAppended.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f2f4680216a56a6479d51c0c8a9f51a5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Misc/AudioSourceGroup.cs b/KFAttached/Misc/AudioSourceGroup.cs new file mode 100644 index 0000000..9b4c7b4 --- /dev/null +++ b/KFAttached/Misc/AudioSourceGroup.cs @@ -0,0 +1,15 @@ +using System; +using UnityEngine; + +[Serializable] +public class AudioSourceGroup +{ + [SerializeField] + public string groupName; + [SerializeField] + public AudioClip[] clips = new AudioClip[0]; + [SerializeField] + public AudioSource source; + + public bool IsValid => source != null && clips != null && clips.Length > 0 && source != null; +} diff --git a/KFAttached/Misc/AudioSourceGroup.cs.meta b/KFAttached/Misc/AudioSourceGroup.cs.meta new file mode 100644 index 0000000..dc394f5 --- /dev/null +++ b/KFAttached/Misc/AudioSourceGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c8d5d39385ea6d348b03c7cdf161c572 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Misc/IgnoreTint.cs b/KFAttached/Misc/IgnoreTint.cs new file mode 100644 index 0000000..6055923 --- /dev/null +++ b/KFAttached/Misc/IgnoreTint.cs @@ -0,0 +1,6 @@ +using UnityEngine; + +public class IgnoreTint : MonoBehaviour +{ + +} \ No newline at end of file diff --git a/KFAttached/Misc/IgnoreTint.cs.meta b/KFAttached/Misc/IgnoreTint.cs.meta new file mode 100644 index 0000000..3b7e42c --- /dev/null +++ b/KFAttached/Misc/IgnoreTint.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 122c053de47e8a14aa2aa57287456e86 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Misc/ItemAnimatorUpdate.cs b/KFAttached/Misc/ItemAnimatorUpdate.cs new file mode 100644 index 0000000..4c03668 --- /dev/null +++ b/KFAttached/Misc/ItemAnimatorUpdate.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.Animations.Rigging; +using UnityEngine.Playables; + +[AddComponentMenu("")] +[DisallowMultipleComponent] +internal class ItemAnimatorUpdate : MonoBehaviour +{ + private Animator animator; + private RigBuilder weaponRB; + internal PlayableGraph graph; + + private void Awake() + { + animator = GetComponent(); + TryGetComponent(out weaponRB); + } + + private void Update() + { + //animator.playableGraph.Evaluate(Time.deltaTime); + animator.Update(Time.deltaTime); + //graph.Evaluate(Time.deltaTime); + //weaponRB?.Evaluate(Time.deltaTime); + } +} \ No newline at end of file diff --git a/KFAttached/Misc/ItemAnimatorUpdate.cs.meta b/KFAttached/Misc/ItemAnimatorUpdate.cs.meta new file mode 100644 index 0000000..54307c3 --- /dev/null +++ b/KFAttached/Misc/ItemAnimatorUpdate.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8b9545ce69a57b542aef2de6d8093b73 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Misc/PlayerRigLateUpdate.cs b/KFAttached/Misc/PlayerRigLateUpdate.cs new file mode 100644 index 0000000..db2ee45 --- /dev/null +++ b/KFAttached/Misc/PlayerRigLateUpdate.cs @@ -0,0 +1,34 @@ +using System; +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[Obsolete] +[AddComponentMenu("")] +public class PlayerRigLateUpdate : MonoBehaviour +{ +#if NotEditor + //private Animator animator; + //private RigBuilder rigBuilder; + //private void Awake() + //{ + // animator = GetComponent(); + // rigBuilder = GetComponent(); + //} + + //private void OnAnimatorMove() + //{ + // if (rigBuilder.enabled) + // ForceToManualUpdate(); + //} + + //private void LateUpdate() + //{ + // rigBuilder.Evaluate(Time.deltaTime); + //} + + //private void ForceToManualUpdate() + //{ + // RigTargets.RebuildRig(animator, rigBuilder); + //} +#endif +} diff --git a/KFAttached/Misc/PlayerRigLateUpdate.cs.meta b/KFAttached/Misc/PlayerRigLateUpdate.cs.meta new file mode 100644 index 0000000..ec2c2dc --- /dev/null +++ b/KFAttached/Misc/PlayerRigLateUpdate.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f95fdfaecc53e6146a222aa23dd93ae2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Misc/ScopeBase.cs b/KFAttached/Misc/ScopeBase.cs new file mode 100644 index 0000000..b8d26a0 --- /dev/null +++ b/KFAttached/Misc/ScopeBase.cs @@ -0,0 +1,5 @@ +using UnityEngine; + +public class ScopeBase : MonoBehaviour +{ +} \ No newline at end of file diff --git a/KFAttached/Misc/ScopeBase.cs.meta b/KFAttached/Misc/ScopeBase.cs.meta new file mode 100644 index 0000000..b05c636 --- /dev/null +++ b/KFAttached/Misc/ScopeBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 22006be69eab7844e8873de3daedd07c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Misc/WeaponCameraFollow.cs b/KFAttached/Misc/WeaponCameraFollow.cs new file mode 100644 index 0000000..9c56384 --- /dev/null +++ b/KFAttached/Misc/WeaponCameraFollow.cs @@ -0,0 +1,62 @@ +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.Rendering.PostProcessing; + +[AddComponentMenu("")] +public class WeaponCameraFollow : MonoBehaviour +{ + public RenderTexture targetTexture; +#if NotEditor + public ActionModuleDynamicSensitivity.DynamicSensitivityData dynamicSensitivityData; + public EntityPlayerLocal player; +#endif + + private void OnEnable() + { +#if NotEditor + OcclusionManager.Instance.SetMultipleCameras(true); + if (dynamicSensitivityData != null) + { + dynamicSensitivityData.activated = true; + } + //UpdateAntialiasing(); +#endif + } + + private void OnDisable() + { +#if NotEditor + OcclusionManager.Instance.SetMultipleCameras(false); + if (dynamicSensitivityData != null) + { + dynamicSensitivityData.activated = false; + } +#endif + if (!targetTexture || !targetTexture.IsCreated()) + { + return; + } + var cmd = new CommandBuffer(); + cmd.SetRenderTarget(targetTexture); + cmd.ClearRenderTarget(true, true, Color.black); + Graphics.ExecuteCommandBuffer(cmd); + cmd.Dispose(); + } + +#if NotEditor + public void UpdateAntialiasing() + { + var pipCamera = GetComponent(); + var layer = GetComponent(); + var prevFsr = player.renderManager.fsr; + int num = player.renderManager.dlssEnabled ? 0 : GamePrefs.GetInt(EnumGamePrefs.OptionsGfxAA); + float @float = GamePrefs.GetFloat(EnumGamePrefs.OptionsGfxAASharpness); + player.renderManager.FSRInit(layer.superResolution); + player.renderManager.SetAntialiasing(num, @float, layer); + Rect rect = pipCamera.rect; + rect.x = ((layer.antialiasingMode == PostProcessLayer.Antialiasing.SuperResolution) ? 1E-07f : 0f); + pipCamera.rect = rect; + player.renderManager.fsr = prevFsr; + } +#endif +} diff --git a/KFAttached/Misc/WeaponCameraFollow.cs.meta b/KFAttached/Misc/WeaponCameraFollow.cs.meta new file mode 100644 index 0000000..44e623e --- /dev/null +++ b/KFAttached/Misc/WeaponCameraFollow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b9ea69da20db0064282418fbfd9a57c7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Render.meta b/KFAttached/Render.meta new file mode 100644 index 0000000..e9febb3 --- /dev/null +++ b/KFAttached/Render.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e92eb85b5d313f248a46263f550fcc0f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Render/BokehBlurTargetRef.cs b/KFAttached/Render/BokehBlurTargetRef.cs new file mode 100644 index 0000000..ac2b51d --- /dev/null +++ b/KFAttached/Render/BokehBlurTargetRef.cs @@ -0,0 +1,12 @@ +using UnityEngine; + +namespace KFCommonUtilityLib.KFAttached.Render +{ + [AddComponentMenu("")] + public class BokehBlurTargetRef : MonoBehaviour + { +#if NotEditor + internal MagnifyScope target; +#endif + } +} diff --git a/KFAttached/Render/BokehBlurTargetRef.cs.meta b/KFAttached/Render/BokehBlurTargetRef.cs.meta new file mode 100644 index 0000000..0c73ea8 --- /dev/null +++ b/KFAttached/Render/BokehBlurTargetRef.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 16ae170c638bd434dbe48e1b9e46ac99 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Render/MagnifyScope.cs b/KFAttached/Render/MagnifyScope.cs new file mode 100644 index 0000000..245fe1c --- /dev/null +++ b/KFAttached/Render/MagnifyScope.cs @@ -0,0 +1,405 @@ +#if NotEditor +using HarmonyLib; +using System; +using System.Reflection; +#endif +using UnityEngine; +using UnityEngine.Rendering.PostProcessing; + +namespace KFCommonUtilityLib.KFAttached.Render +{ + [AddComponentMenu("KFAttachments/Render Utils/Magnify Scope")] + [RequireComponent(typeof(Renderer))] + public class MagnifyScope : MonoBehaviour + { +#if NotEditor + private static Shader newShader; + private static FieldInfo fieldResources = AccessTools.Field(typeof(PostProcessLayer), "m_Resources"); +#endif + private RenderTexture targetTexture; + private Renderer renderTarget; + + private Camera pipCamera; + [Header("Core")] + [SerializeField] + private bool manualControl = false; + [SerializeField] + private Transform cameraJoint; + [SerializeField] + private float aspectRatio = 1.0f; + [SerializeField] + private bool hideFpvModelInScope = false; + [SerializeField] + private bool variableZoom = false; + [Header("Reticle Scaling")] + [SerializeField] + private bool scaleReticle = false; + [SerializeField] + private Vector2 reticleSizeRange = new Vector2(1, 1); + //[SerializeField] + //private bool scaleDownReticle = false; + //[SerializeField] + //private float reticleScaleRatio = 1.0f; + [Header("Camera Texture Size And Procedural Aiming")] + [SerializeField] + private Transform aimRef; + [SerializeField] + private float lensSizeFull; + [SerializeField] + private float lensSizeValid; + + private float initialReticleScale = 1f; + private float initialFov = 55f; + private float textureHeight = Screen.height; + +#if NotEditor + private EntityPlayerLocal player; + private int itemSlot = -1; + private ItemActionZoom.ItemActionDataZoom zoomActionData; + private bool IsVariableZoom => variableZoom && variableZoomData != null; + private ActionModuleVariableZoom.VariableZoomData variableZoomData; + private float targetStep = 0; + private float currentStep = 0; + private float stepVelocity = 0; +#else + [Header("Editor Debug")] + public Camera debugCamera; + public float debugScale = 2f; +#endif + private void Awake() + { + renderTarget = GetComponent(); + if (!renderTarget) + { + Destroy(this); + return; + } +#if NotEditor + + if (newShader == null) + { + newShader = LoadManager.LoadAsset("#@modfolder(CommonUtilityLib):Resources/PIPScope.unity3d?PIPScope.shadergraph", null, null, false, true).Asset; + } + if (renderTarget.material.shader.name == "Shader Graphs/MagnifyScope" || renderTarget.material.shader.name == "Shader Graphs/PIPScopeNew") + { + renderTarget.material.shader = newShader; + } + initialReticleScale = renderTarget.material.GetFloat("_ReticleScale"); +#else + if(debugCamera == null) + { + Destroy(this); + return; + } +#endif + //if (!player.playerCamera.TryGetComponent(out var bokeh)) + //{ + // bokeh = player.playerCamera.gameObject.AddComponent(); + //} + //bokeh.target = this; + // Precompute rotations + } + +#if NotEditor + private void Start() + { + var entity = GetComponentInParent(); + if (!entity) + { + Destroy(gameObject); + return; + } + player = entity; + itemSlot = player.inventory.holdingItemIdx; + OnEnable(); + } +#endif + + private void OnEnable() + { + float targetFov; +#if NotEditor + if (!player) + { + return; + } + CalcInitialFov(); + //inventory holding item is not set when creating model, this might be an issue for items with base scope that has this script attached + //workaround taken from alternative action module, which keeps a reference to the ItemValue being set until its custom data is created + //afterwards it's set to null so we still need to access holding item when this method is triggered by mods + if (itemSlot != player.inventory.holdingItemIdx) + { + Log.Out($"Scope shader script: Expecting holding item idx {itemSlot} but getting {player.inventory.holdingItemIdx}!"); + return; + } + var zoomAction = (ItemActionZoom)((ActionModuleAlternative.InventorySetItemTemp?.ItemClass ?? player.inventory.holdingItem).Actions[1]); + zoomActionData = (ItemActionZoom.ItemActionDataZoom)player.inventory.holdingItemData.actionData[1]; + variableZoomData = (zoomActionData as IModuleContainerFor)?.Instance; + if (variableZoomData != null && (variableZoom || variableZoomData.forceFov)) + { + if (variableZoom) + { + variableZoomData.shouldUpdate = false; + targetStep = currentStep = variableZoomData.curStep; + stepVelocity = 0f; + targetFov = CalcCurrentFov(); + } + else + { + targetFov = variableZoomData.fovRange.min; + } + } + else + { + string originalRatio = zoomAction.Properties.GetString("ZoomRatio"); + if (string.IsNullOrEmpty(originalRatio)) + { + originalRatio = "0"; + } + targetFov = StringParsers.ParseFloat(player.inventory.holdingItemItemValue.GetPropertyOverride("ZoomRatio", originalRatio)); + targetFov = ScaleToFov(targetFov); + } + +#else + if(debugCamera == null) + { + Destroy(this); + } + targetFov = Mathf.Rad2Deg * 2 * Mathf.Atan(Mathf.Tan(Mathf.Deg2Rad * 27.5f) / Mathf.Sqrt(debugScale)); +#endif + CreateCamera(); + UpdateFOV(targetFov); + } + +#if NotEditor + private void Update() + { + if (IsVariableZoom) + { + if (variableZoomData.shouldUpdate) + { + variableZoomData.shouldUpdate = false; + targetStep = variableZoomData.curStep; + } + if (currentStep != targetStep) + { + if (variableZoomData.isToggleOnly) + { + currentStep = targetStep; + } + else + { + currentStep = Mathf.SmoothDamp(currentStep, targetStep, ref stepVelocity, 0.05f); + } + UpdateFOV(CalcCurrentFov()); + } + } + + if (!manualControl && zoomActionData != null) + { + if (player.bFirstPersonView) + { + bool aimingGun = player.AimingGun; + if (aimingGun && !pipCamera.gameObject.activeSelf) + { + pipCamera.gameObject.SetActive(true); + } + else if (!aimingGun && !zoomActionData.bZoomInProgress && pipCamera.gameObject.activeSelf) + { + pipCamera.gameObject.SetActive(false); + } + } + else if (pipCamera.gameObject.activeSelf) + { + pipCamera.gameObject.SetActive(false); + } + } + } +#endif + + private void OnDisable() + { + DestroyCamera(); +#if NotEditor + currentStep = targetStep = stepVelocity = 0f; +#else + if(debugCamera == null) + { + Destroy(this); + } +#endif + } + + private float ScaleToFov(float scale) + { + return Mathf.Rad2Deg * 2 * Mathf.Atan(Mathf.Tan(Mathf.Deg2Rad * initialFov * 0.5f) / scale); + } + + private float FovToScale(float fov) + { + return Mathf.Tan(Mathf.Deg2Rad * initialFov * 0.5f) / Mathf.Tan(Mathf.Deg2Rad * fov * 0.5f); + } + +#if NotEditor + private void CalcInitialFov() + { + if (aimRef) + { + var distance = Mathf.Abs(Vector3.Dot(renderTarget.bounds.center - aimRef.position, aimRef.forward)); + var scaleFov = lensSizeValid / (2 * distance * Mathf.Tan(Mathf.Deg2Rad * 27.5f)); + var scaleTexture = lensSizeFull / (2 * distance * Mathf.Tan(Mathf.Deg2Rad * 27.5f)); + textureHeight = scaleTexture * Screen.height; + //textureHeight = Mathf.Abs(player.playerCamera.WorldToScreenPoint(player.playerCamera.transform.forward * distance + player.playerCamera.transform.up * height).y - + // player.playerCamera.WorldToScreenPoint(player.playerCamera.transform.forward * distance - player.playerCamera.transform.up * height).y); + initialFov = Mathf.Rad2Deg * 2 * Mathf.Atan(Mathf.Tan(Mathf.Deg2Rad * 27.5f) * scaleFov); + Log.Out($"distance {distance}, scale fov {scaleFov}, scale texture {scaleTexture} texture height {textureHeight} initial fov {initialFov}"); + return; + } + textureHeight = Screen.height * 0.5f; + initialFov = 15; + } + + private static float CalcFovStep(float t, float fovMin, float fovMax) + { + return 2f * Mathf.Rad2Deg * Mathf.Atan(Mathf.Lerp(Mathf.Tan(fovMax * 0.5f * Mathf.Deg2Rad), Mathf.Tan(fovMin * 0.5f * Mathf.Deg2Rad), t)); + } + + private float CalcCurrentFov() + { + if (!IsVariableZoom) + { + throw new Exception("Variable Zoom is not set!"); + } + float targetFov; + if (variableZoomData.forceFov) + { + targetFov = CalcFovStep(currentStep, variableZoomData.fovRange.min, variableZoomData.fovRange.max); + } + else + { + targetFov = ScaleToFov(Mathf.Lerp(variableZoomData.minScale, variableZoomData.maxScale, currentStep)); + } + return targetFov; + } +#endif + + private void DestroyCamera() + { + if (targetTexture && targetTexture.IsCreated()) + { + targetTexture.Release(); + Destroy(targetTexture); + } + if (pipCamera) + { + Destroy(pipCamera.gameObject); + } + } + + private void UpdateFOV(float targetFov) + { + if (targetFov > 0) + { + pipCamera.fieldOfView = targetFov; +#if NotEditor + if (scaleReticle && IsVariableZoom) + { + renderTarget.material.SetFloat("_ReticleScale", Mathf.Lerp(reticleSizeRange.x, reticleSizeRange.y, currentStep)); + //if (variableZoomData.maxScale > variableZoomData.minScale) + //{ + // float minScale; + // if (reticleScaleRatio >= 1) + // { + // minScale = scaleDownReticle ? 1 - (variableZoomData.maxScale * reticleScaleRatio - variableZoomData.minScale) / (variableZoomData.maxScale * reticleScaleRatio) : 1; + // } + // else + // { + // minScale = scaleDownReticle ? 1 - reticleScaleRatio * (variableZoomData.maxScale - variableZoomData.minScale) / variableZoomData.maxScale : 1; + // } + // float maxScale; + // if (reticleScaleRatio >= 1) + // { + // maxScale = scaleDownReticle ? 1 : variableZoomData.maxScale * reticleScaleRatio / variableZoomData.minScale; + // } + // else + // { + // maxScale = scaleDownReticle ? 1 : 1 + reticleScaleRatio * (variableZoomData.maxScale - variableZoomData.minScale) / variableZoomData.minScale; + // } + // float reticleScale = Mathf.Lerp(minScale, maxScale, variableZoomData.curStep); + // renderTarget.material.SetFloat("_ReticleScale", initialReticleScale / reticleScale); + //} + //else + //{ + // renderTarget.material.SetFloat("_ReticleScale", initialReticleScale); + //} + } + //Log.Out($"target fov {targetFov} target scale {targetScale}"); +#endif + } + } + + private void CreateCamera() + { + const float texScale = 1f; + targetTexture = new RenderTexture((int)(textureHeight * aspectRatio), (int)(textureHeight), 24, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Linear) + { + filterMode = FilterMode.Bilinear, + wrapMode = TextureWrapMode.Clamp + }; + renderTarget.material.mainTexture = targetTexture; + GameObject cameraGO = new GameObject("KFPiPCam"); + if (cameraJoint != null) + { + cameraGO.transform.parent = cameraJoint.transform; + } + else + { + cameraGO.transform.parent = transform; + } + + pipCamera = cameraGO.AddComponent(); + pipCamera.targetTexture = targetTexture; + pipCamera.depth = -2; + pipCamera.fieldOfView = 55; + pipCamera.nearClipPlane = 0.05f; + pipCamera.farClipPlane = 5000; + pipCamera.aspect = aspectRatio; + pipCamera.rect = new Rect(0, 0, texScale, texScale); +#if NotEditor + //pipCamera.CopyFrom(player.playerCamera); + pipCamera.cullingMask = player.playerCamera.cullingMask; + //renderTarget.material.SetFloat("_AspectMain", player.playerCamera.aspect); + //renderTarget.material.SetFloat("_AspectScope", pipCamera.aspect); +#else + pipCamera.CopyFrom(debugCamera); +#endif + if (cameraJoint == null || hideFpvModelInScope) + { + pipCamera.cullingMask &= ~(1024); + } + else + { + pipCamera.cullingMask |= 1024; + } + cameraGO.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); + cameraGO.transform.localScale = Vector3.one; + +#if NotEditor + WeaponCameraFollow weaponCameraFollow = cameraGO.AddComponent(); + weaponCameraFollow.targetTexture = targetTexture; + weaponCameraFollow.dynamicSensitivityData = (zoomActionData as IModuleContainerFor)?.Instance; + weaponCameraFollow.player = player; + var old = player.playerCamera.GetComponent(); + var layer = pipCamera.gameObject.GetOrAddComponent(); + //layer.antialiasingMode = old.antialiasingMode; + //layer.superResolution = (SuperResolution)old.superResolution.GetType().CreateInstance(); + layer.Init(fieldResources.GetValue(old) as PostProcessResources); + //weaponCameraFollow.UpdateAntialiasing(); +#endif + } + + internal void RenderImageCallback(RenderTexture source, RenderTexture destination) + { + } + } +} diff --git a/KFAttached/Render/MagnifyScope.cs.meta b/KFAttached/Render/MagnifyScope.cs.meta new file mode 100644 index 0000000..b4968bd --- /dev/null +++ b/KFAttached/Render/MagnifyScope.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 54496e2804e57d84da57d0241db8bdf6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/Render/MagnifyScopeTargetRef.cs b/KFAttached/Render/MagnifyScopeTargetRef.cs new file mode 100644 index 0000000..cd2450e --- /dev/null +++ b/KFAttached/Render/MagnifyScopeTargetRef.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace KFCommonUtilityLib.KFAttached.Render +{ + [AddComponentMenu("")] + internal class MagnifyScopeTargetRef : MonoBehaviour + { + private HashSet targets = new HashSet(); + + private void OnRenderImage(RenderTexture source, RenderTexture destination) + { + foreach (var target in targets) + { + target.RenderImageCallback(source, destination); + } + Graphics.Blit(source, destination); + } + + internal void AddTarget(MagnifyScope target) + { + if (targets.Count == 0) + { + enabled = true; + } + targets.Add(target); + } + + internal void RemoveTarget(MagnifyScope target) + { + targets.Remove(target); + if(targets.Count == 0 ) + { + enabled = false; + } + } + } +} diff --git a/KFAttached/Render/MagnifyScopeTargetRef.cs.meta b/KFAttached/Render/MagnifyScopeTargetRef.cs.meta new file mode 100644 index 0000000..fececde --- /dev/null +++ b/KFAttached/Render/MagnifyScopeTargetRef.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b65f1de3a5df39443a8af191b067c5d4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors.meta b/KFAttached/RigAdaptors.meta new file mode 100644 index 0000000..bed2421 --- /dev/null +++ b/KFAttached/RigAdaptors.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3fabd6babec972349939cbd377f41ba1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/Adaptors.meta b/KFAttached/RigAdaptors/Adaptors.meta new file mode 100644 index 0000000..40fb700 --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cecc0dfc3c12e4f4dab5bbe3f4ba7c1d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/Adaptors/BlendConstraintAdaptor.cs b/KFAttached/RigAdaptors/Adaptors/BlendConstraintAdaptor.cs new file mode 100644 index 0000000..1d04bfb --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/BlendConstraintAdaptor.cs @@ -0,0 +1,56 @@ +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class BlendConstraintAdaptor : RigAdaptorAbs +{ + [SerializeField] + private string m_ConstrainedObject; + [SerializeField] + private Transform m_SourceA; + [SerializeField] + private Transform m_SourceB; + [SerializeField] + private bool m_BlendPosition; + [SerializeField] + private bool m_BlendRotation; + [SerializeField] + private float m_PositionWeight; + [SerializeField] + private float m_RotationWeight; + [SerializeField] + private bool m_MaintainPositionOffsets; + [SerializeField] + private bool m_MaintainRotationOffsets; + + public override void ReadRigData() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_ConstrainedObject = constraint.data.constrainedObject?.name; + m_SourceA = constraint.data.sourceObjectA; + m_SourceB = constraint.data.sourceObjectB; + m_BlendPosition = constraint.data.blendPosition; + m_BlendRotation = constraint.data.blendRotation; + m_PositionWeight = constraint.data.positionWeight; + m_RotationWeight = constraint.data.rotationWeight; + m_MaintainPositionOffsets = constraint.data.maintainPositionOffsets; + m_MaintainRotationOffsets = constraint.data.maintainRotationOffsets; + } + + public override void FindRigTargets() + { + var constraint = GetComponent(); + constraint.Reset(); + constraint.weight = weight; + constraint.data.constrainedObject = targetRoot.FindInAllChildren(m_ConstrainedObject); + constraint.data.sourceObjectA = m_SourceA; + constraint.data.sourceObjectB = m_SourceB; + constraint.data.blendPosition = m_BlendPosition; + constraint.data.blendRotation = m_BlendRotation; + constraint.data.positionWeight = m_PositionWeight; + constraint.data.rotationWeight = m_RotationWeight; + constraint.data.maintainPositionOffsets = m_MaintainPositionOffsets; + constraint.data.maintainRotationOffsets = m_MaintainRotationOffsets; + } +} diff --git a/KFAttached/RigAdaptors/Adaptors/BlendConstraintAdaptor.cs.meta b/KFAttached/RigAdaptors/Adaptors/BlendConstraintAdaptor.cs.meta new file mode 100644 index 0000000..be3d6df --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/BlendConstraintAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b2ca56f5a0b464646817033fcff9f693 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/Adaptors/ChainIKConstraintAdaptor.cs b/KFAttached/RigAdaptors/Adaptors/ChainIKConstraintAdaptor.cs new file mode 100644 index 0000000..286a69a --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/ChainIKConstraintAdaptor.cs @@ -0,0 +1,56 @@ +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class ChainIKConstraintAdaptor : RigAdaptorAbs +{ + [SerializeField] + private string m_Root; + [SerializeField] + private string m_Tip; + [SerializeField] + private Transform m_Target; + [SerializeField] + private float m_ChainRotationWeight; + [SerializeField] + private float m_TipRotationWeight; + [SerializeField] + private int m_MaxIterations; + [SerializeField] + private float m_Tolerance; + [SerializeField] + private bool m_MaintainTargetPositionOffset; + [SerializeField] + private bool m_MaintainTargetRotationOffset; + + public override void ReadRigData() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_Root = constraint.data.root?.name; + m_Tip = constraint.data.tip?.name; + m_Target = constraint.data.target; + m_ChainRotationWeight = constraint.data.chainRotationWeight; + m_TipRotationWeight = constraint.data.tipRotationWeight; + m_MaxIterations = constraint.data.maxIterations; + m_Tolerance = constraint.data.tolerance; + m_MaintainTargetPositionOffset = constraint.data.maintainTargetPositionOffset; + m_MaintainTargetRotationOffset = constraint.data.maintainTargetRotationOffset; + } + + public override void FindRigTargets() + { + var constraint = GetComponent(); + constraint.Reset(); + constraint.weight = weight; + constraint.data.root = targetRoot.FindInAllChildren(m_Root); + constraint.data.tip = targetRoot.FindInAllChildren(m_Tip); + constraint.data.target = m_Target; + constraint.data.chainRotationWeight = m_ChainRotationWeight; + constraint.data.tipRotationWeight = m_TipRotationWeight; + constraint.data.maxIterations = m_MaxIterations; + constraint.data.tolerance = m_Tolerance; + constraint.data.maintainTargetPositionOffset = m_MaintainTargetPositionOffset; + constraint.data.maintainTargetRotationOffset = m_MaintainTargetRotationOffset; + } +} diff --git a/KFAttached/RigAdaptors/Adaptors/ChainIKConstraintAdaptor.cs.meta b/KFAttached/RigAdaptors/Adaptors/ChainIKConstraintAdaptor.cs.meta new file mode 100644 index 0000000..40211d1 --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/ChainIKConstraintAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3b4dd415747e268478ef7e27093e78a4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/Adaptors/DampedTransformAdaptor.cs b/KFAttached/RigAdaptors/Adaptors/DampedTransformAdaptor.cs new file mode 100644 index 0000000..2fa5f47 --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/DampedTransformAdaptor.cs @@ -0,0 +1,38 @@ +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class DampedTransformAdaptor : RigAdaptorAbs +{ + [SerializeField] + private string m_ConstrainedObject; + [SerializeField] + private Transform m_Source; + [SerializeField] + private float m_DampPosition; + [SerializeField] + private float m_DampRotation; + [SerializeField] + private bool m_MaintainAim; + public override void FindRigTargets() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_ConstrainedObject = constraint.data.constrainedObject?.name; + m_Source = constraint.data.sourceObject; + m_DampPosition = constraint.data.dampPosition; + m_DampRotation = constraint.data.dampRotation; + m_MaintainAim = constraint.data.maintainAim; + } + + public override void ReadRigData() + { + var constraint = GetComponent(); + constraint.weight = weight; + constraint.data.constrainedObject = targetRoot.FindInAllChildren(m_ConstrainedObject); + constraint.data.sourceObject = m_Source; + constraint.data.dampPosition = m_DampPosition; + constraint.data.dampRotation = m_DampRotation; + constraint.data.maintainAim = m_MaintainAim; + } +} diff --git a/KFAttached/RigAdaptors/Adaptors/DampedTransformAdaptor.cs.meta b/KFAttached/RigAdaptors/Adaptors/DampedTransformAdaptor.cs.meta new file mode 100644 index 0000000..44d505e --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/DampedTransformAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7eae293f02f20ea4e83de9504d5a5f93 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/Adaptors/Data.meta b/KFAttached/RigAdaptors/Adaptors/Data.meta new file mode 100644 index 0000000..45662ed --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/Data.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 28f7c996c8fc946498369133083c03d9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/Adaptors/Data/TwistNode.cs b/KFAttached/RigAdaptors/Adaptors/Data/TwistNode.cs new file mode 100644 index 0000000..9e798e9 --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/Data/TwistNode.cs @@ -0,0 +1,22 @@ +using System; +using UnityEngine; + +namespace KFCommonUtilityLib.RigAdaptors.Adaptors.Data +{ + [Serializable] + public class TwistNode + { + [SerializeField] + public string name; + [SerializeField] + public float weight; + + public TwistNode() { } + + public TwistNode(string name, float weight) + { + this.name = name; + this.weight = weight; + } + } +} diff --git a/KFAttached/RigAdaptors/Adaptors/Data/TwistNode.cs.meta b/KFAttached/RigAdaptors/Adaptors/Data/TwistNode.cs.meta new file mode 100644 index 0000000..6f59232 --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/Data/TwistNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ce4dc6aaaa5e6564d8f5975388620f86 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/Adaptors/MultiAimConstraintAdaptor.cs b/KFAttached/RigAdaptors/Adaptors/MultiAimConstraintAdaptor.cs new file mode 100644 index 0000000..0eacca8 --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/MultiAimConstraintAdaptor.cs @@ -0,0 +1,68 @@ +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class MultiAimConstraintAdaptor : RigAdaptorAbs +{ + [SerializeField] + private string m_ConstrainedObject; + [SerializeField] + private WeightedTransformArray m_SourceObjects; + [SerializeField] + private Vector3 m_Offset; + [SerializeField] + private Vector2 m_limits; + [SerializeField] + private MultiAimConstraintData.Axis m_AimAxis; + [SerializeField] + private MultiAimConstraintData.Axis m_UpAxis; + [SerializeField] + private MultiAimConstraintData.WorldUpType m_WorldUpType; + [SerializeField] + private string m_WorldUpObject; + [SerializeField] + private MultiAimConstraintData.Axis m_WorldUpAxis; + [SerializeField] + private bool m_MaintainOffset; + [SerializeField] + private Vector3Bool m_ConstrainedAxes; + + public override void FindRigTargets() + { + var constraint = GetComponent(); + constraint.Reset(); + constraint.weight = weight; + constraint.data.constrainedObject = targetRoot.FindInAllChildren(m_ConstrainedObject); + constraint.data.sourceObjects = m_SourceObjects; + constraint.data.offset = m_Offset; + constraint.data.limits = m_limits; + constraint.data.aimAxis = m_AimAxis; + constraint.data.upAxis = m_UpAxis; + constraint.data.worldUpType = m_WorldUpType; + if (!string.IsNullOrEmpty(m_WorldUpObject)) + constraint.data.worldUpObject = targetRoot.FindInAllChildren(m_WorldUpObject); + constraint.data.worldUpAxis = m_WorldUpAxis; + constraint.data.maintainOffset = m_MaintainOffset; + constraint.data.constrainedXAxis = m_ConstrainedAxes.x; + constraint.data.constrainedYAxis = m_ConstrainedAxes.y; + constraint.data.constrainedZAxis = m_ConstrainedAxes.z; + } + + public override void ReadRigData() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_ConstrainedObject = constraint.data.constrainedObject?.name; + m_SourceObjects = constraint.data.sourceObjects; + m_Offset = constraint.data.offset; + m_limits = constraint.data.limits; + m_AimAxis = constraint.data.aimAxis; + m_UpAxis = constraint.data.upAxis; + m_WorldUpType = constraint.data.worldUpType; + if ((m_WorldUpType == MultiAimConstraintData.WorldUpType.ObjectUp || m_WorldUpType == MultiAimConstraintData.WorldUpType.ObjectRotationUp) && constraint.data.worldUpObject) + m_WorldUpObject = constraint.data.worldUpObject.name; + m_WorldUpAxis = constraint.data.worldUpAxis; + m_MaintainOffset = constraint.data.maintainOffset; + m_ConstrainedAxes = new Vector3Bool(constraint.data.constrainedXAxis, constraint.data.constrainedYAxis, constraint.data.constrainedZAxis); + } +} diff --git a/KFAttached/RigAdaptors/Adaptors/MultiAimConstraintAdaptor.cs.meta b/KFAttached/RigAdaptors/Adaptors/MultiAimConstraintAdaptor.cs.meta new file mode 100644 index 0000000..36bedcc --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/MultiAimConstraintAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 80b6e2441c0c1ab46a65e9ff120057de +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/Adaptors/MultiParentConstraintAdaptor.cs b/KFAttached/RigAdaptors/Adaptors/MultiParentConstraintAdaptor.cs new file mode 100644 index 0000000..6531c31 --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/MultiParentConstraintAdaptor.cs @@ -0,0 +1,47 @@ +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class MultiParentConstraintAdaptor : RigAdaptorAbs +{ + [SerializeField] + private string m_ConstrainedObject; + [SerializeField] + private WeightedTransformArray m_SourceObjects; + [SerializeField] + private Vector3Bool m_ConstrainedPositionAxes; + [SerializeField] + private Vector3Bool m_ConstrainedRotationAxes; + [SerializeField] + private bool m_MaintainPositionOffset; + [SerializeField] + private bool m_MaintainRotationOffset; + public override void FindRigTargets() + { + var constraint = GetComponent(); + constraint.Reset(); + constraint.weight = weight; + constraint.data.constrainedObject = targetRoot.FindInAllChildren(m_ConstrainedObject); + constraint.data.sourceObjects = m_SourceObjects; + constraint.data.constrainedPositionXAxis = m_ConstrainedPositionAxes.x; + constraint.data.constrainedPositionYAxis = m_ConstrainedPositionAxes.y; + constraint.data.constrainedPositionZAxis = m_ConstrainedPositionAxes.z; + constraint.data.constrainedRotationXAxis = m_ConstrainedRotationAxes.x; + constraint.data.constrainedRotationYAxis = m_ConstrainedRotationAxes.y; + constraint.data.constrainedRotationZAxis = m_ConstrainedRotationAxes.z; + constraint.data.maintainPositionOffset = m_MaintainPositionOffset; + constraint.data.maintainRotationOffset = m_MaintainRotationOffset; + } + + public override void ReadRigData() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_ConstrainedObject = constraint.data.constrainedObject?.name; + m_SourceObjects = constraint.data.sourceObjects; + m_ConstrainedPositionAxes = new Vector3Bool(constraint.data.constrainedPositionXAxis, constraint.data.constrainedPositionYAxis, constraint.data.constrainedPositionZAxis); + m_ConstrainedRotationAxes = new Vector3Bool(constraint.data.constrainedRotationXAxis, constraint.data.constrainedRotationYAxis, constraint.data.constrainedRotationZAxis); + m_MaintainPositionOffset = constraint.data.maintainPositionOffset; + m_MaintainRotationOffset = constraint.data.maintainRotationOffset; + } +} diff --git a/KFAttached/RigAdaptors/Adaptors/MultiParentConstraintAdaptor.cs.meta b/KFAttached/RigAdaptors/Adaptors/MultiParentConstraintAdaptor.cs.meta new file mode 100644 index 0000000..9a209ff --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/MultiParentConstraintAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1efc37cc0b8c5d748832a228eef2373c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/Adaptors/MultiPositionConstraintAdaptor.cs b/KFAttached/RigAdaptors/Adaptors/MultiPositionConstraintAdaptor.cs new file mode 100644 index 0000000..3a9d50c --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/MultiPositionConstraintAdaptor.cs @@ -0,0 +1,42 @@ +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class MultiPositionConstraintAdaptor : RigAdaptorAbs +{ + [SerializeField] + private string m_ConstrainedObject; + [SerializeField] + private WeightedTransformArray m_SourceObjects; + [SerializeField] + private Vector3 m_Offset; + [SerializeField] + private Vector3Bool m_ConstrainedAxes; + [SerializeField] + private bool m_MaintainOffset; + + public override void FindRigTargets() + { + var constraint = GetComponent(); + constraint.Reset(); + constraint.weight = weight; + constraint.data.constrainedObject = targetRoot.FindInAllChildren(m_ConstrainedObject); + constraint.data.sourceObjects = m_SourceObjects; + constraint.data.offset = m_Offset; + constraint.data.constrainedXAxis = m_ConstrainedAxes.x; + constraint.data.constrainedYAxis = m_ConstrainedAxes.y; + constraint.data.constrainedZAxis = m_ConstrainedAxes.z; + constraint.data.maintainOffset = m_MaintainOffset; + } + + public override void ReadRigData() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_ConstrainedObject = constraint.data.constrainedObject?.name; + m_SourceObjects = constraint.data.sourceObjects; + m_Offset = constraint.data.offset; + m_ConstrainedAxes = new Vector3Bool(constraint.data.constrainedXAxis, constraint.data.constrainedYAxis, constraint.data.constrainedZAxis); + m_MaintainOffset = constraint.data.maintainOffset; + } +} diff --git a/KFAttached/RigAdaptors/Adaptors/MultiPositionConstraintAdaptor.cs.meta b/KFAttached/RigAdaptors/Adaptors/MultiPositionConstraintAdaptor.cs.meta new file mode 100644 index 0000000..69c4636 --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/MultiPositionConstraintAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d705cece31c4abd4495c5fdf8203afe5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/Adaptors/MultiReferentialConstraintAdaptor.cs b/KFAttached/RigAdaptors/Adaptors/MultiReferentialConstraintAdaptor.cs new file mode 100644 index 0000000..072fd7c --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/MultiReferentialConstraintAdaptor.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class MultiReferentialConstraintAdaptor : RigAdaptorAbs +{ + [SerializeField] + private int m_Driver; + [SerializeField] + private List m_SourceObjects; + + public override void FindRigTargets() + { + var constraint = GetComponent(); + constraint.Reset(); + constraint.weight = weight; + constraint.data.driver = m_Driver; + constraint.data.sourceObjects = m_SourceObjects; + } + + public override void ReadRigData() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_Driver = constraint.data.driver; + m_SourceObjects = constraint.data.sourceObjects; + } +} diff --git a/KFAttached/RigAdaptors/Adaptors/MultiReferentialConstraintAdaptor.cs.meta b/KFAttached/RigAdaptors/Adaptors/MultiReferentialConstraintAdaptor.cs.meta new file mode 100644 index 0000000..e404446 --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/MultiReferentialConstraintAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e103ecfa73363f14a90b16031c25e8b6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/Adaptors/MultiRotationConstraintAdaptor.cs b/KFAttached/RigAdaptors/Adaptors/MultiRotationConstraintAdaptor.cs new file mode 100644 index 0000000..4fbe272 --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/MultiRotationConstraintAdaptor.cs @@ -0,0 +1,42 @@ +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class MultiRotationConstraintAdaptor : RigAdaptorAbs +{ + [SerializeField] + private string m_ConstrainedObject; + [SerializeField] + private WeightedTransformArray m_SourceObjects; + [SerializeField] + private Vector3 m_Offset; + [SerializeField] + private Vector3Bool m_ConstrainedAxes; + [SerializeField] + private bool m_MaintainOffset; + + public override void FindRigTargets() + { + var constraint = GetComponent(); + constraint.Reset(); + constraint.weight = weight; + constraint.data.constrainedObject = targetRoot.FindInAllChildren(m_ConstrainedObject); + constraint.data.sourceObjects = m_SourceObjects; + constraint.data.offset = m_Offset; + constraint.data.constrainedXAxis = m_ConstrainedAxes.x; + constraint.data.constrainedYAxis = m_ConstrainedAxes.y; + constraint.data.constrainedZAxis = m_ConstrainedAxes.z; + constraint.data.maintainOffset = m_MaintainOffset; + } + + public override void ReadRigData() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_ConstrainedObject = constraint.data.constrainedObject?.name; + m_SourceObjects = constraint.data.sourceObjects; + m_Offset = constraint.data.offset; + m_ConstrainedAxes = new Vector3Bool(constraint.data.constrainedXAxis, constraint.data.constrainedYAxis, constraint.data.constrainedZAxis); + m_MaintainOffset = constraint.data.maintainOffset; + } +} diff --git a/KFAttached/RigAdaptors/Adaptors/MultiRotationConstraintAdaptor.cs.meta b/KFAttached/RigAdaptors/Adaptors/MultiRotationConstraintAdaptor.cs.meta new file mode 100644 index 0000000..8a9c356 --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/MultiRotationConstraintAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: de8f43467d976b544b7967ab5425d23b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/Adaptors/OverrideTransformAdaptor.cs b/KFAttached/RigAdaptors/Adaptors/OverrideTransformAdaptor.cs new file mode 100644 index 0000000..69179b1 --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/OverrideTransformAdaptor.cs @@ -0,0 +1,48 @@ +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class OverrideTransformAdaptor : RigAdaptorAbs +{ + [SerializeField] + private string m_ConstrainedObject; + [SerializeField] + private Transform m_OverrideSource; + [SerializeField] + private Vector3 m_OverridePosition; + [SerializeField] + private Vector3 m_OverrideRotation; + [SerializeField] + private float m_PositionWeight; + [SerializeField] + private float m_RotationWeight; + [SerializeField] + private OverrideTransformData.Space m_Space; + + public override void FindRigTargets() + { + var constraint = GetComponent(); + constraint.Reset(); + constraint.weight = weight; + constraint.data.constrainedObject = targetRoot.FindInAllChildren(m_ConstrainedObject); + constraint.data.sourceObject = m_OverrideSource; + constraint.data.position = m_OverridePosition; + constraint.data.rotation = m_OverrideRotation; + constraint.data.positionWeight = m_PositionWeight; + constraint.data.rotationWeight = m_RotationWeight; + constraint.data.space = m_Space; + } + + public override void ReadRigData() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_ConstrainedObject = constraint.data.constrainedObject?.name; + m_OverrideSource = constraint.data.sourceObject; + m_OverridePosition = constraint.data.position; + m_OverrideRotation = constraint.data.rotation; + m_PositionWeight = constraint.data.positionWeight; + m_RotationWeight = constraint.data.rotationWeight; + m_Space = constraint.data.space; + } +} diff --git a/KFAttached/RigAdaptors/Adaptors/OverrideTransformAdaptor.cs.meta b/KFAttached/RigAdaptors/Adaptors/OverrideTransformAdaptor.cs.meta new file mode 100644 index 0000000..6aa2c70 --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/OverrideTransformAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4b059f9f28d4d2e46a30530eeede98a4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/Adaptors/RigAdaptorAbs.cs b/KFAttached/RigAdaptors/Adaptors/RigAdaptorAbs.cs new file mode 100644 index 0000000..85782bf --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/RigAdaptorAbs.cs @@ -0,0 +1,34 @@ +using System; +using UnityEngine; +using UnityEngine.Animations.Rigging; + +public abstract class RigAdaptorAbs : MonoBehaviour +{ + [NonSerialized] + public Transform targetRoot; + [SerializeField] + protected float weight = 1f; + public abstract void ReadRigData(); + public abstract void FindRigTargets(); + + protected void WeightedTransformArrayToAdaptor(WeightedTransformArray array, out string[] transforms, out float[] weights) + { + transforms = new string[array.Count]; + weights = new float[array.Count]; + for (int i = 0; i < array.Count; i++) + { + transforms[i] = array[i].transform?.name; + weights[i] = array[i].weight; + } + } + + protected WeightedTransformArray WeightedTransformArrayFromAdaptor(Transform targetRoot, string[] transforms, float[] weights) + { + WeightedTransformArray array = new WeightedTransformArray(); + for (int i = 0; i < transforms.Length; i++) + { + array.Add(new WeightedTransform(targetRoot.FindInAllChildren(transforms[i]), weights[i])); + } + return array; + } +} diff --git a/KFAttached/RigAdaptors/Adaptors/RigAdaptorAbs.cs.meta b/KFAttached/RigAdaptors/Adaptors/RigAdaptorAbs.cs.meta new file mode 100644 index 0000000..c348e47 --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/RigAdaptorAbs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a19d5243587a11344927b47b3a9240fd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/Adaptors/TwistChainConstraintAdaptor.cs b/KFAttached/RigAdaptors/Adaptors/TwistChainConstraintAdaptor.cs new file mode 100644 index 0000000..00e4aa3 --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/TwistChainConstraintAdaptor.cs @@ -0,0 +1,40 @@ +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class TwistChainConstraintAdaptor : RigAdaptorAbs +{ + [SerializeField] + private string m_Root; + [SerializeField] + private string m_Tip; + [SerializeField] + private Transform m_RootTarget; + [SerializeField] + private Transform m_TipTarget; + [SerializeField] + private AnimationCurve m_Curve; + + public override void FindRigTargets() + { + var constraint = GetComponent(); + constraint.Reset(); + constraint.weight = weight; + constraint.data.root = targetRoot.FindInAllChildren(m_Root); + constraint.data.tip = targetRoot.FindInAllChildren(m_Tip); + constraint.data.rootTarget = m_RootTarget; + constraint.data.tipTarget = m_TipTarget; + constraint.data.curve = m_Curve; + } + + public override void ReadRigData() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_Root = constraint.data.root?.name; + m_Tip = constraint.data.tip?.name; + m_RootTarget = constraint.data.rootTarget; + m_TipTarget = constraint.data.tipTarget; + m_Curve = constraint.data.curve; + } +} diff --git a/KFAttached/RigAdaptors/Adaptors/TwistChainConstraintAdaptor.cs.meta b/KFAttached/RigAdaptors/Adaptors/TwistChainConstraintAdaptor.cs.meta new file mode 100644 index 0000000..72b2cd4 --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/TwistChainConstraintAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e71b15efb8d89b9428eaee7b14e80f8d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/Adaptors/TwistCorrectionAdaptor.cs b/KFAttached/RigAdaptors/Adaptors/TwistCorrectionAdaptor.cs new file mode 100644 index 0000000..9688006 --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/TwistCorrectionAdaptor.cs @@ -0,0 +1,50 @@ +using System.Linq; +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class TwistCorrectionAdaptor : RigAdaptorAbs +{ + [SerializeField] + private string m_Source; + [SerializeField] + private TwistCorrectionData.Axis m_TwistAxis; + [SerializeField] + private string[] m_TwistNodes; + public override void FindRigTargets() + { + var constraint = GetComponent(); + if (m_TwistNodes == null) + { + Log.Error("twist nodes array not serialized!"); + Component.Destroy(constraint); + Component.Destroy(this); + return; + } + constraint.Reset(); + constraint.weight = weight; + constraint.data.sourceObject = targetRoot.FindInAllChildren(m_Source); + constraint.data.twistAxis = m_TwistAxis; + var twistNodes = new WeightedTransformArray(m_TwistNodes.Length); + for (int i = 0; i < m_TwistNodes.Length; i++) + { + string[] node = m_TwistNodes[i].Split(';'); + if (node.Length == 2) + { + if (!string.IsNullOrEmpty(node[0])) + twistNodes.SetTransform(i, targetRoot.FindInAllChildren(node[0])); + twistNodes.SetWeight(i, float.Parse(node[1])); + } + } + constraint.data.twistNodes = twistNodes; + } + + public override void ReadRigData() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_Source = constraint.data.sourceObject.name; + m_TwistAxis = constraint.data.twistAxis; + m_TwistNodes = constraint.data.twistNodes.Select(n => (n.transform.gameObject?.name ?? "") + ';' + n.weight.ToString()).ToArray(); + } +} diff --git a/KFAttached/RigAdaptors/Adaptors/TwistCorrectionAdaptor.cs.meta b/KFAttached/RigAdaptors/Adaptors/TwistCorrectionAdaptor.cs.meta new file mode 100644 index 0000000..14d38ea --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/TwistCorrectionAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0de395249ff56c646ab5045a6b20568a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/Adaptors/TwoBoneIKConstraintAdaptor.cs b/KFAttached/RigAdaptors/Adaptors/TwoBoneIKConstraintAdaptor.cs new file mode 100644 index 0000000..51a9cb2 --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/TwoBoneIKConstraintAdaptor.cs @@ -0,0 +1,59 @@ +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class TwoBoneIKConstraintAdaptor : RigAdaptorAbs +{ + [SerializeField] + private string m_Root; + [SerializeField] + private string m_Mid; + [SerializeField] + private string m_Tip; + [SerializeField] + private Transform m_Target; + [SerializeField] + private Transform m_Hint; + [SerializeField] + private float m_TargetPositionWeight; + [SerializeField] + private float m_TargetRotationWeight; + [SerializeField] + private float m_HintWeight; + [SerializeField] + private bool m_MaintainTargetPositionOffset; + [SerializeField] + private bool m_MaintainTargetRotationOffset; + public override void FindRigTargets() + { + var constraint = GetComponent(); + constraint.Reset(); + constraint.weight = weight; + constraint.data.root = targetRoot.FindInAllChildren(m_Root); + constraint.data.mid = targetRoot.FindInAllChildren(m_Mid); + constraint.data.tip = targetRoot.FindInAllChildren(m_Tip); + constraint.data.target = m_Target; + constraint.data.hint = m_Hint; + constraint.data.targetPositionWeight = m_TargetPositionWeight; + constraint.data.targetRotationWeight = m_TargetRotationWeight; + constraint.data.hintWeight = m_HintWeight; + constraint.data.maintainTargetPositionOffset = m_MaintainTargetPositionOffset; + constraint.data.maintainTargetRotationOffset = m_MaintainTargetRotationOffset; + } + + public override void ReadRigData() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_Root = constraint.data.root?.name; + m_Mid = constraint.data.mid?.name; + m_Tip = constraint.data.tip?.name; + m_Target = constraint.data.target; + m_Hint = constraint.data.hint; + m_TargetPositionWeight = constraint.data.targetPositionWeight; + m_TargetRotationWeight = constraint.data.targetRotationWeight; + m_HintWeight = constraint.data.hintWeight; + m_MaintainTargetPositionOffset = constraint.data.maintainTargetPositionOffset; + m_MaintainTargetRotationOffset = constraint.data.maintainTargetRotationOffset; + } +} diff --git a/KFAttached/RigAdaptors/Adaptors/TwoBoneIKConstraintAdaptor.cs.meta b/KFAttached/RigAdaptors/Adaptors/TwoBoneIKConstraintAdaptor.cs.meta new file mode 100644 index 0000000..6e81144 --- /dev/null +++ b/KFAttached/RigAdaptors/Adaptors/TwoBoneIKConstraintAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 917124d705d34eb4bb6e982c423de951 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/AnimationTargetsAbs.cs b/KFAttached/RigAdaptors/AnimationTargetsAbs.cs new file mode 100644 index 0000000..e7fa442 --- /dev/null +++ b/KFAttached/RigAdaptors/AnimationTargetsAbs.cs @@ -0,0 +1,542 @@ +#if NotEditor +using KFCommonUtilityLib.Scripts.StaticManagers; +using UniLinq; +#else +using System.Linq; +#endif +using System.Diagnostics; +using UnityEngine; +using UnityEngine.Animations; +using UnityEngine.Animations.Rigging; +using System; +using System.Collections.Generic; + +[AddComponentMenu("")] +public abstract class AnimationTargetsAbs : MonoBehaviour +{ + protected enum ParentName + { + Spine3, + LeftHand, + RightHand, + } + protected static readonly string[] ParentNames = { "Spine3", "LeftHand", "RightHand" }; + protected static string GetParentName(ParentName name) => ParentNames[(int)name]; + [Header("TPV Fields")] + [SerializeField] + protected Transform itemTpv; + [SerializeField] + protected RuntimeAnimatorController weaponRuntimeControllerTpv; + [SerializeField] + protected AvatarMask weaponRigMaskTpv; + [SerializeField] + protected ParentName parentNameTpv; + + private Rig[] rigTpv; + private RigLayer[] rigLayerTpv; + protected Animator itemAnimatorTpv; + protected bool fpvSet = false; + protected bool tpvSet = false; + private Dictionary dict_attachments = new Dictionary(); + + public abstract Transform ItemFpv { get; protected set; } + public abstract Transform AttachmentRef { get; protected set; } + public Transform ItemTpv { get => itemTpv; protected set => itemTpv = value; } + public Transform ItemTpvOrSelf => itemTpv ? itemTpv : transform; + public bool IsFpv { get; set; } + public bool IsAnimationSet => (IsFpv && fpvSet) || (!IsFpv && tpvSet); + public bool Destroyed { get; protected set; } + public Transform PlayerAnimatorTrans { get; private set; } + public Animator ItemAnimator => IsFpv ? ItemAnimatorFpv : ItemAnimatorTpv; + public Transform ItemCurrent => IsFpv ? ItemFpv : ItemTpv; + public Transform ItemCurrentOrDefault => IsFpv ? ItemFpv : ItemTpvOrSelf; + public AnimationGraphBuilder GraphBuilder { get; private set; } + + protected abstract Animator ItemAnimatorFpv { get; } + protected virtual Animator ItemAnimatorTpv => itemAnimatorTpv; + + private Transform spine1, spine2, spine3; + + protected virtual void Awake() + { + foreach (var bindings in GetComponentsInChildren(true)) + { + bindings.targets = this; + } +#if NotEditor + gameObject.GetOrAddComponent().attachmentReference = AttachmentRef; +#endif + if (itemTpv) + { + rigTpv = itemTpv.GetComponentsInChildren(true); +#if NotEditor + if (rigTpv.Length > 0) + { + int uid = TypeBasedUID.UID; + foreach (var rig in rigTpv) + { + rig.gameObject.name += $"_UID_{uid}"; + AnimationRiggingManager.AddRigExcludeName(rig.gameObject.name); + } + } + rigLayerTpv = new RigLayer[rigTpv.Length]; +#endif + itemTpv.gameObject.SetActive(false); + } + } + + //attaching the same prefab multiple times is not allowed! + public void AttachPrefab(GameObject prefab) + { + if (!Destroyed && dict_attachments != null && prefab.TryGetComponent(out var appended)) + { + appended.Merge(this); + dict_attachments[prefab.name] = prefab.gameObject; + } + } + + public GameObject GetPrefab(string name) + { + if (Destroyed || dict_attachments == null || !dict_attachments.TryGetValue(name, out var prefab)) + { + return null; + } + return prefab; + } + + public void Init(Transform playerAnimatorTrans, bool isFpv) + { + if (Destroyed || (isFpv && fpvSet) || (!isFpv && tpvSet)) + { + return; + } + if (!playerAnimatorTrans) + { + Destroy(); + return; + } + var animator = playerAnimatorTrans.GetComponentInChildren(true); + if (!animator) + { + Destroy(); + return; + } + fpvSet = false; + tpvSet = false; + playerAnimatorTrans = animator.transform; + PlayerAnimatorTrans = playerAnimatorTrans; + GraphBuilder = playerAnimatorTrans.AddMissingComponent(); + IsFpv = isFpv; + if (!isFpv) + { + itemAnimatorTpv = animator; + } + else + { + itemAnimatorTpv = null; + } + spine1 = PlayerAnimatorTrans.FindInAllChildren("Spine1"); + spine2 = spine1.Find("Spine2"); + spine3 = spine2.Find("Spine3"); + +#if NotEditor + Utils.SetLayerRecursively(gameObject, 24, Utils.ExcludeLayerZoom); + if (ItemFpv) + { + Utils.SetLayerRecursively(ItemFpv.gameObject, 10, Utils.ExcludeLayerZoom); + } + if (ItemTpv) + { + Utils.SetLayerRecursively(ItemTpv.gameObject, 24, Utils.ExcludeLayerZoom); + } +#endif + if (ItemTpv) + { + ItemTpv.parent = isFpv ? playerAnimatorTrans.parent : playerAnimatorTrans; + ItemTpv.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); + ItemTpv.localScale = Vector3.one; + } + if (!Destroyed) + { + Init(); + SetEnabled(false); + //Log.Out($"Init rig\n{StackTraceUtility.ExtractStackTrace()}"); + } + } + + protected virtual void Init() + { + if (!itemTpv) + { + return; + } + + itemTpv.SetParent(PlayerAnimatorTrans.parent); + itemTpv.position = Vector3.zero; + itemTpv.localPosition = Vector3.zero; + itemTpv.localRotation = Quaternion.identity; + if (!IsFpv) + { + itemAnimatorTpv = PlayerAnimatorTrans.GetComponent(); + if (rigTpv.Length > 0) + { + foreach (var rig in rigTpv) + { + if (rig.TryGetComponent(out var rc)) + { + rc.targetRoot = PlayerAnimatorTrans; + rc.Rebind(); + } + } + } + } + else + { + itemAnimatorTpv = null; + } + } + + public void Setup() + { + if (!PlayerAnimatorTrans) + { + Destroy(); + return; + } + if (IsFpv && ItemFpv && !fpvSet) + { + fpvSet = SetupFpv(); + } + else if (!IsFpv && ItemTpv && !tpvSet) + { + tpvSet = SetupTpv(); + } + } + + protected abstract bool SetupFpv(); + + protected virtual bool SetupTpv() + { + Stopwatch sw = new Stopwatch(); + sw.Start(); + + itemTpv.SetParent(itemAnimatorTpv.transform.FindInAllChildren(GetParentName(parentNameTpv))); + itemTpv.position = Vector3.zero; + itemTpv.localPosition = Vector3.zero; + itemTpv.localRotation = Quaternion.identity; + + GraphBuilder.InitWeapon(ItemTpv, weaponRuntimeControllerTpv, weaponRigMaskTpv); + + var rigBuilder = PlayerAnimatorTrans.AddMissingComponent(); +#if NotEditor + foreach (var layer in rigBuilder.layers) + { + if (layer.name == SDCSUtils.IKRIG) + { + layer.active = false; + } + } +#endif + if (rigTpv.Length > 0) + { + rigBuilder.layers.RemoveAll(r => rigLayerTpv.Any(layer => layer?.name == r.name)); + for (int i = 0; i < rigTpv.Length; i++) + { + rigBuilder.layers.Insert(i, rigLayerTpv[i] = new RigLayer(rigTpv[i], true)); + } + } + BuildRig(PlayerAnimatorTrans.GetComponent(), rigBuilder); + + sw.Stop(); + string info = $"setup tpv animation graph took {sw.ElapsedMilliseconds} ms"; + //info += $"\n{StackTraceUtility.ExtractStackTrace()}"; + Log.Out(info); + return true; + } + + public void Remove() + { + if (!PlayerAnimatorTrans) + { + Destroy(); + return; + } + if (IsFpv && ItemFpv && fpvSet) + { + RemoveFpv(); + fpvSet = false; + } + else if (!IsFpv && ItemTpv && tpvSet) + { + RemoveTpv(); + tpvSet = false; + } + } + + protected abstract void RemoveFpv(); + + protected virtual void RemoveTpv() + { + Stopwatch sw = new Stopwatch(); + sw.Start(); + + itemTpv.SetParent(PlayerAnimatorTrans.parent); + itemTpv.position = Vector3.zero; + itemTpv.localPosition = Vector3.zero; + itemTpv.localRotation = Quaternion.identity; + + var rigBuilder = PlayerAnimatorTrans.AddMissingComponent(); +#if NotEditor + foreach (var layer in rigBuilder.layers) + { + if (layer.name == SDCSUtils.IKRIG) + { + layer.active = true; + } + } +#endif + if (rigTpv.Length > 0) + { + rigBuilder.layers.RemoveAll(r => rigLayerTpv.Any(layer => layer?.name == r.name)); + Array.Clear(rigLayerTpv, 0, rigLayerTpv.Length); + + //rigTpv.transform.SetParent(transform, false); + //rigTpv.gameObject.SetActive(false); + } + BuildRig(PlayerAnimatorTrans.GetComponent(), rigBuilder); + + sw.Stop(); + string info = $"destroy tpv animation graph took {sw.ElapsedMilliseconds} ms"; + //info += $"\n{StackTraceUtility.ExtractStackTrace()}"; + Log.Out(info); + } + + public virtual void Destroy() + { + + if (AttachmentRef) + { + AttachmentRef.parent = transform; + AttachmentRef = null; + } + + DestroyFpv(); + DestroyTpv(); +#if NotEditor + Destroyed = true; +#endif + PlayerAnimatorTrans = null; + dict_attachments = null; + + Component.DestroyImmediate(this); + //Log.Out(StackTraceUtility.ExtractStackTrace()); + } + + public virtual void DestroyFpv() + { + if (ItemFpv) + { + if (IsFpv && fpvSet && PlayerAnimatorTrans) + { + GraphBuilder.SetCurrentTarget(null); + } + ItemFpv.parent = null; + GameObject.DestroyImmediate(ItemFpv.gameObject); + } + fpvSet = false; + ItemFpv = null; + Log.Out("destroy fpv"); + } + + public virtual void DestroyTpv() + { + if (ItemTpv) + { + if (!IsFpv && tpvSet && PlayerAnimatorTrans) + { + GraphBuilder.SetCurrentTarget(null); + } + ItemTpv.parent = null; + GameObject.DestroyImmediate(ItemTpv.gameObject); + } + tpvSet = false; + ItemTpv = null; + Log.Out("destroy tpv"); + } + + public virtual void SetEnabled(bool enabled) + { + if (Destroyed) + { + return; + } + if (AttachmentRef) + { + AttachmentRef.parent = enabled ? (IsFpv ? ItemFpv : ItemTpvOrSelf) : transform; + } + if (enabled) + { + if (ItemFpv) + { + ItemFpv.gameObject.SetActive(IsFpv); + } + if (ItemTpv) + { + ItemTpv.gameObject.SetActive(!IsFpv); + } + Setup(); + } + else + { + Remove(); + if (ItemFpv) + { + ItemFpv.gameObject.SetActive(false); + } + if (ItemTpv) + { + ItemTpv.gameObject.SetActive(false); + } + } + if (ItemTpv) + { + gameObject.SetActive(false); + } + else + { + gameObject.SetActive(!IsFpv); + } + } + + protected void BuildRig(Animator animator, RigBuilder rb) + { + animator.UnbindAllStreamHandles(); + animator.UnbindAllSceneHandles(); + rb.Build(); + animator.Rebind(); + } + + private readonly static int[] resetHashes = new int[] + { + Animator.StringToHash("Reload"), + Animator.StringToHash("PowerAttack"), + Animator.StringToHash("UseItem"), + Animator.StringToHash("ItemUse"), + Animator.StringToHash("WeaponFire") + }; + +#if NotEditor + //VRoid switch view workaround + public void OnEnable() + { + var player = GetComponentInParent(); + if ((player && player.bFirstPersonView) || ItemTpv) + { + gameObject.SetActive(false); + } + } + + public virtual void UpdatePlayerAvatar(AvatarController avatarController, bool rigWeaponChanged) + { + //var itemCurrent = ItemCurrent; + //if (itemCurrent && !itemCurrent.gameObject.activeSelf) + //{ + // Log.Out("Rigged weapon not active, enabling it..."); + // SetEnabled(true); + //} + if (IsAnimationSet) + { + foreach (var hash in resetHashes) + { + var role = GraphBuilder.GetWrapperRoleByParamHash(hash); + if (role == AnimationGraphBuilder.ParamInWrapper.Vanilla || role == AnimationGraphBuilder.ParamInWrapper.Both) + { + GraphBuilder.VanillaWrapper.ResetTrigger(hash); + } + } + } + if (IsFpv && fpvSet) + { + GraphBuilder.VanillaWrapper.Play("idle", 0, 0f); + avatarController.UpdateInt(AvatarController.weaponHoldTypeHash, -1, false); + } + else if (!IsFpv && tpvSet) + { + //avatarController.UpdateInt(AvatarController.weaponHoldTypeHash, 0, false); + //GraphBuilder.VanillaWrapper.Play("Unarmed", GraphBuilder.VanillaWrapper.GetLayerIndex("StandingIdleTurn"), 0); + GraphBuilder.VanillaWrapper.Play("Empty", GraphBuilder.VanillaWrapper.GetLayerIndex("RightHandHoldPoses"), 0); + GraphBuilder.VanillaWrapper.Play("Empty", GraphBuilder.VanillaWrapper.GetLayerIndex("RangedRightHandHoldPoses"), 0); + GraphBuilder.VanillaWrapper.Play("Empty", GraphBuilder.VanillaWrapper.GetLayerIndex("AdditiveOffsetHoldPoses"), 0); + GraphBuilder.VanillaWrapper.Play("Empty", GraphBuilder.VanillaWrapper.GetLayerIndex("RightArmHoldPoses"), 0); + GraphBuilder.VanillaWrapper.Play("Empty", GraphBuilder.VanillaWrapper.GetLayerIndex("BothArmsHoldPoses"), 0); + //GraphBuilder.VanillaWrapper.Play("Empty", GraphBuilder.VanillaWrapper.GetLayerIndex("AdditiveAimPoses"), 0); + GraphBuilder.VanillaWrapper.Play("Empty", GraphBuilder.VanillaWrapper.GetLayerIndex("UpperBodyAttack"), 0); + GraphBuilder.VanillaWrapper.Play("Empty", GraphBuilder.VanillaWrapper.GetLayerIndex("BowDrawAndFire"), 0); + GraphBuilder.VanillaWrapper.Play("Empty", GraphBuilder.VanillaWrapper.GetLayerIndex("UpperBodyUseAndReload"), 0); + GraphBuilder.VanillaWrapper.Play("Empty", GraphBuilder.VanillaWrapper.GetLayerIndex("AdditiveRangedAttack"), 0); + } + } + + //public void UpdateTpvSpineRotation(EntityPlayer player) + //{ + // if (!IsFpv && tpvSet && player && !player.IsDead()) + // { + // float xOffset = player.rotation.x / 3f; + // float yOffset = 0f; + // if (player.IsCrouching) + // { + // xOffset += 10f; + // yOffset += 5f; + // } + // if (player.MovementState > 0) + // { + // xOffset += player.speedForward; + // } + // if (Time.timeScale > 0.001f) + // { + // spine1.transform.localEulerAngles = new Vector3(spine1.transform.localEulerAngles.x - xOffset, spine1.transform.localEulerAngles.y - yOffset, spine1.transform.localEulerAngles.z); + // spine2.transform.localEulerAngles = new Vector3(spine2.transform.localEulerAngles.x - xOffset, spine2.transform.localEulerAngles.y - yOffset, spine2.transform.localEulerAngles.z); + // spine3.transform.localEulerAngles = new Vector3(spine3.transform.localEulerAngles.x - xOffset, spine3.transform.localEulerAngles.y - yOffset, spine3.transform.localEulerAngles.z); + // return; + // } + // spine1.transform.localEulerAngles = new Vector3(-xOffset, spine1.transform.localEulerAngles.y - yOffset, spine1.transform.localEulerAngles.z); + // spine2.transform.localEulerAngles = new Vector3(-xOffset, spine2.transform.localEulerAngles.y - yOffset, spine2.transform.localEulerAngles.z); + // spine3.transform.localEulerAngles = new Vector3(-xOffset, spine3.transform.localEulerAngles.y - yOffset, spine3.transform.localEulerAngles.z); + // } + //} + +#else + public void Update() + { + if (IsAnimationSet) + { + foreach (var hash in resetHashes) + { + var role = GraphBuilder.GetWrapperRoleByParamHash(hash); + if (role == AnimationGraphBuilder.ParamInWrapper.Vanilla || role == AnimationGraphBuilder.ParamInWrapper.Both) + { + GraphBuilder.VanillaWrapper.ResetTrigger(hash); + } + } + } + if (IsFpv && fpvSet) + { + GraphBuilder.VanillaWrapper.Play("idle", 0, 0f); + } + else if (!IsFpv && tpvSet) + { + //GraphBuilder.VanillaWrapper.Play("Unarmed", GraphBuilder.VanillaWrapper.GetLayerIndex("StandingIdleTurn"), 0); + GraphBuilder.VanillaWrapper.Play("Empty", GraphBuilder.VanillaWrapper.GetLayerIndex("RightHandHoldPoses"), 0); + GraphBuilder.VanillaWrapper.Play("Empty", GraphBuilder.VanillaWrapper.GetLayerIndex("RangedRightHandHoldPoses"), 0); + GraphBuilder.VanillaWrapper.Play("Empty", GraphBuilder.VanillaWrapper.GetLayerIndex("AdditiveOffsetHoldPoses"), 0); + GraphBuilder.VanillaWrapper.Play("Empty", GraphBuilder.VanillaWrapper.GetLayerIndex("RightArmHoldPoses"), 0); + GraphBuilder.VanillaWrapper.Play("Empty", GraphBuilder.VanillaWrapper.GetLayerIndex("BothArmsHoldPoses"), 0); + //GraphBuilder.VanillaWrapper.Play("Empty", GraphBuilder.VanillaWrapper.GetLayerIndex("AdditiveAimPoses"), 0); + GraphBuilder.VanillaWrapper.Play("Empty", GraphBuilder.VanillaWrapper.GetLayerIndex("UpperBodyAttack"), 0); + GraphBuilder.VanillaWrapper.Play("Empty", GraphBuilder.VanillaWrapper.GetLayerIndex("BowDrawAndFire"), 0); + GraphBuilder.VanillaWrapper.Play("Empty", GraphBuilder.VanillaWrapper.GetLayerIndex("UpperBodyUseAndReload"), 0); + GraphBuilder.VanillaWrapper.Play("Empty", GraphBuilder.VanillaWrapper.GetLayerIndex("AdditiveRangedAttack"), 0); + } + } +#endif +} \ No newline at end of file diff --git a/KFAttached/RigAdaptors/AnimationTargetsAbs.cs.meta b/KFAttached/RigAdaptors/AnimationTargetsAbs.cs.meta new file mode 100644 index 0000000..d10dc98 --- /dev/null +++ b/KFAttached/RigAdaptors/AnimationTargetsAbs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9257aed4b0cc243458e5209dd3fb523d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/PlayGraphTargets.cs b/KFAttached/RigAdaptors/PlayGraphTargets.cs new file mode 100644 index 0000000..03bd0d2 --- /dev/null +++ b/KFAttached/RigAdaptors/PlayGraphTargets.cs @@ -0,0 +1,165 @@ +#if NotEditor +using KFCommonUtilityLib.Scripts.StaticManagers; +using UniLinq; +#else +using System.Linq; +#endif +using System.Diagnostics; +using UnityEngine; +using UnityEngine.Animations.Rigging; +using System; + +[AddComponentMenu("KFAttachments/RigAdaptors/PlayGraph Targets")] +public class PlayGraphTargets : AnimationTargetsAbs +{ + [Header("FPV Fields")] + [SerializeField] + public Transform itemFpv; + [SerializeField] + public Transform attachmentReference; + [SerializeField] + private RuntimeAnimatorController weaponRuntimeControllerFpv; + [SerializeField] + private ParentName parentNameFpv; + + private Rig[] rigFpv; + private RigLayer[] rigLayerFpv; + private Animator itemAnimatorFpv; + public override Transform ItemFpv { get => itemFpv; protected set => itemFpv = value; } + + public override Transform AttachmentRef { get => attachmentReference; protected set => attachmentReference = value; } + + protected override Animator ItemAnimatorFpv => itemAnimatorFpv; + + protected override void Awake() + { + base.Awake(); + if (!itemFpv) + { + return; + } + + rigFpv = itemFpv.GetComponentsInChildren(); +#if NotEditor + if (rigFpv.Length > 0) + { + int uid = TypeBasedUID.UID; + foreach (var rig in rigFpv) + { + rig.gameObject.name += $"_UID_{uid}"; + AnimationRiggingManager.AddRigExcludeName(rig.gameObject.name); + } + } + rigLayerFpv = new RigLayer[rigFpv.Length]; +#endif + itemFpv.gameObject.SetActive(false); + } + + protected override void Init() + { + base.Init(); + if (!itemFpv) + { + return; + } + + itemFpv.SetParent(PlayerAnimatorTrans.parent); + itemFpv.position = Vector3.zero; + itemFpv.localPosition = Vector3.zero; + itemFpv.localRotation = Quaternion.identity; + + if (IsFpv) + { + itemAnimatorFpv = PlayerAnimatorTrans.GetComponent(); + if (rigFpv.Length > 0) + { + foreach (var rig in rigFpv) + { + if (rig.TryGetComponent(out var rc)) + { + rc.targetRoot = PlayerAnimatorTrans; + rc.Rebind(); + } + } + } + } + else + { + itemAnimatorFpv = null; + } + } + + protected override bool SetupFpv() + { + Stopwatch sw = new Stopwatch(); + sw.Start(); + + itemFpv.SetParent(itemAnimatorFpv.transform.FindInAllChildren(GetParentName(parentNameFpv))); + itemFpv.position = Vector3.zero; + itemFpv.localPosition = Vector3.zero; + itemFpv.localRotation = Quaternion.identity; + + GraphBuilder.InitWeapon(itemFpv, weaponRuntimeControllerFpv, null); + var rigBuilder = PlayerAnimatorTrans.AddMissingComponent(); +#if NotEditor + foreach (var layer in rigBuilder.layers) + { + if (layer.name == SDCSUtils.IKRIG) + { + layer.active = false; + } + } +#endif + if (rigFpv.Length > 0) + { + rigBuilder.layers.RemoveAll(r => rigLayerFpv.Any(layer => layer?.name == r.name)); + for (int i = 0; i < rigFpv.Length; i++) + { + rigBuilder.layers.Insert(i, rigLayerFpv[i] = new RigLayer(rigFpv[i], true)); + } + } + BuildRig(PlayerAnimatorTrans.GetComponent(), rigBuilder); + + sw.Stop(); + string info = $"setup fpv animation graph took {sw.ElapsedMilliseconds} ms"; + //info += $"\n{StackTraceUtility.ExtractStackTrace()}"; + Log.Out(info); + return true; + } + + protected override void RemoveFpv() + { + Stopwatch sw = new Stopwatch(); + sw.Start(); + + itemFpv.SetParent(PlayerAnimatorTrans.parent); + itemFpv.position = Vector3.zero; + itemFpv.localPosition = Vector3.zero; + itemFpv.localRotation = Quaternion.identity; + + var rigBuilder = PlayerAnimatorTrans.AddMissingComponent(); +#if NotEditor + foreach (var layer in rigBuilder.layers) + { + if (layer.name == SDCSUtils.IKRIG) + { + layer.active = true; + } + } +#endif + if (rigFpv.Length > 0) + { + rigBuilder.layers.RemoveAll(r => rigLayerFpv.Any(layer => layer?.name == r.name)); + Array.Clear(rigLayerFpv, 0, rigLayerFpv.Length); + + //rigFpv.transform.SetParent(transform, false); + //rigFpv.gameObject.SetActive(false); + } + BuildRig(PlayerAnimatorTrans.GetComponent(), rigBuilder); + + sw.Stop(); + string info = $"destroy fpv animation graph took {sw.ElapsedMilliseconds} ms"; + //info += $"\n{StackTraceUtility.ExtractStackTrace()}"; + Log.Out(info); + } +} \ No newline at end of file diff --git a/KFAttached/RigAdaptors/PlayGraphTargets.cs.meta b/KFAttached/RigAdaptors/PlayGraphTargets.cs.meta new file mode 100644 index 0000000..ce559c4 --- /dev/null +++ b/KFAttached/RigAdaptors/PlayGraphTargets.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6cdb3ae68f5838c49a4ee601f977eb76 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/ReverseAdaptors.meta b/KFAttached/RigAdaptors/ReverseAdaptors.meta new file mode 100644 index 0000000..c958550 --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8c37be1fa2e9bdc4eae9a203d5cf95c4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/BlendConstraintReverseAdaptor.cs b/KFAttached/RigAdaptors/ReverseAdaptors/BlendConstraintReverseAdaptor.cs new file mode 100644 index 0000000..9384a6b --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/BlendConstraintReverseAdaptor.cs @@ -0,0 +1,56 @@ +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class BlendConstraintReverseAdaptor : RigAdaptorAbs +{ + [SerializeField] + private Transform m_ConstrainedObject; + [SerializeField] + private string m_SourceA; + [SerializeField] + private string m_SourceB; + [SerializeField] + private bool m_BlendPosition; + [SerializeField] + private bool m_BlendRotation; + [SerializeField] + private float m_PositionWeight; + [SerializeField] + private float m_RotationWeight; + [SerializeField] + private bool m_MaintainPositionOffsets; + [SerializeField] + private bool m_MaintainRotationOffsets; + + public override void ReadRigData() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_ConstrainedObject = constraint.data.constrainedObject; + m_SourceA = constraint.data.sourceObjectA?.name; + m_SourceB = constraint.data.sourceObjectB?.name; + m_BlendPosition = constraint.data.blendPosition; + m_BlendRotation = constraint.data.blendRotation; + m_PositionWeight = constraint.data.positionWeight; + m_RotationWeight = constraint.data.rotationWeight; + m_MaintainPositionOffsets = constraint.data.maintainPositionOffsets; + m_MaintainRotationOffsets = constraint.data.maintainRotationOffsets; + } + + public override void FindRigTargets() + { + var constraint = GetComponent(); + constraint.Reset(); + constraint.weight = weight; + constraint.data.constrainedObject = m_ConstrainedObject; + constraint.data.sourceObjectA = targetRoot.FindInAllChildren(m_SourceA); + constraint.data.sourceObjectB = targetRoot.FindInAllChildren(m_SourceB); + constraint.data.blendPosition = m_BlendPosition; + constraint.data.blendRotation = m_BlendRotation; + constraint.data.positionWeight = m_PositionWeight; + constraint.data.rotationWeight = m_RotationWeight; + constraint.data.maintainPositionOffsets = m_MaintainPositionOffsets; + constraint.data.maintainRotationOffsets = m_MaintainRotationOffsets; + } +} diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/BlendConstraintReverseAdaptor.cs.meta b/KFAttached/RigAdaptors/ReverseAdaptors/BlendConstraintReverseAdaptor.cs.meta new file mode 100644 index 0000000..2bdfff3 --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/BlendConstraintReverseAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 98945ef0fc0d660459661507c533176e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/ChainIKConstraintReverseAdaptor.cs b/KFAttached/RigAdaptors/ReverseAdaptors/ChainIKConstraintReverseAdaptor.cs new file mode 100644 index 0000000..d9f138d --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/ChainIKConstraintReverseAdaptor.cs @@ -0,0 +1,56 @@ +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class ChainIKConstraintReverseAdaptor : RigAdaptorAbs +{ + [SerializeField] + private Transform m_Root; + [SerializeField] + private Transform m_Tip; + [SerializeField] + private string m_Target; + [SerializeField] + private float m_ChainRotationWeight; + [SerializeField] + private float m_TipRotationWeight; + [SerializeField] + private int m_MaxIterations; + [SerializeField] + private float m_Tolerance; + [SerializeField] + private bool m_MaintainTargetPositionOffset; + [SerializeField] + private bool m_MaintainTargetRotationOffset; + + public override void ReadRigData() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_Root = constraint.data.root; + m_Tip = constraint.data.tip; + m_Target = constraint.data.target?.name; + m_ChainRotationWeight = constraint.data.chainRotationWeight; + m_TipRotationWeight = constraint.data.tipRotationWeight; + m_MaxIterations = constraint.data.maxIterations; + m_Tolerance = constraint.data.tolerance; + m_MaintainTargetPositionOffset = constraint.data.maintainTargetPositionOffset; + m_MaintainTargetRotationOffset = constraint.data.maintainTargetRotationOffset; + } + + public override void FindRigTargets() + { + var constraint = GetComponent(); + constraint.Reset(); + constraint.weight = weight; + constraint.data.root = m_Root; + constraint.data.tip = m_Tip; + constraint.data.target = targetRoot.FindInAllChildren(m_Target); + constraint.data.chainRotationWeight = m_ChainRotationWeight; + constraint.data.tipRotationWeight = m_TipRotationWeight; + constraint.data.maxIterations = m_MaxIterations; + constraint.data.tolerance = m_Tolerance; + constraint.data.maintainTargetPositionOffset = m_MaintainTargetPositionOffset; + constraint.data.maintainTargetRotationOffset = m_MaintainTargetRotationOffset; + } +} diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/ChainIKConstraintReverseAdaptor.cs.meta b/KFAttached/RigAdaptors/ReverseAdaptors/ChainIKConstraintReverseAdaptor.cs.meta new file mode 100644 index 0000000..a1b4153 --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/ChainIKConstraintReverseAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d07f318626844d943a83719329ef46e3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/DampedTransformReverseAdaptor.cs b/KFAttached/RigAdaptors/ReverseAdaptors/DampedTransformReverseAdaptor.cs new file mode 100644 index 0000000..95a8d2f --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/DampedTransformReverseAdaptor.cs @@ -0,0 +1,38 @@ +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class DampedTransformReverseAdaptor : RigAdaptorAbs +{ + [SerializeField] + private Transform m_ConstrainedObject; + [SerializeField] + private string m_Source; + [SerializeField] + private float m_DampPosition; + [SerializeField] + private float m_DampRotation; + [SerializeField] + private bool m_MaintainAim; + public override void FindRigTargets() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_ConstrainedObject = constraint.data.constrainedObject; + m_Source = constraint.data.sourceObject?.name; + m_DampPosition = constraint.data.dampPosition; + m_DampRotation = constraint.data.dampRotation; + m_MaintainAim = constraint.data.maintainAim; + } + + public override void ReadRigData() + { + var constraint = GetComponent(); + constraint.weight = weight; + constraint.data.constrainedObject = m_ConstrainedObject; + constraint.data.sourceObject = targetRoot.FindInAllChildren(m_Source); + constraint.data.dampPosition = m_DampPosition; + constraint.data.dampRotation = m_DampRotation; + constraint.data.maintainAim = m_MaintainAim; + } +} diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/DampedTransformReverseAdaptor.cs.meta b/KFAttached/RigAdaptors/ReverseAdaptors/DampedTransformReverseAdaptor.cs.meta new file mode 100644 index 0000000..98f5512 --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/DampedTransformReverseAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 024d749c0fd809c4ba3d82d3a3618415 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/MultiAimConstraintReverseAdaptor.cs b/KFAttached/RigAdaptors/ReverseAdaptors/MultiAimConstraintReverseAdaptor.cs new file mode 100644 index 0000000..fcb324e --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/MultiAimConstraintReverseAdaptor.cs @@ -0,0 +1,69 @@ +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class MultiAimConstraintReverseAdaptor : RigAdaptorAbs +{ + [SerializeField] + private Transform m_ConstrainedObject; + [SerializeField] + private string[] m_SourceObjectNames; + [SerializeField] + private float[] m_SourceObjectWeights; + [SerializeField] + private Vector3 m_Offset; + [SerializeField] + private Vector2 m_limits; + [SerializeField] + private MultiAimConstraintData.Axis m_AimAxis; + [SerializeField] + private MultiAimConstraintData.Axis m_UpAxis; + [SerializeField] + private MultiAimConstraintData.WorldUpType m_WorldUpType; + [SerializeField] + private Transform m_WorldUpObject; + [SerializeField] + private MultiAimConstraintData.Axis m_WorldUpAxis; + [SerializeField] + private bool m_MaintainOffset; + [SerializeField] + private Vector3Bool m_ConstrainedAxes; + + public override void FindRigTargets() + { + var constraint = GetComponent(); + constraint.Reset(); + constraint.weight = weight; + constraint.data.constrainedObject = m_ConstrainedObject; + constraint.data.sourceObjects = WeightedTransformArrayFromAdaptor(targetRoot, m_SourceObjectNames, m_SourceObjectWeights); + constraint.data.offset = m_Offset; + constraint.data.limits = m_limits; + constraint.data.aimAxis = m_AimAxis; + constraint.data.upAxis = m_UpAxis; + constraint.data.worldUpType = m_WorldUpType; + constraint.data.worldUpObject = m_WorldUpObject; + constraint.data.worldUpAxis = m_WorldUpAxis; + constraint.data.maintainOffset = m_MaintainOffset; + constraint.data.constrainedXAxis = m_ConstrainedAxes.x; + constraint.data.constrainedYAxis = m_ConstrainedAxes.y; + constraint.data.constrainedZAxis = m_ConstrainedAxes.z; + } + + public override void ReadRigData() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_ConstrainedObject = constraint.data.constrainedObject; + WeightedTransformArrayToAdaptor(constraint.data.sourceObjects, out m_SourceObjectNames, out m_SourceObjectWeights); + m_Offset = constraint.data.offset; + m_limits = constraint.data.limits; + m_AimAxis = constraint.data.aimAxis; + m_UpAxis = constraint.data.upAxis; + m_WorldUpType = constraint.data.worldUpType; + if ((m_WorldUpType == MultiAimConstraintData.WorldUpType.ObjectUp || m_WorldUpType == MultiAimConstraintData.WorldUpType.ObjectRotationUp) && constraint.data.worldUpObject) + m_WorldUpObject = constraint.data.worldUpObject; + m_WorldUpAxis = constraint.data.worldUpAxis; + m_MaintainOffset = constraint.data.maintainOffset; + m_ConstrainedAxes = new Vector3Bool(constraint.data.constrainedXAxis, constraint.data.constrainedYAxis, constraint.data.constrainedZAxis); + } +} diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/MultiAimConstraintReverseAdaptor.cs.meta b/KFAttached/RigAdaptors/ReverseAdaptors/MultiAimConstraintReverseAdaptor.cs.meta new file mode 100644 index 0000000..e7a0994 --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/MultiAimConstraintReverseAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 57060a9b209c4cb489b39a7405f25bcb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/MultiParentConstraintReverseAdaptor.cs b/KFAttached/RigAdaptors/ReverseAdaptors/MultiParentConstraintReverseAdaptor.cs new file mode 100644 index 0000000..e1e919e --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/MultiParentConstraintReverseAdaptor.cs @@ -0,0 +1,49 @@ +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class MultiParentConstraintReverseAdaptor : RigAdaptorAbs +{ + [SerializeField] + private Transform m_ConstrainedObject; + [SerializeField] + private string[] m_SourceObjectNames; + [SerializeField] + private float[] m_SourceObjectWeights; + [SerializeField] + private Vector3Bool m_ConstrainedPositionAxes; + [SerializeField] + private Vector3Bool m_ConstrainedRotationAxes; + [SerializeField] + private bool m_MaintainPositionOffset; + [SerializeField] + private bool m_MaintainRotationOffset; + public override void FindRigTargets() + { + var constraint = GetComponent(); + constraint.Reset(); + constraint.weight = weight; + constraint.data.constrainedObject = m_ConstrainedObject; + constraint.data.sourceObjects = WeightedTransformArrayFromAdaptor(targetRoot, m_SourceObjectNames, m_SourceObjectWeights); + constraint.data.constrainedPositionXAxis = m_ConstrainedPositionAxes.x; + constraint.data.constrainedPositionYAxis = m_ConstrainedPositionAxes.y; + constraint.data.constrainedPositionZAxis = m_ConstrainedPositionAxes.z; + constraint.data.constrainedRotationXAxis = m_ConstrainedRotationAxes.x; + constraint.data.constrainedRotationYAxis = m_ConstrainedRotationAxes.y; + constraint.data.constrainedRotationZAxis = m_ConstrainedRotationAxes.z; + constraint.data.maintainPositionOffset = m_MaintainPositionOffset; + constraint.data.maintainRotationOffset = m_MaintainRotationOffset; + } + + public override void ReadRigData() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_ConstrainedObject = constraint.data.constrainedObject; + WeightedTransformArrayToAdaptor(constraint.data.sourceObjects, out m_SourceObjectNames, out m_SourceObjectWeights); + m_ConstrainedPositionAxes = new Vector3Bool(constraint.data.constrainedPositionXAxis, constraint.data.constrainedPositionYAxis, constraint.data.constrainedPositionZAxis); + m_ConstrainedRotationAxes = new Vector3Bool(constraint.data.constrainedRotationXAxis, constraint.data.constrainedRotationYAxis, constraint.data.constrainedRotationZAxis); + m_MaintainPositionOffset = constraint.data.maintainPositionOffset; + m_MaintainRotationOffset = constraint.data.maintainRotationOffset; + } +} diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/MultiParentConstraintReverseAdaptor.cs.meta b/KFAttached/RigAdaptors/ReverseAdaptors/MultiParentConstraintReverseAdaptor.cs.meta new file mode 100644 index 0000000..01d9458 --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/MultiParentConstraintReverseAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e4720f809bab89e4bab5459297567d0c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/MultiPositionConstraintReverseAdaptor.cs b/KFAttached/RigAdaptors/ReverseAdaptors/MultiPositionConstraintReverseAdaptor.cs new file mode 100644 index 0000000..d73688e --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/MultiPositionConstraintReverseAdaptor.cs @@ -0,0 +1,44 @@ +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class MultiPositionConstraintReverseAdaptor : RigAdaptorAbs +{ + [SerializeField] + private Transform m_ConstrainedObject; + [SerializeField] + private string[] m_SourceObjectNames; + [SerializeField] + private float[] m_SourceObjectWeights; + [SerializeField] + private Vector3 m_Offset; + [SerializeField] + private Vector3Bool m_ConstrainedAxes; + [SerializeField] + private bool m_MaintainOffset; + + public override void FindRigTargets() + { + var constraint = GetComponent(); + constraint.Reset(); + constraint.weight = weight; + constraint.data.constrainedObject = m_ConstrainedObject; + constraint.data.sourceObjects = WeightedTransformArrayFromAdaptor(targetRoot, m_SourceObjectNames, m_SourceObjectWeights); + constraint.data.offset = m_Offset; + constraint.data.constrainedXAxis = m_ConstrainedAxes.x; + constraint.data.constrainedYAxis = m_ConstrainedAxes.y; + constraint.data.constrainedZAxis = m_ConstrainedAxes.z; + constraint.data.maintainOffset = m_MaintainOffset; + } + + public override void ReadRigData() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_ConstrainedObject = constraint.data.constrainedObject; + WeightedTransformArrayToAdaptor(constraint.data.sourceObjects, out m_SourceObjectNames, out m_SourceObjectWeights); + m_Offset = constraint.data.offset; + m_ConstrainedAxes = new Vector3Bool(constraint.data.constrainedXAxis, constraint.data.constrainedYAxis, constraint.data.constrainedZAxis); + m_MaintainOffset = constraint.data.maintainOffset; + } +} diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/MultiPositionConstraintReverseAdaptor.cs.meta b/KFAttached/RigAdaptors/ReverseAdaptors/MultiPositionConstraintReverseAdaptor.cs.meta new file mode 100644 index 0000000..06df0d8 --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/MultiPositionConstraintReverseAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 700d88da068a59848930b99154a10879 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/MultiReferentialConstraintReverseAdaptor.cs b/KFAttached/RigAdaptors/ReverseAdaptors/MultiReferentialConstraintReverseAdaptor.cs new file mode 100644 index 0000000..43b8303 --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/MultiReferentialConstraintReverseAdaptor.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class MultiReferentialConstraintReverseAdaptor : RigAdaptorAbs +{ + [SerializeField] + private int m_Driver; + [SerializeField] + private List m_SourceObjects; + + public override void FindRigTargets() + { + var constraint = GetComponent(); + constraint.Reset(); + constraint.weight = weight; + constraint.data.driver = m_Driver; + constraint.data.sourceObjects = new List(); + foreach(var sourceObject in m_SourceObjects) + { + constraint.data.sourceObjects.Add(targetRoot.FindInAllChildren(sourceObject)); + } + } + + public override void ReadRigData() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_Driver = constraint.data.driver; + m_SourceObjects = new List(); + foreach (var sourceObject in constraint.data.sourceObjects) + { + m_SourceObjects.Add(sourceObject?.name); + } + } +} diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/MultiReferentialConstraintReverseAdaptor.cs.meta b/KFAttached/RigAdaptors/ReverseAdaptors/MultiReferentialConstraintReverseAdaptor.cs.meta new file mode 100644 index 0000000..0b5e2b4 --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/MultiReferentialConstraintReverseAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c564e8b8f4881e6498f344db3bbd63b0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/MultiRotationConstraintReverseAdaptor.cs b/KFAttached/RigAdaptors/ReverseAdaptors/MultiRotationConstraintReverseAdaptor.cs new file mode 100644 index 0000000..9c3a819 --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/MultiRotationConstraintReverseAdaptor.cs @@ -0,0 +1,44 @@ +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class MultiRotationConstraintReverseAdaptor : RigAdaptorAbs +{ + [SerializeField] + private Transform m_ConstrainedObject; + [SerializeField] + private string[] m_SourceObjectNames; + [SerializeField] + private float[] m_SourceObjectWeights; + [SerializeField] + private Vector3 m_Offset; + [SerializeField] + private Vector3Bool m_ConstrainedAxes; + [SerializeField] + private bool m_MaintainOffset; + + public override void FindRigTargets() + { + var constraint = GetComponent(); + constraint.Reset(); + constraint.weight = weight; + constraint.data.constrainedObject = m_ConstrainedObject; + constraint.data.sourceObjects = WeightedTransformArrayFromAdaptor(targetRoot, m_SourceObjectNames, m_SourceObjectWeights); + constraint.data.offset = m_Offset; + constraint.data.constrainedXAxis = m_ConstrainedAxes.x; + constraint.data.constrainedYAxis = m_ConstrainedAxes.y; + constraint.data.constrainedZAxis = m_ConstrainedAxes.z; + constraint.data.maintainOffset = m_MaintainOffset; + } + + public override void ReadRigData() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_ConstrainedObject = constraint.data.constrainedObject; + WeightedTransformArrayToAdaptor(constraint.data.sourceObjects, out m_SourceObjectNames, out m_SourceObjectWeights); + m_Offset = constraint.data.offset; + m_ConstrainedAxes = new Vector3Bool(constraint.data.constrainedXAxis, constraint.data.constrainedYAxis, constraint.data.constrainedZAxis); + m_MaintainOffset = constraint.data.maintainOffset; + } +} diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/MultiRotationConstraintReverseAdaptor.cs.meta b/KFAttached/RigAdaptors/ReverseAdaptors/MultiRotationConstraintReverseAdaptor.cs.meta new file mode 100644 index 0000000..ae26b20 --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/MultiRotationConstraintReverseAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b08d9d6ae2d5244b83a65aacd535ab8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/OverrideTransformReverseAdaptor.cs b/KFAttached/RigAdaptors/ReverseAdaptors/OverrideTransformReverseAdaptor.cs new file mode 100644 index 0000000..0f4f190 --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/OverrideTransformReverseAdaptor.cs @@ -0,0 +1,48 @@ +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class OverrideTransformReverseAdaptor : RigAdaptorAbs +{ + [SerializeField] + private Transform m_ConstrainedObject; + [SerializeField] + private string m_OverrideSource; + [SerializeField] + private Vector3 m_OverridePosition; + [SerializeField] + private Vector3 m_OverrideRotation; + [SerializeField] + private float m_PositionWeight; + [SerializeField] + private float m_RotationWeight; + [SerializeField] + private OverrideTransformData.Space m_Space; + + public override void FindRigTargets() + { + var constraint = GetComponent(); + constraint.Reset(); + constraint.weight = weight; + constraint.data.constrainedObject = m_ConstrainedObject; + constraint.data.sourceObject = targetRoot.FindInAllChildren(m_OverrideSource); + constraint.data.position = m_OverridePosition; + constraint.data.rotation = m_OverrideRotation; + constraint.data.positionWeight = m_PositionWeight; + constraint.data.rotationWeight = m_RotationWeight; + constraint.data.space = m_Space; + } + + public override void ReadRigData() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_ConstrainedObject = constraint.data.constrainedObject; + m_OverrideSource = constraint.data.sourceObject?.name; + m_OverridePosition = constraint.data.position; + m_OverrideRotation = constraint.data.rotation; + m_PositionWeight = constraint.data.positionWeight; + m_RotationWeight = constraint.data.rotationWeight; + m_Space = constraint.data.space; + } +} diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/OverrideTransformReverseAdaptor.cs.meta b/KFAttached/RigAdaptors/ReverseAdaptors/OverrideTransformReverseAdaptor.cs.meta new file mode 100644 index 0000000..a86cc4f --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/OverrideTransformReverseAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d6971ec144ef3f84b8c6ce116c7ea3de +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/TwistChainConstraintReverseAdaptor.cs b/KFAttached/RigAdaptors/ReverseAdaptors/TwistChainConstraintReverseAdaptor.cs new file mode 100644 index 0000000..724e47f --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/TwistChainConstraintReverseAdaptor.cs @@ -0,0 +1,40 @@ +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class TwistChainConstraintReverseAdaptor : RigAdaptorAbs +{ + [SerializeField] + private Transform m_Root; + [SerializeField] + private Transform m_Tip; + [SerializeField] + private string m_RootTarget; + [SerializeField] + private string m_TipTarget; + [SerializeField] + private AnimationCurve m_Curve; + + public override void FindRigTargets() + { + var constraint = GetComponent(); + constraint.Reset(); + constraint.weight = weight; + constraint.data.root = m_Root; + constraint.data.tip = m_Tip; + constraint.data.rootTarget = targetRoot.FindInAllChildren(m_RootTarget); + constraint.data.tipTarget = targetRoot.FindInAllChildren(m_TipTarget); + constraint.data.curve = m_Curve; + } + + public override void ReadRigData() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_Root = constraint.data.root; + m_Tip = constraint.data.tip; + m_RootTarget = constraint.data.rootTarget?.name; + m_TipTarget = constraint.data.tipTarget?.name; + m_Curve = constraint.data.curve; + } +} diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/TwistChainConstraintReverseAdaptor.cs.meta b/KFAttached/RigAdaptors/ReverseAdaptors/TwistChainConstraintReverseAdaptor.cs.meta new file mode 100644 index 0000000..ed67847 --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/TwistChainConstraintReverseAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 769f53150f7d575498374023f393136c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/TwistCorrectionReverseAdaptor.cs b/KFAttached/RigAdaptors/ReverseAdaptors/TwistCorrectionReverseAdaptor.cs new file mode 100644 index 0000000..ae414d6 --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/TwistCorrectionReverseAdaptor.cs @@ -0,0 +1,15 @@ +using System.Linq; +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class TwistCorrectionReverseAdaptor : RigAdaptorAbs +{ + public override void FindRigTargets() + { + } + + public override void ReadRigData() + { + } +} diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/TwistCorrectionReverseAdaptor.cs.meta b/KFAttached/RigAdaptors/ReverseAdaptors/TwistCorrectionReverseAdaptor.cs.meta new file mode 100644 index 0000000..a7a6a5a --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/TwistCorrectionReverseAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a25e34cfebf5e5e45a0be99dad327b64 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/TwoBoneIKConstraintReverseAdaptor.cs b/KFAttached/RigAdaptors/ReverseAdaptors/TwoBoneIKConstraintReverseAdaptor.cs new file mode 100644 index 0000000..6c79278 --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/TwoBoneIKConstraintReverseAdaptor.cs @@ -0,0 +1,59 @@ +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("")] +public class TwoBoneIKConstraintReverseAdaptor : RigAdaptorAbs +{ + [SerializeField] + private Transform m_Root; + [SerializeField] + private Transform m_Mid; + [SerializeField] + private Transform m_Tip; + [SerializeField] + private string m_Target; + [SerializeField] + private string m_Hint; + [SerializeField] + private float m_TargetPositionWeight; + [SerializeField] + private float m_TargetRotationWeight; + [SerializeField] + private float m_HintWeight; + [SerializeField] + private bool m_MaintainTargetPositionOffset; + [SerializeField] + private bool m_MaintainTargetRotationOffset; + public override void FindRigTargets() + { + var constraint = GetComponent(); + constraint.Reset(); + constraint.weight = weight; + constraint.data.root = m_Root; + constraint.data.mid = m_Mid; + constraint.data.tip = m_Tip; + constraint.data.target = targetRoot.FindInAllChildren(m_Target); + constraint.data.hint = targetRoot.FindInAllChildren(m_Hint); + constraint.data.targetPositionWeight = m_TargetPositionWeight; + constraint.data.targetRotationWeight = m_TargetRotationWeight; + constraint.data.hintWeight = m_HintWeight; + constraint.data.maintainTargetPositionOffset = m_MaintainTargetPositionOffset; + constraint.data.maintainTargetRotationOffset = m_MaintainTargetRotationOffset; + } + + public override void ReadRigData() + { + var constraint = GetComponent(); + weight = constraint.weight; + m_Root = constraint.data.root; + m_Mid = constraint.data.mid; + m_Tip = constraint.data.tip; + m_Target = constraint.data.target?.name; + m_Hint = constraint.data.hint?.name; + m_TargetPositionWeight = constraint.data.targetPositionWeight; + m_TargetRotationWeight = constraint.data.targetRotationWeight; + m_HintWeight = constraint.data.hintWeight; + m_MaintainTargetPositionOffset = constraint.data.maintainTargetPositionOffset; + m_MaintainTargetRotationOffset = constraint.data.maintainTargetRotationOffset; + } +} diff --git a/KFAttached/RigAdaptors/ReverseAdaptors/TwoBoneIKConstraintReverseAdaptor.cs.meta b/KFAttached/RigAdaptors/ReverseAdaptors/TwoBoneIKConstraintReverseAdaptor.cs.meta new file mode 100644 index 0000000..ccc1523 --- /dev/null +++ b/KFAttached/RigAdaptors/ReverseAdaptors/TwoBoneIKConstraintReverseAdaptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9347c9017333b794d8117c27ec493d06 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/RigConverter.cs b/KFAttached/RigAdaptors/RigConverter.cs new file mode 100644 index 0000000..db86555 --- /dev/null +++ b/KFAttached/RigAdaptors/RigConverter.cs @@ -0,0 +1,309 @@ +#if UNITY_EDITOR +using System; +using UnityEditor; +using UnityEngine.Animations.Rigging; +#endif +using UnityEngine; +using System.Linq; + +[AddComponentMenu("KFAttachments/RigAdaptors/Rig Converter")] +public class RigConverter : MonoBehaviour +{ + public Transform targetRoot; + +#if UNITY_EDITOR + [ContextMenu("Convert Rig Constraints to Adaptors")] + private void Convert() + { + foreach (var constraint in GetComponentsInChildren(true)) + { + if (constraint.component.TryGetComponent(out var role) && role.role == RigConverterRole.Role.Ignore) + { + continue; + } + var adaptorName = constraint.GetType().Name; + if (role && role.role == RigConverterRole.Role.Reverse) + { + adaptorName += "Reverse"; + } + adaptorName += "Adaptor,KFCommonUtilityLib"; + var adaptorType = Type.GetType(adaptorName); + var adaptor = (RigAdaptorAbs)constraint.component.transform.AddMissingComponent(adaptorType); + adaptor.ReadRigData(); + adaptor.hideFlags = HideFlags.NotEditable; + EditorUtility.SetDirty(adaptor); + } + Save(); + } + + [ContextMenu("Read Adaptor Value to Constraints")] + private void Read() + { + Rebind(); + Save(); + } + + [ContextMenu("Remove All Adaptors")] + private void RemoveAll() + { + var constraints = GetComponentsInChildren(true); + foreach (var constraint in constraints) + { + DestroyImmediate(constraint); + } + Save(); + } + + [ContextMenu("Fix A22 Constraints")] + private void Fix() + { + Rebind(); + foreach (var constraint in GetComponentsInChildren()) + { + string name = constraint.component.transform.name; + if (char.IsDigit(name[^1])) + { + if (name.Contains("ArmRollCorrections") && constraint.component is MultiRotationConstraint mrcArm) + { + int index = int.Parse(name.Substring(name.Length - 1, 1)); + var source = mrcArm.data.sourceObjects; + source.SetWeight(0, index * 0.25f); + mrcArm.data.sourceObjects = source; + mrcArm.data.constrainedXAxis = true; + mrcArm.data.constrainedYAxis = false; + mrcArm.data.constrainedZAxis = false; + } + else if (name.Contains("FingerTarget") && name.Length == 13 && constraint.component is TwoBoneIKConstraint tbc) + { + int index = int.Parse(name.Substring(name.Length - 1, 1)); + string targetNameBase = tbc.data.root.transform.name[..^1]; + tbc.data.root = targetRoot.FindInAllChildren($"{targetNameBase}1"); + tbc.data.mid = targetRoot.FindInAllChildren($"{targetNameBase}{(index == 5 ? 3 : 2)}"); + tbc.data.tip = targetRoot.FindInAllChildren($"{targetNameBase}4"); + } + else if (name.Contains("FingerTargetRollCorrection") && constraint.component is MultiRotationConstraint mrcFinger) + { + int index = int.Parse(name.Substring(name.Length - 1, 1)); + mrcFinger.data.constrainedXAxis = true; + mrcFinger.data.constrainedYAxis = false; + mrcFinger.data.constrainedZAxis = index == 1; + } + } + } + Convert(); + } + + [ContextMenu("Convert To New Rig Setup")] + private void Renew() + { + Rebind(); + string leftHandTransName = null, rightHandTransName = null; + foreach (var tbik in GetComponentsInChildren()) + { + if(tbik.data.tip.name.Contains("hand", StringComparison.OrdinalIgnoreCase)) + { + Transform target = tbik.data.target; + if(target.name.Contains("target", StringComparison.OrdinalIgnoreCase)) + { + target = target.parent; + } + if (tbik.data.tip.name.StartsWith("Left", StringComparison.OrdinalIgnoreCase)) + { + leftHandTransName = target.name; + } + else + { + rightHandTransName = target.name; + } + } + } + if(leftHandTransName == null || rightHandTransName == null) + { + Log.Error("Left/Right hand transform not found on weapon skeleton!"); + return; + } + foreach (var constraint in GetComponentsInChildren()) + { + Transform trans = constraint.component.transform; + string name = trans.name; + if (char.IsDigit(name[^1]) && name.StartsWith("FingerTarget") && !trans.parent.name.StartsWith("FingerTarget") && constraint is TwoBoneIKConstraint tbik) + { + int index = int.Parse(name.Substring(name.Length - 1, 1)); + if(index > 4) + { + continue; + } + GameObject newConstraint = new(name); + newConstraint.transform.SetParent(trans, true); + newConstraint.transform.SetAsFirstSibling(); + newConstraint.transform.position = trans.position; + EditorUtility.CopySerialized(tbik, newConstraint.AddComponent()); + DestroyImmediate(tbik); + if (trans.GetComponent() is TwoBoneIKConstraintAdaptor adaptor) + { + DestroyImmediate(adaptor); + } + + string targetNameBase = tbik.data.root.transform.name[..^1]; + bool isLeft = targetNameBase.StartsWith("Left"); + GameObject metacarpal = new($"MetacarpalAiming{index}"); + var aimConstraint = metacarpal.AddComponent(); + aimConstraint.data.constrainedObject = targetRoot.FindInAllChildren($"{targetNameBase}0"); + aimConstraint.data.aimAxis = isLeft ? MultiAimConstraintData.Axis.X_NEG : MultiAimConstraintData.Axis.X; + aimConstraint.data.upAxis = isLeft ? MultiAimConstraintData.Axis.Y : MultiAimConstraintData.Axis.Y_NEG; + aimConstraint.data.worldUpType = MultiAimConstraintData.WorldUpType.None; + aimConstraint.data.constrainedXAxis = false; + aimConstraint.data.constrainedYAxis = false; + aimConstraint.data.constrainedZAxis = true; + Transform curSource = tbik.data.target; + while(((isLeft && curSource.parent.parent.name != leftHandTransName) || (!isLeft && curSource.parent.parent.name != rightHandTransName)) && !curSource.parent.name.Contains("1")) + { + curSource = curSource.parent; + } + var sourceArr = aimConstraint.data.sourceObjects; + var source = new WeightedTransform(curSource.parent.name.Contains("1") ? curSource.parent : curSource, 1); + sourceArr.Add(source); + aimConstraint.data.sourceObjects = sourceArr; + metacarpal.transform.SetParent(trans.transform); + metacarpal.transform.SetAsFirstSibling(); + } + } + Convert(); + } + + public void CreateEmpty() + { + CreateEmptyForSide("Left"); + CreateEmptyForSide("Right"); + } + + private static string[] PhalangeBoneNames = new[] + { + "Thumb", + "Index", + "Middle", + "Pinky", + "Ring" + }; + + private void CreateEmptyForSide(string side) + { + WeightedTransformArray sourceObjects = new(); + + Transform shoulderReposition = new GameObject($"{side}ShoulderReposition").transform; + shoulderReposition.parent = transform; + MultiPositionConstraint shoulderRepositionConstraint = shoulderReposition.gameObject.AddComponent(); + shoulderRepositionConstraint.data.constrainedObject = targetRoot.FindInAllChildren($"{side}Shoulder"); + shoulderRepositionConstraint.data.constrainedXAxis = shoulderRepositionConstraint.data.constrainedYAxis = shoulderRepositionConstraint.data.constrainedZAxis = true; + + Transform armRollCorrection = new GameObject($"{side}ArmRollCorrections").transform; + armRollCorrection.parent = transform; + for (int i = 1; i <= 4; i++) + { + Transform child = new GameObject($"{armRollCorrection.name}{i}").transform; + child.parent = armRollCorrection; + MultiRotationConstraint armRollCorrectionConstraint = child.gameObject.AddComponent(); + armRollCorrectionConstraint.data.constrainedObject = targetRoot.FindInAllChildren($"{side}ForeArmRoll{i}"); + armRollCorrectionConstraint.data.constrainedXAxis = true; + armRollCorrectionConstraint.data.constrainedYAxis = false; + armRollCorrectionConstraint.data.constrainedZAxis = false; + sourceObjects.Add(new WeightedTransform(null, i * .25f)); + armRollCorrectionConstraint.data.sourceObjects = sourceObjects; + sourceObjects.Clear(); + } + + Transform armTarget = new GameObject($"{side}ArmTarget").transform; + armTarget.parent = transform; + TwoBoneIKConstraint armTargetConstraint = armTarget.gameObject.AddComponent(); + armTargetConstraint.data.root = targetRoot.FindInAllChildren($"{side}Arm"); + armTargetConstraint.data.mid = targetRoot.FindInAllChildren($"{side}ForeArm"); + armTargetConstraint.data.tip = targetRoot.FindInAllChildren($"{side}Hand"); + + bool isLeft = side == "Left"; + Transform handTargets = new GameObject($"{side}HandTargets").transform; + handTargets.parent = transform; + for (int i = 1; i <= 4; i++) + { + string fingerTargetName = $"FingerTarget{i}"; + Transform fingerTargetParent = new GameObject(fingerTargetName).transform; + fingerTargetParent.parent = handTargets; + + Transform metacarpalAiming = new GameObject($"MetacarpalAiming{i}").transform; + metacarpalAiming.parent = fingerTargetParent; + MultiAimConstraint metacarpalAimingConstraint = metacarpalAiming.gameObject.AddComponent(); + metacarpalAimingConstraint.data.constrainedObject = targetRoot.FindInAllChildren($"{side}Hand{PhalangeBoneNames[i]}0"); + metacarpalAimingConstraint.data.aimAxis = isLeft ? MultiAimConstraintData.Axis.X_NEG : MultiAimConstraintData.Axis.X; + metacarpalAimingConstraint.data.upAxis = isLeft ? MultiAimConstraintData.Axis.Y : MultiAimConstraintData.Axis.Y_NEG; + metacarpalAimingConstraint.data.worldUpType = MultiAimConstraintData.WorldUpType.None; + metacarpalAimingConstraint.data.constrainedXAxis = false; + metacarpalAimingConstraint.data.constrainedYAxis = false; + metacarpalAimingConstraint.data.constrainedZAxis = true; + + Transform fingerTarget = new GameObject(fingerTargetName).transform; + fingerTarget.parent = fingerTargetParent; + TwoBoneIKConstraint fingerTargetConstraint = fingerTarget.gameObject.AddComponent(); + fingerTargetConstraint.data.root = targetRoot.FindInAllChildren($"{side}Hand{PhalangeBoneNames[i]}1"); + fingerTargetConstraint.data.mid = targetRoot.FindInAllChildren($"{side}Hand{PhalangeBoneNames[i]}2"); + fingerTargetConstraint.data.tip = targetRoot.FindInAllChildren($"{side}Hand{PhalangeBoneNames[i]}4"); + } + + Transform thumbTargetParent = new GameObject("FingerTarget5").transform; + thumbTargetParent.parent = handTargets; + + Transform thumbTargetRollCorrection1 = new GameObject("FingerTargetRollCorrection1").transform; + thumbTargetRollCorrection1.parent = thumbTargetParent; + MultiRotationConstraint thumbTargetRollCorrectionConstraint = thumbTargetRollCorrection1.gameObject.AddComponent(); + thumbTargetRollCorrectionConstraint.data.constrainedObject = targetRoot.FindInAllChildren($"{side}HandThumb1"); + thumbTargetRollCorrectionConstraint.data.constrainedXAxis = thumbTargetRollCorrectionConstraint.data.constrainedYAxis = thumbTargetRollCorrectionConstraint.data.constrainedZAxis = true; + sourceObjects.Add(new WeightedTransform(null, .5f)); + thumbTargetRollCorrectionConstraint.data.sourceObjects = sourceObjects; + sourceObjects.Clear(); + + Transform thumbTargetRollCorrection2 = new GameObject("FingerTargetRollCorrection2").transform; + thumbTargetRollCorrection2.parent = thumbTargetParent; + thumbTargetRollCorrectionConstraint = thumbTargetRollCorrection2.gameObject.AddComponent(); + thumbTargetRollCorrectionConstraint.data.constrainedObject = targetRoot.FindInAllChildren($"{side}HandThumb2"); + thumbTargetRollCorrectionConstraint.data.constrainedXAxis = true; + thumbTargetRollCorrectionConstraint.data.constrainedYAxis = false; + thumbTargetRollCorrectionConstraint.data.constrainedZAxis = false; + sourceObjects.Add(new WeightedTransform(null, .3f)); + thumbTargetRollCorrectionConstraint.data.sourceObjects = sourceObjects; + sourceObjects.Clear(); + + Transform thumbTargetRollCorrection3 = new GameObject("FingerTargetRollCorrection3").transform; + thumbTargetRollCorrection3.parent = thumbTargetParent; + thumbTargetRollCorrectionConstraint = thumbTargetRollCorrection3.gameObject.AddComponent(); + thumbTargetRollCorrectionConstraint.data.constrainedObject = targetRoot.FindInAllChildren($"{side}HandThumb3"); + thumbTargetRollCorrectionConstraint.data.constrainedXAxis = true; + thumbTargetRollCorrectionConstraint.data.constrainedYAxis = false; + thumbTargetRollCorrectionConstraint.data.constrainedZAxis = false; + sourceObjects.Add(new WeightedTransform(null, .2f)); + thumbTargetRollCorrectionConstraint.data.sourceObjects = sourceObjects; + sourceObjects.Clear(); + + Transform thumbTarget = new GameObject("FingerTarget5").transform; + thumbTarget.parent = thumbTargetParent; + TwoBoneIKConstraint thumbTargetConstraint = thumbTarget.gameObject.AddComponent(); + thumbTargetConstraint.data.root = targetRoot.FindInAllChildren($"{side}HandThumb1"); + thumbTargetConstraint.data.mid = targetRoot.FindInAllChildren($"{side}HandThumb3"); + thumbTargetConstraint.data.tip = targetRoot.FindInAllChildren($"{side}HandThumb4"); + thumbTargetConstraint.data.targetRotationWeight = 0; + } + + private void Save() + { + var root = GetComponentInParent(true).gameObject; + if(PrefabUtility.IsOutermostPrefabInstanceRoot(root)) + PrefabUtility.ApplyPrefabInstance(root, InteractionMode.AutomatedAction); + } +#endif + + public void Rebind() + { + foreach (var adaptor in GetComponentsInChildren(true)) + { + adaptor.targetRoot = targetRoot; + adaptor.FindRigTargets(); + } + } +} diff --git a/KFAttached/RigAdaptors/RigConverter.cs.meta b/KFAttached/RigAdaptors/RigConverter.cs.meta new file mode 100644 index 0000000..72fdd1c --- /dev/null +++ b/KFAttached/RigAdaptors/RigConverter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a3d68c3fc2fae4143b22cdc25d70652e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/RigConverterRole.cs b/KFAttached/RigAdaptors/RigConverterRole.cs new file mode 100644 index 0000000..4552368 --- /dev/null +++ b/KFAttached/RigAdaptors/RigConverterRole.cs @@ -0,0 +1,14 @@ +using UnityEngine; + +[AddComponentMenu("KFAttachments/RigAdaptors/Rig Converter Ignore")] +public class RigConverterRole : MonoBehaviour +{ + public enum Role + { + Normal, + Reverse, + Ignore + } + + public Role role; +} diff --git a/KFAttached/RigAdaptors/RigConverterRole.cs.meta b/KFAttached/RigAdaptors/RigConverterRole.cs.meta new file mode 100644 index 0000000..7436ac0 --- /dev/null +++ b/KFAttached/RigAdaptors/RigConverterRole.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d0ab4e79948fd2844a1e3857e46066dd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/RigTargets.cs b/KFAttached/RigAdaptors/RigTargets.cs new file mode 100644 index 0000000..5cfbee2 --- /dev/null +++ b/KFAttached/RigAdaptors/RigTargets.cs @@ -0,0 +1,183 @@ +#if NotEditor +using KFCommonUtilityLib.Scripts.StaticManagers; +using UniLinq; +#else +using System.Linq; +#endif +using System.Diagnostics; +using UnityEngine; +using UnityEngine.Animations.Rigging; + +[AddComponentMenu("KFAttachments/RigAdaptors/Rig Targets")] +public class RigTargets : AnimationTargetsAbs +{ + [Header("Fpv Fields")] + [SerializeField] + public Transform itemFpv; + [SerializeField] + public Rig rig; + [SerializeField] + public Transform attachmentReference; + + private RigLayer rigLayerFpv; + + private Animator itemAnimator; + + public override Transform ItemFpv { get => itemFpv; protected set => itemFpv = value; } + public override Transform AttachmentRef { get => attachmentReference; protected set => attachmentReference = value; } + protected override Animator ItemAnimatorFpv => itemAnimator; + protected override void Awake() + { + base.Awake(); + if (!itemFpv) + return; + itemAnimator = itemFpv.GetComponentInChildren(true); +#if NotEditor + itemAnimator.writeDefaultValuesOnDisable = true; +#endif +#if NotEditor + rig.gameObject.name += $"_UID_{TypeBasedUID.UID}"; + AnimationRiggingManager.AddRigExcludeName(rig.gameObject.name); + + itemFpv.gameObject.SetActive(false); +#endif + } + + protected override void Init() + { + base.Init(); + if (!itemFpv) + { + return; + } + + if (IsFpv) + { + if (ItemAnimatorFpv.TryGetComponent(out var delayRenderer)) + { + Destroy(delayRenderer); + } + itemFpv.SetParent(PlayerAnimatorTrans.parent, false); + itemFpv.SetAsFirstSibling(); + itemFpv.position = Vector3.zero; + itemFpv.localPosition = Vector3.zero; + itemFpv.localRotation = Quaternion.identity; + var rc = rig.GetComponent(); + rc.targetRoot = PlayerAnimatorTrans; + rc.Rebind(); + } + else + { + itemFpv.SetParent(PlayerAnimatorTrans.parent); + itemFpv.position = Vector3.zero; + itemFpv.localPosition = Vector3.zero; + itemFpv.localRotation = Quaternion.identity; + } + //Log.Out($"set parent to {PlayerAnimatorTrans.parent.parent.name}/{PlayerAnimatorTrans.parent.name}\n{StackTraceUtility.ExtractStackTrace()}"); + +//#if NotEditor +// Utils.SetLayerRecursively(itemFpv.gameObject, 10, Utils.ExcludeLayerZoom); +// Utils.SetLayerRecursively(gameObject, 24, Utils.ExcludeLayerZoom); +//#endif + //LogInfo(itemFpv.localPosition.ToString() + " / " + itemFpv.localEulerAngles.ToString()); + } + + protected override bool SetupFpv() + { + Stopwatch sw = new Stopwatch(); + sw.Start(); + rig.transform.SetParent(PlayerAnimatorTrans, false); + rig.transform.position = Vector3.zero; + rig.transform.localPosition = Vector3.zero; + rig.transform.localRotation = Quaternion.identity; + + var rigBuilder = PlayerAnimatorTrans.AddMissingComponent(); + rigBuilder.layers.Insert(0, rigLayerFpv = new RigLayer(rig, true)); +#if NotEditor + foreach (var layer in rigBuilder.layers) + { + if (layer.name == SDCSUtils.IKRIG) + { + layer.active = false; + } + } +#endif + BuildRig(rigBuilder.GetComponent(), rigBuilder); + sw.Stop(); + string info = $"setup animation rig took {sw.ElapsedMilliseconds} ms"; + //info += $"\n{StackTraceUtility.ExtractStackTrace()}"; + Log.Out(info); + return true; + } + + protected override void RemoveFpv() + { + Stopwatch sw = new Stopwatch(); + sw.Start(); + + var rigBuilder = PlayerAnimatorTrans.AddMissingComponent(); + int removed = rigBuilder.layers.RemoveAll(r => r.name == rigLayerFpv.name); +//#if NotEditor +// Log.Out($"Removed {removed} layers, remaining:\n{string.Join("\n", rigBuilder.layers.Select(layer => layer.name))}"); +//#endif + rig.transform.SetParent(transform, false); + rig.gameObject.SetActive(false); + rigLayerFpv = null; +#if NotEditor + foreach (var layer in rigBuilder.layers) + { + if (layer.name == SDCSUtils.IKRIG) + { + layer.active = true; + } + } +#endif + BuildRig(rigBuilder.GetComponent(), rigBuilder); + sw.Stop(); + string info = $"destroy animation rig took {sw.ElapsedMilliseconds} ms"; + //info += $"\n{StackTraceUtility.ExtractStackTrace()}"; + Log.Out(info); + } + + public override void DestroyFpv() + { +#if NotEditor + if (rig) + { + AnimationRiggingManager.RemoveRigExcludeName(rig.gameObject.name); + } +#endif + base.DestroyFpv(); + if (rig) + { + rig.transform.parent = null; + GameObject.DestroyImmediate(rig.gameObject); + } + rig = null; + } + + public override void SetEnabled(bool enabled) + { + //var t = new StackTrace(); + + //LogInfo($"set enabled {isFPV} stack trace:\n{t.ToString()}"); + if (itemFpv) + { + itemFpv.localPosition = enabled ? Vector3.zero : new Vector3(0, -100, 0); + } + base.SetEnabled(enabled); +#if NotEditor + if (enabled && IsFpv && ItemAnimatorFpv && !ItemAnimatorFpv.TryGetComponent(out var delayRenderer)) + { + delayRenderer = ItemAnimatorFpv.gameObject.AddComponent(); + } +#endif + } + +#if NotEditor + public override void UpdatePlayerAvatar(AvatarController avatarController, bool rigWeaponChanged) + { + base.UpdatePlayerAvatar(avatarController, rigWeaponChanged); + } +#endif +} diff --git a/KFAttached/RigAdaptors/RigTargets.cs.meta b/KFAttached/RigAdaptors/RigTargets.cs.meta new file mode 100644 index 0000000..9ee893c --- /dev/null +++ b/KFAttached/RigAdaptors/RigTargets.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7b5aed79c0403ae438dd1f929cc1391e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/Utils.meta b/KFAttached/RigAdaptors/Utils.meta new file mode 100644 index 0000000..b7c1fc5 --- /dev/null +++ b/KFAttached/RigAdaptors/Utils.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bfcfb8cce097e304f91fab84a5e76ed8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/Utils/InventorySlotGurad.cs b/KFAttached/RigAdaptors/Utils/InventorySlotGurad.cs new file mode 100644 index 0000000..a5b86d3 --- /dev/null +++ b/KFAttached/RigAdaptors/Utils/InventorySlotGurad.cs @@ -0,0 +1,25 @@ +public class InventorySlotGurad +{ + public int Slot { get; private set; } = -1; + +#if NotEditor + public bool IsValid(EntityAlive entity) + { + if (entity && entity.inventory != null) + { + if (Slot < 0) + { + Slot = entity.inventory.holdingItemIdx; + return true; + } + if (Slot != entity.inventory.holdingItemIdx) + { + Log.Warning($"trying to set ammo for slot {Slot} while holding slot {entity.inventory.holdingItemIdx} on entity {entity.entityId}!"); + return false; + } + return true; + } + return false; + } +#endif +} diff --git a/KFAttached/RigAdaptors/Utils/InventorySlotGurad.cs.meta b/KFAttached/RigAdaptors/Utils/InventorySlotGurad.cs.meta new file mode 100644 index 0000000..d38bc39 --- /dev/null +++ b/KFAttached/RigAdaptors/Utils/InventorySlotGurad.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5cf53ce461844eb4eb8f418707f838d1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/Utils/KFExtensions.cs b/KFAttached/RigAdaptors/Utils/KFExtensions.cs new file mode 100644 index 0000000..65b67af --- /dev/null +++ b/KFAttached/RigAdaptors/Utils/KFExtensions.cs @@ -0,0 +1,431 @@ +using System; +using UnityEngine; + +public static class KFExtensions +{ + public static Transform FindInAllChildren(this Transform target, string name, bool onlyActive = false) + { + if (name == null) + { + return null; + } + + if (!onlyActive || (onlyActive && (bool)target.gameObject && target.gameObject.activeSelf)) + { + if (target.name == name) + { + return target; + } + + for (int i = 0; i < target.childCount; i++) + { + Transform transform = target.GetChild(i).FindInAllChildren(name, onlyActive); + if (transform != null) + { + return transform; + } + } + + return null; + } + + return null; + } + public static T AddMissingComponent(this Transform transform) where T : Component + { + if (!transform.TryGetComponent(out var val)) + { + val = transform.gameObject.AddComponent(); + } + + return val; + } + public static Component AddMissingComponent(this Transform transform, Type type) + { + if (!transform.TryGetComponent(type, out var val)) + { + val = transform.gameObject.AddComponent(type); + } + + return val; + } + public static void RotateAroundPivot(this Transform self, Transform pivot, Vector3 angles) + { + Vector3 dir = self.InverseTransformVector(self.position - pivot.position); // get point direction relative to pivot + Quaternion rot = Quaternion.Euler(angles); + dir = rot * dir; // rotate it + self.localPosition = dir + self.InverseTransformPoint(pivot.position); // calculate rotated point + self.localRotation = rot; + } + + public static void RotateAroundPivot(this Transform self, Transform pivot, Quaternion rotation) + { + Vector3 dir = self.InverseTransformVector(self.position - pivot.position); // get point direction relative to pivot + dir = rotation * dir; // rotate it + self.localPosition = dir + self.InverseTransformPoint(pivot.position); // calculate rotated point + self.localRotation = rotation; + } + + public static Vector3 Random(Vector3 min, Vector3 max) + { + return new Vector3(UnityEngine.Random.Range(min.x, max.x), UnityEngine.Random.Range(min.y, max.y), UnityEngine.Random.Range(min.z, max.z)); + } + + public static Vector3 Clamp(Vector3 val, Vector3 min, Vector3 max) + { + return new Vector3(Mathf.Clamp(val.x, min.x, max.x), Mathf.Clamp(val.y, min.y, max.y), Mathf.Clamp(val.z, min.z, max.z)); + } + + public static Vector3 SmoothStep(Vector3 from, Vector3 to, float t) + { + return new Vector3(Mathf.SmoothStep(from.x, to.x, t), Mathf.SmoothStep(from.y, to.y, t), Mathf.SmoothStep(from.z, to.z, t)); + } + + public static float AngleToInferior(float angle) + { + angle %= 360; + angle = angle > 180 ? angle - 360 : angle; + return angle; + } + + public static Vector3 AngleToInferior(Vector3 angle) + { + return new Vector3(AngleToInferior(angle.x), AngleToInferior(angle.y), AngleToInferior(angle.z)); + } + + public static IAnimatorWrapper GetWrapperForParam(this Animator self, AnimatorControllerParameter param, bool prefVanilla = false) + { + if (!self.TryGetComponent(out var builder)) + return AnimationGraphBuilder.DummyWrapper; + switch (builder.GetWrapperRoleByParam(param)) + { + case AnimationGraphBuilder.ParamInWrapper.Both: + if (prefVanilla) + { + return builder.VanillaWrapper; + } + else + { + return builder.WeaponWrapper; + } + case AnimationGraphBuilder.ParamInWrapper.Vanilla: + return builder.VanillaWrapper; + case AnimationGraphBuilder.ParamInWrapper.Weapon: + return builder.WeaponWrapper; + case AnimationGraphBuilder.ParamInWrapper.Attachments: + return builder.AttachmentWrapper; + default: + return AnimationGraphBuilder.DummyWrapper; + } + } + + public static IAnimatorWrapper GetWrapperForParamHash(this Animator self, int nameHash, bool prefVanilla = false) + { + if (!self.TryGetComponent(out var builder)) + return AnimationGraphBuilder.DummyWrapper; + switch (builder.GetWrapperRoleByParamHash(nameHash)) + { + case AnimationGraphBuilder.ParamInWrapper.Both: + if (prefVanilla) + { + return builder.VanillaWrapper; + } + else + { + return builder.WeaponWrapper; + } + case AnimationGraphBuilder.ParamInWrapper.Vanilla: + return builder.VanillaWrapper; + case AnimationGraphBuilder.ParamInWrapper.Weapon: + return builder.WeaponWrapper; + case AnimationGraphBuilder.ParamInWrapper.Attachments: + return builder.AttachmentWrapper; + default: + return AnimationGraphBuilder.DummyWrapper; + } + } + + public static IAnimatorWrapper GetWrapperForParamName(this Animator self, string name, bool prefVanilla = false) + { + if (!self.TryGetComponent(out var builder)) + return AnimationGraphBuilder.DummyWrapper; + switch (builder.GetWrapperRoleByParamName(name)) + { + case AnimationGraphBuilder.ParamInWrapper.Both: + if (prefVanilla) + { + return builder.VanillaWrapper; + } + else + { + return builder.WeaponWrapper; + } + case AnimationGraphBuilder.ParamInWrapper.Vanilla: + return builder.VanillaWrapper; + case AnimationGraphBuilder.ParamInWrapper.Weapon: + return builder.WeaponWrapper; + case AnimationGraphBuilder.ParamInWrapper.Attachments: + return builder.AttachmentWrapper; + default: + return AnimationGraphBuilder.DummyWrapper; + } + } + + public static bool GetWrappedBool(this Animator self, int _propertyHash) + { + if (self) + { + var wrapper = self.GetWrapperForParamHash(_propertyHash); + if (wrapper != null && wrapper.IsValid) + { + return wrapper.GetBool(_propertyHash); + } + return self.GetBool(_propertyHash); + } + return false; + } + + public static int GetWrappedInt(this Animator self, int _propertyHash) + { + if (self) + { + var wrapper = self.GetWrapperForParamHash(_propertyHash); + if (wrapper != null && wrapper.IsValid) + { + return wrapper.GetInteger(_propertyHash); + } + return self.GetInteger(_propertyHash); + } + return 0; + } + + public static float GetWrappedFloat(this Animator self, int _propertyHash) + { + if (self) + { + var wrapper = self.GetWrapperForParamHash(_propertyHash); + if (wrapper != null && wrapper.IsValid) + { + return wrapper.GetFloat(_propertyHash); + } + return self.GetFloat(_propertyHash); + } + return float.NaN; + } + + public static void SetWrappedTrigger(this Animator self, int _propertyHash) + { + if (self) + { + if (self.TryGetComponent(out var builder)) + { + var role = builder.GetWrapperRoleByParamHash(_propertyHash); + switch(role) + { + case AnimationGraphBuilder.ParamInWrapper.Both: + builder.VanillaWrapper.SetTrigger(_propertyHash); + builder.WeaponWrapper.SetTrigger(_propertyHash); + break; + case AnimationGraphBuilder.ParamInWrapper.Vanilla: + builder.VanillaWrapper.SetTrigger(_propertyHash); + break; + case AnimationGraphBuilder.ParamInWrapper.Weapon: + builder.WeaponWrapper.SetTrigger(_propertyHash); + break; + default: + break; + } + builder.SetChildTrigger(_propertyHash); + } + else + { + self.SetTrigger(_propertyHash); + } + } + } + + public static void ResetWrappedTrigger(this Animator self, int _propertyHash) + { + if (self) + { + if (self.TryGetComponent(out var builder)) + { + var role = builder.GetWrapperRoleByParamHash(_propertyHash); + switch (role) + { + case AnimationGraphBuilder.ParamInWrapper.Both: + builder.VanillaWrapper.ResetTrigger(_propertyHash); + builder.WeaponWrapper.ResetTrigger(_propertyHash); + break; + case AnimationGraphBuilder.ParamInWrapper.Vanilla: + builder.VanillaWrapper.ResetTrigger(_propertyHash); + break; + case AnimationGraphBuilder.ParamInWrapper.Weapon: + builder.WeaponWrapper.ResetTrigger(_propertyHash); + break; + default: + break; + } + builder.ResetChildTrigger(_propertyHash); + } + else + { + self.ResetTrigger(_propertyHash); + } + } + } + + public static void SetWrappedBool(this Animator self, int _propertyHash, bool _value) + { + if (self) + { + if (self.TryGetComponent(out var builder)) + { + var role = builder.GetWrapperRoleByParamHash(_propertyHash); + switch (role) + { + case AnimationGraphBuilder.ParamInWrapper.Both: + builder.VanillaWrapper.SetBool(_propertyHash, _value); + builder.WeaponWrapper.SetBool(_propertyHash, _value); + break; + case AnimationGraphBuilder.ParamInWrapper.Vanilla: + builder.VanillaWrapper.SetBool(_propertyHash, _value); + break; + case AnimationGraphBuilder.ParamInWrapper.Weapon: + builder.WeaponWrapper.SetBool(_propertyHash, _value); + break; + default: + break; + } + builder.SetChildBool(_propertyHash, _value); + } + else + { + self.SetBool(_propertyHash, _value); + } + } + } + + public static void SetWrappedInt(this Animator self, int _propertyHash, int _value) + { + if (self) + { + if (self.TryGetComponent(out var builder)) + { + var role = builder.GetWrapperRoleByParamHash(_propertyHash); + switch (role) + { + case AnimationGraphBuilder.ParamInWrapper.Both: + builder.VanillaWrapper.SetInteger(_propertyHash, _value); + builder.WeaponWrapper.SetInteger(_propertyHash, _value); + break; + case AnimationGraphBuilder.ParamInWrapper.Vanilla: + builder.VanillaWrapper.SetInteger(_propertyHash, _value); + break; + case AnimationGraphBuilder.ParamInWrapper.Weapon: + builder.WeaponWrapper.SetInteger(_propertyHash, _value); + break; + default: + break; + } + builder.SetChildInteger(_propertyHash, _value); + } + else + { + self.SetInteger(_propertyHash, _value); + } + } + } + + public static void SetWrappedFloat(this Animator self, int _propertyHash, float _value) + { + if (self) + { + if (self.TryGetComponent(out var builder)) + { + var role = builder.GetWrapperRoleByParamHash(_propertyHash); + switch (role) + { + case AnimationGraphBuilder.ParamInWrapper.Both: + builder.VanillaWrapper.SetFloat(_propertyHash, _value); + builder.WeaponWrapper.SetFloat(_propertyHash, _value); + break; + case AnimationGraphBuilder.ParamInWrapper.Vanilla: + builder.VanillaWrapper.SetFloat(_propertyHash, _value); + break; + case AnimationGraphBuilder.ParamInWrapper.Weapon: + builder.WeaponWrapper.SetFloat(_propertyHash, _value); + break; + default: + break; + } + builder.SetChildFloat(_propertyHash, _value); + } + else + { + self.SetFloat(_propertyHash, _value); + } + } + } + + public static AnimatorControllerParameter[] GetWrappedParameters(this Animator self) + { + if (self) + { + if (self.TryGetComponent(out var builder) && builder.HasWeaponOverride) + { + return builder.Parameters; + } + return self.parameters; + } + return null; + } + + public static IAnimatorWrapper GetItemAnimatorWrapper(this Animator self) + { + if (self.TryGetComponent(out var builder)) + return builder.WeaponWrapper; + return new AnimatorWrapper(self); + } + + public static bool IsVanillaInTransition(this Animator self, int layerIndex) + { + if (self) + { + if (self.TryGetComponent(out var builder)) + { + return builder.VanillaWrapper.IsInTransition(layerIndex); + } + return self.IsInTransition(layerIndex); + } + return false; + } + + public static AnimatorStateInfo GetCurrentVanillaStateInfo(this Animator self, int layerIndex) + { + if (self) + { + if (self.TryGetComponent(out var builder)) + { + return builder.VanillaWrapper.GetCurrentAnimatorStateInfo(layerIndex); + } + return self.GetCurrentAnimatorStateInfo(layerIndex); + } + return default; + } + + public static void SetVanillaLayerWeight(this Animator self, int layerIndex, float weight) + { + if (self) + { + if (self.TryGetComponent(out var builder)) + { + builder.VanillaWrapper.SetLayerWeight(layerIndex, weight); + } + else + { + self.SetLayerWeight(layerIndex, weight); + } + } + } +} \ No newline at end of file diff --git a/KFAttached/RigAdaptors/Utils/KFExtensions.cs.meta b/KFAttached/RigAdaptors/Utils/KFExtensions.cs.meta new file mode 100644 index 0000000..aad325b --- /dev/null +++ b/KFAttached/RigAdaptors/Utils/KFExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a4347de315a685e4482f8ad79c488d19 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFAttached/RigAdaptors/Utils/QuaternionUtil.cs b/KFAttached/RigAdaptors/Utils/QuaternionUtil.cs new file mode 100644 index 0000000..65aa046 --- /dev/null +++ b/KFAttached/RigAdaptors/Utils/QuaternionUtil.cs @@ -0,0 +1,64 @@ +using UnityEngine; + +/* +Copyright 2016 Max Kaufmann (max.kaufmann@gmail.com) + +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. +*/ + +public static class QuaternionUtil { + + public static Quaternion AngVelToDeriv(Quaternion Current, Vector3 AngVel) { + var Spin = new Quaternion(AngVel.x, AngVel.y, AngVel.z, 0f); + var Result = Spin * Current; + return new Quaternion(0.5f * Result.x, 0.5f * Result.y, 0.5f * Result.z, 0.5f * Result.w); + } + + public static Vector3 DerivToAngVel(Quaternion Current, Quaternion Deriv) { + var Result = Deriv * Quaternion.Inverse(Current); + return new Vector3(2f * Result.x, 2f * Result.y, 2f * Result.z); + } + + public static Quaternion IntegrateRotation(Quaternion Rotation, Vector3 AngularVelocity, float DeltaTime) { + if (DeltaTime < Mathf.Epsilon) return Rotation; + var Deriv = AngVelToDeriv(Rotation, AngularVelocity); + var Pred = new Vector4( + Rotation.x + Deriv.x * DeltaTime, + Rotation.y + Deriv.y * DeltaTime, + Rotation.z + Deriv.z * DeltaTime, + Rotation.w + Deriv.w * DeltaTime + ).normalized; + return new Quaternion(Pred.x, Pred.y, Pred.z, Pred.w); + } + + public static Quaternion SmoothDamp(Quaternion rot, Quaternion target, ref Quaternion deriv, float time) { + if (Time.deltaTime < Mathf.Epsilon) return rot; + // account for double-cover + var Dot = Quaternion.Dot(rot, target); + var Multi = Dot > 0f ? 1f : -1f; + target.x *= Multi; + target.y *= Multi; + target.z *= Multi; + target.w *= Multi; + // smooth damp (nlerp approx) + var Result = new Vector4( + Mathf.SmoothDamp(rot.x, target.x, ref deriv.x, time), + Mathf.SmoothDamp(rot.y, target.y, ref deriv.y, time), + Mathf.SmoothDamp(rot.z, target.z, ref deriv.z, time), + Mathf.SmoothDamp(rot.w, target.w, ref deriv.w, time) + ).normalized; + + // ensure deriv is tangent + var derivError = Vector4.Project(new Vector4(deriv.x, deriv.y, deriv.z, deriv.w), Result); + deriv.x -= derivError.x; + deriv.y -= derivError.y; + deriv.z -= derivError.z; + deriv.w -= derivError.w; + + return new Quaternion(Result.x, Result.y, Result.z, Result.w); + } +} diff --git a/KFAttached/RigAdaptors/Utils/QuaternionUtil.cs.meta b/KFAttached/RigAdaptors/Utils/QuaternionUtil.cs.meta new file mode 100644 index 0000000..94dcb7c --- /dev/null +++ b/KFAttached/RigAdaptors/Utils/QuaternionUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 52358614fb4cc7645abfdec7b22f0dc8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KFCommonUtilityLib.csproj b/KFCommonUtilityLib.csproj new file mode 100644 index 0000000..465e48b --- /dev/null +++ b/KFCommonUtilityLib.csproj @@ -0,0 +1,718 @@ + + + + + Debug + AnyCPU + {1A3E0DEF-D290-4D3E-BC2D-1B66AB174F47} + Library + Properties + KFCommonUtilityLib + KFCommonUtilityLib + v4.8 + 512 + true + 9.0 + + + true + portable + false + .\ + TRACE;DEBUG;NotEditor + prompt + 4 + true + + + portable + true + .\ + TRACE;NotEditor + prompt + 4 + true + true + + + OnOutputUpdated + + + + ..\0_TFP_Harmony\MonoMod.Utils.dll + False + + + ..\..\7DaysToDie_Data\Managed\mscorlib.dll + False + + + ..\0_TFP_Harmony\0Harmony.dll + False + + + ..\..\7DaysToDie_Data\Managed\Assembly-CSharp.dll + False + + + ..\..\7DaysToDie_Data\Managed\Assembly-CSharp-firstpass.dll + False + + + ..\..\7DaysToDie_Data\Managed\Autodesk.Fbx.dll + False + + + False + .\DOTween.dll + False + + + False + .\GearsAPI.dll + False + + + ..\..\7DaysToDie_Data\Managed\InControl.dll + False + + + ..\..\7DaysToDie_Data\Managed\LogLibrary.dll + False + + + ..\0_TFP_Harmony\Mono.Cecil.dll + False + + + ..\0_TFP_Harmony\Mono.Cecil.Mdb.dll + False + + + ..\0_TFP_Harmony\Mono.Cecil.Pdb.dll + False + + + ..\0_TFP_Harmony\Mono.Cecil.Rocks.dll + False + + + + + + + + + + + False + .\TreeCollections.dll + False + + + ..\..\7DaysToDie_Data\Managed\Unity.Animation.Rigging.dll + False + + + ..\..\7DaysToDie_Data\Managed\Unity.Burst.dll + False + + + ..\..\7DaysToDie_Data\Managed\Unity.Formats.Fbx.Runtime.dll + False + + + ..\..\7DaysToDie_Data\Managed\Unity.Mathematics.dll + False + + + ..\..\7DaysToDie_Data\Managed\Unity.Postprocessing.Runtime.dll + False + + + ..\..\7DaysToDie_Data\Managed\Unity.Profiling.Core.dll + False + + + ..\..\7DaysToDie_Data\Managed\Unity.TextMeshPro.dll + False + + + ..\..\7DaysToDie_Data\Managed\UnityEngine.dll + False + + + ..\..\7DaysToDie_Data\Managed\UnityEngine.AnimationModule.dll + False + + + ..\..\7DaysToDie_Data\Managed\UnityEngine.AudioModule.dll + False + + + ..\..\7DaysToDie_Data\Managed\UnityEngine.CoreModule.dll + False + + + ..\..\7DaysToDie_Data\Managed\UnityEngine.ImageConversionModule.dll + False + + + ..\..\7DaysToDie_Data\Managed\UnityEngine.IMGUIModule.dll + False + + + ..\..\7DaysToDie_Data\Managed\UnityEngine.InputLegacyModule.dll + False + + + ..\..\7DaysToDie_Data\Managed\UnityEngine.ParticleSystemModule.dll + False + + + ..\..\7DaysToDie_Data\Managed\UnityEngine.PhysicsModule.dll + False + + + ..\..\7DaysToDie_Data\Managed\UnityEngine.TextRenderingModule.dll + False + + + ..\..\7DaysToDie_Data\Managed\UnityEngine.UI.dll + False + + + ..\..\7DaysToDie_Data\Managed\UnityEngine.UIModule.dll + False + + + + + + BlendConstraintReverseAdaptor.cs + + + ChainIKConstraintReverseAdaptor.cs + + + DampedTransformReverseAdaptor.cs + + + MultiAimConstraintReverseAdaptor.cs + + + MultiParentConstraintReverseAdaptor.cs + + + MultiPositionConstraintReverseAdaptor.cs + + + MultiReferentialConstraintReverseAdaptor.cs + + + MultiRotationConstraintReverseAdaptor.cs + + + OverrideTransformReverseAdaptor.cs + + + TwistChainConstraintReverseAdaptor.cs + + + TwistCorrectionReverseAdaptor.cs + + + TwoBoneIKConstraintReverseAdaptor.cs + + + + + + AnimationProceduralRecoildAbs.cs + + + AnimationAimRecoilReferences.cs + + + AnimationRandomRecoil.cs + + + AnimationRandomSound.cs + + + AnimationReloadEvents.cs + + + AnimationFiringEvents.cs + + + AnimationSmokeParticle.cs + + + RigWeightOverTime.cs + + + AnimationAimRecoilResetState.cs + + + AnimationCustomReloadState.cs + + + AnimationRandomRecoilState.cs + + + AnimationResetRigWeightState.cs + + + AnimatorRandomSwitch.cs + + + + KFCommonUtilityLib.asmdef + + + ApexWeaponHudControllerBase.cs + + + ChargeUpController.cs + + + RigActivationBinding.cs + + + TransformActivationBinding.cs + + + WeaponColorController.cs + + + WeaponColorControllerBase.cs + + + WeaponLabelController.cs + + + WeaponLabelControllerBase.cs + + + WeaponLabelControllerChargeUp.cs + + + WeaponLabelControllerDevotion.cs + + + WeaponTextProController.cs + + + LeanAudio.cs + + + LeanSmooth.cs + + + LeanTest.cs + + + LeanTween.cs + + + LeanTweenExt.cs + + + LTDescr.cs + + + LTDescrOptional.cs + + + LTSeq.cs + + + AttachmentReference.cs + + + AudioSourceGroup.cs + + + BlendConstraintAdaptor.cs + + + ChainIKConstraintAdaptor.cs + + + DampedTransformAdaptor.cs + + + TwistNode.cs + + + MultiAimConstraintAdaptor.cs + + + MultiParentConstraintAdaptor.cs + + + MultiPositionConstraintAdaptor.cs + + + MultiReferentialConstraintAdaptor.cs + + + MultiRotationConstraintAdaptor.cs + + + OverrideTransformAdaptor.cs + + + RigAdaptorAbs.cs + + + TwistChainConstraintAdaptor.cs + + + TwistCorrectionAdaptor.cs + + + TwoBoneIKConstraintAdaptor.cs + + + RigConverter.cs + + + RigTargets.cs + + + KFExtensions.cs + + + FPSDemoGUI.cs + + + FPSDemoReactivator.cs + + + FPSFireManager.cs + + + MouseLock.cs + + + FPSLightCurves.cs + + + FPSParticleSystemScaler.cs + + + FPSRandomRotateAngle.cs + + + FPSShaderColorGradient.cs + + + FPSShaderFloatCurves.cs + + + MaterialType.cs + + + AnimatorActionIndexDebug.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {a32d3424-8454-4b87-9554-ab0734819328} + CustomPlayerActionManager + + + + + + + + + KFAttached + J:\Unity Projects\AnimationPlayground\Assets\ + + + + if exist "$(UnityProjectPath)" ( + echo Unity project found, copying attached scripts... + robocopy "$(ProjectDir)$(AttachedScriptFolder)" "$(UnityProjectPath)$(AttachedScriptFolder)" /MIR || set errorlevel=0 + ) + + + \ No newline at end of file diff --git a/KFCommonUtilityLib.dll b/KFCommonUtilityLib.dll new file mode 100644 index 0000000..a4a9a28 Binary files /dev/null and b/KFCommonUtilityLib.dll differ diff --git a/KFCommonUtilityLib.pdb b/KFCommonUtilityLib.pdb new file mode 100644 index 0000000..395c0fc Binary files /dev/null and b/KFCommonUtilityLib.pdb differ diff --git a/ModInfo.xml b/ModInfo.xml new file mode 100644 index 0000000..c503f18 --- /dev/null +++ b/ModInfo.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/ModSettings.xml b/ModSettings.xml new file mode 100644 index 0000000..f2d0585 --- /dev/null +++ b/ModSettings.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..7900a46 --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// 有关程序集的一般信息由以下 +// 控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("CommonUtilityLib")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("CommonUtilityLib")] +[assembly: AssemblyCopyright("Copyright © 2022")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 会使此程序集中的类型 +//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 +//请将此类型的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("1a3e0def-d290-4d3e-bc2d-1b66ab174f47")] + +// 程序集的版本信息由下列四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 +//通过使用 "*",如下所示: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/README.md b/README.md new file mode 100644 index 0000000..bb9ccf3 --- /dev/null +++ b/README.md @@ -0,0 +1,144 @@ +# Killing Floor 2 Utility Library +This mod adds ItemActions, MinEventActions, Requirements and MonoScripts that provide utilities inspired by Killing Floor 2. It's mainly a common lib for my future killing floor 2 style mods, but might also be useful for other modders. + +This Readme will summarize the usage of these utilitties. + +## ItemActions +There are 3 new actions available currently. +#### 🔴ItemActionHoldOpen +This action adds an **empty** state to the weapon, to easily enable guns to go into a hold open state when magazine is depleted. The state is synced on all clients, and retained on switching equipment. + +To use this action on your weapon, replace +```xml + + +``` +with +```xml + + +``` +, then add an **empty** Bool parameter to your animator. + +Note this only works for Ranged weapons, not Launcher weapons. + +#### 🔴ItemActionAltMode +This action derives from ItemActionHoldOpen, allows you to manage weapon mode with cvar easily. It adds following xml properties: + +- `Cvar_State_Switch`: The cvar that controls the mode. When the value is 0, original properties are used; when the value is greater than 0, the alt properties with that value as index is used instead. +- `Alt_Sound_Start`: The start sounds of each mode, separated by comma(,). Replaces `Sound_start` when in alt mode. +- `Alt_Sound_Empty`: The empty sounds of each mode, separated by comma(,). Replaces `Sound_empty` when in alt mode. +- `Alt_Sound_Loop`: The loop sounds of each mode, separated by comma(,). Replaces `Sound_loop` when in alt mode. +- `Alt_Sound_End`: The end sounds of each mode, separated by comma(,). Replaces `Sound_end` when in alt mode. +- `Alt_InfiniteAmmo`: Whether each alt mode is infinite ammo, separated by comma(,). Valid values are **true** and **false**. + +Sound properties should also work on item modifiers. + +It also adds Bool parameters to the animator, with the name of `"altMode" + Cvar_State_Switch value`, in which the value starts from 1 instead of 0. So for example you have 2 alt modes, then the parameters should be "altMode1" and "altMode2". + +To use this action on your weapon, in addition to ItemActionHoldOpen, replace the class name with **AltMode,KFCommonUtilityLib**, and add your properties accordingly. + +Note that not all properties are required, the missing sounds are defaulted to no sound and infinite ammo is defaulted to false. + +#### 🔴ItemActionRechargeable +This action derives from ItemActionAltMode, allows you to consume different cvars according to weapon mode on bursting shots easily. It adds following xml properties: + +- `Cvar_To_Consume`: The cvar "stock" to consume on fire shots, separated by comma(,). +- `Cvar_Consumption`: The cvar "consumption" on each shot, separated by comma(,). When the stock value is less than consumption value, the corresponding empty sound of current mode is played. +- `Cvar_No_Consumption_Burst_Count`: The shot count that does not consume the stock, separated by comma(,). This is decreased by 1 on each shot. Only in effect when greater than 0. + +These properties only work when in alt modes. + +To use this action on your weapon, in addition to ItemActionAltMode, replace the class name with **Rechargeable,KFCommonUtilityLib**, and add your properties accordingly. + +Note that while `Cvar_No_Consumption_Burst_Count` is not required, the other 2 are a must. + +## MinEventActions +There are currently 6 trigger actions avaliable . + +#### 🔴MinEventActionAddBuffToTargetAndSelf +A simple addon to AddBuff that adds the same buff to the initiator as the targets. + +Syntax is the same as AddBuff, replace `action` value with **AddBuffToTargetAndSelf,KFCommonUtilityLib**. + +#### 🔴MinEventActionBroadcastPlaySoundLocal +A simple addon to PlaySound that ensures broadcast sounds only start broadcasting on the local client. + +Does an additional isRemote check on the entity, so that redundant triggers on other clients won't play the sound. + +Syntax is the same as PlaySound, replace `action` value with **BroadcastPlaySoundLocal,KFCommonUtilityLib**. + +#### 🔴MinEventActionDecreaseProgressionLevelAndRefundSP +A simple addon to SetProgressionLevel that refunds all skill points spent on those decreased levels. + +Syntax is the same as SetProgressionLevel, replace `action` value with **DecreaseProgressionLevelAndRefundSP,KFCommonUtilityLib**. `level` must be less than current progression level. + +#### 🔴MinEventActionModifyCVarWithSelfRef +A simple addon to ModifyCVar that takes "@cvar" reference from the initiator instead of each target. + +When using ModifyCVar with "@cvar", the actuall value is taken from each target. this action changes the behaviour to take the value from the initiator only. + +Syntax is the same as ModifyCVar, replace `action` value with **ModifyCVarWithSelfRef,KFCommonUtilityLib**. + +------------ + + +### WeaponLabels +The following 3 actions controls the 3D Text gameobjects on your weapon, setting their text by string, cvar value or rounds in magazine. Moreover, they can change the color of certain materials. + +Drag **KFUtilAttached** folder into your unity project and attach the script inside to the root transform of your weapon. Then create 3D Text objects and drag them onto the script. You can also drag mesh renderers onto the script to change its color on certain shader properties in xml. + +By "slot", the attribute refers to the index of the objects you dragged onto the script. + +**Text and colors are synced on all clients through NetPackages, thus you should avoid setting them frequently.** +#### 🔴MinEventActionSetStringOnWeaponLabel +This action changes the text of specified 3D Text object. The syntax is as follows: + +```xml + +``` + +When `cvar` is presented, the text will be the cvar value; when `text` is presented, the text will be the string you put on it. + +`slot` is defaulted to 0. + +#### 🔴MinEventActionSetAmmoOnWeaponLabel +This action changed the text of specified 3D Text object to the round count in your magazine. The syntax is as follows: + +```xml + +``` +`slot` is defaulted to 0. + +#### 🔴MinEventActionSetWeaponLabelColor +This action changes the color of specified 3D Text object or the material of specified mesh renderer. The syntax is as follows: + +```xml + + +``` + +When `is_text` is set to true, only `slot0` is parsed, which represents the index of 3D Text object; when `is_text` is set to false, `slot0` represents the index of mesh renderers you dragged onto the script, and `slot1` represents the index of materials on that renderer, while `name` stands for the color property name of the shader. + +`is_text` is defaulted to true, `slot0` and `slot1` are both defaulted to 0. + +For more information about property name and color format, refer to [Unity Doc](https://docs.unity3d.com/ScriptReference/Material.SetColor.html). + +------------ + +## Requirements +There is currently only 1 requriement avaliable. +#### 🔴RoundsInHoldingItem +This requirement can be used to check rounds in magazine on `onSelfEquipStart`. I add this because vanilla `RoundsInMagazine` does not work properly when you start equipping a gun. + +Syntax is the same as `RoundsInMagazine`, replace `name` value with **RoundsInHoldingItem,KFCommonUtilityLib**. + +## Explosion Scripts +There is currently only 1 explosion script avaliable. + +If you have no idea what this does, please refer to my custom explosion particle tutorial. +#### 🔴ExplosionAreaBuffTick +This script requires a trigger collider on the root transform, and add buffs specified in `Explosion.Buff` to all entities inside the collider every `Explosion.TickInterval` seconds. Moreover, it fires `onSelfAttackedOther` event from the item and initiator every tick. + +This script takes following custom property: +- `Explosion.TickInterval`: interval between each tick. Default value is 0.5. diff --git a/Resources/PIPScope.unity3d b/Resources/PIPScope.unity3d new file mode 100644 index 0000000..0972abd Binary files /dev/null and b/Resources/PIPScope.unity3d differ diff --git a/Scripts/Attributes/ActionDataTargetAttribute.cs b/Scripts/Attributes/ActionDataTargetAttribute.cs new file mode 100644 index 0000000..a19cafd --- /dev/null +++ b/Scripts/Attributes/ActionDataTargetAttribute.cs @@ -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; } + } +} diff --git a/Scripts/Attributes/MethodTargetAttribute.cs b/Scripts/Attributes/MethodTargetAttribute.cs new file mode 100644 index 0000000..24bc271 --- /dev/null +++ b/Scripts/Attributes/MethodTargetAttribute.cs @@ -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()))); + } + } +} diff --git a/Scripts/Attributes/PatchTargetAttribute.cs b/Scripts/Attributes/PatchTargetAttribute.cs new file mode 100644 index 0000000..54f5903 --- /dev/null +++ b/Scripts/Attributes/PatchTargetAttribute.cs @@ -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; } + } +} diff --git a/Scripts/Attributes/TypeTargetExtensionAttribute.cs b/Scripts/Attributes/TypeTargetExtensionAttribute.cs new file mode 100644 index 0000000..ddd60d1 --- /dev/null +++ b/Scripts/Attributes/TypeTargetExtensionAttribute.cs @@ -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; + } + } +} diff --git a/Scripts/ConsoleCmd/ConsoleCmdCalibrateWeapon.cs b/Scripts/ConsoleCmd/ConsoleCmdCalibrateWeapon.cs new file mode 100644 index 0000000..157d2d0 --- /dev/null +++ b/Scripts/ConsoleCmd/ConsoleCmdCalibrateWeapon.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +public class ConsoleCmdCalibrateWeapon : ConsoleCmdAbstract +{ + public override void Execute(List _params, CommandSenderInfo _senderInfo) + { + if (!_senderInfo.IsLocalGame || _params.Count < 2) + { + Log.Error("too few params: expecting 2 at least"); + return; + } + + bool flag = Enum.TryParse(_params[0], out var calType); + if (!flag) + { + Log.Error("Only following commands are valid: " + string.Join(",", Enum.GetNames(typeof(CalibrateType)))); + return; + } + + flag = Enum.TryParse(_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 + } +} + diff --git a/Scripts/ConsoleCmd/ConsoleCmdListParticleScripts.cs b/Scripts/ConsoleCmd/ConsoleCmdListParticleScripts.cs new file mode 100644 index 0000000..f266313 --- /dev/null +++ b/Scripts/ConsoleCmd/ConsoleCmdListParticleScripts.cs @@ -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 _params, CommandSenderInfo _senderInfo) + { + if(_params.Count > 1) + { + Log.Error("Invalid param count: expecting 0 or 1!"); + return; + } + + if (_params.Count == 0) + { + HashSet typeNames = new HashSet(); + foreach (var pe in ParticleEffect.loadedTs.Values) + { + foreach (var script in pe.GetComponentsInChildren()) + { + 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()) + { + 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."; + } +} \ No newline at end of file diff --git a/Scripts/ConsoleCmd/ConsoleCmdMultiActionItemValueDebug.cs b/Scripts/ConsoleCmd/ConsoleCmdMultiActionItemValueDebug.cs new file mode 100644 index 0000000..6acb6fe --- /dev/null +++ b/Scripts/ConsoleCmd/ConsoleCmdMultiActionItemValueDebug.cs @@ -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 _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."; + } + } +} diff --git a/Scripts/ConsoleCmd/ConsoleCmdPlayerDebugInfo.cs b/Scripts/ConsoleCmd/ConsoleCmdPlayerDebugInfo.cs new file mode 100644 index 0000000..081a733 --- /dev/null +++ b/Scripts/ConsoleCmd/ConsoleCmdPlayerDebugInfo.cs @@ -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 _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(); + 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().Select(c => c.GetType().Name + " on " + c.transform.name))}"); + foreach (var animator in player.RootTransform.GetComponentsInChildren()) + { + 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."; + } + } +} diff --git a/Scripts/ConsoleCmd/ConsoleCmdPrintLocalCache.cs b/Scripts/ConsoleCmd/ConsoleCmdPrintLocalCache.cs new file mode 100644 index 0000000..ca8d927 --- /dev/null +++ b/Scripts/ConsoleCmd/ConsoleCmdPrintLocalCache.cs @@ -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 _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 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."; + } + } +} diff --git a/Scripts/ConsoleCmd/ConsoleCmdReloadLog.cs b/Scripts/ConsoleCmd/ConsoleCmdReloadLog.cs new file mode 100644 index 0000000..039e860 --- /dev/null +++ b/Scripts/ConsoleCmd/ConsoleCmdReloadLog.cs @@ -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 _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."; + } +} \ No newline at end of file diff --git a/Scripts/FbxExporter07.cs b/Scripts/FbxExporter07.cs new file mode 100644 index 0000000..c44512f --- /dev/null +++ b/Scripts/FbxExporter07.cs @@ -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"; + + /// + /// Create instance of example + /// + public static FbxExporter07 Create() { return new FbxExporter07(); } + + /// + /// Export GameObject's as a skinned mesh with bones + /// + protected void ExportSkinnedMesh(Animator unityAnimator, FbxScene fbxScene, FbxNode fbxParentNode) + { + GameObject unityGo = unityAnimator.gameObject; + + SkinnedMeshRenderer unitySkin = unityGo.GetComponentInChildren(); + + 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 boneNodes = new Dictionary(); + + // 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"); + } + } + + /// + /// Export bones of skinned mesh + /// + protected bool ExportSkeleton(MeshInfo meshInfo, FbxScene fbxScene, FbxNode fbxParentNode, Dictionary boneNodes, out FbxNode meshNode) + { + SkinnedMeshRenderer unitySkinnedMeshRenderer = meshInfo.renderer as SkinnedMeshRenderer; + meshNode = null; + if (unitySkinnedMeshRenderer.bones.Length <= 0) + { + return false; + } + + Dictionary boneBindPose = new Dictionary(); + + 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 dict_empty_parents = new Dictionary(); + // set the hierarchy for the FbxNodes + foreach (KeyValuePair 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(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(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); + } + + /// + /// Export binding of mesh to skeleton + /// + protected void ExportSkin(MeshInfo meshInfo, FbxScene fbxScene, FbxMesh fbxMesh, FbxNode fbxRootNode, Dictionary 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 boneCluster = new Dictionary(); + + 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); + } + + /// + /// set weight vertices to cluster + /// + protected void SetVertexWeights(MeshInfo meshInfo, Dictionary 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]); + } + } + } + + /// + /// Export bind pose of mesh to skeleton + /// + protected void ExportBindPose(FbxNode fbxRootNode, FbxNode meshNode, FbxScene fbxScene, Dictionary 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); + } + + /// + /// Unconditionally export this mesh object to the file. + /// We have decided; this mesh is definitely getting exported. + /// + 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(); + Log.Out($"Exporting Components: {unityGo.name}, animator: {(unityAnimator == null ? null : unityAnimator.ToString())}"); + if (unityAnimator == null) + return; + + ExportSkinnedMesh(unityAnimator, fbxScene, fbxParentNode); + + return; + } + + /// + /// Export all the objects in the set. + /// Return the number of objects in the set that we exported. + /// + public int ExportAll(IEnumerable 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; + } + } + + /// + /// Number of nodes exported including siblings and decendents + /// + public int NumNodes { private set; get; } + + /// + /// Clean up this class on garbage collection + /// + public void Dispose() { } + + static bool Verbose { get { return true; } } + const string NamePrefix = ""; + + /// + /// manage the selection of a filename + /// + static string LastFilePath { get; set; } + const string Extension = "fbx"; + + /// + ///Information about the mesh that is important for exporting. + /// + public struct MeshInfo + { + /// + /// The transform of the mesh. + /// + public Matrix4x4 xform; + public Mesh mesh; + public Renderer renderer; + + /// + /// The gameobject in the scene to which this mesh is attached. + /// This can be null: don't rely on it existing! + /// + public GameObject unityObject; + + /// + /// Return true if there's a valid mesh information + /// + /// The vertex count. + public bool IsValid { get { return mesh != null; } } + + /// + /// Gets the vertex count. + /// + /// The vertex count. + public int VertexCount { get { return mesh.vertexCount; } } + + /// + /// 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] + /// + /// The triangles. + public int[] Triangles { get { return mesh.triangles; } } + + /// + /// Gets the vertices, represented in local coordinates. + /// + /// The vertices. + public Vector3[] Vertices { get { return mesh.vertices; } } + + /// + /// Gets the normals for the vertices. + /// + /// The normals. + public Vector3[] Normals { get { return mesh.normals; } } + + /// + /// Gets the uvs. + /// + /// The uv. + public Vector2[] UV { get { return mesh.uv; } } + + public BoneWeight[] BoneWeights { get { return mesh.boneWeights; } } + + public Matrix4x4[] BindPoses { get { return mesh.bindposes; } } + + /// + /// Initializes a new instance of the struct. + /// + /// The GameObject the mesh is attached to. + /// A mesh we want to export + public MeshInfo(GameObject gameObject, Mesh mesh, Renderer renderer) + { + this.renderer = renderer; + this.mesh = mesh; + this.xform = gameObject.transform.localToWorldMatrix; + this.unityObject = gameObject; + } + } + + /// + /// Get a mesh renderer's mesh. + /// + private MeshInfo GetSkinnedMeshInfo(GameObject gameObject) + { + // Verify that we are rendering. Otherwise, don't export. + var renderer = gameObject.GetComponentInChildren(); + 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); + } + + /// + /// Get the GameObject + /// + 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 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); + } + } +} diff --git a/Scripts/Global/CustomEnums.cs b/Scripts/Global/CustomEnums.cs new file mode 100644 index 0000000..a3dea1b --- /dev/null +++ b/Scripts/Global/CustomEnums.cs @@ -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 +} diff --git a/Scripts/Input/PlayerActionKFLib.cs b/Scripts/Input/PlayerActionKFLib.cs new file mode 100644 index 0000000..252be6e --- /dev/null +++ b/Scripts/Input/PlayerActionKFLib.cs @@ -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); + } +} \ No newline at end of file diff --git a/Scripts/Input/PlayerActionToggleFireMode.cs b/Scripts/Input/PlayerActionToggleFireMode.cs new file mode 100644 index 0000000..1a3d56c --- /dev/null +++ b/Scripts/Input/PlayerActionToggleFireMode.cs @@ -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 }); + } +} diff --git a/Scripts/Input/PlayerActionToggleMode.cs b/Scripts/Input/PlayerActionToggleMode.cs new file mode 100644 index 0000000..6674983 --- /dev/null +++ b/Scripts/Input/PlayerActionToggleMode.cs @@ -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 }); + } +} diff --git a/Scripts/Items/ItemActionAltMode.cs b/Scripts/Items/ItemActionAltMode.cs new file mode 100644 index 0000000..ad7cc30 --- /dev/null +++ b/Scripts/Items/ItemActionAltMode.cs @@ -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[] 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[_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(); + 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; + } +} + diff --git a/Scripts/Items/ItemActionHoldOpen.cs b/Scripts/Items/ItemActionHoldOpen.cs new file mode 100644 index 0000000..38a3d9a --- /dev/null +++ b/Scripts/Items/ItemActionHoldOpen.cs @@ -0,0 +1,172 @@ +using System.Collections; +using UnityEngine; + +public class ItemActionHoldOpen : ItemActionRanged +{ + private const string emptyAnimatorBool = "empty"; + //private EntityAlive lastHoldingEntity = null; + //private HashSet hashset_dirty = new HashSet(); + //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(); + 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); + // } + // */ + //} + +} + diff --git a/Scripts/Items/ItemActionRampUp.cs b/Scripts/Items/ItemActionRampUp.cs new file mode 100644 index 0000000..2c7c158 --- /dev/null +++ b/Scripts/Items/ItemActionRampUp.cs @@ -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; + } +} + diff --git a/Scripts/Items/ItemActionRechargeable.cs b/Scripts/Items/ItemActionRechargeable.cs new file mode 100644 index 0000000..42fa850 --- /dev/null +++ b/Scripts/Items/ItemActionRechargeable.cs @@ -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); + } +} + diff --git a/Scripts/Items/Modular/ActionModuleAlternative.cs b/Scripts/Items/Modular/ActionModuleAlternative.cs new file mode 100644 index 0000000..abc647f --- /dev/null +++ b/Scripts/Items/Modular/ActionModuleAlternative.cs @@ -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); + } + } +} diff --git a/Scripts/Items/Modular/ActionModuleAnimationLocked.cs b/Scripts/Items/Modular/ActionModuleAnimationLocked.cs new file mode 100644 index 0000000..3d93ce9 --- /dev/null +++ b/Scripts/Items/Modular/ActionModuleAnimationLocked.cs @@ -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) + { + + } + } +} diff --git a/Scripts/Items/Modular/ActionModuleCustomAnimationDelay.cs b/Scripts/Items/Modular/ActionModuleCustomAnimationDelay.cs new file mode 100644 index 0000000..66dc1db --- /dev/null +++ b/Scripts/Items/Modular/ActionModuleCustomAnimationDelay.cs @@ -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 Transpiler_OnHoldingUpdate(IEnumerable 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); + } + } +} \ No newline at end of file diff --git a/Scripts/Items/Modular/ActionModuleDisplayAsBuff.cs b/Scripts/Items/Modular/ActionModuleDisplayAsBuff.cs new file mode 100644 index 0000000..ac43cb3 --- /dev/null +++ b/Scripts/Items/Modular/ActionModuleDisplayAsBuff.cs @@ -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); + } + } +} diff --git a/Scripts/Items/Modular/ActionModuleDynamicGraze.cs b/Scripts/Items/Modular/ActionModuleDynamicGraze.cs new file mode 100644 index 0000000..2610c1e --- /dev/null +++ b/Scripts/Items/Modular/ActionModuleDynamicGraze.cs @@ -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); + } +} \ No newline at end of file diff --git a/Scripts/Items/Modular/ActionModuleDynamicMuzzleFlash.cs b/Scripts/Items/Modular/ActionModuleDynamicMuzzleFlash.cs new file mode 100644 index 0000000..9ea4506 --- /dev/null +++ b/Scripts/Items/Modular/ActionModuleDynamicMuzzleFlash.cs @@ -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) + { + + } + } +} diff --git a/Scripts/Items/Modular/ActionModuleDynamicSensitivity.cs b/Scripts/Items/Modular/ActionModuleDynamicSensitivity.cs new file mode 100644 index 0000000..42c7be6 --- /dev/null +++ b/Scripts/Items/Modular/ActionModuleDynamicSensitivity.cs @@ -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 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) + { + + } + } +} \ No newline at end of file diff --git a/Scripts/Items/Modular/ActionModuleErgoAffected.cs b/Scripts/Items/Modular/ActionModuleErgoAffected.cs new file mode 100644 index 0000000..c302f23 --- /dev/null +++ b/Scripts/Items/Modular/ActionModuleErgoAffected.cs @@ -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 Transpiler_ItemActionRanged_updateAccuracy(IEnumerable 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 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 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; + } + } + } +} diff --git a/Scripts/Items/Modular/ActionModuleFireModeSelector.cs b/Scripts/Items/Modular/ActionModuleFireModeSelector.cs new file mode 100644 index 0000000..8c63d86 --- /dev/null +++ b/Scripts/Items/Modular/ActionModuleFireModeSelector.cs @@ -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 modeCache = new List(); + private List nameCache = new List(); + 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(); + } + + 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 fireModeData) + { + fireModeData.Instance.CycleFireMode(actionData); + } + } + } +} \ No newline at end of file diff --git a/Scripts/Items/Modular/ActionModuleHoldOpen.cs b/Scripts/Items/Modular/ActionModuleHoldOpen.cs new file mode 100644 index 0000000..75a1c46 --- /dev/null +++ b/Scripts/Items/Modular/ActionModuleHoldOpen.cs @@ -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; + } +} \ No newline at end of file diff --git a/Scripts/Items/Modular/ActionModuleInspectable.cs b/Scripts/Items/Modular/ActionModuleInspectable.cs new file mode 100644 index 0000000..b11d2d0 --- /dev/null +++ b/Scripts/Items/Modular/ActionModuleInspectable.cs @@ -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); + } + } +} diff --git a/Scripts/Items/Modular/ActionModuleInterruptReload.cs b/Scripts/Items/Modular/ActionModuleInterruptReload.cs new file mode 100644 index 0000000..fd836fe --- /dev/null +++ b/Scripts/Items/Modular/ActionModuleInterruptReload.cs @@ -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(out var targets) && !targets.Destroyed && targets.IsAnimationSet) + { + __customData.itemAnimator = targets.GraphBuilder.WeaponWrapper; + if (__customData.itemAnimator.IsValid) + { + __customData.eventBridge = targets.ItemAnimator.GetComponent(); + } + } + } + + 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(out var targets) && !targets.Destroyed) + //{ + // itemAnimator = targets.ItemAnimator; + // if (itemAnimator) + // { + // eventBridge = itemAnimator.GetComponent(); + // } + //} + } + + 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 dataModule && rangedAction is IModuleContainerFor 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; + } +} \ No newline at end of file diff --git a/Scripts/Items/Modular/ActionModuleInvariableRPM.cs b/Scripts/Items/Modular/ActionModuleInvariableRPM.cs new file mode 100644 index 0000000..0dbf9f8 --- /dev/null +++ b/Scripts/Items/Modular/ActionModuleInvariableRPM.cs @@ -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 Transpiler_OnHoldingUpdate_ItemActionRanged(IEnumerable 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); + } +} \ No newline at end of file diff --git a/Scripts/Items/Modular/ActionModuleLocalPassiveCache.cs b/Scripts/Items/Modular/ActionModuleLocalPassiveCache.cs new file mode 100644 index 0000000..8c5ec9e --- /dev/null +++ b/Scripts/Items/Modular/ActionModuleLocalPassiveCache.cs @@ -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 + { + //public float[] cache; + //public bool[] markedForCache; + //public ActionModuleLocalPassiveCache _cacheModule; + public ItemInventoryData invData; + private Dictionary dict_hash_value = new Dictionary(); + private Dictionary dict_hash_name = new Dictionary(); + + 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 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 GetEnumerator() + { + return dict_hash_value.Keys.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/Scripts/Items/Modular/ActionModuleMetaConsumer.cs b/Scripts/Items/Modular/ActionModuleMetaConsumer.cs new file mode 100644 index 0000000..d656fef --- /dev/null +++ b/Scripts/Items/Modular/ActionModuleMetaConsumer.cs @@ -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[] consumeTags; + private float[] consumeStocks; + private float[] consumeValues; + private static FastTags TagsConsumption = FastTags.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 commonTags = string.IsNullOrEmpty(tags) ? FastTags.none : FastTags.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.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 Transpiler_ItemActionRanged_ExecuteAction(IEnumerable 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), nameof(IModuleContainerFor.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)), + 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; + //} +} \ No newline at end of file diff --git a/Scripts/Items/Modular/ActionModuleMetaRecharger.cs b/Scripts/Items/Modular/ActionModuleMetaRecharger.cs new file mode 100644 index 0000000..2a1894b --- /dev/null +++ b/Scripts/Items/Modular/ActionModuleMetaRecharger.cs @@ -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 tagsOriginal; + public FastTags tagsInterval; + public FastTags tagsMaximum; + public FastTags tagsValue; + public FastTags tagsDecrease; + public FastTags tagsDecreaseInterval; + } + + public string[] rechargeDatas; + public RechargeTags[] rechargeTags; + private static readonly FastTags TagsInterval = FastTags.Parse("RechargeDataInterval"); + private static readonly FastTags TagsMaximum = FastTags.Parse("RechargeDataMaximum"); + private static readonly FastTags TagsValue = FastTags.Parse("RechargeDataValue"); + private static readonly FastTags TagsDecrease = FastTags.Parse("RechargeDataDecrease"); + private static readonly FastTags TagsDecreaseInterval = FastTags.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 commonTags = string.IsNullOrEmpty(tags) ? FastTags.none : FastTags.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.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; + } + } +} diff --git a/Scripts/Items/Modular/ActionModuleMultiActionFix.cs b/Scripts/Items/Modular/ActionModuleMultiActionFix.cs new file mode 100644 index 0000000..009fbfc --- /dev/null +++ b/Scripts/Items/Modular/ActionModuleMultiActionFix.cs @@ -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) + { + + } + } +} \ No newline at end of file diff --git a/Scripts/Items/Modular/ActionModuleMultiBarrel.cs b/Scripts/Items/Modular/ActionModuleMultiBarrel.cs new file mode 100644 index 0000000..72b245e --- /dev/null +++ b/Scripts/Items/Modular/ActionModuleMultiBarrel.cs @@ -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 Transpiler_ExecuteAction_ItemActionRanged(IEnumerable 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), nameof(IModuleContainerFor.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)), + 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 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 dataModule) + { + dataModule.Instance.SetCurrentBarrel(__instance.actionData.invData.itemValue.Meta); + } + } +} \ No newline at end of file diff --git a/Scripts/Items/Modular/ActionModuleRampUp.cs b/Scripts/Items/Modular/ActionModuleRampUp.cs new file mode 100644 index 0000000..cbca03c --- /dev/null +++ b/Scripts/Items/Modular/ActionModuleRampUp.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/Scripts/Items/Modular/ActionModuleTagged.cs b/Scripts/Items/Modular/ActionModuleTagged.cs new file mode 100644 index 0000000..b4a7dd8 --- /dev/null +++ b/Scripts/Items/Modular/ActionModuleTagged.cs @@ -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.Parse(string.Join(",", tags_result)) : FastTags.none; + //Log.Out($"tags: {string.Join(",", tags_result)}"); + } + + public class TaggedData + { + public FastTags tags; + public TaggedData(ItemInventoryData _invData, int _indexInEntityOfAction, ActionModuleTagged _module) + { + + } + } +} diff --git a/Scripts/Items/Modular/ActionModuleTranspilerTest.cs b/Scripts/Items/Modular/ActionModuleTranspilerTest.cs new file mode 100644 index 0000000..6a482fa --- /dev/null +++ b/Scripts/Items/Modular/ActionModuleTranspilerTest.cs @@ -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 Transpiler_InvalidTest(IEnumerable 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 Transpiler_RangedTest(IEnumerable 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 Transpiler_CatapultTest(IEnumerable 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()}"); + } +} \ No newline at end of file diff --git a/Scripts/Items/Modular/ActionModuleVariableZoom.cs b/Scripts/Items/Modular/ActionModuleVariableZoom.cs new file mode 100644 index 0000000..47bbff2 --- /dev/null +++ b/Scripts/Items/Modular/ActionModuleVariableZoom.cs @@ -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) + { + + } + } +} diff --git a/Scripts/Items/ModularActions/ActionModuleAlternative.cs b/Scripts/Items/ModularActions/ActionModuleAlternative.cs new file mode 100644 index 0000000..abc647f --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleAlternative.cs @@ -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); + } + } +} diff --git a/Scripts/Items/ModularActions/ActionModuleAnimationLocked.cs b/Scripts/Items/ModularActions/ActionModuleAnimationLocked.cs new file mode 100644 index 0000000..3d93ce9 --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleAnimationLocked.cs @@ -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) + { + + } + } +} diff --git a/Scripts/Items/ModularActions/ActionModuleCustomAnimationDelay.cs b/Scripts/Items/ModularActions/ActionModuleCustomAnimationDelay.cs new file mode 100644 index 0000000..66dc1db --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleCustomAnimationDelay.cs @@ -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 Transpiler_OnHoldingUpdate(IEnumerable 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); + } + } +} \ No newline at end of file diff --git a/Scripts/Items/ModularActions/ActionModuleDisplayAsBuff.cs b/Scripts/Items/ModularActions/ActionModuleDisplayAsBuff.cs new file mode 100644 index 0000000..ac43cb3 --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleDisplayAsBuff.cs @@ -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); + } + } +} diff --git a/Scripts/Items/ModularActions/ActionModuleDynamicGraze.cs b/Scripts/Items/ModularActions/ActionModuleDynamicGraze.cs new file mode 100644 index 0000000..2610c1e --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleDynamicGraze.cs @@ -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); + } +} \ No newline at end of file diff --git a/Scripts/Items/ModularActions/ActionModuleDynamicMuzzleFlash.cs b/Scripts/Items/ModularActions/ActionModuleDynamicMuzzleFlash.cs new file mode 100644 index 0000000..9ea4506 --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleDynamicMuzzleFlash.cs @@ -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) + { + + } + } +} diff --git a/Scripts/Items/ModularActions/ActionModuleDynamicSensitivity.cs b/Scripts/Items/ModularActions/ActionModuleDynamicSensitivity.cs new file mode 100644 index 0000000..80b9adb --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleDynamicSensitivity.cs @@ -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 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) + { + + } + } +} \ No newline at end of file diff --git a/Scripts/Items/ModularActions/ActionModuleErgoAffected.cs b/Scripts/Items/ModularActions/ActionModuleErgoAffected.cs new file mode 100644 index 0000000..7e64e0f --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleErgoAffected.cs @@ -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 Transpiler_ItemActionRanged_updateAccuracy(IEnumerable 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 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 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; + } + } + } +} diff --git a/Scripts/Items/ModularActions/ActionModuleFireModeSelector.cs b/Scripts/Items/ModularActions/ActionModuleFireModeSelector.cs new file mode 100644 index 0000000..af47bdb --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleFireModeSelector.cs @@ -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 modeCache = new List(); + private List nameCache = new List(); + 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(); + } + + 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 fireModeData) + { + fireModeData.Instance.CycleFireMode(actionData); + } + } + } +} \ No newline at end of file diff --git a/Scripts/Items/ModularActions/ActionModuleHoldOpen.cs b/Scripts/Items/ModularActions/ActionModuleHoldOpen.cs new file mode 100644 index 0000000..75a1c46 --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleHoldOpen.cs @@ -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; + } +} \ No newline at end of file diff --git a/Scripts/Items/ModularActions/ActionModuleInspectable.cs b/Scripts/Items/ModularActions/ActionModuleInspectable.cs new file mode 100644 index 0000000..b11d2d0 --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleInspectable.cs @@ -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); + } + } +} diff --git a/Scripts/Items/ModularActions/ActionModuleInterruptReload.cs b/Scripts/Items/ModularActions/ActionModuleInterruptReload.cs new file mode 100644 index 0000000..fd836fe --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleInterruptReload.cs @@ -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(out var targets) && !targets.Destroyed && targets.IsAnimationSet) + { + __customData.itemAnimator = targets.GraphBuilder.WeaponWrapper; + if (__customData.itemAnimator.IsValid) + { + __customData.eventBridge = targets.ItemAnimator.GetComponent(); + } + } + } + + 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(out var targets) && !targets.Destroyed) + //{ + // itemAnimator = targets.ItemAnimator; + // if (itemAnimator) + // { + // eventBridge = itemAnimator.GetComponent(); + // } + //} + } + + 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 dataModule && rangedAction is IModuleContainerFor 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; + } +} \ No newline at end of file diff --git a/Scripts/Items/ModularActions/ActionModuleInvariableRPM.cs b/Scripts/Items/ModularActions/ActionModuleInvariableRPM.cs new file mode 100644 index 0000000..0dbf9f8 --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleInvariableRPM.cs @@ -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 Transpiler_OnHoldingUpdate_ItemActionRanged(IEnumerable 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); + } +} \ No newline at end of file diff --git a/Scripts/Items/ModularActions/ActionModuleLocalPassiveCache.cs b/Scripts/Items/ModularActions/ActionModuleLocalPassiveCache.cs new file mode 100644 index 0000000..8c5ec9e --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleLocalPassiveCache.cs @@ -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 + { + //public float[] cache; + //public bool[] markedForCache; + //public ActionModuleLocalPassiveCache _cacheModule; + public ItemInventoryData invData; + private Dictionary dict_hash_value = new Dictionary(); + private Dictionary dict_hash_name = new Dictionary(); + + 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 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 GetEnumerator() + { + return dict_hash_value.Keys.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/Scripts/Items/ModularActions/ActionModuleMetaConsumer.cs b/Scripts/Items/ModularActions/ActionModuleMetaConsumer.cs new file mode 100644 index 0000000..d656fef --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleMetaConsumer.cs @@ -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[] consumeTags; + private float[] consumeStocks; + private float[] consumeValues; + private static FastTags TagsConsumption = FastTags.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 commonTags = string.IsNullOrEmpty(tags) ? FastTags.none : FastTags.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.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 Transpiler_ItemActionRanged_ExecuteAction(IEnumerable 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), nameof(IModuleContainerFor.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)), + 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; + //} +} \ No newline at end of file diff --git a/Scripts/Items/ModularActions/ActionModuleMetaRecharger.cs b/Scripts/Items/ModularActions/ActionModuleMetaRecharger.cs new file mode 100644 index 0000000..2a1894b --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleMetaRecharger.cs @@ -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 tagsOriginal; + public FastTags tagsInterval; + public FastTags tagsMaximum; + public FastTags tagsValue; + public FastTags tagsDecrease; + public FastTags tagsDecreaseInterval; + } + + public string[] rechargeDatas; + public RechargeTags[] rechargeTags; + private static readonly FastTags TagsInterval = FastTags.Parse("RechargeDataInterval"); + private static readonly FastTags TagsMaximum = FastTags.Parse("RechargeDataMaximum"); + private static readonly FastTags TagsValue = FastTags.Parse("RechargeDataValue"); + private static readonly FastTags TagsDecrease = FastTags.Parse("RechargeDataDecrease"); + private static readonly FastTags TagsDecreaseInterval = FastTags.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 commonTags = string.IsNullOrEmpty(tags) ? FastTags.none : FastTags.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.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; + } + } +} diff --git a/Scripts/Items/ModularActions/ActionModuleMultiActionFix.cs b/Scripts/Items/ModularActions/ActionModuleMultiActionFix.cs new file mode 100644 index 0000000..009fbfc --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleMultiActionFix.cs @@ -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) + { + + } + } +} \ No newline at end of file diff --git a/Scripts/Items/ModularActions/ActionModuleMultiBarrel.cs b/Scripts/Items/ModularActions/ActionModuleMultiBarrel.cs new file mode 100644 index 0000000..72b245e --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleMultiBarrel.cs @@ -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 Transpiler_ExecuteAction_ItemActionRanged(IEnumerable 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), nameof(IModuleContainerFor.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)), + 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 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 dataModule) + { + dataModule.Instance.SetCurrentBarrel(__instance.actionData.invData.itemValue.Meta); + } + } +} \ No newline at end of file diff --git a/Scripts/Items/ModularActions/ActionModuleProceduralAiming.cs b/Scripts/Items/ModularActions/ActionModuleProceduralAiming.cs new file mode 100644 index 0000000..8b489e4 --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleProceduralAiming.cs @@ -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 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 registeredReferences = new List(); + 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 module) + { + if (__instance.AimingGun != module.Instance.isAiming) + { + module.Instance.isAiming = __instance.AimingGun; + module.Instance.UpdateCurrentReference(true); + } + module.Instance.LateUpdateAiming(); + } + } +} \ No newline at end of file diff --git a/Scripts/Items/ModularActions/ActionModuleRampUp.cs b/Scripts/Items/ModularActions/ActionModuleRampUp.cs new file mode 100644 index 0000000..cbca03c --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleRampUp.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/Scripts/Items/ModularActions/ActionModuleTagged.cs b/Scripts/Items/ModularActions/ActionModuleTagged.cs new file mode 100644 index 0000000..b4a7dd8 --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleTagged.cs @@ -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.Parse(string.Join(",", tags_result)) : FastTags.none; + //Log.Out($"tags: {string.Join(",", tags_result)}"); + } + + public class TaggedData + { + public FastTags tags; + public TaggedData(ItemInventoryData _invData, int _indexInEntityOfAction, ActionModuleTagged _module) + { + + } + } +} diff --git a/Scripts/Items/ModularActions/ActionModuleTranspilerTest.cs b/Scripts/Items/ModularActions/ActionModuleTranspilerTest.cs new file mode 100644 index 0000000..6a482fa --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleTranspilerTest.cs @@ -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 Transpiler_InvalidTest(IEnumerable 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 Transpiler_RangedTest(IEnumerable 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 Transpiler_CatapultTest(IEnumerable 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()}"); + } +} \ No newline at end of file diff --git a/Scripts/Items/ModularActions/ActionModuleVariableZoom.cs b/Scripts/Items/ModularActions/ActionModuleVariableZoom.cs new file mode 100644 index 0000000..c423d09 --- /dev/null +++ b/Scripts/Items/ModularActions/ActionModuleVariableZoom.cs @@ -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); + if (__customData.scopeValueIndex == -1 && _data.invData.itemValue.ItemClass is not IModuleContainerFor) + { + __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 variableZoomData) + { + variableZoomData.Instance.ToggleZoom(); + } + } + } + + //[HarmonyPatch(typeof(Inventory), nameof(Inventory.SetItem), new Type[] { typeof(int), typeof(ItemValue), typeof(int), typeof(bool) })] + //[HarmonyTranspiler] + //private static IEnumerable Transpiler_Test(IEnumerable 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()}"); + //} +} \ No newline at end of file diff --git a/Scripts/Items/ModularClasses/ItemModuleSortable.cs b/Scripts/Items/ModularClasses/ItemModuleSortable.cs new file mode 100644 index 0000000..9613cf7 --- /dev/null +++ b/Scripts/Items/ModularClasses/ItemModuleSortable.cs @@ -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 +{ + 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 Transpiler_HandleSlotChangedEvent_XUiC_ItemPartStackGrid(IEnumerable 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)); + } + } +} \ No newline at end of file diff --git a/Scripts/Items/ModularClasses/ItemModuleVariableZoom.cs b/Scripts/Items/ModularClasses/ItemModuleVariableZoom.cs new file mode 100644 index 0000000..a6e5252 --- /dev/null +++ b/Scripts/Items/ModularClasses/ItemModuleVariableZoom.cs @@ -0,0 +1,6 @@ +using KFCommonUtilityLib.Scripts.Attributes; + +[TypeTarget(typeof(ItemClass))] +public class ItemModuleVariableZoom +{ +} \ No newline at end of file diff --git a/Scripts/MinEventActions/MinEventActionAddBuffToTargetAndSelf.cs b/Scripts/MinEventActions/MinEventActionAddBuffToTargetAndSelf.cs new file mode 100644 index 0000000..e7763dc --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionAddBuffToTargetAndSelf.cs @@ -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; + } +} diff --git a/Scripts/MinEventActions/MinEventActionAddItemToInventory.cs b/Scripts/MinEventActions/MinEventActionAddItemToInventory.cs new file mode 100644 index 0000000..4a691cf --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionAddItemToInventory.cs @@ -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); + } +} + diff --git a/Scripts/MinEventActions/MinEventActionAddRoundsToInventory.cs b/Scripts/MinEventActions/MinEventActionAddRoundsToInventory.cs new file mode 100644 index 0000000..d6892a6 --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionAddRoundsToInventory.cs @@ -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); + } +} + diff --git a/Scripts/MinEventActions/MinEventActionAddRoundsToMagazine.cs b/Scripts/MinEventActions/MinEventActionAddRoundsToMagazine.cs new file mode 100644 index 0000000..576b0ad --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionAddRoundsToMagazine.cs @@ -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; + } +} + diff --git a/Scripts/MinEventActions/MinEventActionAmmoAccessBase.cs b/Scripts/MinEventActions/MinEventActionAmmoAccessBase.cs new file mode 100644 index 0000000..51e4280 --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionAmmoAccessBase.cs @@ -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; + } +} diff --git a/Scripts/MinEventActions/MinEventActionAttachPrefabToEntitySync.cs b/Scripts/MinEventActions/MinEventActionAttachPrefabToEntitySync.cs new file mode 100644 index 0000000..40c2136 --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionAttachPrefabToEntitySync.cs @@ -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 dict_loaded = new Dictionary(); + //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().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().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(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; + } + } +} diff --git a/Scripts/MinEventActions/MinEventActionBroadcastPlaySoundLocal.cs b/Scripts/MinEventActions/MinEventActionBroadcastPlaySoundLocal.cs new file mode 100644 index 0000000..e49cd69 --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionBroadcastPlaySoundLocal.cs @@ -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); + } +} + diff --git a/Scripts/MinEventActions/MinEventActionCVarExpression.cs b/Scripts/MinEventActions/MinEventActionCVarExpression.cs new file mode 100644 index 0000000..569d809 --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionCVarExpression.cs @@ -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 context; + private Expression 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(); + List variableInfos = new List(); + Dictionary varStrs = new Dictionary(); + 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(_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; + } + } +} \ No newline at end of file diff --git a/Scripts/MinEventActions/MinEventActionDecreaseProgressionLevelAndRefundSP.cs b/Scripts/MinEventActions/MinEventActionDecreaseProgressionLevelAndRefundSP.cs new file mode 100644 index 0000000..8668770 --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionDecreaseProgressionLevelAndRefundSP.cs @@ -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; + } + } + } + } +} diff --git a/Scripts/MinEventActions/MinEventActionItemAccessBase.cs b/Scripts/MinEventActions/MinEventActionItemAccessBase.cs new file mode 100644 index 0000000..7324526 --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionItemAccessBase.cs @@ -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; + } + } +} + diff --git a/Scripts/MinEventActions/MinEventActionItemCountRandomBase.cs b/Scripts/MinEventActions/MinEventActionItemCountRandomBase.cs new file mode 100644 index 0000000..638d373 --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionItemCountRandomBase.cs @@ -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; + } + } +} diff --git a/Scripts/MinEventActions/MinEventActionModifyCVarWithLocalCache.cs b/Scripts/MinEventActions/MinEventActionModifyCVarWithLocalCache.cs new file mode 100644 index 0000000..ad07d45 --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionModifyCVarWithLocalCache.cs @@ -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 && 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)(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; + } +} \ No newline at end of file diff --git a/Scripts/MinEventActions/MinEventActionModifyCVarWithSelfRef.cs b/Scripts/MinEventActions/MinEventActionModifyCVarWithSelfRef.cs new file mode 100644 index 0000000..72a9ebc --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionModifyCVarWithSelfRef.cs @@ -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); + } +} + diff --git a/Scripts/MinEventActions/MinEventActionOverrideZoomFOV.cs b/Scripts/MinEventActions/MinEventActionOverrideZoomFOV.cs new file mode 100644 index 0000000..d43f4d8 --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionOverrideZoomFOV.cs @@ -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; + } +} diff --git a/Scripts/MinEventActions/MinEventActionRemoteHoldingBase.cs b/Scripts/MinEventActions/MinEventActionRemoteHoldingBase.cs new file mode 100644 index 0000000..42adc45 --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionRemoteHoldingBase.cs @@ -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); + } +} + diff --git a/Scripts/MinEventActions/MinEventActionRemoveItemFromInventory.cs b/Scripts/MinEventActions/MinEventActionRemoveItemFromInventory.cs new file mode 100644 index 0000000..886f3c0 --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionRemoveItemFromInventory.cs @@ -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); + } +} + diff --git a/Scripts/MinEventActions/MinEventActionRemovePrefabFromHeldItem.cs b/Scripts/MinEventActions/MinEventActionRemovePrefabFromHeldItem.cs new file mode 100644 index 0000000..eb42d14 --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionRemovePrefabFromHeldItem.cs @@ -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(out var targets)) + { + GameObject prefab = targets.GetPrefab(prefabName); + if (prefab) + { + child = prefab.transform; + } + } + if (!child) + { + child = parent.Find(prefabName); + } + if (child) + { + if (child.TryGetComponent(out var reference)) + { + reference.Remove(); + } + child.parent = null; + GameObject.Destroy(child.gameObject); + } + } + } +} \ No newline at end of file diff --git a/Scripts/MinEventActions/MinEventActionRemoveRoundsFromInventory.cs b/Scripts/MinEventActions/MinEventActionRemoveRoundsFromInventory.cs new file mode 100644 index 0000000..a3ea44e --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionRemoveRoundsFromInventory.cs @@ -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); + } +} + diff --git a/Scripts/MinEventActions/MinEventActionSetAmmoOnWeaponLabel.cs b/Scripts/MinEventActions/MinEventActionSetAmmoOnWeaponLabel.cs new file mode 100644 index 0000000..0045357 --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionSetAmmoOnWeaponLabel.cs @@ -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); + } +} + diff --git a/Scripts/MinEventActions/MinEventActionSetMetadataOnWeaponLabel.cs b/Scripts/MinEventActions/MinEventActionSetMetadataOnWeaponLabel.cs new file mode 100644 index 0000000..97e1123 --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionSetMetadataOnWeaponLabel.cs @@ -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); + } +} \ No newline at end of file diff --git a/Scripts/MinEventActions/MinEventActionSetPassiveOnWeaponLabel.cs b/Scripts/MinEventActions/MinEventActionSetPassiveOnWeaponLabel.cs new file mode 100644 index 0000000..1027da0 --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionSetPassiveOnWeaponLabel.cs @@ -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 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(_attribute.Value, true); + break; + case "tags": + tags = FastTags.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); + } +} \ No newline at end of file diff --git a/Scripts/MinEventActions/MinEventActionSetStringOnWeaponLabel.cs b/Scripts/MinEventActions/MinEventActionSetStringOnWeaponLabel.cs new file mode 100644 index 0000000..8b8478a --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionSetStringOnWeaponLabel.cs @@ -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)); + } +} + diff --git a/Scripts/MinEventActions/MinEventActionSetWeaponLabelColor.cs b/Scripts/MinEventActions/MinEventActionSetWeaponLabelColor.cs new file mode 100644 index 0000000..371faee --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionSetWeaponLabelColor.cs @@ -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); + } +} + diff --git a/Scripts/MinEventActions/MinEventActionUpdateLocalCache.cs b/Scripts/MinEventActions/MinEventActionUpdateLocalCache.cs new file mode 100644 index 0000000..b67e5fe --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionUpdateLocalCache.cs @@ -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 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 && base.CanExecute(_eventType, _params); + } + + public override void Execute(MinEventParams _params) + { + ActionModuleLocalPassiveCache.LocalPassiveCacheData _data = ((IModuleContainerFor)(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(_attribute.Value, true); + return true; + case "tags": + tags = FastTags.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; + } +} \ No newline at end of file diff --git a/Scripts/NetPackages/NetPackageEntityActionIndex.cs b/Scripts/NetPackages/NetPackageEntityActionIndex.cs new file mode 100644 index 0000000..a087684 --- /dev/null +++ b/Scripts/NetPackages/NetPackageEntityActionIndex.cs @@ -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().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(); + } + } +} diff --git a/Scripts/NetPackages/NetPackageEntitySpawnWithCVar.cs b/Scripts/NetPackages/NetPackageEntitySpawnWithCVar.cs new file mode 100644 index 0000000..33f6eb8 --- /dev/null +++ b/Scripts/NetPackages/NetPackageEntitySpawnWithCVar.cs @@ -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); + } + } +} diff --git a/Scripts/NetPackages/NetPackageFixedReload.cs b/Scripts/NetPackages/NetPackageFixedReload.cs new file mode 100644 index 0000000..daaf677 --- /dev/null +++ b/Scripts/NetPackages/NetPackageFixedReload.cs @@ -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); + } +} \ No newline at end of file diff --git a/Scripts/NetPackages/NetPackageRemoteAttachPrefab.cs b/Scripts/NetPackages/NetPackageRemoteAttachPrefab.cs new file mode 100644 index 0000000..b94efb8 --- /dev/null +++ b/Scripts/NetPackages/NetPackageRemoteAttachPrefab.cs @@ -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().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); + } + } +} diff --git a/Scripts/NetPackages/NetPackageSyncWeaponLabelColor.cs b/Scripts/NetPackages/NetPackageSyncWeaponLabelColor.cs new file mode 100644 index 0000000..e4652a1 --- /dev/null +++ b/Scripts/NetPackages/NetPackageSyncWeaponLabelColor.cs @@ -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.Instance.IsServer ? "server" : "client") + " color: " + color.ToString() + " entity: " + holdingEntity.entityId + " from net: " + fromNet); + if (SingletonMonoBehaviour.Instance.IsServer && SingletonMonoBehaviour.Instance.ClientCount() > 0) + { + int allButAttachedToEntityId = holdingEntity.entityId; + if (holdingEntity && holdingEntity.AttachedMainEntity) + allButAttachedToEntityId = holdingEntity.AttachedMainEntity.entityId; + SingletonMonoBehaviour.Instance.SendPackage(NetPackageManager.GetPackage().Setup(holdingEntity.entityId, isText, color, slot0, slot1, nameId), false, -1, allButAttachedToEntityId, allButAttachedToEntityId, null, 15); + } + else if (SingletonMonoBehaviour.Instance.IsClient && !fromNet) + SingletonMonoBehaviour.Instance.SendToServer(NetPackageManager.GetPackage().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(); + //if (holdingEntity.emodel.avatarController is AvatarMultiBodyController multiBody && multiBody.HeldItemTransform != null) + // controller = multiBody.HeldItemTransform.GetComponent(); + //else if (holdingEntity.emodel.avatarController is LegacyAvatarController legacy && legacy.HeldItemTransform != null) + // controller = legacy.HeldItemTransform.GetComponent(); + return controller && controller.setLabelColor(slot0, color); + } + else + { + WeaponColorControllerBase controller = holdingEntity.inventory.GetHoldingItemTransform()?.GetComponent(); + //if (holdingEntity.emodel.avatarController is AvatarMultiBodyController multiBody && multiBody.HeldItemTransform != null) + // controller = multiBody.HeldItemTransform.GetComponent(); + //else if (holdingEntity.emodel.avatarController is LegacyAvatarController legacy && legacy.HeldItemTransform != null) + // controller = legacy.HeldItemTransform.GetComponent(); + 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; +} + diff --git a/Scripts/NetPackages/NetPackageSyncWeaponLabelText.cs b/Scripts/NetPackages/NetPackageSyncWeaponLabelText.cs new file mode 100644 index 0000000..a505b24 --- /dev/null +++ b/Scripts/NetPackages/NetPackageSyncWeaponLabelText.cs @@ -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.Instance.IsServer ? "server" : "client") + " slot: " + slot + " text: " + data + " entity: " + holdingEntity.entityId + " from net: " + fromNet); + if (SingletonMonoBehaviour.Instance.IsServer && SingletonMonoBehaviour.Instance.ClientCount() > 0) + { + int allButAttachedToEntityId = holdingEntity.entityId; + if (holdingEntity.AttachedMainEntity) + allButAttachedToEntityId = holdingEntity.AttachedMainEntity.entityId; + SingletonMonoBehaviour.Instance.SendPackage(NetPackageManager.GetPackage().Setup(holdingEntity.entityId, slot, data), false, -1, allButAttachedToEntityId, allButAttachedToEntityId, null, 15); + } + else if (SingletonMonoBehaviour.Instance.IsClient && !fromNet) + SingletonMonoBehaviour.Instance.SendToServer(NetPackageManager.GetPackage().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(); + //if (holdingEntity.emodel.avatarController is AvatarMultiBodyController multiBody && multiBody.HeldItemTransform != null) + // controller = multiBody.HeldItemTransform.GetComponent(); + //else if (holdingEntity.emodel.avatarController is LegacyAvatarController legacy && legacy.HeldItemTransform) + // controller = legacy.HeldItemTransform.GetComponent(); + + return controller && controller.setLabelText(slot, data); + } + + private int entityId; + private int slot; + private string data; +} + diff --git a/Scripts/Requirements/ActionHasTags.cs b/Scripts/Requirements/ActionHasTags.cs new file mode 100644 index 0000000..cbaa4c3 --- /dev/null +++ b/Scripts/Requirements/ActionHasTags.cs @@ -0,0 +1,52 @@ +using KFCommonUtilityLib; +using System.Xml.Linq; + +public class ActionHasTags : TargetedCompareRequirementBase +{ + private FastTags actionTags; + + private bool hasAllTags; + + public override bool IsValid(MinEventParams _params) + { + if (!base.IsValid(_params)) + { + return false; + } + + bool flag = false; + if (_params.ItemActionData is IModuleContainerFor 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.Parse(_attribute.Value); + return true; + } + + if (localName == "has_all_tags") + { + hasAllTags = StringParsers.ParseBool(_attribute.Value); + return true; + } + } + + return flag; + } +} \ No newline at end of file diff --git a/Scripts/Requirements/ActionIndexIs.cs b/Scripts/Requirements/ActionIndexIs.cs new file mode 100644 index 0000000..cd866e4 --- /dev/null +++ b/Scripts/Requirements/ActionIndexIs.cs @@ -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; + } +} diff --git a/Scripts/Requirements/AmmoIndexIs.cs b/Scripts/Requirements/AmmoIndexIs.cs new file mode 100644 index 0000000..60608d5 --- /dev/null +++ b/Scripts/Requirements/AmmoIndexIs.cs @@ -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; + } +} \ No newline at end of file diff --git a/Scripts/Requirements/FireModeIs.cs b/Scripts/Requirements/FireModeIs.cs new file mode 100644 index 0000000..9701e8b --- /dev/null +++ b/Scripts/Requirements/FireModeIs.cs @@ -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 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; + } +} \ No newline at end of file diff --git a/Scripts/Requirements/HoldingActionIndexIs.cs b/Scripts/Requirements/HoldingActionIndexIs.cs new file mode 100644 index 0000000..756e5bf --- /dev/null +++ b/Scripts/Requirements/HoldingActionIndexIs.cs @@ -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; + } +} diff --git a/Scripts/Requirements/HoldingAmmoIndexIs.cs b/Scripts/Requirements/HoldingAmmoIndexIs.cs new file mode 100644 index 0000000..7c74a97 --- /dev/null +++ b/Scripts/Requirements/HoldingAmmoIndexIs.cs @@ -0,0 +1,8 @@ +public class HoldingAmmoIndexIs : AmmoIndexIs +{ + public override bool ParamsValid(MinEventParams _params) + { + itemValueCache = _params.Self?.inventory?.holdingItemItemValue; + return itemValueCache != null; + } +} \ No newline at end of file diff --git a/Scripts/Requirements/HoldingFireModeIs.cs b/Scripts/Requirements/HoldingFireModeIs.cs new file mode 100644 index 0000000..4ca3864 --- /dev/null +++ b/Scripts/Requirements/HoldingFireModeIs.cs @@ -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 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; + } +} \ No newline at end of file diff --git a/Scripts/Requirements/IsActionUnlocked.cs b/Scripts/Requirements/IsActionUnlocked.cs new file mode 100644 index 0000000..7868e84 --- /dev/null +++ b/Scripts/Requirements/IsActionUnlocked.cs @@ -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 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; + } +} diff --git a/Scripts/Requirements/IsHoldingItemModificationActivated.cs b/Scripts/Requirements/IsHoldingItemModificationActivated.cs new file mode 100644 index 0000000..1b92ee5 --- /dev/null +++ b/Scripts/Requirements/IsHoldingItemModificationActivated.cs @@ -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; + } +} \ No newline at end of file diff --git a/Scripts/Requirements/IsInJeep.cs b/Scripts/Requirements/IsInJeep.cs new file mode 100644 index 0000000..e941c13 --- /dev/null +++ b/Scripts/Requirements/IsInJeep.cs @@ -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; + } +} diff --git a/Scripts/Requirements/IsLocal.cs b/Scripts/Requirements/IsLocal.cs new file mode 100644 index 0000000..21c1db1 --- /dev/null +++ b/Scripts/Requirements/IsLocal.cs @@ -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); + } +} \ No newline at end of file diff --git a/Scripts/Requirements/IsModificationActivated.cs b/Scripts/Requirements/IsModificationActivated.cs new file mode 100644 index 0000000..2a7bb2d --- /dev/null +++ b/Scripts/Requirements/IsModificationActivated.cs @@ -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; + } +} \ No newline at end of file diff --git a/Scripts/Requirements/ItemActionIndexIs.cs b/Scripts/Requirements/ItemActionIndexIs.cs new file mode 100644 index 0000000..dd71424 --- /dev/null +++ b/Scripts/Requirements/ItemActionIndexIs.cs @@ -0,0 +1,7 @@ +//public class ItemActionIndexIs : ActionIndexIs +//{ +// public override bool IsValid(MinEventParams _params) +// { +// return (_params.ItemValue == null && index == 0) || _params.ItemValue?.GetActionIndexForItemValue() == index; +// } +//} \ No newline at end of file diff --git a/Scripts/Requirements/ItemInInventory.cs b/Scripts/Requirements/ItemInInventory.cs new file mode 100644 index 0000000..497be89 --- /dev/null +++ b/Scripts/Requirements/ItemInInventory.cs @@ -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; + } +} diff --git a/Scripts/Requirements/PercentInHoldingItem.cs b/Scripts/Requirements/PercentInHoldingItem.cs new file mode 100644 index 0000000..9ca87fa --- /dev/null +++ b/Scripts/Requirements/PercentInHoldingItem.cs @@ -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; + } +} + diff --git a/Scripts/Requirements/PercentInMagazine.cs b/Scripts/Requirements/PercentInMagazine.cs new file mode 100644 index 0000000..0ec6628 --- /dev/null +++ b/Scripts/Requirements/PercentInMagazine.cs @@ -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; + } +} + diff --git a/Scripts/Requirements/RoundsInHoldingItem.cs b/Scripts/Requirements/RoundsInHoldingItem.cs new file mode 100644 index 0000000..f1da9eb --- /dev/null +++ b/Scripts/Requirements/RoundsInHoldingItem.cs @@ -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; + } +} + diff --git a/Scripts/Requirements/RoundsInInventory.cs b/Scripts/Requirements/RoundsInInventory.cs new file mode 100644 index 0000000..d2b2465 --- /dev/null +++ b/Scripts/Requirements/RoundsInInventory.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +public class RoundsInInventory : RequirementBase +{ + private static Dictionary cached = new Dictionary(); + + 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; + } +} diff --git a/Scripts/Requirements/RoundsInMagazineBase.cs b/Scripts/Requirements/RoundsInMagazineBase.cs new file mode 100644 index 0000000..e4891f4 --- /dev/null +++ b/Scripts/Requirements/RoundsInMagazineBase.cs @@ -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; + } +} + diff --git a/Scripts/StaticManagers/AnimationRiggingManager.cs b/Scripts/StaticManagers/AnimationRiggingManager.cs new file mode 100644 index 0000000..3e247de --- /dev/null +++ b/Scripts/StaticManagers/AnimationRiggingManager.cs @@ -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(); + // //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 hash_rig_items = new HashSet(); + private static readonly HashSet hash_items_take_over_reload_time = new HashSet(); + private static readonly HashSet hash_items_parse_later = new HashSet(); + private static readonly HashSet hash_rig_names = new HashSet(); + private static readonly HashSet hash_rig_changed_players = new HashSet(); + + //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(); + } + return null; + } + + public static AnimationTargetsAbs GetRigTargetsFromInventory(Inventory inventory) + { + Transform holdingItemTransform = inventory?.GetHoldingItemTransform(); + if (holdingItemTransform) + { + return holdingItemTransform.GetComponent(); + } + 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().TryGetComponent(out var builder)) + // //{ + // // builder.DestroyGraph(!targets || targets.Destroyed); + // //} + // if (controller is AvatarLocalPlayerController localPlayerController && localPlayerController.isFPV && localPlayerController.FPSArms != null) + // { + // if (localPlayerController.FPSArms.Animator.TryGetComponent(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(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(); + //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(); + 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(); + 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(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()) + { + particle.gameObject.SetActive(true); + particle.Clear(); + particle.Play(); + } + var temp = fire.gameObject.GetOrAddComponent(); + temp.life = 5; + //temp.Restart(); + if (fire.TryGetComponent(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().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()) + { + particle.gameObject.SetActive(true); + particle.Clear(); + particle.Play(); + } + var temp = smoke.gameObject.GetOrAddComponent(); + temp.life = 5; + //temp.Restart(); + if (smoke.TryGetComponent(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}"); + // } + //} + } +} diff --git a/Scripts/StaticManagers/BackgroundInventoryUpdateManager.cs b/Scripts/StaticManagers/BackgroundInventoryUpdateManager.cs new file mode 100644 index 0000000..8dc03cd --- /dev/null +++ b/Scripts/StaticManagers/BackgroundInventoryUpdateManager.cs @@ -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[]> dict_updaters = new Dictionary[]>(); + private static readonly Dictionary[]> dict_disabled = new Dictionary[]>(); + + 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[inv.GetSlotCount()]; + dict_updaters[entity.entityId] = arr_updaters; + } + if (arr_updaters[slot] == null) + { + arr_updaters[slot] = new List(); + } + 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(); + } + } + } + } +} diff --git a/Scripts/StaticManagers/CustomEffectEnumManager.cs b/Scripts/StaticManagers/CustomEffectEnumManager.cs new file mode 100644 index 0000000..f418b30 --- /dev/null +++ b/Scripts/StaticManagers/CustomEffectEnumManager.cs @@ -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(bool requestMin = false, int requestedMin = 0, bool requestMax = false, int requestedMax = int.MaxValue) where T : struct, Enum + { + if (EnumHolder.Registered) + return; + EnumHolder.Registered = true; + EnumHolder.RequestMinMax(requestMin, requestedMin, requestMax, requestedMax); + OnInitDefault += EnumHolder.InitDefault; + OnInitFinal += EnumHolder.InitFinal; + OnPrintResult += EnumHolder.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(string name, bool ignoreCase = false) where T : struct, Enum + { + if (!EnumHolder.Registered) + { + throw new Exception($"Enum not registered: {typeof(T).Name}"); + } + return EnumHolder.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 where T : struct, Enum + { + private static int max, min; + private static readonly TypeCode typecode; + private static readonly Dictionary dict_default_enums = new Dictionary(); + private static readonly Dictionary dict_default_enums_lower = new Dictionary(); + private static readonly LinkedList<(int start, int end)> link_default_holes = new LinkedList<(int start, int end)>(); + private static Dictionary dict_final_enums = new Dictionary(); + private static Dictionary dict_final_enums_lower = new Dictionary(); + 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().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(dict_default_enums); + dict_final_enums_lower = new Dictionary(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; + } + } + } +} diff --git a/Scripts/StaticManagers/DelayLoadModuleManager.cs b/Scripts/StaticManagers/DelayLoadModuleManager.cs new file mode 100644 index 0000000..0b1a39a --- /dev/null +++ b/Scripts/StaticManagers/DelayLoadModuleManager.cs @@ -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 dlls)> list_delay_load = new List<(string mod, List dlls)>(); + + private static readonly List loaded = new List(); + + public static void RegisterDelayloadDll(string modName, string dllNameWithoutExtension) + { + List dlls; + int index = list_delay_load.FindIndex(p => p.mod == modName); + if (index < 0) + { + dlls = new List() { 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); + // } + //} + } + } +} diff --git a/Scripts/StaticManagers/ItemActionModuleManager.cs b/Scripts/StaticManagers/ItemActionModuleManager.cs new file mode 100644 index 0000000..69f7a66 --- /dev/null +++ b/Scripts/StaticManagers/ItemActionModuleManager.cs @@ -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 assemblies; + + // public static void Init() + // { + // assemblies = new Dictionary(); + // 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 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 list_created = new List(); + private static AssemblyDefinition workingAssembly = null; + private static DefaultAssemblyResolver resolver; + private static ModuleAttributes moduleAttributes; + private static ModuleCharacteristics moduleCharacteristics; + private static readonly Dictionary> dict_replacement_mapping = new Dictionary>(); + + 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().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(), 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()).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())))); + 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())))); + 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())))); + 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())))); + 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); + + // + Dictionary dict_overrides = new Dictionary(); + // + Dictionary list_mtdinf_patches)>> dict_transpilers = new Dictionary list_mtdinf_patches)>>(); + //> + Dictionary> dict_all_states = new Dictionary>(); + + //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(); + 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 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())); + } + 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 dict_replacers = new Dictionary(); + 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 dict_targets = GetMethodOverrideTargets(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(); + 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 dict_targets = GetMethodOverrideTargets(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); + } + + /// + /// + /// + /// + /// + /// + /// + /// + private static Dictionary GetMethodOverrideTargets(Type itemActionType, Type moduleType, ModuleDefinition module) where T : Attribute, IMethodTarget + { + Dictionary dict_overrides = new Dictionary(); + const BindingFlags searchFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; + foreach (var mtd in moduleType.GetMethods(searchFlags)) + { + IMethodTarget attr = mtd.GetCustomAttribute(); + 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; + } + + /// + /// Get or create override MethodDefinition of mtdref_base. + /// + /// + /// + /// + /// + /// + private static MethodPatchInfo GetOrCreateOverride(Dictionary 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; + } + + /// + /// Create a List that loads all arguments required to call the method onto stack. + /// + /// The override method. + /// The patch method to be called. + /// The injected module field. + /// The injected data field. + /// The assembly's main module. + /// The base ItemAction type. + /// + /// + /// + /// + private static List 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 dict_states, string moduleID) + { + var mtddef_derived = mtdpinf_derived.Method; + var il = mtddef_derived.Body.GetILProcessor(); + //Match parameters + List list_inst_pars = new List(); + 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 list_inst_pars, ILProcessor il, ModuleDefinition module, bool isPostfix, Dictionary 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; + } + + /// + /// Check if type is already generated in previous assemblies. + /// + /// Full type name. + /// The retrieved type, null if not found. + /// true if found. + 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; + } + + /// + /// Check if type is already generated in current working assembly definition. + /// + /// Full type name. + /// The retrieved type definition, null if not found. + /// true if found. + 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; + } + } +} \ No newline at end of file diff --git a/Scripts/StaticManagers/LocalItemTagsManager.cs b/Scripts/StaticManagers/LocalItemTagsManager.cs new file mode 100644 index 0000000..ee7190c --- /dev/null +++ b/Scripts/StaticManagers/LocalItemTagsManager.cs @@ -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 +{ + /// + /// only used for item modifier tags. + /// + public static class LocalItemTagsManager + { + public static bool CanInstall(FastTags 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 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 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 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 GetTags(ItemValue itemValue) + { + var str = string.Join(",", itemValue.GetPropertyOverrides("ItemTagsAppend")); + FastTags tagsToAdd = string.IsNullOrEmpty(str) ? FastTags.none : FastTags.Parse(str); + str = string.Join(",", itemValue.GetPropertyOverrides("ItemTagsRemove")); + FastTags tagsToRemove = string.IsNullOrEmpty(str) ? FastTags.none : FastTags.Parse(str); + return (itemValue.ItemClass.ItemTags | tagsToAdd).Remove(tagsToRemove); + } + + public static FastTags GetTagsAsIfNotInstalled(ItemValue itemValue, ItemValue modValue) + { + var str = string.Join(",", itemValue.GetPropertyOverridesWithoutMod(modValue, "ItemTagsAppend")); + FastTags tagsToAdd = string.IsNullOrEmpty(str) ? FastTags.none : FastTags.Parse(str); + str = string.Join(",", itemValue.GetPropertyOverridesWithoutMod(modValue, "ItemTagsRemove")); + FastTags tagsToRemove = string.IsNullOrEmpty(str) ? FastTags.none : FastTags.Parse(str); + return (itemValue.ItemClass.ItemTags | tagsToAdd).Remove(tagsToRemove); + } + + public static FastTags 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 tagsToAdd = string.IsNullOrEmpty(str) ? FastTags.none : FastTags.Parse(str); + str = string.Join(",", itemValue.GetPropertyOverrides("ItemTagsRemove")); + if (modClass.GetPropertyOverride("ItemTagsRemove", itemName, ref val)) + { + str = string.Join(",", str, val); + } + FastTags tagsToRemove = string.IsNullOrEmpty(str) ? FastTags.none : FastTags.Parse(str); + return (itemValue.ItemClass.ItemTags | tagsToAdd).Remove(tagsToRemove); + } + + public static FastTags 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 tagsToAdd = string.IsNullOrEmpty(str) ? FastTags.none : FastTags.Parse(str); + str = string.Join(",", itemValue.GetPropertyOverridesWithoutMod(modValue, "ItemTagsRemove")); + if (modClass.GetPropertyOverride("ItemTagsRemove", itemName, ref val)) + { + str = string.Join(",", str, val); + } + FastTags tagsToRemove = string.IsNullOrEmpty(str) ? FastTags.none : FastTags.Parse(str); + return (itemValue.ItemClass.ItemTags | tagsToAdd).Remove(tagsToRemove); + } + + public static IEnumerable 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 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; + } + } + } + } +} diff --git a/Scripts/StaticManagers/MultiActionManager.cs b/Scripts/StaticManagers/MultiActionManager.cs new file mode 100644 index 0000000..79ffde7 --- /dev/null +++ b/Scripts/StaticManagers/MultiActionManager.cs @@ -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; + + /// + /// when set CurIndex from local input, also set manager to dirty to update the index on other clients + /// + 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 dict_mappings = new Dictionary(); + private static readonly Dictionary dict_indice = new Dictionary(); + private static readonly Dictionary[]> dict_item_action_exclude_tags = new Dictionary[]>(); + private static readonly Dictionary dict_item_action_exclude_mod_property = new Dictionary(); + private static readonly Dictionary dict_item_action_exclude_mod_passive = new Dictionary(); + private static readonly Dictionary dict_item_action_exclude_mod_trigger = new Dictionary(); + + //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[] 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[ItemClass.cMaxActionNames]; + dict_item_action_exclude_tags.Add(item.Id, tags); + } + tags[i] = FastTags.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 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); + } + } + } +} diff --git a/Scripts/StaticManagers/RecoilManager.cs b/Scripts/StaticManagers/RecoilManager.cs new file mode 100644 index 0000000..2b30a1c --- /dev/null +++ b/Scripts/StaticManagers/RecoilManager.cs @@ -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 PistolTag = FastTags.Parse("pistol"); + private static readonly FastTags ShotgunTag = FastTags.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(); + } + + 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 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 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 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; + } + } + } +} diff --git a/Scripts/Utilities/EasingFunctions.cs b/Scripts/Utilities/EasingFunctions.cs new file mode 100644 index 0000000..f24a93d --- /dev/null +++ b/Scripts/Utilities/EasingFunctions.cs @@ -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; + } + } +} diff --git a/Scripts/Utilities/EntityInventoryExtension.cs b/Scripts/Utilities/EntityInventoryExtension.cs new file mode 100644 index 0000000..af07210 --- /dev/null +++ b/Scripts/Utilities/EntityInventoryExtension.cs @@ -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; + } + } +} + diff --git a/Scripts/Utilities/ExpressionParser/ExpressionParser.cs b/Scripts/Utilities/ExpressionParser/ExpressionParser.cs new file mode 100644 index 0000000..3495173 --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/ExpressionParser.cs @@ -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(); + + public abstract class ExpressionParser + { + private readonly Dictionary _builderCached = new Dictionary(); + private Parser _parserCached; + + public Expression CompilePredicate(string input, ExpressionContext context, bool cache) + { + var expr = Compile(input, context, cache); + return () => IsTrue(expr.Invoke()); + } + + public Expression Compile(string input, ExpressionContext context, bool cache) + { + context = context ?? new ExpressionContext(); + + _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 Operator(string op, BinaryFunc fun) + { + return String(op).Token().Return(fun).Named(op); + } + + private static Parser BinaryOperator(Parser op, + Parser operand, Func 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 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 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 ExprBuilder(ExpressionContext context); + + private delegate T UnaryFunc(T a); + + private delegate T BinaryFunc(T a, T b); + + private ExprBuilder MakeFunction(string name, List 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>(); + var results = new List>(); + 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 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 func) + { + if (parameterBuilders.Count < 1) + { + throw new FunctionNotDefinedException(name, "Wrong parameters count"); + } + + return context => + { + var inner = new List>(); + + 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 parser) + { + var value = parser(valueString); + return context => () => value; + } + } + + public class ExpressionContext + { + private readonly ExpressionContext _parent; + private readonly Func> _unregisteredVariableResolver; + + private readonly Dictionary> _variables = new Dictionary>(); + + public ExpressionContext(ExpressionContext parent = null, + Func> unregisteredVariableResolver = null) + { + _parent = parent; + _unregisteredVariableResolver = unregisteredVariableResolver; + } + + public void RegisterVariable(string name, Expression value) + { + if (_variables.ContainsKey(name)) + { + throw new InvalidOperationException($"Variable {name} already registered"); + } + + _variables.Add(name, value); + } + + public Expression 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 : ExpressionContext + { + } + + 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}") + { + } + } +} \ No newline at end of file diff --git a/Scripts/Utilities/ExpressionParser/FloatExpressionParser.cs b/Scripts/Utilities/ExpressionParser/FloatExpressionParser.cs new file mode 100644 index 0000000..4b848c1 --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/FloatExpressionParser.cs @@ -0,0 +1,38 @@ +using System; +using System.Globalization; +using UnityEngine; + +namespace CodeWriter.ExpressionParser +{ + public class FloatExpressionParser : ExpressionParser + { + public static readonly ExpressionParser 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); + } +} \ No newline at end of file diff --git a/Scripts/Utilities/ExpressionParser/Sprache/CommentParser.cs b/Scripts/Utilities/ExpressionParser/Sprache/CommentParser.cs new file mode 100644 index 0000000..2c003b3 --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/Sprache/CommentParser.cs @@ -0,0 +1,124 @@ +namespace Sprache +{ + /// + /// Constructs customizable comment parsers. + /// + public class CommentParser : IComment + { + /// + ///Single-line comment header. + /// + public string Single { get; set; } + + /// + ///Newline character preference. + /// + public string NewLine { get; set; } + + /// + ///Multi-line comment opener. + /// + public string MultiOpen { get; set; } + + /// + ///Multi-line comment closer. + /// + public string MultiClose { get; set; } + + /// + /// Initializes a Comment with C-style headers and Windows newlines. + /// + public CommentParser() + { + Single = "//"; + MultiOpen = "/*"; + MultiClose = "*/"; + NewLine = "\n"; + } + + /// + /// 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. + /// + /// + /// + /// + public CommentParser(string multiOpen, string multiClose, string newLine) + { + Single = null; + MultiOpen = multiOpen; + MultiClose = multiClose; + NewLine = newLine; + } + + /// + /// Initializes a Comment with custom headers and newline characters. + /// + /// + /// + /// + /// + public CommentParser(string single, string multiOpen, string multiClose, string newLine) + { + Single = single; + MultiOpen = multiOpen; + MultiClose = multiClose; + NewLine = newLine; + } + + /// + ///Parse a single-line comment. + /// + public Parser 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 { } + } + + /// + ///Parse a multi-line comment. + /// + public Parser 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 { } + } + + /// + ///Parse a comment. + /// + public Parser 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 { } + } + } +} diff --git a/Scripts/Utilities/ExpressionParser/Sprache/IComment.cs b/Scripts/Utilities/ExpressionParser/Sprache/IComment.cs new file mode 100644 index 0000000..039cb47 --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/Sprache/IComment.cs @@ -0,0 +1,43 @@ +namespace Sprache +{ + /// + /// Represents a customizable comment parser. + /// + public interface IComment + { + /// + /// Single-line comment header. + /// + string Single { get; set; } + + /// + /// Newline character preference. + /// + string NewLine { get; set; } + + /// + /// Multi-line comment opener. + /// + string MultiOpen { get; set; } + + /// + /// Multi-line comment closer. + /// + string MultiClose { get; set; } + + /// + /// Parse a single-line comment. + /// + Parser SingleLineComment { get; } + + /// + /// Parse a multi-line comment. + /// + Parser MultiLineComment { get; } + + /// + /// Parse a comment. + /// + Parser AnyComment { get; } + } +} diff --git a/Scripts/Utilities/ExpressionParser/Sprache/ICommentedOfT.cs b/Scripts/Utilities/ExpressionParser/Sprache/ICommentedOfT.cs new file mode 100644 index 0000000..d4451a3 --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/Sprache/ICommentedOfT.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace Sprache +{ + /// + /// Represents a commented result with its leading and trailing comments. + /// + /// Type of the matched result. + public interface ICommented + { + /// + /// Gets the leading comments. + /// + IEnumerable LeadingComments { get; } + + /// + /// Gets the resulting value. + /// + T Value { get; } + + /// + /// Gets the trailing comments. + /// + IEnumerable TrailingComments { get; } + } +} diff --git a/Scripts/Utilities/ExpressionParser/Sprache/IInput.cs b/Scripts/Utilities/ExpressionParser/Sprache/IInput.cs new file mode 100644 index 0000000..b6c77c5 --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/Sprache/IInput.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; + +namespace Sprache +{ + /// + /// Represents an input for parsing. + /// + public interface IInput : IEquatable + { + /// + /// Advances the input. + /// + /// A new that is advanced. + /// The input is already at the end of the source. + IInput Advance(); + + /// + /// Gets the whole source. + /// + string Source { get; } + + /// + /// Gets the current . + /// + char Current { get; } + + /// + /// Gets a value indicating whether the end of the source is reached. + /// + bool AtEnd { get; } + + /// + /// Gets the current positon. + /// + int Position { get; } + + /// + /// Gets the current line number. + /// + int Line { get; } + + /// + /// Gets the current column. + /// + int Column { get; } + + /// + /// Memos used by this input + /// + IDictionary Memos { get; } + } +} diff --git a/Scripts/Utilities/ExpressionParser/Sprache/IPositionAware.cs b/Scripts/Utilities/ExpressionParser/Sprache/IPositionAware.cs new file mode 100644 index 0000000..4434d10 --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/Sprache/IPositionAware.cs @@ -0,0 +1,18 @@ + +namespace Sprache +{ + /// + /// An interface for objects that have a source . + /// + /// Type of the matched result. + public interface IPositionAware + { + /// + /// Set the start and the matched length. + /// + /// The start position + /// The matched length. + /// The matched result. + T SetPos(Position startPos, int length); + } +} diff --git a/Scripts/Utilities/ExpressionParser/Sprache/IResultOfT.cs b/Scripts/Utilities/ExpressionParser/Sprache/IResultOfT.cs new file mode 100644 index 0000000..137e4bc --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/Sprache/IResultOfT.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; + +namespace Sprache +{ + /// + /// Represents a parsing result. + /// + /// The result type. + public interface IResult + { + /// + /// Gets the resulting value. + /// + T Value { get; } + + /// + /// Gets a value indicating whether wether parsing was successful. + /// + bool WasSuccessful { get; } + + /// + /// Gets the error message. + /// + string Message { get; } + + /// + /// Gets the parser expectations in case of error. + /// + IEnumerable Expectations { get; } + + /// + /// Gets the remainder of the input. + /// + IInput Remainder { get; } + } +} diff --git a/Scripts/Utilities/ExpressionParser/Sprache/ITextSpanOfT.cs b/Scripts/Utilities/ExpressionParser/Sprache/ITextSpanOfT.cs new file mode 100644 index 0000000..08ef5ac --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/Sprache/ITextSpanOfT.cs @@ -0,0 +1,29 @@ +namespace Sprache +{ + /// + /// Represents a text span of the matched result. + /// + /// Type of the matched result. + public interface ITextSpan + { + /// + /// Gets the resulting value. + /// + T Value { get; } + + /// + /// Gets the starting . + /// + Position Start { get; } + + /// + /// Gets the ending . + /// + Position End { get; } + + /// + /// Gets the length of the text span. + /// + int Length { get; } + } +} \ No newline at end of file diff --git a/Scripts/Utilities/ExpressionParser/Sprache/Input.cs b/Scripts/Utilities/ExpressionParser/Sprache/Input.cs new file mode 100644 index 0000000..75d8ae9 --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/Sprache/Input.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; + +namespace Sprache +{ + /// + /// Represents an input for parsing. + /// + public class Input : IInput + { + private readonly string _source; + private readonly int _position; + private readonly int _line; + private readonly int _column; + + /// + /// Gets the list of memos assigned to the instance. + /// + public IDictionary Memos { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The source. + 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(); + } + + /// + /// Advances the input. + /// + /// A new that is advanced. + /// The input is already at the end of the source. + 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); + } + + /// + /// Gets the whole source. + /// + public string Source { get { return _source; } } + + /// + /// Gets the current . + /// + public char Current { get { return _source[_position]; } } + + /// + /// Gets a value indicating whether the end of the source is reached. + /// + public bool AtEnd { get { return _position == _source.Length; } } + + /// + /// Gets the current positon. + /// + public int Position { get { return _position; } } + + /// + /// Gets the current line number. + /// + public int Line { get { return _line; } } + + /// + /// Gets the current column. + /// + public int Column { get { return _column; } } + + /// + /// Returns a string that represents the current object. + /// + /// + /// A string that represents the current object. + /// + public override string ToString() + { + return string.Format("Line {0}, Column {1}", _line, _column); + } + + /// + /// Serves as a hash function for a particular type. + /// + /// + /// A hash code for the current . + /// + public override int GetHashCode() + { + unchecked + { + return ((_source != null ? _source.GetHashCode() : 0) * 397) ^ _position; + } + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// + /// true if the specified is equal to the current ; otherwise, false. + /// + /// The object to compare with the current object. + public override bool Equals(object obj) + { + return Equals(obj as IInput); + } + + /// + /// Indicates whether the current is equal to another object of the same type. + /// + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + /// An object to compare with this object. + 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; + } + + /// + /// Indicates whether the left is equal to the right . + /// + /// The left . + /// The right . + /// true if both objects are equal. + public static bool operator ==(Input left, Input right) + { + return Equals(left, right); + } + + /// + /// Indicates whether the left is not equal to the right . + /// + /// The left . + /// The right . + /// true if the objects are not equal. + public static bool operator !=(Input left, Input right) + { + return !Equals(left, right); + } + } +} diff --git a/Scripts/Utilities/ExpressionParser/Sprache/Option.cs b/Scripts/Utilities/ExpressionParser/Sprache/Option.cs new file mode 100644 index 0000000..3f80b68 --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/Sprache/Option.cs @@ -0,0 +1,145 @@ +using System; + +namespace Sprache +{ + /// + /// Represents an optional result. + /// + /// The result type. + public interface IOption + { + /// + /// Gets a value indicating whether this instance is empty. + /// + bool IsEmpty { get; } + + /// + /// Gets a value indicating whether this instance is defined. + /// + bool IsDefined { get; } + + /// + /// Gets the matched result or a default value. + /// + /// + T GetOrDefault(); + + /// + /// Gets the matched result. + /// + T Get(); + } + + /// + /// Extensions for . + /// + public static class OptionExtensions + { + /// + /// Gets the value or else returns a default value. + /// + /// The result type. + /// + /// The default value. + /// + public static T GetOrElse(this IOption option, T defaultValue) + { + if (option == null) throw new ArgumentNullException(nameof(option)); + return option.IsEmpty ? defaultValue : option.Get(); + } + + /// + /// Maps a function over the value or else returns an empty option. + /// + /// The input type. + /// The output type. + /// The option containing the value to apply to. + /// The function to apply to the value of . + /// An options result containing the result if there was an input value. + public static IOption Select(this IOption option, Func map) + { + if (option == null) throw new ArgumentNullException(nameof(option)); + return option.IsDefined ? (IOption) new Some(map(option.Get())) : new None(); + } + + /// + /// Binds the value to a function with optional result and flattens the result to a single optional. + /// A result projection is applied aftherwards. + /// + /// The input type. + /// The output type of . + /// The final output type. + /// The option containing the value to bind to. + /// The function that receives the input values and returns an optional value. + /// The function that is projects the result of . + /// An option result containing the result if there were was an input value and bind result. + public static IOption SelectMany(this IOption option, Func> bind, Func project) + { + if (option == null) throw new ArgumentNullException(nameof(option)); + if (option.IsEmpty) return new None(); + + var t = option.Get(); + return bind(t).Select(u => project(t,u)); + } + + /// + /// Binds the value to a function with optional result and flattens the result to a single optional. + /// + /// The input type. + /// The output type. + /// The option containing the value to bind to. + /// The function that receives the input values and returns an optional value. + /// An option result containing the result if there were was an input value and bind result. + public static IOption SelectMany(this IOption option, Func> bind) => option.SelectMany(bind, (_,x) => x); + } + + internal abstract class AbstractOption : IOption + { + 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 : AbstractOption + { + 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 : AbstractOption + { + public override bool IsEmpty + { + get { return true; } + } + + public override T Get() + { + throw new InvalidOperationException("Cannot get value from None."); + } + } +} diff --git a/Scripts/Utilities/ExpressionParser/Sprache/Parse.Commented.cs b/Scripts/Utilities/ExpressionParser/Sprache/Parse.Commented.cs new file mode 100644 index 0000000..248dcc1 --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/Sprache/Parse.Commented.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using UniLinq; + +namespace Sprache +{ + partial class Parse + { + /// + /// Represents a text span of the matched result. + /// + /// Type of the matched result. + private class TextSpan : ITextSpan + { + public T Value { get; set; } + + public Position Start { get; set; } + + public Position End { get; set; } + + public int Length { get; set; } + } + + /// + /// Constructs a parser that returns the of the parsed value. + /// + /// The result type of the given parser. + /// The parser to wrap. + /// A parser for the text span of the given parser. + public static Parser> Span(this Parser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return i => + { + var r = parser(i); + if (r.WasSuccessful) + { + var span = new TextSpan + { + 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>(r.Remainder, r.Message, r.Expectations); + }; + } + + /// + /// Represents a commented result with its leading and trailing comments. + /// + /// Type of the matched result. + private class CommentedValue : ICommented + { + public CommentedValue(T value) + { + LeadingComments = TrailingComments = EmptyStringList; + Value = value; + } + + public CommentedValue(IEnumerable leading, T value, IEnumerable trailing) + { + LeadingComments = leading ?? EmptyStringList; + Value = value; + TrailingComments = trailing ?? EmptyStringList; + } + + public T Value { get; } + + public IEnumerable LeadingComments { get; } + + public IEnumerable TrailingComments { get; } + } + + private static readonly string[] EmptyStringList = new string[0]; + + private static readonly IComment DefaultCommentParser = new CommentParser(); + + /// + /// 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. + /// + /// The result type of the given parser. + /// The parser to wrap. + /// The comment parser. + /// An extended Token() version of the given parser. + public static Parser> Commented(this Parser 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 first, ITextSpan 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>()) + .Where(c => IsSameLine(valueSpan, c)).Count() + from trailingComments in commentSpan.Repeat(trailingCount) + select new CommentedValue(leadingComments, valueSpan.Value, trailingComments.Select(c => c.Value)); + } + } +} diff --git a/Scripts/Utilities/ExpressionParser/Sprache/Parse.Optional.cs b/Scripts/Utilities/ExpressionParser/Sprache/Parse.Optional.cs new file mode 100644 index 0000000..87ed1ee --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/Sprache/Parse.Optional.cs @@ -0,0 +1,80 @@ +using System; + +namespace Sprache +{ + partial class Parse + { + /// + /// 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. + /// + /// The result type of the given parser. + /// The parser to wrap. + /// An optional version of the given parser. + public static Parser> Optional(this Parser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return i => + { + var pr = parser(i); + + if (pr.WasSuccessful) + return Result.Success(new Some(pr.Value), pr.Remainder); + + return Result.Success(new None(), i); + }; + } + + /// + /// Constructs the eXclusive version of the Optional{T} parser. + /// + /// The result type of the given parser + /// The parser to wrap + /// An eXclusive optional version of the given parser. + /// + public static Parser> XOptional(this Parser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return i => + { + var result = parser(i); + + if (result.WasSuccessful) + return Result.Success(new Some(result.Value), result.Remainder); + + if (result.Remainder.Equals(i)) + return Result.Success(new None(), i); + + return Result.Failure>(result.Remainder, result.Message, result.Expectations); + }; + } + + /// + /// 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. + /// + /// The result type of the given parser. + /// The parser to wrap. + /// A non-consuming version of the given parser. + public static Parser> Preview(this Parser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return i => + { + var result = parser(i); + + if (result.WasSuccessful) + return Result.Success(new Some(result.Value), i); + + return Result.Success(new None(), i); + }; + } + } +} diff --git a/Scripts/Utilities/ExpressionParser/Sprache/Parse.Positioned.cs b/Scripts/Utilities/ExpressionParser/Sprache/Parse.Positioned.cs new file mode 100644 index 0000000..2ded180 --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/Sprache/Parse.Positioned.cs @@ -0,0 +1,27 @@ + +namespace Sprache +{ + partial class Parse + { + /// + /// Construct a parser that will set the position to the position-aware + /// T on succsessful match. + /// + /// + /// + /// + public static Parser Positioned(this Parser parser) where T : IPositionAware + { + 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; + }; + } + } +} diff --git a/Scripts/Utilities/ExpressionParser/Sprache/Parse.Primitives.cs b/Scripts/Utilities/ExpressionParser/Sprache/Parse.Primitives.cs new file mode 100644 index 0000000..0b7b631 --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/Sprache/Parse.Primitives.cs @@ -0,0 +1,34 @@ +namespace Sprache +{ + partial class Parse + { + /// + /// \n or \r\n + /// + public static Parser LineEnd = + (from r in Char('\r').Optional() + from n in Char('\n') + select r.IsDefined ? r.Get().ToString() + n : n.ToString()) + .Named("LineEnd"); + + /// + /// line ending or end of input + /// + public static Parser LineTerminator = + Return("").End() + .Or(LineEnd.End()) + .Or(LineEnd) + .Named("LineTerminator"); + + /// + /// Parser for identifier starting with and continuing with + /// + public static Parser Identifier(Parser firstLetterParser, Parser tailLetterParser) + { + return + from firstLetter in firstLetterParser + from tail in tailLetterParser.Many().Text() + select firstLetter + tail; + } + } +} \ No newline at end of file diff --git a/Scripts/Utilities/ExpressionParser/Sprache/Parse.Sequence.cs b/Scripts/Utilities/ExpressionParser/Sprache/Parse.Sequence.cs new file mode 100644 index 0000000..819b565 --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/Sprache/Parse.Sequence.cs @@ -0,0 +1,158 @@ +namespace Sprache +{ + using System; + using System.Collections.Generic; + using UniLinq; + + partial class Parse + { + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static Parser> DelimitedBy(this Parser parser, Parser delimiter) + { + return DelimitedBy(parser, delimiter, null, null); + } + + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static Parser> DelimitedBy(this Parser parser, Parser 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); + } + + /// + /// Fails on the first itemParser failure, if it reads at least one character. + /// + /// + /// + /// + /// + /// + /// + public static Parser> XDelimitedBy(this Parser itemParser, Parser 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); + } + + /// + /// + /// + /// + /// + /// + /// + /// + public static Parser> Repeat(this Parser parser, int count) + { + return Repeat(parser, count, count); + } + + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static Parser> Repeat(this Parser parser, int? minimumCount, int? maximumCount) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return i => + { + var remainder = i; + var result = new List(); + + 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>(i, msg, new[] { exp }); + } + + return Result.Success>(result, remainder); + }; + } + + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static Parser Contained(this Parser parser, Parser open, Parser 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; + } + } +} diff --git a/Scripts/Utilities/ExpressionParser/Sprache/Parse.cs b/Scripts/Utilities/ExpressionParser/Sprache/Parse.cs new file mode 100644 index 0000000..735802d --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/Sprache/Parse.cs @@ -0,0 +1,782 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using UniLinq; + +namespace Sprache +{ + /// + /// Parsers and combinators. + /// + public static partial class Parse + { + /// + /// TryParse a single character matching 'predicate' + /// + /// + /// + /// + public static Parser Char(Predicate 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(i, + $"unexpected '{i.Current}'", + new[] { description }); + } + + return Result.Failure(i, + "Unexpected end of input reached", + new[] { description }); + }; + } + + /// + /// Parse a single character except those matching . + /// + /// Characters not to match. + /// Description of characters that don't match. + /// A parser for characters except those matching . + public static Parser CharExcept(Predicate predicate, string description) + { + return Char(c => !predicate(c), "any character except " + description); + } + + /// + /// Parse a single character c. + /// + /// + /// + public static Parser Char(char c) + { + return Char(ch => c == ch, char.ToString(c)); + } + + + /// + /// Parse a single character of any in c + /// + /// + /// + public static Parser Chars(params char[] c) + { + return Char(c.Contains, StringExtensions.Join("|", c)); + } + + /// + /// Parse a single character of any in c + /// + /// + /// + public static Parser Chars(string c) + { + return Char(c.ToEnumerable().Contains, StringExtensions.Join("|", c.ToEnumerable())); + } + + + /// + /// Parse a single character except c. + /// + /// + /// + public static Parser CharExcept(char c) + { + return CharExcept(ch => c == ch, char.ToString(c)); + } + + /// + /// Parses a single character except for those in the given parameters + /// + /// + /// + public static Parser CharExcept(IEnumerable c) + { + var chars = c as char[] ?? c.ToArray(); + return CharExcept(chars.Contains, StringExtensions.Join("|", chars)); + } + + /// + /// Parses a single character except for those in c + /// + /// + /// + public static Parser CharExcept(string c) + { + return CharExcept(c.ToEnumerable().Contains, StringExtensions.Join("|", c.ToEnumerable())); + } + + /// + /// Parse a single character in a case-insensitive fashion. + /// + /// + /// + public static Parser IgnoreCase(char c) + { + return Char(ch => char.ToLower(c) == char.ToLower(ch), char.ToString(c)); + } + + /// + /// Parse a string in a case-insensitive fashion. + /// + /// + /// + public static Parser> IgnoreCase(string s) + { + if (s == null) throw new ArgumentNullException(nameof(s)); + + return s + .ToEnumerable() + .Select(IgnoreCase) + .Aggregate(Return(Enumerable.Empty()), + (a, p) => a.Concat(p.Once())) + .Named(s); + } + + /// + /// Parse any character. + /// + public static readonly Parser AnyChar = Char(c => true, "any character"); + + /// + /// Parse a whitespace. + /// + public static readonly Parser WhiteSpace = Char(char.IsWhiteSpace, "whitespace"); + + /// + /// Parse a digit. + /// + public static readonly Parser Digit = Char(char.IsDigit, "digit"); + + /// + /// Parse a letter. + /// + public static readonly Parser Letter = Char(char.IsLetter, "letter"); + + /// + /// Parse a letter or digit. + /// + public static readonly Parser LetterOrDigit = Char(char.IsLetterOrDigit, "letter or digit"); + + /// + /// Parse a lowercase letter. + /// + public static readonly Parser Lower = Char(char.IsLower, "lowercase letter"); + + /// + /// Parse an uppercase letter. + /// + public static readonly Parser Upper = Char(char.IsUpper, "uppercase letter"); + + /// + /// Parse a numeric character. + /// + public static readonly Parser Numeric = Char(char.IsNumber, "numeric character"); + + /// + /// Parse a string of characters. + /// + /// + /// + public static Parser> String(string s) + { + if (s == null) throw new ArgumentNullException(nameof(s)); + + return s + .ToEnumerable() + .Select(Char) + .Aggregate(Return(Enumerable.Empty()), + (a, p) => a.Concat(p.Once())) + .Named(s); + } + + /// + /// 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. + /// + /// The result type of the given parser + /// The parser to wrap + /// A parser that is the opposite of the given parser. + public static Parser Not(this Parser 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(i, msg, new string[0]); + } + return Result.Success(null, i); + }; + } + + /// + /// Parse first, and if successful, then parse second. + /// + /// + /// + /// + /// + /// + public static Parser Then(this Parser first, Func> 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)); + } + + /// + /// Parse a stream of elements. + /// + /// + /// + /// + /// Implemented imperatively to decrease stack usage. + public static Parser> Many(this Parser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return i => + { + var remainder = i; + var result = new List(); + 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>(result, remainder); + }; + } + + /// + /// Parse a stream of elements, failing if any element is only partially parsed. + /// + /// The type of element to parse. + /// A parser that matches a single element. + /// A that matches the sequence. + /// + /// + /// Using may be preferable to + /// where the first character of each match identified by + /// 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. + /// + /// + /// + public static Parser> XMany(this Parser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return parser.Many().Then(m => parser.Once().XOr(Return(m))); + } + + /// + /// TryParse a stream of elements with at least one item. + /// + /// + /// + /// + public static Parser> AtLeastOnce(this Parser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return parser.Once().Then(t1 => parser.Many().Select(ts => t1.Concat(ts))); + } + + /// + /// TryParse a stream of elements with at least one item. Except the first + /// item, all other items will be matched with the XMany operator. + /// + /// + /// + /// + public static Parser> XAtLeastOnce(this Parser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return parser.Once().Then(t1 => parser.XMany().Select(ts => t1.Concat(ts))); + } + + /// + /// Parse end-of-input. + /// + /// + /// + /// + public static Parser End(this Parser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return i => parser(i).IfSuccess(s => + s.Remainder.AtEnd + ? s + : Result.Failure( + s.Remainder, + string.Format("unexpected '{0}'", s.Remainder.Current), + new[] { "end of input" })); + } + + /// + /// Take the result of parsing, and project it onto a different domain. + /// + /// + /// + /// + /// + /// + public static Parser Select(this Parser parser, Func convert) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + if (convert == null) throw new ArgumentNullException(nameof(convert)); + + return parser.Then(t => Return(convert(t))); + } + + /// + /// Parse the token, embedded in any amount of whitespace characters. + /// + /// + /// + /// + public static Parser Token(this Parser 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; + } + + /// + /// Refer to another parser indirectly. This allows circular compile-time dependency between parsers. + /// + /// + /// + /// + public static Parser Ref(Func> reference) + { + if (reference == null) throw new ArgumentNullException(nameof(reference)); + + Parser p = null; + + return i => + { + if (p == null) + p = reference(); + + if (i.Memos.ContainsKey(p)) + { + var pResult = i.Memos[p] as IResult; + if (pResult.WasSuccessful) + return pResult; + throw new ParseException(pResult.ToString()); + } + + i.Memos[p] = Result.Failure(i, + "Left recursion in the grammar.", + new string[0]); + var result = p(i); + i.Memos[p] = result; + return result; + }; + } + + /// + /// Convert a stream of characters to a string. + /// + /// + /// + public static Parser Text(this Parser> characters) + { + return characters.Select(chs => new string(chs.ToArray())); + } + + /// + /// Parse first, if it succeeds, return first, otherwise try second. + /// + /// + /// + /// + /// + public static Parser Or(this Parser first, Parser 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; + }; + } + + /// + /// Names part of the grammar for help with error messages. + /// + /// + /// + /// + /// + public static Parser Named(this Parser 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(f.Remainder, f.Message, new[] { name }) : + f); + } + + /// + /// Parse first, if it succeeds, return first, otherwise try second. + /// Assumes that the first parsed character will determine the parser chosen (see Try). + /// + /// + /// + /// + /// + public static Parser XOr(this Parser first, Parser 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 DetermineBestError(IResult firstFailure, IResult secondFailure) + { + if (secondFailure.Remainder.Position > firstFailure.Remainder.Position) + return secondFailure; + + if (secondFailure.Remainder.Position == firstFailure.Remainder.Position) + return Result.Failure( + firstFailure.Remainder, + firstFailure.Message, + firstFailure.Expectations.Union(secondFailure.Expectations)); + + return firstFailure; + } + + /// + /// Parse a stream of elements containing only one item. + /// + /// + /// + /// + public static Parser> Once(this Parser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return parser.Select(r => (IEnumerable)new[] { r }); + } + + /// + /// Concatenate two streams of elements. + /// + /// + /// + /// + /// + public static Parser> Concat(this Parser> first, Parser> 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)); + } + + /// + /// Succeed immediately and return value. + /// + /// + /// + /// + public static Parser Return(T value) + { + return i => Result.Success(value, i); + } + + /// + /// Version of Return with simpler inline syntax. + /// + /// + /// + /// + /// + /// + public static Parser Return(this Parser parser, U value) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + return parser.Select(t => value); + } + + /// + /// Attempt parsing only if the parser fails. + /// + /// + /// + /// + /// + /// + public static Parser Except(this Parser parser, Parser 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(i, "Excepted parser succeeded.", new[] { "other than the excepted input" }); + return parser(i); + }; + } + + /// + /// Parse a sequence of items until a terminator is reached. + /// Returns the sequence, discarding the terminator. + /// + /// + /// + /// + /// + /// + public static Parser> Until(this Parser parser, Parser until) + { + return parser.Except(until).Many().Then(r => until.Return(r)); + } + + /// + /// Succeed if the parsed value matches predicate. + /// + /// + /// + /// + /// + public static Parser Where(this Parser parser, Func 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(i, + string.Format("Unexpected {0}.", s.Value), + new string[0])); + } + + /// + /// Monadic combinator Then, adapted for Linq comprehension syntax. + /// + /// + /// + /// + /// + /// + /// + /// + public static Parser SelectMany( + this Parser parser, + Func> selector, + Func 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))); + } + + /// + /// Chain a left-associative operator. + /// + /// + /// + /// + /// + /// + /// + public static Parser ChainOperator( + Parser op, + Parser operand, + Func 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)); + } + + /// + /// Chain a left-associative operator. + /// + /// + /// + /// + /// + /// + /// + public static Parser XChainOperator( + Parser op, + Parser operand, + Func 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 ChainOperatorRest( + T firstOperand, + Parser op, + Parser operand, + Func apply, + Func, Parser, Parser> 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)); + } + + /// + /// Chain a right-associative operator. + /// + /// + /// + /// + /// + /// + /// + public static Parser ChainRightOperator( + Parser op, + Parser operand, + Func 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)); + } + + /// + /// Chain a right-associative operator. + /// + /// + /// + /// + /// + /// + /// + public static Parser XChainRightOperator( + Parser op, + Parser operand, + Func 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 ChainRightOperatorRest( + T lastOperand, + Parser op, + Parser operand, + Func apply, + Func, Parser, Parser> 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)); + } + + /// + /// Parse a number. + /// + public static readonly Parser Number = Numeric.AtLeastOnce().Text(); + + static Parser 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 DecimalWithLeadingDigits(CultureInfo ci = null) + { + return Number.Then(n => DecimalWithoutLeadingDigits(ci).XOr(Return("")).Select(f => n + f)); + } + + /// + /// Parse a decimal number using the current culture's separator character. + /// + public static readonly Parser Decimal = DecimalWithLeadingDigits().XOr(DecimalWithoutLeadingDigits()); + + /// + /// Parse a decimal number with separator '.'. + /// + public static readonly Parser DecimalInvariant = DecimalWithLeadingDigits(CultureInfo.InvariantCulture) + .XOr(DecimalWithoutLeadingDigits(CultureInfo.InvariantCulture)); + } +} diff --git a/Scripts/Utilities/ExpressionParser/Sprache/ParseException.cs b/Scripts/Utilities/ExpressionParser/Sprache/ParseException.cs new file mode 100644 index 0000000..0de7f61 --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/Sprache/ParseException.cs @@ -0,0 +1,50 @@ +using System; + +namespace Sprache +{ + /// + /// Represents an error that occurs during parsing. + /// + public class ParseException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public ParseException() { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public ParseException(string message) : base(message) { } + + /// + /// Initializes a new instance of the class with a specified error message + /// and the position where the error occured. + /// + /// The message that describes the error. + /// The position where the error occured. + public ParseException(string message, Position position) : base(message) + { + if (position == null) throw new ArgumentNullException(nameof(position)); + + Position = position; + } + + /// + /// Initializes a new instance of the class with a specified error message + /// and a reference to the inner exception that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, + /// or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public ParseException(string message, Exception innerException) : base(message, innerException) { } + + /// + /// Gets the position of the parsing failure if one is available; otherwise, null. + /// + public Position Position { + get; + } + } +} diff --git a/Scripts/Utilities/ExpressionParser/Sprache/ParserOfT.cs b/Scripts/Utilities/ExpressionParser/Sprache/ParserOfT.cs new file mode 100644 index 0000000..ebcce83 --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/Sprache/ParserOfT.cs @@ -0,0 +1,54 @@ +using System; + +namespace Sprache +{ + /// + /// Represents a parser. + /// + /// The type of the result. + /// The input to parse. + /// The result of the parser. + public delegate IResult Parser(IInput input); + + /// + /// Contains some extension methods for . + /// + public static class ParserExtensions + { + /// + /// Tries to parse the input without throwing an exception. + /// + /// The type of the result. + /// The parser. + /// The input. + /// The result of the parser + public static IResult TryParse(this Parser parser, string input) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + if (input == null) throw new ArgumentNullException(nameof(input)); + + return parser(new Input(input)); + } + + /// + /// Parses the specified input string. + /// + /// The type of the result. + /// The parser. + /// The input. + /// The result of the parser. + /// It contains the details of the parsing error. + public static T Parse(this Parser 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)); + } + } +} diff --git a/Scripts/Utilities/ExpressionParser/Sprache/Position.cs b/Scripts/Utilities/ExpressionParser/Sprache/Position.cs new file mode 100644 index 0000000..a300dc0 --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/Sprache/Position.cs @@ -0,0 +1,136 @@ +using System; + +namespace Sprache +{ + /// + /// Represents a position in the input. + /// + public class Position : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The position. + /// The line number. + /// The column. + public Position(int pos, int line, int column) + { + Pos = pos; + Line = line; + Column = column; + } + + /// + /// Creates an new instance from a given object. + /// + /// The current input. + /// A new instance. + public static Position FromInput(IInput input) + { + return new Position(input.Position, input.Line, input.Column); + } + + /// + /// Gets the current positon. + /// + public int Pos + { + get; + private set; + } + + /// + /// Gets the current line number. + /// + public int Line + { + get; + private set; + } + + /// + /// Gets the current column. + /// + public int Column + { + get; + private set; + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// + /// true if the specified is equal to the current ; otherwise, false. + /// + /// The object to compare with the current object. + public override bool Equals(object obj) + { + return Equals(obj as Position); + } + + /// + /// Indicates whether the current is equal to another object of the same type. + /// + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + /// An object to compare with this object. + 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; + } + + /// + /// Indicates whether the left is equal to the right . + /// + /// The left . + /// The right . + /// true if both objects are equal. + public static bool operator ==(Position left, Position right) + { + return Equals(left, right); + } + + /// + /// Indicates whether the left is not equal to the right . + /// + /// The left . + /// The right . + /// true if the objects are not equal. + public static bool operator !=(Position left, Position right) + { + return !Equals(left, right); + } + + /// + /// Serves as a hash function for a particular type. + /// + /// + /// A hash code for the current . + /// + public override int GetHashCode() + { + var h = 31; + h = h * 13 + Pos; + h = h * 13 + Line; + h = h * 13 + Column; + return h; + } + + /// + /// Returns a string that represents the current object. + /// + /// + /// A string that represents the current object. + /// + public override string ToString() + { + return string.Format("Line {0}, Column {1}", Line, Column); + } + } +} diff --git a/Scripts/Utilities/ExpressionParser/Sprache/Result.cs b/Scripts/Utilities/ExpressionParser/Sprache/Result.cs new file mode 100644 index 0000000..9e3908b --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/Sprache/Result.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using UniLinq; + +namespace Sprache +{ + /// + /// Contains helper functions to create instances. + /// + public static class Result + { + /// + /// Creates a success result. + /// + /// The type of the result (value). + /// The sucessfully parsed value. + /// The remainder of the input. + /// The new . + public static IResult Success(T value, IInput remainder) + { + return new Result(value, remainder); + } + + /// + /// Creates a failure result. + /// + /// The type of the result. + /// The remainder of the input. + /// The error message. + /// The parser expectations. + /// The new . + public static IResult Failure(IInput remainder, string message, IEnumerable expectations) + { + return new Result(remainder, message, expectations); + } + } + + internal class Result : IResult + { + private readonly T _value; + private readonly IInput _remainder; + private readonly bool _wasSuccessful; + private readonly string _message; + private readonly IEnumerable _expectations; + + public Result(T value, IInput remainder) + { + _value = value; + _remainder = remainder; + _wasSuccessful = true; + _message = null; + _expectations = Enumerable.Empty(); + } + + public Result(IInput remainder, string message, IEnumerable 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 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); + } + } +} diff --git a/Scripts/Utilities/ExpressionParser/Sprache/ResultHelper.cs b/Scripts/Utilities/ExpressionParser/Sprache/ResultHelper.cs new file mode 100644 index 0000000..72a3dc1 --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/Sprache/ResultHelper.cs @@ -0,0 +1,26 @@ +using System; + +namespace Sprache +{ + internal static class ResultHelper + { + public static IResult IfSuccess(this IResult result, Func, IResult> next) + { + if(result == null) throw new ArgumentNullException(nameof(result)); + + if (result.WasSuccessful) + return next(result); + + return Result.Failure(result.Remainder, result.Message, result.Expectations); + } + + public static IResult IfFailure(this IResult result, Func, IResult> next) + { + if (result == null) throw new ArgumentNullException(nameof(result)); + + return result.WasSuccessful + ? result + : next(result); + } + } +} \ No newline at end of file diff --git a/Scripts/Utilities/ExpressionParser/Sprache/StringExtensions.cs b/Scripts/Utilities/ExpressionParser/Sprache/StringExtensions.cs new file mode 100644 index 0000000..d8c1607 --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/Sprache/StringExtensions.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using UniLinq; + +namespace Sprache +{ + internal static class StringExtensions + { + public static IEnumerable 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(string separator, IEnumerable values) + { +#if STRING_JOIN_ENUMERABLE + return string.Join(separator, values); +#else + return string.Join(separator, values.Select(v => v.ToString()).ToArray()); +#endif + } + } +} diff --git a/Scripts/Utilities/ExpressionParser/Sprache/licence.txt b/Scripts/Utilities/ExpressionParser/Sprache/licence.txt new file mode 100644 index 0000000..fe2caa2 --- /dev/null +++ b/Scripts/Utilities/ExpressionParser/Sprache/licence.txt @@ -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. \ No newline at end of file diff --git a/Scripts/Utilities/Modular/IModuleProcessor.cs b/Scripts/Utilities/Modular/IModuleProcessor.cs new file mode 100644 index 0000000..59248f8 --- /dev/null +++ b/Scripts/Utilities/Modular/IModuleProcessor.cs @@ -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 list_inst_pars, ILProcessor il); + } +} diff --git a/Scripts/Utilities/Modular/ItemActionDataModuleProcessor.cs b/Scripts/Utilities/Modular/ItemActionDataModuleProcessor.cs new file mode 100644 index 0000000..e85a0f1 --- /dev/null +++ b/Scripts/Utilities/Modular/ItemActionDataModuleProcessor.cs @@ -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 list_inst_pars, ILProcessor il) + { + return false; + } + } +} diff --git a/Scripts/Utilities/Modular/ItemActionModuleManager.cs b/Scripts/Utilities/Modular/ItemActionModuleManager.cs new file mode 100644 index 0000000..d2ae4a9 --- /dev/null +++ b/Scripts/Utilities/Modular/ItemActionModuleManager.cs @@ -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 assemblies; + + // public static void Init() + // { + // assemblies = new Dictionary(); + // 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> dict_replacement_mapping = new Dictionary>(); + + 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(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; + } + } + } + } + } +} \ No newline at end of file diff --git a/Scripts/Utilities/Modular/ItemActionModuleProcessor.cs b/Scripts/Utilities/Modular/ItemActionModuleProcessor.cs new file mode 100644 index 0000000..b975dcf --- /dev/null +++ b/Scripts/Utilities/Modular/ItemActionModuleProcessor.cs @@ -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()?.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())))); + //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 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; + } + } +} diff --git a/Scripts/Utilities/Modular/ItemClassModuleManager.cs b/Scripts/Utilities/Modular/ItemClassModuleManager.cs new file mode 100644 index 0000000..e624ed8 --- /dev/null +++ b/Scripts/Utilities/Modular/ItemClassModuleManager.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; + +namespace KFCommonUtilityLib +{ + public static class ItemClassModuleManager + { + private static readonly Dictionary dict_classtypes = new Dictionary(); + + 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(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; + } + } +} diff --git a/Scripts/Utilities/Modular/ItemClassModuleProcessor.cs b/Scripts/Utilities/Modular/ItemClassModuleProcessor.cs new file mode 100644 index 0000000..065d0da --- /dev/null +++ b/Scripts/Utilities/Modular/ItemClassModuleProcessor.cs @@ -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 list_inst_pars, ILProcessor il) + { + return false; + } + } +} diff --git a/Scripts/Utilities/Modular/ModuleManagers.cs b/Scripts/Utilities/Modular/ModuleManagers.cs new file mode 100644 index 0000000..e8c22c7 --- /dev/null +++ b/Scripts/Utilities/Modular/ModuleManagers.cs @@ -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 + { + public readonly static List extensions = new List(); + } + 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 list_registered_path = new HashSet(); + private static readonly List list_created = new List(); + 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(); + 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(Type extType) + { + if (typeof(T).GetCustomAttribute() == null) + { + return false; + } + + if (!ModuleExtensions.extensions.Contains(extType)) + ModuleExtensions.extensions.Add(extType); + return true; + } + + public static Type[] GetModuleExtensions() + { + if (typeof(T).GetCustomAttribute() == null) + { + return Array.Empty(); + } + + return ModuleExtensions.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(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().BaseType.IsAssignableFrom(targetType)).ToArray(); + return PatchType(targetType, baseType, moduleTypes, new T(), out typename); + } + + public static bool PatchType(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(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(), 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; + } + + /// + /// Check if type is already generated in previous assemblies. + /// + /// Full type name. + /// The retrieved type, null if not found. + /// true if found. + 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; + } + + /// + /// Check if type is already generated in current working assembly definition. + /// + /// Full type name. + /// The retrieved type definition, null if not found. + /// true if found. + public static bool TryFindInCur(string name, out TypeDefinition typedef) + { + typedef = WorkingAssembly?.MainModule.GetType(name); + return typedef != null; + } + } +} diff --git a/Scripts/Utilities/Modular/ModuleManipulator.cs b/Scripts/Utilities/Modular/ModuleManipulator.cs new file mode 100644 index 0000000..033fc2b --- /dev/null +++ b/Scripts/Utilities/Modular/ModuleManipulator.cs @@ -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 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())))); + 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())))); + 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())))); + 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); + + // + Dictionary dict_overrides = new Dictionary(); + // + //TODO: USE TREE INSTEAD OF LIST + Dictionary> dict_transpilers = new Dictionary>(); + //> + Dictionary> dict_all_states = new Dictionary>(); + + //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(); + foreach (var hp in mtd.GetCustomAttributes()) + { + //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()); + 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)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 dict_replacers = new Dictionary(); + foreach (var pair in dict_transpilers) + { + List 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 dict_targets = GetMethodOverrideTargets(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(); + 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 dict_targets = GetMethodOverrideTargets(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}"); + } + } + + /// + /// + /// + /// + /// + /// + /// + /// + private Dictionary GetMethodOverrideTargets(int moduleIndex) where T : Attribute, IMethodTarget + { + Type moduleType = moduleTypes[moduleIndex]; + Dictionary dict_overrides = new Dictionary(); + 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() != null) + { + foreach (HarmonyPatch hp in mtd.GetCustomAttributes()) + { + 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; + } + + /// + /// Get or create override MethodDefinition of mtdref_base. + /// + /// + /// + /// + /// + /// + private MethodPatchInfo GetOrCreateOverride(Dictionary 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; + } + + /// + /// Create a List that loads all arguments required to call the method onto stack. + /// + /// The root definition of this method. + /// The override method. + /// The patch method to be called. + /// The injected module field. + /// The injected data field. + /// The assembly's main module. + /// The base ItemAction type. + /// + /// + /// + /// + private List MatchArguments(MethodDefinition mtddef_root, MethodPatchInfo mtdpinf_derived, MethodDefinition mtddef_target, int moduleIndex, bool isPostfix, Dictionary 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 list_inst_pars = new List(); + 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 list_inst_pars, ILProcessor il, bool isPostfix, Dictionary 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); + } + } +} diff --git a/Scripts/Utilities/Modular/ModuleUtils.cs b/Scripts/Utilities/Modular/ModuleUtils.cs new file mode 100644 index 0000000..bd2f9db --- /dev/null +++ b/Scripts/Utilities/Modular/ModuleUtils.cs @@ -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(); + } + } +} diff --git a/Scripts/Utilities/MonoCecilExtensions.cs b/Scripts/Utilities/MonoCecilExtensions.cs new file mode 100644 index 0000000..34d83fb --- /dev/null +++ b/Scripts/Utilities/MonoCecilExtensions.cs @@ -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; + +/// +/// 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. +/// +public static class MonoCecilExtensions +{ + /// + /// Represents an information container for updating Mono.Cecil definitions. + /// + public class UpdateInfo + { + /// + /// A collection of CustomAttribute objects that have been updated. + /// + public readonly Collection updatedAttributes = new Collection(); + + /// + /// A collection of InterfaceImplementation objects that have been updated. + /// + public readonly Collection updatedInterfaces = new Collection(); + + /// + /// A collection of FieldDefinition objects that have been updated. + /// + public readonly Collection updatedFields = new Collection(); + + /// + /// A collection of PropertyDefinition objects that have been updated. + /// + public readonly Collection updatedProperties = new Collection(); + + /// + /// A collection of MethodDefinition objects that have been updated. + /// + public readonly Collection updatedMethods = new Collection(); + + /// + /// A collection of source TypeDefinition objects that are being merged. + /// + public readonly Collection srcTypes = new Collection(); + + /// + /// A collection of destination TypeDefinition objects where source objects are merged into. + /// + public readonly Collection destTypes = new Collection(); + }; + + /// + /// A dictionary mapping from AssemblyDefinition objects to their corresponding UpdateInfo objects. + /// Used to keep track of the updates made to each assembly. + /// + public static readonly Dictionary assemblyUpdateInfo = new Dictionary (); + + /// + /// Additional search directories for resolving assembly types. + /// + public static readonly Collection additionalSearchDirectories = new Collection(); + + // Basic extension methods for loading assemblies, adding elements to collections, and finding types, fields, and methods in Mono.Cecil objects. + #region Base + + /// + /// This extension method loads an assembly from a given location. + /// + /// The location of the assembly to be loaded. + /// A boolean value to determine if the assembly is read-only or writable. + /// The AssemblyDefinition object of the loaded assembly if successful + 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, + }); + } + + /// + /// This extension method finds a method of a given type in an assembly. + /// + /// The assembly where the type and method are located. + /// The full or simple name of the type. + /// The full or simple name of the method. + /// The MethodDefinition object of the found method. Null if not found. + 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); + } + + /// + /// This extension method finds a type in an assembly using its full name or simple name. + /// + /// The assembly where the type is located. + /// The full or simple name of the type. + /// The TypeDefinition object of the found type. Null if not found. + 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); + } + + /// + /// This extension method finds a type in an assembly using its full name or simple name. + /// + /// The assembly where the type is located. + /// The type to locate. + /// The TypeDefinition object of the found type. Null if not found. + 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); + } + + /// + /// This extension method finds a field in a type. + /// + /// The type where the field is located. + /// The full or simple name of the field. + /// The FieldDefinition object of the found field. Null if not found. + 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); + } + + /// + /// This extension method finds a method in a type. + /// + /// The type where the method is located. + /// The full or simple name of the method. + /// The MethodDefinition object of the found method. Null if not found. + 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); + } + + /// + /// This extension method finds all methods in a type that match a given method signature. + /// + /// The type where the methods are located. + /// The full or simple name of the methods. + /// A collection of MethodDefinition objects for the found methods. Empty collection if none found. + public static Collection FindMethods(this TypeDefinition type, string methodSignature) + { + var collection = new Collection(); + + // 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 + + /// + /// 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. + /// + /// The assembly to which the type will be added. + /// The source type that will be added to the assembly. + /// Avoid name conflicts by adding a '_' suffix to the copied class name. + 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 + + /// + /// 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. + /// + /// The destination type definition where fields, properties, and methods from source will be added. + /// The source type definition whose fields, properties, and methods will be cloned and added to the destination. + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + + // 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 + + /// + /// 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. + /// + /// 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. + /// Avoid signature conflicts by changing original method parameters to be base object types for duplicate methods + 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 + + /// + /// Clones a CustomAttribute. + /// + /// The attribute to be cloned. + /// A clone of the original attribute. + 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; + } + + /// + /// Clones a InterfaceImplementation. + /// + /// The interface to be cloned. + /// A clone of the original interface. + 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; + } + + /// + /// Clones a FieldDefinition. + /// + /// The field to be cloned. + /// A clone of the original field. + 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; + } + + /// + /// Clones a PropertyDefinition. + /// + /// The property to be cloned. + /// A clone of the original property. + 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; + } + + /// + /// Clones a ParameterDefinition. + /// + /// The parameter to be cloned. + /// A clone of the original parameter. + 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; + } + + /// + /// Clones a VariableDefinition. + /// + /// The variable to be cloned. + /// A clone of the original variable. + 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); + } + + /// + /// Clones an Instruction. + /// + /// The instruction to be cloned. + /// A clone of the original instruction. + 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; + } + + /// + /// Clones a MethodDefinition. + /// + /// The method to be cloned. + /// A clone of the original method. + 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(); + + // 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(); + + // 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(); + + // 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 + + /// + /// Updates the InterfaceType of the given InterfaceImplementation, if it matches the source type, to the destination type. + /// + /// InterfaceImplementation that may have its InterfaceType updated. + /// The source type which could be replaced. + /// The destination type which could replace the source type. + 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; + } + + /// + /// Updates the FieldType of the given FieldDefinition, if it matches the source type, to the destination type. + /// + /// FieldDefinition that may have its FieldType updated. + /// The source type which could be replaced. + /// The destination type which could replace the source type. + 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; + } + + /// + /// 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. + /// + /// FieldReference that may have its FieldType, and DeclaringType updated. + /// The source type which could be replaced. + /// The destination type which could replace the source type. + /// A FieldReference with updated types, or the original FieldReference if no updates were made. + 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; + } + + /// + /// Updates the PropertyType of the given PropertyDefinition, if it matches the source type, to the destination type. + /// + /// PropertyDefinition that may have its PropertyType updated. + /// The source type which could be replaced. + /// The destination type which could replace the source type. + 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; + } + + /// + /// Updates the ParameterType of the given ParameterDefinition, if it matches the source type, to the destination type. + /// + /// ParameterDefinition that may have its ParameterType updated. + /// The source type which could be replaced. + /// The destination type which could replace the source type. + 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; + } + + /// + /// Updates the VariableType of the given VariableDefinition, if it matches the source type, to the destination type. + /// + /// VariableDefinition that may have its VariableType updated. + /// The source type which could be replaced. + /// The destination type which could replace the source type. + 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; + } + + /// + /// 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. + /// + /// MethodDefinition that may have its ReturnType, ParameterTypes, and VariableTypes updated. + /// The source type which could be replaced. + /// The destination type which could replace the source type. + 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); + } + + /// + /// 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. + /// + /// MethodReference that may have its ReturnType, DeclaringType and ParameterTypes updated. + /// The source type which could be replaced. + /// The destination type which could replace the source type. + /// A MethodReference with updated types, or the original MethodReference if no updates were made. + 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; + } + + /// + /// Updates the ReturnType and Parameters of the CallSite to the destination type, if they match the source type, to the destination type. + /// + /// CallSite that needs its return type and parameters updated. + /// The original type which is being replaced. + /// The new type which is replacing the original type. + 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 + + /// + /// 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. + /// + /// Instruction that needs its operand updated. + /// The original type which is being replaced. + /// The new type which is replacing the original type. + 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 + } + + /// + /// Updates all instructions in the method's body. + /// + /// Method whose instructions are to be updated. + /// The original type which is being replaced. + /// The new type which is replacing the original type. + 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 + + /// + /// 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. + /// + /// PropertyDefinition whose getter and setter need to be updated. + /// The original type which is being replaced. + /// The new type which is replacing the original type. + 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 + + /// + /// Imports the constructor reference for a given attribute into a module. + /// + /// The custom attribute whose constructor reference needs to be imported. + /// The module type into whose module the reference should be imported. + public static void ImportReferences(this CustomAttribute attribute, ModuleDefinition module) + { + // Import the constructor reference into the module + attribute.Constructor = module.ImportReference(attribute.Constructor); + } + + /// + /// Imports the interface type and custom attributes references of an interface into a module. + /// + /// The interface whose references need to be imported. + /// The module type into whose module the references should be imported. + 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); + } + + /// + /// Imports the field type and custom attributes references of a field into a module. + /// + /// The field whose references need to be imported. + /// The module type into whose module the references should be imported. + 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(); + } + + /// + /// Imports the property type and custom attributes references of a property into a module. + /// + /// The property whose references need to be imported. + /// The module type into whose module the references should be imported. + 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(); + } + + /// + /// Imports the parameter type and custom attributes references of a parameter into a module. + /// + /// The parameter whose references need to be imported. + /// The module type into whose module the references should be imported. + 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); + } + + /// + /// Imports the variable type references of a variable into a module. + /// + /// The variable whose type references need to be imported. + /// The module type into whose module the references should be imported. + public static void ImportReferences(this VariableDefinition variable, ModuleDefinition module) + { + // Import the variable type reference into the module + variable.VariableType = module.ImportReference(variable.VariableType); + } + + /// + /// Imports the method type references and the custom attributes of a method into a module. + /// + /// The method whose references need to be imported. + /// The module type into whose module the references should be imported. + 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); + } + } + + /// + /// Imports the return type references of a CallSite into a module. + /// + /// The CallSite whose return type references need to be imported. + /// The module type into whose module the references should be imported. + 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); + } + + /// + /// Imports the operand type references of an instruction into a module. + /// + /// The instruction whose operand references need to be imported. + /// The module type into whose module the references should be imported. + 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 + + /// + /// Swaps the method references within the provided instruction between two given methods. + /// + /// The instruction to modify. + /// The first method to swap. + /// The second method to swap. + 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; + } + } + + /// + /// Swaps the method references within the provided collection of instructions between two given methods. + /// + /// The collection of instructions to modify. + /// The first method to swap. + /// The second method to swap. + public static void SwapMethodReferences(this Collection instructions, MethodDefinition leftMethod, MethodDefinition rightMethod) + { + // Swap method references for each instruction in the collection + foreach (var instruction in instructions) + instruction.SwapMethodReferences(leftMethod, rightMethod); + } + + /// + /// Swaps the method references within the body of the provided method between two given methods. + /// + /// The method to modify. + /// The first method to swap. + /// The second method to swap. + 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); + } + + /// + /// Swaps the attributes, parameters, custom attributes, and generic parameters between two given methods. + /// + /// The first method to swap. + /// The second method to swap. + 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(leftMethod.Parameters); + var leftCustomAttributes = new Collection(leftMethod.CustomAttributes); + var leftGenericParameters = new Collection(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); + } + + /// + /// Finds and swaps methods with the same full name within the given type. + /// + /// The type to modify. + /// Avoid signature conflicts by changing original method parameters to be base object types + 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(); + + // 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 + /// + /// 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. + /// + /// The instruction to be checked for optimization. + /// The method definition that contains the instruction. + /// Returns true if the instruction can be optimized out. Otherwise, returns false. + /// + /// 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. + /// + 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 + + /// + /// Optimizes a given method by removing any instructions + /// that can be optimized out. + /// + /// + /// 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. + /// + 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 +} diff --git a/Scripts/Utilities/MultiActionProjectileRewrites.cs b/Scripts/Utilities/MultiActionProjectileRewrites.cs new file mode 100644 index 0000000..0202031 --- /dev/null +++ b/Scripts/Utilities/MultiActionProjectileRewrites.cs @@ -0,0 +1,738 @@ +using KFCommonUtilityLib.Scripts.StaticManagers; +using System.Collections.Generic; +using UnityEngine; + +namespace KFCommonUtilityLib.Scripts.Utilities +{ + /// + /// 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. + /// + 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.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 _buffActions, ItemActionAttack.AttackHitInfo _attackDetails, int _flags = 1, int _actionExp = 0, + float _actionExpBonus = 0f, ItemActionAttack rangeCheckedAction = null, + Dictionary _toolBonuses = null, + ItemActionAttack.EnumAttackMode _attackMode = ItemActionAttack.EnumAttackMode.RealNoHarvesting, + Dictionary _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.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 equipmentTags = FastTags.none; + if (hitEntityAlive.Health > 0) + { + equipmentTags = FastTags.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.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 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.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 _toolBonuses, Dictionary> _harvestItems) + { + if (!_harvestItems.ContainsKey(EnumDropEvent.Harvest)) + { + return 1f; + } + List 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 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 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 _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); + } + } + } + } + } +} \ No newline at end of file diff --git a/Scripts/Utilities/MultiActionUtils.cs b/Scripts/Utilities/MultiActionUtils.cs new file mode 100644 index 0000000..d69020d --- /dev/null +++ b/Scripts/Utilities/MultiActionUtils.cs @@ -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.Instance.IsServer) + { + SingletonMonoBehaviour.Instance.SendToServer(NetPackageManager.GetPackage().Setup(entityId, actionIndex), false); + return; + } + SingletonMonoBehaviour.Instance.SendPackage(NetPackageManager.GetPackage().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 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 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 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; + } + } +} \ No newline at end of file diff --git a/Scripts/Utilities/SaveTextureToFileUtility.cs b/Scripts/Utilities/SaveTextureToFileUtility.cs new file mode 100644 index 0000000..d4aef81 --- /dev/null +++ b/Scripts/Utilities/SaveTextureToFileUtility.cs @@ -0,0 +1,134 @@ +using Unity.Collections; +using UnityEngine; +using UnityEngine.Rendering; + +public class SaveTextureToFileUtility +{ + public enum SaveTextureFileFormat + { + EXR, JPG, PNG, TGA + }; + + /// + /// Saves a Texture2D to disk with the specified filename and image format + /// + /// + /// + /// + /// + 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; + } + } + + + /// + /// Saves a RenderTexture to disk with the specified filename and image format + /// + /// + /// + /// + /// + 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 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(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 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(); + } + +} \ No newline at end of file diff --git a/Scripts/Utilities/TemporaryMuzzleFlash.cs b/Scripts/Utilities/TemporaryMuzzleFlash.cs new file mode 100644 index 0000000..b457650 --- /dev/null +++ b/Scripts/Utilities/TemporaryMuzzleFlash.cs @@ -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(transform.GetComponentsInChildren()); + } + Destroy(gameObject); + } + } +} diff --git a/Scripts/Utilities/TypeBasedUID.cs b/Scripts/Utilities/TypeBasedUID.cs new file mode 100644 index 0000000..4ddb061 --- /dev/null +++ b/Scripts/Utilities/TypeBasedUID.cs @@ -0,0 +1,5 @@ +public class TypeBasedUID +{ + private static int uid = 0; + public static int UID { get => uid++; } +} \ No newline at end of file diff --git a/TreeCollections.dll b/TreeCollections.dll new file mode 100644 index 0000000..a2d5f1b Binary files /dev/null and b/TreeCollections.dll differ diff --git a/closerex Closer_ex-7D2D-mods main 0-KFCommonUtilityLib.zip b/closerex Closer_ex-7D2D-mods main 0-KFCommonUtilityLib.zip new file mode 100644 index 0000000..a8a7746 Binary files /dev/null and b/closerex Closer_ex-7D2D-mods main 0-KFCommonUtilityLib.zip differ