diff options
| author | 2026-10-21 17:18:23 -0400 | |
|---|---|---|
| committer | 2026-10-25 14:03:16 -0400 | |
| commit | 5658a33e763cb3ed296f683b5dd128dc94f113e9 (patch) | |
| tree | 4eb2dd55de4a83a6c4c6451c7dc35087521dccdd | |
| parent | a83f8f9368e7a94b82df4abd1bd90801c9472932 (diff) | |
| download | WorldTuningTool-5658a33e763cb3ed296f683b5dd128dc94f113e9.tar.gz WorldTuningTool-5658a33e763cb3ed296f683b5dd128dc94f113e9.tar.bz2 WorldTuningTool-5658a33e763cb3ed296f683b5dd128dc94f113e9.zip | |
Factor out Parameter class
Make it less dependent on Plugin specific code so it can be more easily
reused.
Signed-off-by: Andrew Opalach <andrew@akon.city>
| -rw-r--r-- | LUT.cs | 2 | ||||
| -rw-r--r-- | Lights.cs | 3 | ||||
| -rw-r--r-- | Parameter.cs | 950 | ||||
| -rw-r--r-- | Plugin.cs | 1244 | ||||
| -rw-r--r-- | Utility.cs | 128 |
5 files changed, 1187 insertions, 1140 deletions
@@ -13,7 +13,7 @@ using SharpPluginLoader.Core.Resources; namespace WorldTuningTool { - using static Plugin; + using static Utility; internal static class WorldLUTs { @@ -9,7 +9,8 @@ using SharpPluginLoader.Core.Entities; namespace WorldTuningTool { - using static Plugin; + using static Utility; + using static PFuncs; internal static class Lights { diff --git a/Parameter.cs b/Parameter.cs new file mode 100644 index 0000000..db564a3 --- /dev/null +++ b/Parameter.cs @@ -0,0 +1,950 @@ +using System.Numerics; + +using ImGuiNET; +using SharpPluginLoader.Core.Memory; + +namespace WorldTuningTool +{ + using static Utility; + using static PFuncs; + + public enum ParameterType + { + BOOL, + FLAG, + BYTE, + INT, + ALIGNED_INT, + HEX, + BITS, + COLOR, + COLOR_FACTOR, + FLOAT, + VECTOR3, + VECTOR2, + VECTOR4, + PATCH_FLOAT, + SHADOW_RESOLUTION + } + + public static class ParameterFlags + { + public const int ViewOnly = 1; + } + + public static class PFuncs + { + public static bool IgnoreMinMax = false; + + public static bool InTransition = false; + public static List<Parameter> DirtyParams = new(); + public static void BeginTransition() + { + Assert(!InTransition && DirtyParams.Count == 0); + InTransition = true; + } + public static void EndTransition() + { + Assert(InTransition); + InTransition = false; + foreach (Parameter param in DirtyParams) + { + param.Resolve(); + } + DirtyParams.Clear(); + } + + public static Action<int>? HandleShadowResolution = null; + public static Action<Parameter>? DrawStageOverrideTooltip = null; + public static Action<Parameter>? DrawExtraButtons = null; + + public static Parameter<T> P<T>(string name, nint offset, ParameterType type, (T? min, T? max, T? step) m = default) where T : unmanaged + { + return new Parameter<T>(name, offset, 0, type, m.min, m.max, m.step, 0); + } + + public static Parameter<T> P<T>(string name, long offset, ParameterType type, (T? min, T? max, T? step) m = default) where T : unmanaged + { + return new Parameter<T>(name, (nint)offset, 0, type, m.min, m.max, m.step, 0); + } + + public static Parameter<T> V<T>(string name, nint offset, ParameterType type, (T? min, T? max, T? step) m = default) where T : unmanaged + { + return new Parameter<T>(name, offset, 0, type, m.min, m.max, m.step, ParameterFlags.ViewOnly); + } + + public static Parameter<T> M<T>(string name, nint offset, ParameterType type, int mask, (T? min, T? max, T? step) m = default) where T : unmanaged + { + return new Parameter<T>(name, offset, mask, type, m.min, m.max, m.step, 0); + } + + public static (float?, float?, float?) pStep(float step) { return (null, null, step); } + public static (float?, float?, float?) pMin(float min) { return (min, null, null); } + public static (float?, float?, float?) pMax(float max) { return (null, max, null); } + public static (float?, float?, float?) pMinStep(float min, float step) { return (min, null, step); } + public static (float?, float?, float?) pMaxStep(float max, float step) { return (null, max, step); } + public static (float?, float?, float?) pMinMaxStep(float min, float max, float step) { return (min, max, step); } + public static (int?, int?, int?) pStep(int step) { return (null, null, step); } + public static (int?, int?, int?) pMin(int min) { return (min, null, null); } + public static (int?, int?, int?) pMax(int max) { return (null, max, null); } + public static (int?, int?, int?) pMinStep(int min, int step) { return (min, null, step); } + public static (int?, int?, int?) pMaxStep(int max, int step) { return (null, max, step); } + public static (int?, int?, int?) pMinMaxStep(int min, int max, int step) { return (min, max, step); } + public static (byte?, byte?, byte?) pStep(byte step) { return (null, null, step); } + } + + public abstract class Parameter + { + public string Name; + private string hiddenName; + public ParameterType Type; + + private nint offset; + private int mask; + private nint lastAddr = 0x0; + private bool overrideValue = false; + private bool overrideForStage = false; + private bool overrideWasOn = false; + + private int flags; + public bool ViewOnly => (flags & ParameterFlags.ViewOnly) == ParameterFlags.ViewOnly; + + protected float stepf, minf, maxf; + protected int step, min, max; + + private bool fallbackUpdate = true; + private bool pendingWrite = false; + private Vector4 valueV = default; + private int valueInt = 0; + private Vector4 oValueV; // Original value. + private int oValueInt; + + private bool pendingUpdate = true; + private bool queuedOverride = false; + private bool queuedValue = false; + private Vector4 qValueV; // Queued value. + private int qValueInt; + + private bool bValue = false; + private Vector4 bValueV; // B value in A/B. + private int bValueInt; + + public Parameter(string name, nint offset, int mask, ParameterType type, int flags) + { + Name = name; + hiddenName = "##" + Name; + Type = type; + switch (Type) + { + case ParameterType.FLAG: + Assert(mask != 0); + break; + case ParameterType.PATCH_FLOAT: + valueV.X = Get<float>(offset); + pendingUpdate = false; + break; + case ParameterType.SHADOW_RESOLUTION: + valueInt = 1; + pendingUpdate = false; + break; + } + this.offset = offset; + this.mask = mask; + this.flags = flags; + } + + public bool PendingUpdate() + { + return pendingUpdate; + } + + public (Vector4, int) GetValue() + { + return (valueV, valueInt); + } + + public int GetMask() + { + return mask; + } + + public void Resolve() + { + Assert(!InTransition && pendingWrite); + pendingWrite = false; + maybeWriteCurrentValue(); + } + + private void maybeWriteCurrentValue() + { + if (InTransition) + { + if (!DirtyParams.Contains(this)) + { + pendingWrite = true; + DirtyParams.Add(this); + } + else + { + Assert(pendingWrite); + } + return; + } + Assert(!pendingWrite); + switch (Type) + { + case ParameterType.PATCH_FLOAT: + new Patch(offset, BitConverter.GetBytes(valueV.X)).Enable(); + break; + case ParameterType.SHADOW_RESOLUTION: + if (HandleShadowResolution != null) + { + HandleShadowResolution(valueInt); + } + break; + } + if (lastAddr == 0x0) + { + return; + } + switch (Type) + { + case ParameterType.BOOL: + case ParameterType.BYTE: + GetRef<byte>(lastAddr, offset) = (byte)valueInt; + break; + case ParameterType.FLAG: + { + ref int i1 = ref GetRef<int>(lastAddr, offset); + if (valueInt != 0) + { + i1 |= mask; + } + else + { + i1 &= ~mask; + } + break; + } + case ParameterType.INT: + case ParameterType.ALIGNED_INT: + case ParameterType.HEX: + case ParameterType.BITS: + case ParameterType.COLOR: + { + ref int i1 = ref GetRef<int>(lastAddr, offset); + if (mask != 0) + { + i1 = (i1 & ~mask) | ((valueInt << BitIndex(mask)) & mask); + } + else + { + i1 = valueInt; + } + break; + } + case ParameterType.FLOAT: + GetRef<float>(lastAddr, offset) = valueV.X; + break; + case ParameterType.VECTOR2: + GetRef<Vector2>(lastAddr, offset) = new(valueV.X, valueV.Y); + break; + case ParameterType.VECTOR3: + case ParameterType.COLOR_FACTOR: + GetRef<Vector3>(lastAddr, offset) = new(valueV.X, valueV.Y, valueV.Z); + break; + case ParameterType.VECTOR4: + GetRef<Vector4>(lastAddr, offset) = valueV; + break; + } + } + + public void ForceOverrideOn() + { + if (!queuedOverride && !overrideValue) + { + OverrideOn(false); + } + } + + public void ForceOverrideOff() + { + if (queuedOverride || overrideValue) + { + OverrideOff(); + } + } + + public void OverrideOn(bool forStage) + { + if (pendingUpdate) + { + if (queuedOverride) + { + Assert(!overrideWasOn && forStage); + overrideWasOn = true; + } + else + { + queuedOverride = true; + } + } + else + { + if (overrideValue) + { + Assert(!overrideWasOn && forStage); + overrideWasOn = true; + } + else + { + oValueV = valueV; + oValueInt = valueInt; + maybeWriteCurrentValue(); + overrideValue = true; + } + } + overrideForStage = forStage; + } + + public void OverrideOff() + { + if (queuedOverride) + { + if (overrideWasOn) + { + Assert(overrideForStage); + overrideWasOn = false; + } + else + { + queuedOverride = false; + } + } + else + { + Assert(overrideValue); + if (overrideWasOn) + { + Assert(overrideForStage); + overrideWasOn = false; + } + else + { + valueV = oValueV; + valueInt = oValueInt; + maybeWriteCurrentValue(); + bValue = false; + overrideValue = false; + } + } + overrideForStage = false; + } + + public void SetOverrideValue(Vector4 v4, int i1) + { + if (queuedOverride) + { + qValueV = v4; + qValueInt = i1; + queuedValue = true; + } + else + { + valueV = v4; + valueInt = i1; + maybeWriteCurrentValue(); + } + } + + public void Update(nint baseObject, bool fromHook = false) + { + lastAddr = baseObject; + + if (baseObject == 0x0) + { + pendingUpdate = true; + return; + } + + if (fromHook) + { + fallbackUpdate = false; + } + else if (!fallbackUpdate) + { + fallbackUpdate = true; + return; + } + + // Trying to track changes to the original value is a "best effort" + // approach because it can't work if the in-game value is updated + // to the same value as a current override. + switch (Type) + { + case ParameterType.BOOL: + case ParameterType.BYTE: + { + ref byte b1 = ref GetRef<byte>(baseObject, offset); + byte b = b1; + if (overrideValue) + { + if (b != (byte)valueInt) + { + oValueInt = b; + } + b1 = (byte)valueInt; + } + else + { + valueInt = b; + } + break; + } + case ParameterType.FLAG: + { + ref int i1 = ref GetRef<int>(baseObject, offset); + int b1 = ByteFlag((i1 & mask) == mask); + if (overrideValue) + { + if (b1 != valueInt) + { + oValueInt = b1; + } + if (valueInt != 0) + { + i1 |= mask; + } + else + { + i1 &= ~mask; + } + } + else + { + valueInt = b1; + } + break; + } + case ParameterType.INT: + case ParameterType.ALIGNED_INT: + case ParameterType.HEX: + case ParameterType.BITS: + case ParameterType.COLOR: + { + ref int i1 = ref GetRef<int>(baseObject, offset); + int m = i1; + if (mask != 0) + { + m = (m & mask) >> BitIndex(mask); + } + if (overrideValue) + { + if (m != valueInt) + { + oValueInt = m; + } + if (mask != 0) + { + i1 = (i1 & ~mask) | ((valueInt << BitIndex(mask)) & mask); + } + else + { + i1 = valueInt; + } + } + else + { + valueInt = m; + } + break; + } + case ParameterType.FLOAT: + { + ref float f1 = ref GetRef<float>(baseObject, offset); + float f = f1; + if (overrideValue) + { + if (f != valueV.X) + { + oValueV.X = f; + } + f1 = valueV.X; + } + else + { + valueV.X = f; + } + break; + } + case ParameterType.VECTOR2: + { + ref Vector2 v2 = ref GetRef<Vector2>(baseObject, offset); + Vector2 v = v2; + if (overrideValue) + { + Vector2 valueV2 = new(valueV.X, valueV.Y); + if (v != valueV2) + { + oValueV = new Vector4(v.X, v.Y, 0.0f, 0.0f); + } + v2 = valueV2; + } + else + { + valueV = new Vector4(v.X, v.Y, 0.0f, 0.0f); + } + break; + } + case ParameterType.VECTOR3: + case ParameterType.COLOR_FACTOR: + { + ref Vector3 v3 = ref GetRef<Vector3>(baseObject, offset); + Vector3 v = v3; + if (overrideValue) + { + Vector3 valueV3 = new(valueV.X, valueV.Y, valueV.Z); + if (v != valueV3) + { + oValueV = new Vector4(v.X, v.Y, v.Z, 0.0f); + } + v3 = valueV3; + } + else + { + valueV = new Vector4(v.X, v.Y, v.Z, 0.0f); + } + break; + } + case ParameterType.VECTOR4: + { + ref Vector4 v4 = ref GetRef<Vector4>(baseObject, offset); + Vector4 v = v4; + if (overrideValue) + { + if (v != valueV) + { + oValueV = v; + } + v4 = valueV; + } + else + { + valueV = v; + } + break; + } + } + + if (pendingUpdate) + { + pendingUpdate = false; + if (queuedOverride) + { + Assert(!overrideValue); + OverrideOn(overrideForStage); + if (queuedValue) + { + valueV = qValueV; + valueInt = qValueInt; + maybeWriteCurrentValue(); + queuedValue = false; + } + queuedOverride = false; + } + } + } + + public void AdjustValue(ref Vector4 v4, ref int i1) + { + if (Type == ParameterType.SHADOW_RESOLUTION) // Hard limit. + { +#if OVERSIZED_SHADOW_MAP + i1 = Math.Clamp(i1, 1, 6); +#else + i1 = Math.Clamp(i1, 1, 5); +#endif + } + else if (!IgnoreMinMax) + { + switch (Type) + { + case ParameterType.BOOL: + case ParameterType.FLAG: + case ParameterType.BYTE: + case ParameterType.INT: + case ParameterType.HEX: + case ParameterType.BITS: + i1 = Math.Clamp(i1, min, max); + break; + case ParameterType.ALIGNED_INT: + i1 = Math.Clamp((i1 + (step - 1))&~(step - 1), min, max); + break; + case ParameterType.FLOAT: + case ParameterType.PATCH_FLOAT: + case ParameterType.VECTOR2: + case ParameterType.VECTOR3: + case ParameterType.COLOR_FACTOR: + case ParameterType.VECTOR4: + v4 = new Vector4( + Math.Clamp(v4.X, minf, maxf), + Math.Clamp(v4.Y, minf, maxf), + Math.Clamp(v4.Z, minf, maxf), + Math.Clamp(v4.W, minf, maxf)); + break; + } + } + } + + public bool DrawValue(ref Vector4 v4, ref int i1, float width, bool drawLabel) + { + bool valueChanged = false; + string label = drawLabel ? Name : hiddenName; + float displayMinf = IgnoreMinMax ? 0.0f : minf; + float displayMaxf = IgnoreMinMax ? 0.0f : maxf; + switch (Type) + { + case ParameterType.BOOL: + case ParameterType.FLAG: + { + bool b = i1 != 0; + if (ImGui.Checkbox(label, ref b)) + { + i1 = ByteFlag(b); + valueChanged = true; + } + break; + } + case ParameterType.BYTE: + { + ImGuiInputTextFlags flags = ImGuiInputTextFlags.EnterReturnsTrue; + ImGui.SetNextItemWidth(width * defaultParamWidth); + if (ImGui.InputInt(label, ref i1, step, 0, flags)) + { + valueChanged = true; + } + break; + } + case ParameterType.INT: + case ParameterType.ALIGNED_INT: + case ParameterType.HEX: + case ParameterType.SHADOW_RESOLUTION: + { + ImGuiInputTextFlags flags = ImGuiInputTextFlags.EnterReturnsTrue; + if (Type == ParameterType.HEX) + { + flags |= ImGuiInputTextFlags.CharsHexadecimal; + } + ImGui.SetNextItemWidth(width * defaultParamWidth); + if (ImGui.InputInt(label, ref i1, step, 0, flags)) + { + valueChanged = true; + } + break; + } + case ParameterType.BITS: + { + for (int i = 0; i < 32; i++) + { + int bit = (1 << i); + if ((mask & bit) == bit) + { + bool set = (i1 & bit) == bit; + if (ImGui.Checkbox($"##Bit{i}", ref set)) + { + if (set) + { + i1 |= bit; + } + else + { + i1 &= ~bit; + } + valueChanged = true; + } + ImGui.SameLine(); + } + } + ImGui.Text(label); + break; + } + case ParameterType.COLOR: + { + Vector4 colorV = ColorVectorFromInt(i1); + ImGui.SetNextItemWidth(width * defaultParamWidth * 3.0f); + if (ImGui.ColorEdit4(label, ref colorV)) + { + i1 = ColorVectorToInt(colorV); + valueChanged = true; + } + break; + } + case ParameterType.FLOAT: + case ParameterType.PATCH_FLOAT: + { + float f1 = v4.X; + ImGui.SetNextItemWidth(width * defaultParamWidth); + if (ImGui.DragFloat(label, ref f1, stepf, displayMinf, displayMaxf, "%.6f", ImGuiSliderFlags.NoRoundToFormat)) + { + v4 = new Vector4(f1, 0.0f, 0.0f, 0.0f); + valueChanged = true; + } + break; + } + case ParameterType.VECTOR2: + { + Vector2 v2 = new(v4.X, v4.Y); + ImGui.SetNextItemWidth(width * defaultParamWidth * 2.0f); + if (ImGui.DragFloat2(label, ref v2, stepf, displayMinf, displayMaxf, "%.5f", ImGuiSliderFlags.NoRoundToFormat)) + { + v4 = new Vector4(v2.X, v2.Y, 0.0f, 0.0f); + valueChanged = true; + } + break; + } + case ParameterType.VECTOR3: + case ParameterType.COLOR_FACTOR: + { + Vector3 v3 = new(v4.X, v4.Y, v4.Z); + if (Type == ParameterType.COLOR_FACTOR) + { + Vector3 v3Color = v3; + // Only scale the color vector if max is unbound. Otherwise, assume that intensity and color are seperate. + if (maxf == Single.MaxValue) + { + float v3Max = MathF.Max(Math.Abs(v3.X), MathF.Max(Math.Abs(v3.Y), Math.Abs(v3.Z))); + if (v3Max != 0.0f) + { + // I believe a compromise is needed here. These are the options I can think of. + //v3Color /= v3Max; // Preview will be full "brightness" at low intensities. + v3Color = v3Color * (v3Color / v3Max); // Preview will converge towards white at high intensities. + } + } + ImGui.ColorEdit3(label, ref v3Color, ImGuiColorEditFlags.NoPicker | ImGuiColorEditFlags.NoLabel | ImGuiColorEditFlags.NoInputs); + ImGui.SameLine(); + } + ImGui.SetNextItemWidth(width * defaultParamWidth * 3.0f); + if (ImGui.DragFloat3(label, ref v3, stepf, displayMinf, displayMaxf, "%.5f", ImGuiSliderFlags.NoRoundToFormat)) + { + v4 = new Vector4(v3.X, v3.Y, v3.Z, 0.0f); + valueChanged = true; + } + break; + } + case ParameterType.VECTOR4: + { + ImGui.SetNextItemWidth(width * defaultParamWidth * 4.0f); + if (ImGui.DragFloat4(label, ref v4, stepf, displayMinf, displayMaxf, "%.5f", ImGuiSliderFlags.NoRoundToFormat)) + { + valueChanged = true; + } + break; + } + } + if (valueChanged) + { + AdjustValue(ref v4, ref i1); + } + return valueChanged; + } + + public void DrawReferenceValue(string name, Vector4 v4, int i1) + { + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, ImGui.GetStyle().Alpha * fadeAlphaFactor); + switch (Type) + { + case ParameterType.BOOL: + case ParameterType.FLAG: + ImGui.Text($"({name}: {i1 != 0})"); + break; + case ParameterType.BYTE: + case ParameterType.INT: + case ParameterType.ALIGNED_INT: + case ParameterType.SHADOW_RESOLUTION: + ImGui.Text($"({name}: {i1})"); + break; + case ParameterType.HEX: + case ParameterType.BITS: + case ParameterType.COLOR: + ImGui.Text($"({name}: {i1:X})"); + break; + case ParameterType.FLOAT: + case ParameterType.PATCH_FLOAT: + ImGui.Text($"({name}: {v4.X:0.000000})"); + break; + case ParameterType.VECTOR2: + ImGui.Text($"({name}: {v4.X:0.00000}, {v4.Y:0.00000})"); + break; + case ParameterType.VECTOR3: + case ParameterType.COLOR_FACTOR: + ImGui.Text($"({name}: {v4.X:0.00000}, {v4.Y:0.00000}, {v4.Z:0.00000})"); + break; + case ParameterType.VECTOR4: + ImGui.Text($"({name}: {v4.X:0.00000}, {v4.Y:0.00000}, {v4.Z:0.00000}, {v4.W:0.00000})"); + break; + } + ImGui.PopStyleVar(); + } + + public void Draw(float width, bool assumeOverride = false) + { + Draw(ref valueV, ref valueInt, width, assumeOverride); + } + + public void Draw(ref Vector4 v4, ref int i1, float width, bool assumeOverride) + { + ImGui.PushID(Name); + + if (ViewOnly) + { + ImGui.SetCursorPos(ImGui.GetCursorPos() + new Vector2(ImGui.GetFrameHeight() + ImGui.GetStyle().ItemSpacing.X, 0.0f)); + } + else if (!assumeOverride) + { + if (overrideForStage) + { + ImGui.PushItemFlag(ImGuiItemFlags.Disabled, true); + ImGui.PushItemFlag(ImGuiItemFlags.MixedValue, true); + } + // If OverrideOn() were to set queuedOverride instead of overrideValue, this parameter shouldn't be drawn. + Assert(!PendingUpdate()); + bool toggleOverride = overrideValue; + if (ImGui.Checkbox("##Override", ref toggleOverride)) + { + if (toggleOverride) + { + OverrideOn(false); + } + else + { + OverrideOff(); + } + } + if (ImGui.BeginItemTooltip()) + { + if (overrideForStage && DrawStageOverrideTooltip != null) + { + DrawStageOverrideTooltip(this); + } + else if (overrideValue) + { + ImGui.Text("Set"); + } + else + { + ImGui.Text("Set override"); + } + ImGui.EndTooltip(); + } + if (overrideForStage) + { + ImGui.PopItemFlag(); + ImGui.PopItemFlag(); + } + ImGui.SameLine(); + } + + if (!overrideValue) + { + ImGui.PushItemFlag(ImGuiItemFlags.Disabled, true); + } + if (DrawValue(ref valueV, ref valueInt, width, !bValue)) + { + maybeWriteCurrentValue(); + } + if (!overrideValue) + { + ImGui.PopItemFlag(); + } + + bool showButtons = !assumeOverride && overrideValue && !pendingUpdate; + if (showButtons) + { + ImGui.SameLine(); + if (!bValue) + { + if (ImGui.Button("B")) + { + bValueV = oValueV; + bValueInt = oValueInt; + bValue = true; + } + } + else + { + if (ImGui.Button("A/B")) + { + (bValueV, valueV) = (valueV, bValueV); + (bValueInt, valueInt) = (valueInt, bValueInt); + maybeWriteCurrentValue(); + } + ImGui.SameLine(); + DrawValue(ref bValueV, ref bValueInt, width, true); + ImGui.SameLine(); + if (ImGui.Button("< B")) + { + bValue = false; + } + } + if (DrawExtraButtons != null) + { + DrawExtraButtons(this); + } + ImGui.SameLine(); + DrawReferenceValue("Original", oValueV, oValueInt); + } + + ImGui.PopID(); + } + } + + public class Parameter<T> : Parameter where T : unmanaged + { + public Parameter(string name, nint offset, int mask, ParameterType type, T? min, T? max, T? step, int flags) : base(name, offset, mask, type, flags) + { + if (typeof(T) == typeof(float)) + { + Assert(type == ParameterType.FLOAT || + type == ParameterType.VECTOR2 || + type == ParameterType.VECTOR3 || + type == ParameterType.VECTOR4 || + type == ParameterType.PATCH_FLOAT || + type == ParameterType.COLOR_FACTOR); + this.stepf = (step != null) ? Convert.ToSingle(step) : 0.1f; + this.minf = (min != null) ? Convert.ToSingle(min) : -Single.MaxValue; + this.maxf = (max != null) ? Convert.ToSingle(max) : Single.MaxValue; + } + else + { + this.step = (step != null) ? Convert.ToInt32(step) : 0; + if (typeof(T) == typeof(int)) + { + Assert(type == ParameterType.INT || + type == ParameterType.ALIGNED_INT || + type == ParameterType.HEX || + type == ParameterType.BITS || + type == ParameterType.COLOR || + type == ParameterType.SHADOW_RESOLUTION); + this.min = (min != null) ? Convert.ToInt32(min) : Int32.MinValue; + this.max = (max != null) ? Convert.ToInt32(max) : Int32.MaxValue; + } + else if (typeof(T) == typeof(byte)) + { + Assert(type == ParameterType.BYTE); + this.min = (min != null) ? Convert.ToInt32(min) : 0x0; + this.max = (max != null) ? Convert.ToInt32(max) : 0xFF; + } + else if (typeof(T) == typeof(bool)) + { + Assert(type == ParameterType.BOOL || type == ParameterType.FLAG); + this.min = 0; + this.max = 1; + } + else + { + Assert(); + } + } + } + } +} @@ -1,17 +1,8 @@ -//#define ENABLE_ASSERTS //#define OVERSIZED_SHADOW_MAP //#define SHADER_FEATURES //#define RESOURCE_ADJUSTMENT using System.Numerics; -using System.Globalization; -using System.Runtime.CompilerServices; -#if ENABLE_ASSERTS -using System.Diagnostics; -#endif -#if SHADER_FEATURES -using System.Runtime.InteropServices; -#endif using ImGuiNET; using SharpPluginLoader.Core; @@ -56,6 +47,9 @@ using SharpPluginLoader.Core.Resources; namespace WorldTuningTool { + using static Utility; + using static Parameter; + using static PFuncs; using static Config; public unsafe class Plugin : IPlugin @@ -63,138 +57,6 @@ namespace WorldTuningTool public string Name => "World Tuning Tool"; public string Author => "Akon City Software"; - private static bool loggedAssertFailed = false; - internal static void Assert(bool condition = false, [CallerLineNumber] int line = 0) - { - if (!condition && !loggedAssertFailed) - { - Log.Error($"Assert failed on line {line}, further asserts won't be logged."); - loggedAssertFailed = true; - } -#if ENABLE_ASSERTS - Trace.Assert(condition); -#endif - } - - private static byte ByteFlag(bool f) { return f ? (byte)0x1 : (byte)0x0; } - - internal static T Get<T>(nint addr, nint offset = 0x0) where T : unmanaged - { - return MemoryUtil.Read<T>(addr + offset); - } - - internal static T Get<T>(long addr, nint offset = 0x0) where T : unmanaged - { - return MemoryUtil.Read<T>((nint)addr + offset); - } - - internal static T *GetPtrInline<T>(nint addr, nint offset = 0x0) where T : unmanaged - { - return (T *)(addr + offset); - } - - internal static ref T GetRef<T>(nint addr, nint offset = 0x0) where T : unmanaged - { - return ref MemoryUtil.GetRef<T>(addr + offset); - } - - private static int BitIndex(int mask) - { - return BitOperations.Log2((uint)(mask & (-mask))); - } - - private static Vector4 ColorVectorFromInt(int c) - { - return new Vector4( - ((c ) & 0xFF) / 255.0f, - ((c >> 8 ) & 0xFF) / 255.0f, - ((c >> 16) & 0xFF) / 255.0f, - ((c >> 24) & 0xFF) / 255.0f); - } - - private static int ColorVectorToInt(Vector4 v) - { - return ((int)MathF.Round(v.X * 255.0f) | - ((int)MathF.Round(v.Y * 255.0f) << 8) | - ((int)MathF.Round(v.Z * 255.0f) << 16) | - ((int)MathF.Round(v.W * 255.0f) << 24)); - } - - private static float StringToSingle(string s) { return Convert.ToSingle(s, CultureInfo.InvariantCulture); } - private static bool StringToBoolean(string s) { return Convert.ToBoolean(s, CultureInfo.InvariantCulture); } - private static int StringToInt32(string s, int fromBase = 10) - { - if (fromBase == 10) return Convert.ToInt32(s, CultureInfo.InvariantCulture); - return Convert.ToInt32(s, fromBase); - } - private static string SingleToString(float f, string? format = null) { return f.ToString(format, CultureInfo.InvariantCulture); } - private static string BooleanToString(bool b) { return b.ToString(CultureInfo.InvariantCulture); } - private static string Int32ToString(int i, string? format = null) { return i.ToString(format, CultureInfo.InvariantCulture); } - - internal static Vector2 StringToVector2(string s, char delim = ',') - { - if (String.IsNullOrEmpty(s)) throw new FormatException(); - string[] sp = s.Split(delim).Select((v) => v.Trim()).ToArray(); - if (sp.Length != 2) throw new FormatException(); - return new Vector2(StringToSingle(sp[0]), StringToSingle(sp[1])); - } - - internal static Vector3 StringToVector3(string s, char delim = ',') - { - if (String.IsNullOrEmpty(s)) throw new FormatException(); - string[] sp = s.Split(delim).Select((v) => v.Trim()).ToArray(); - if (sp.Length != 3) throw new FormatException(); - return new Vector3(StringToSingle(sp[0]), StringToSingle(sp[1]), StringToSingle(sp[2])); - } - - internal static Vector4 StringToVector4(string s, char delim = ',') - { - if (String.IsNullOrEmpty(s)) throw new FormatException(); - string[] sp = s.Split(delim).Select((v) => v.Trim()).ToArray(); - if (sp.Length != 4) throw new FormatException(); - return new Vector4(StringToSingle(sp[0]), StringToSingle(sp[1]), StringToSingle(sp[2]), StringToSingle(sp[3])); - } - - private static string Vector2ToString(Vector2 v) - { - return string.Format(CultureInfo.InvariantCulture, "{0},{1}", v.X, v.Y); - } - - private static string Vector3ToString(Vector3 v) - { - return string.Format(CultureInfo.InvariantCulture, "{0},{1},{2}", v.X, v.Y, v.Z); - } - - private static string Vector4ToString(Vector4 v) - { - return string.Format(CultureInfo.InvariantCulture, "{0},{1},{2},{3}", v.X, v.Y, v.Z, v.W); - } - - public enum ParameterType - { - BOOL, - FLAG, - BYTE, - INT, - ALIGNED_INT, - HEX, - BITS, - COLOR, - COLOR_FACTOR, - FLOAT, - VECTOR3, - VECTOR2, - VECTOR4, - PATCH_FLOAT, - SHADOW_RESOLUTION - } - - private const float defaultParamWidth = 0.21f; - private const float fadeAlphaFactor = 0.7f; - private const float disableAlphaFactor = 0.6f; - - private static bool ignoreMinMax = false; - private static IPlugin? Instance = null; private static Config getConfig() @@ -314,1020 +176,134 @@ namespace WorldTuningTool return superseded; } - public static class ParameterFlags - { - public const int ViewOnly = 1; - } - - public abstract class Parameter + internal static Parameter? getParameterByName(string name) { - public string Name; - private string hiddenName; - public ParameterType Type; - - private nint offset; - private int mask; - private nint lastAddr = 0x0; - private bool overrideValue = false; - private bool overrideForStage = false; - private bool overrideWasOn = false; - - private int flags; - public bool ViewOnly => (flags & ParameterFlags.ViewOnly) == ParameterFlags.ViewOnly; - - protected float stepf, minf, maxf; - protected int step, min, max; - - private bool fallbackUpdate = true; - private bool pendingWrite = false; - private Vector4 valueV = default; - private int valueInt = 0; - private Vector4 oValueV; // Original value. - private int oValueInt; - - private bool pendingUpdate = true; - private bool queuedOverride = false; - private bool queuedValue = false; - private Vector4 qValueV; // Queued value. - private int qValueInt; - - private bool bValue = false; - private Vector4 bValueV; // B value in A/B. - private int bValueInt; - - public Parameter(string name, nint offset, int mask, ParameterType type, int flags) - { - Name = name; - hiddenName = "##" + Name; - Type = type; - switch (Type) - { - case ParameterType.FLAG: - Assert(mask != 0); - break; - case ParameterType.PATCH_FLOAT: - valueV.X = Get<float>(offset); - pendingUpdate = false; - break; - case ParameterType.SHADOW_RESOLUTION: - valueInt = 1; - pendingUpdate = false; - break; - } - this.offset = offset; - this.mask = mask; - this.flags = flags; - } - - public bool PendingUpdate() - { - return pendingUpdate; - } - - public (Vector4, int) GetValue() - { - return (valueV, valueInt); - } - - public int GetMask() - { - return mask; - } - - public void Resolve() - { - Assert(!InTransition && pendingWrite); - pendingWrite = false; - maybeWriteCurrentValue(); - } - - private void maybeWriteCurrentValue() - { - if (InTransition) - { - if (!DirtyParams.Contains(this)) - { - pendingWrite = true; - DirtyParams.Add(this); - } - else - { - Assert(pendingWrite); - } - return; - } - Assert(!pendingWrite); - switch (Type) - { - case ParameterType.PATCH_FLOAT: - new Patch(offset, BitConverter.GetBytes(valueV.X)).Enable(); - break; - case ParameterType.SHADOW_RESOLUTION: - if (valueInt > 1) - { - shadowResEnable(valueInt); - } - else - { - shadowResDisable(); - } - break; - } - if (lastAddr == 0x0) - { - return; - } - switch (Type) - { - case ParameterType.BOOL: - case ParameterType.BYTE: - GetRef<byte>(lastAddr, offset) = (byte)valueInt; - break; - case ParameterType.FLAG: - { - ref int i1 = ref GetRef<int>(lastAddr, offset); - if (valueInt != 0) - { - i1 |= mask; - } - else - { - i1 &= ~mask; - } - break; - } - case ParameterType.INT: - case ParameterType.ALIGNED_INT: - case ParameterType.HEX: - case ParameterType.BITS: - case ParameterType.COLOR: - { - ref int i1 = ref GetRef<int>(lastAddr, offset); - if (mask != 0) - { - i1 = (i1 & ~mask) | ((valueInt << BitIndex(mask)) & mask); - } - else - { - i1 = valueInt; - } - break; - } - case ParameterType.FLOAT: - GetRef<float>(lastAddr, offset) = valueV.X; - break; - case ParameterType.VECTOR2: - GetRef<Vector2>(lastAddr, offset) = new Vector2(valueV.X, valueV.Y); - break; - case ParameterType.VECTOR3: - case ParameterType.COLOR_FACTOR: - GetRef<Vector3>(lastAddr, offset) = new Vector3(valueV.X, valueV.Y, valueV.Z); - break; - case ParameterType.VECTOR4: - GetRef<Vector4>(lastAddr, offset) = valueV; - break; - } - } - - public void ForceOverrideOn() - { - if (!queuedOverride && !overrideValue) - { - OverrideOn(false); - } - } - - public void ForceOverrideOff() - { - if (queuedOverride || overrideValue) - { - OverrideOff(); - } - } - - public void OverrideOn(bool forStage) - { - if (pendingUpdate) - { - if (queuedOverride) - { - Assert(!overrideWasOn && forStage); - overrideWasOn = true; - } - else - { - queuedOverride = true; - } - } - else - { - if (overrideValue) - { - Assert(!overrideWasOn && forStage); - overrideWasOn = true; - } - else - { - oValueV = valueV; - oValueInt = valueInt; - maybeWriteCurrentValue(); - overrideValue = true; - } - } - overrideForStage = forStage; - } - - public void OverrideOff() - { - if (queuedOverride) - { - if (overrideWasOn) - { - Assert(overrideForStage); - overrideWasOn = false; - } - else - { - queuedOverride = false; - } - } - else - { - Assert(overrideValue); - if (overrideWasOn) - { - Assert(overrideForStage); - overrideWasOn = false; - } - else - { - valueV = oValueV; - valueInt = oValueInt; - maybeWriteCurrentValue(); - bValue = false; - overrideValue = false; - } - } - overrideForStage = false; - } - - public void SetOverrideValue(Vector4 v4, int i1) + if (OldParameterNames.TryGetValue(name, out string? newName)) { - if (queuedOverride) - { - qValueV = v4; - qValueInt = i1; - queuedValue = true; - } - else - { - valueV = v4; - valueInt = i1; - maybeWriteCurrentValue(); - } + Log.Warn($"Paramter '{name}' renamed to '{newName}'."); + name = newName; } - - public void Update(nint baseObject, bool fromHook = false) + foreach (Parameter param in allParameters) { - lastAddr = baseObject; - - if (baseObject == 0x0) - { - pendingUpdate = true; - return; - } - - if (fromHook) - { - fallbackUpdate = false; - } - else if (!fallbackUpdate) - { - fallbackUpdate = true; - return; - } - - // Trying to track changes to the original value is a "best effort" - // approach because it can't work if the in-game value is updated - // to the same value as a current override. - switch (Type) - { - case ParameterType.BOOL: - case ParameterType.BYTE: - { - ref byte b1 = ref GetRef<byte>(baseObject, offset); - byte b = b1; - if (overrideValue) - { - if (b != (byte)valueInt) - { - oValueInt = b; - } - b1 = (byte)valueInt; - } - else - { - valueInt = b; - } - break; - } - case ParameterType.FLAG: - { - ref int i1 = ref GetRef<int>(baseObject, offset); - int b1 = ByteFlag((i1 & mask) == mask); - if (overrideValue) - { - if (b1 != valueInt) - { - oValueInt = b1; - } - if (valueInt != 0) - { - i1 |= mask; - } - else - { - i1 &= ~mask; - } - } - else - { - valueInt = b1; - } - break; - } - case ParameterType.INT: - case ParameterType.ALIGNED_INT: - case ParameterType.HEX: - case ParameterType.BITS: - case ParameterType.COLOR: - { - ref int i1 = ref GetRef<int>(baseObject, offset); - int m = i1; - if (mask != 0) - { - m = (m & mask) >> BitIndex(mask); - } - if (overrideValue) - { - if (m != valueInt) - { - oValueInt = m; - } - if (mask != 0) - { - i1 = (i1 & ~mask) | ((valueInt << BitIndex(mask)) & mask); - } - else - { - i1 = valueInt; - } - } - else - { - valueInt = m; - } - break; - } - case ParameterType.FLOAT: - { - ref float f1 = ref GetRef<float>(baseObject, offset); - float f = f1; - if (overrideValue) - { - if (f != valueV.X) - { - oValueV.X = f; - } - f1 = valueV.X; - } - else - { - valueV.X = f; - } - break; - } - case ParameterType.VECTOR2: - { - ref Vector2 v2 = ref GetRef<Vector2>(baseObject, offset); - Vector2 v = v2; - if (overrideValue) - { - Vector2 valueV2 = new Vector2(valueV.X, valueV.Y); - if (v != valueV2) - { - oValueV = new Vector4(v.X, v.Y, 0.0f, 0.0f); - } - v2 = valueV2; - } - else - { - valueV = new Vector4(v.X, v.Y, 0.0f, 0.0f); - } - break; - } - case ParameterType.VECTOR3: - case ParameterType.COLOR_FACTOR: - { - ref Vector3 v3 = ref GetRef<Vector3>(baseObject, offset); - Vector3 v = v3; - if (overrideValue) - { - Vector3 valueV3 = new Vector3(valueV.X, valueV.Y, valueV.Z); - if (v != valueV3) - { - oValueV = new Vector4(v.X, v.Y, v.Z, 0.0f); - } - v3 = valueV3; - } - else - { - valueV = new Vector4(v.X, v.Y, v.Z, 0.0f); - } - break; - } - case ParameterType.VECTOR4: - { - ref Vector4 v4 = ref GetRef<Vector4>(baseObject, offset); - Vector4 v = v4; - if (overrideValue) - { - if (v != valueV) - { - oValueV = v; - } - v4 = valueV; - } - else - { - valueV = v; - } - break; - } - } - - if (pendingUpdate) + if (param.Name == name) { - pendingUpdate = false; - if (queuedOverride) - { - Assert(!overrideValue); - OverrideOn(overrideForStage); - if (queuedValue) - { - valueV = qValueV; - valueInt = qValueInt; - maybeWriteCurrentValue(); - queuedValue = false; - } - queuedOverride = false; - } + return param; } } + return null; + } - public void AdjustValue(ref Vector4 v4, ref int i1) - { - if (Type == ParameterType.SHADOW_RESOLUTION) // Hard limit. - { -#if OVERSIZED_SHADOW_MAP - i1 = Math.Clamp(i1, 1, 6); -#else - i1 = Math.Clamp(i1, 1, 5); -#endif - } - else if (!ignoreMinMax) - { - switch (Type) - { - case ParameterType.BOOL: - case ParameterType.FLAG: - case ParameterType.BYTE: - case ParameterType.INT: - case ParameterType.HEX: - case ParameterType.BITS: - i1 = Math.Clamp(i1, min, max); - break; - case ParameterType.ALIGNED_INT: - i1 = Math.Clamp((i1 + (step - 1))&~(step - 1), min, max); - break; - case ParameterType.FLOAT: - case ParameterType.PATCH_FLOAT: - case ParameterType.VECTOR2: - case ParameterType.VECTOR3: - case ParameterType.COLOR_FACTOR: - case ParameterType.VECTOR4: - v4 = new Vector4( - Math.Clamp(v4.X, minf, maxf), - Math.Clamp(v4.Y, minf, maxf), - Math.Clamp(v4.Z, minf, maxf), - Math.Clamp(v4.W, minf, maxf)); - break; - } - } - } + internal static void drawStageOverrideTooltip(Parameter param) + { + Config config = getConfig(); + List<Override>? globalOverrides = config.Overrides[globalStage]; + List<Override>? stageOverrides = maybeGetStageOverrides(config); + ImGui.Text(setByLine(param, config, stageOverrides, globalOverrides, false, true)); + } - public bool DrawValue(ref Vector4 v4, ref int i1, float width, bool drawLabel) + internal static void drawExtraButtons(Parameter param) + { + Config config = getConfig(); + List<Override> selectedOverrides = config.Overrides[selectedStage]; + List<Override>? stageOverrides = maybeGetStageOverrides(config); + // Deciding when to set globalOverrides copies the logic in OnImGuiRender(). + // Selected: Set if selectedStage != globalStage. + // Set: Always set. + List<Override>? globalOverrides = null; + if (selectedSet != "") { - bool valueChanged = false; - string label = drawLabel ? Name : hiddenName; - float displayMinf = ignoreMinMax ? 0.0f : minf; - float displayMaxf = ignoreMinMax ? 0.0f : maxf; - switch (Type) + List<Override> setOverrides = config.Sets[selectedSet]; + if (!overridesContainsParam(setOverrides, param)) { - case ParameterType.BOOL: - case ParameterType.FLAG: - { - bool b = i1 != 0; - if (ImGui.Checkbox(label, ref b)) - { - i1 = ByteFlag(b); - valueChanged = true; - } - break; - } - case ParameterType.BYTE: - { - ImGuiInputTextFlags flags = ImGuiInputTextFlags.EnterReturnsTrue; - ImGui.SetNextItemWidth(width * defaultParamWidth); - if (ImGui.InputInt(label, ref i1, step, 0, flags)) - { - valueChanged = true; - } - break; - } - case ParameterType.INT: - case ParameterType.ALIGNED_INT: - case ParameterType.HEX: - case ParameterType.SHADOW_RESOLUTION: - { - ImGuiInputTextFlags flags = ImGuiInputTextFlags.EnterReturnsTrue; - if (Type == ParameterType.HEX) - { - flags |= ImGuiInputTextFlags.CharsHexadecimal; - } - ImGui.SetNextItemWidth(width * defaultParamWidth); - if (ImGui.InputInt(label, ref i1, step, 0, flags)) - { - valueChanged = true; - } - break; - } - case ParameterType.BITS: - { - for (int i = 0; i < 32; i++) - { - int bit = (1 << i); - if ((mask & bit) == bit) - { - bool set = (i1 & bit) == bit; - if (ImGui.Checkbox($"##Bit{i}", ref set)) - { - if (set) - { - i1 |= bit; - } - else - { - i1 &= ~bit; - } - valueChanged = true; - } - ImGui.SameLine(); - } - } - ImGui.Text(label); - break; - } - case ParameterType.COLOR: - { - Vector4 colorV = ColorVectorFromInt(i1); - ImGui.SetNextItemWidth(width * defaultParamWidth * 3.0f); - if (ImGui.ColorEdit4(label, ref colorV)) - { - i1 = ColorVectorToInt(colorV); - valueChanged = true; - } - break; - } - case ParameterType.FLOAT: - case ParameterType.PATCH_FLOAT: - { - float f1 = v4.X; - ImGui.SetNextItemWidth(width * defaultParamWidth); - if (ImGui.DragFloat(label, ref f1, stepf, displayMinf, displayMaxf, "%.6f", ImGuiSliderFlags.NoRoundToFormat)) - { - v4 = new Vector4(f1, 0.0f, 0.0f, 0.0f); - valueChanged = true; - } - break; - } - case ParameterType.VECTOR2: - { - Vector2 v2 = new Vector2(v4.X, v4.Y); - ImGui.SetNextItemWidth(width * defaultParamWidth * 2.0f); - if (ImGui.DragFloat2(label, ref v2, stepf, displayMinf, displayMaxf, "%.5f", ImGuiSliderFlags.NoRoundToFormat)) - { - v4 = new Vector4(v2.X, v2.Y, 0.0f, 0.0f); - valueChanged = true; - } - break; - } - case ParameterType.VECTOR3: - case ParameterType.COLOR_FACTOR: + ImGui.SameLine(); + if (ImGui.Button("▲")) { - Vector3 v3 = new Vector3(v4.X, v4.Y, v4.Z); - if (Type == ParameterType.COLOR_FACTOR) + // Create the new override before unsetting any lower priority overrides. + // Effectively, copy the current override value. + (Vector4 v4, int i1) = param.GetValue(); + Override ov = new Override(param, v4, i1); + globalOverrides = config.Overrides[globalStage]; + bool superseded = overridesContainsParam(externalOverrides.Values, param); + if (!superseded) { - Vector3 v3Color = v3; - // Only scale the color vector if max is unbound. Otherwise, assume that intensity and color are seperate. - if (maxf == Single.MaxValue) + BeginTransition(); + if (!(stageOverrides != null && unsetIfOverridesContainsParam(stageOverrides, param))) { - float v3Max = MathF.Max(Math.Abs(v3.X), MathF.Max(Math.Abs(v3.Y), Math.Abs(v3.Z))); - if (v3Max != 0.0f) - { - // I believe a compromise is needed here. These are the options I can think of. - //v3Color /= v3Max; // Preview will be full "brightness" at low intensities. - v3Color = v3Color * (v3Color / v3Max); // Preview will converge towards white at high intensities. - } + unsetIfOverridesContainsParam(globalOverrides, param); } - ImGui.ColorEdit3(label, ref v3Color, ImGuiColorEditFlags.NoPicker | ImGuiColorEditFlags.NoLabel | ImGuiColorEditFlags.NoInputs); - ImGui.SameLine(); - } - ImGui.SetNextItemWidth(width * defaultParamWidth * 3.0f); - if (ImGui.DragFloat3(label, ref v3, stepf, displayMinf, displayMaxf, "%.5f", ImGuiSliderFlags.NoRoundToFormat)) - { - v4 = new Vector4(v3.X, v3.Y, v3.Z, 0.0f); - valueChanged = true; + ov.Set(); + EndTransition(); } - break; + setOverrides.Add(ov); + configState |= SetNotSaved; } - case ParameterType.VECTOR4: + if (ImGui.BeginItemTooltip()) { - ImGui.SetNextItemWidth(width * defaultParamWidth * 4.0f); - if (ImGui.DragFloat4(label, ref v4, stepf, displayMinf, displayMaxf, "%.5f", ImGuiSliderFlags.NoRoundToFormat)) - { - valueChanged = true; - } - break; + ImGui.Text($"Add to '{selectedSet}'"); + ImGui.EndTooltip(); } } - if (valueChanged) - { - AdjustValue(ref v4, ref i1); - } - return valueChanged; - } - - public void DrawReferenceValue(string name, Vector4 v4, int i1) - { - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, ImGui.GetStyle().Alpha * fadeAlphaFactor); - switch (Type) - { - case ParameterType.BOOL: - case ParameterType.FLAG: - ImGui.Text($"({name}: {i1 != 0})"); - break; - case ParameterType.BYTE: - case ParameterType.INT: - case ParameterType.ALIGNED_INT: - case ParameterType.SHADOW_RESOLUTION: - ImGui.Text($"({name}: {i1})"); - break; - case ParameterType.HEX: - case ParameterType.BITS: - case ParameterType.COLOR: - ImGui.Text($"({name}: {i1:X})"); - break; - case ParameterType.FLOAT: - case ParameterType.PATCH_FLOAT: - ImGui.Text($"({name}: {v4.X:0.000000})"); - break; - case ParameterType.VECTOR2: - ImGui.Text($"({name}: {v4.X:0.00000}, {v4.Y:0.00000})"); - break; - case ParameterType.VECTOR3: - case ParameterType.COLOR_FACTOR: - ImGui.Text($"({name}: {v4.X:0.00000}, {v4.Y:0.00000}, {v4.Z:0.00000})"); - break; - case ParameterType.VECTOR4: - ImGui.Text($"({name}: {v4.X:0.00000}, {v4.Y:0.00000}, {v4.Z:0.00000}, {v4.W:0.00000})"); - break; - } - ImGui.PopStyleVar(); } - - public string SetByLine(Config config, List<Override>? stageOverrides, List<Override>? globalOverrides, bool forSet, bool allowGlobal) + else if (!overridesContainsParam(selectedOverrides, param)) { - // Reverse order of selectedOverrideSuperseded(). - if (overridesContainsParam(externalOverrides.Values, this)) - { - return "Set by 'External'"; - } - Assert(!forSet); - if (selectedSet != "" && overridesContainsParam(config.Sets[selectedSet], this)) - { - return $"Set by '{selectedSet}'"; - } - if ((allowGlobal || globalOverrides == null) && stageOverrides != null && overridesContainsParam(stageOverrides, this)) - { - return $"Set by '{StageToString(currentStage)}'"; - } - if (allowGlobal) - { - return "Set by \'Global\'"; - } - // Superseded because inactive. - Assert(globalOverrides != null && selectedStage != currentStage); - return ""; - } - - public void Draw(float width, bool assumeOverride = false) - { - Draw(ref valueV, ref valueInt, width, assumeOverride); - } - - public void Draw(ref Vector4 v4, ref int i1, float width, bool assumeOverride) - { - ImGui.PushID(Name); - - if (ViewOnly) - { - ImGui.SetCursorPos(ImGui.GetCursorPos() + new Vector2(ImGui.GetFrameHeight() + ImGui.GetStyle().ItemSpacing.X, 0.0f)); - } - else if (!assumeOverride) + ImGui.SameLine(); + if (ImGui.Button("▲")) { - if (overrideForStage) + (Vector4 v4, int i1) = param.GetValue(); + Override ov = new Override(param, v4, i1); + if (selectedStage != globalStage) { - ImGui.PushItemFlag(ImGuiItemFlags.Disabled, true); - ImGui.PushItemFlag(ImGuiItemFlags.MixedValue, true); + globalOverrides = config.Overrides[globalStage]; } - // If OverrideOn() were to set queuedOverride instead of overrideValue, this parameter shouldn't be drawn. - Assert(!PendingUpdate()); - bool toggleOverride = overrideValue; - if (ImGui.Checkbox("##Override", ref toggleOverride)) + bool superseded = selectedOverrideSuperseded(config, globalOverrides, stageOverrides, param); + if (!superseded) { - if (toggleOverride) - { - OverrideOn(false); - } - else + BeginTransition(); + if (globalOverrides != null) { - OverrideOff(); + unsetIfOverridesContainsParam(globalOverrides, param); } + ov.Set(); + EndTransition(); } - if (ImGui.BeginItemTooltip()) + selectedOverrides.Add(ov); + if (selectedStage == globalStage) { - if (overrideForStage) - { - Config config = getConfig(); - List<Override>? globalOverrides = config.Overrides[globalStage]; - List<Override>? stageOverrides = maybeGetStageOverrides(config); - ImGui.Text(SetByLine(config, stageOverrides, globalOverrides, false, true)); - } - else if (overrideValue) - { - ImGui.Text("Set"); - } - else - { - ImGui.Text("Set override"); - } - ImGui.EndTooltip(); + rebuildConfig(config, selectedOverrides); } - if (overrideForStage) - { - ImGui.PopItemFlag(); - ImGui.PopItemFlag(); - } - ImGui.SameLine(); + configState |= SelectedNotSaved; } - - if (!overrideValue) - { - ImGui.PushItemFlag(ImGuiItemFlags.Disabled, true); - } - if (DrawValue(ref valueV, ref valueInt, width, !bValue)) - { - maybeWriteCurrentValue(); - } - if (!overrideValue) - { - ImGui.PopItemFlag(); - } - - bool showButtons = !assumeOverride && overrideValue && !pendingUpdate; - if (showButtons) + if (ImGui.BeginItemTooltip()) { - ImGui.SameLine(); - if (!bValue) - { - if (ImGui.Button("B")) - { - bValueV = oValueV; - bValueInt = oValueInt; - bValue = true; - } - } - else - { - if (ImGui.Button("A/B")) - { - (bValueV, valueV) = (valueV, bValueV); - (bValueInt, valueInt) = (valueInt, bValueInt); - maybeWriteCurrentValue(); - } - ImGui.SameLine(); - DrawValue(ref bValueV, ref bValueInt, width, true); - ImGui.SameLine(); - if (ImGui.Button("< B")) - { - bValue = false; - } - } - - Config config = getConfig(); - List<Override> selectedOverrides = config.Overrides[selectedStage]; - List<Override>? stageOverrides = maybeGetStageOverrides(config); - // Deciding when to set globalOverrides copies the logic in OnImGuiRender(). - // Selected: Set if selectedStage != globalStage. - // Set: Always set. - List<Override>? globalOverrides = null; - if (selectedSet != "") - { - List<Override> setOverrides = config.Sets[selectedSet]; - if (!overridesContainsParam(setOverrides, this)) - { - ImGui.SameLine(); - if (ImGui.Button("▲")) - { - // Create the new override before unsetting any lower priority overrides. - // Effectively, copy the current override value. - Override ov = new Override(this, v4, i1); - globalOverrides = config.Overrides[globalStage]; - bool superseded = overridesContainsParam(externalOverrides.Values, this); - if (!superseded) - { - BeginTransition(); - if (!(stageOverrides != null && unsetIfOverridesContainsParam(stageOverrides, this))) - { - unsetIfOverridesContainsParam(globalOverrides, this); - } - ov.Set(); - EndTransition(); - } - setOverrides.Add(ov); - configState |= SetNotSaved; - } - if (ImGui.BeginItemTooltip()) - { - ImGui.Text($"Add to '{selectedSet}'"); - ImGui.EndTooltip(); - } - } - } - else if (!overridesContainsParam(selectedOverrides, this)) - { - ImGui.SameLine(); - if (ImGui.Button("▲")) - { - Override ov = new Override(this, v4, i1); - if (selectedStage != globalStage) - { - globalOverrides = config.Overrides[globalStage]; - } - bool superseded = selectedOverrideSuperseded(config, globalOverrides, stageOverrides, this); - if (!superseded) - { - BeginTransition(); - if (globalOverrides != null) - { - unsetIfOverridesContainsParam(globalOverrides, this); - } - ov.Set(); - EndTransition(); - } - selectedOverrides.Add(ov); - if (selectedStage == globalStage) - { - rebuildConfig(config, selectedOverrides); - } - configState |= SelectedNotSaved; - } - if (ImGui.BeginItemTooltip()) - { - ImGui.Text($"Add to '{StageToString(selectedStage)}'"); - ImGui.EndTooltip(); - } - } - - ImGui.SameLine(); - DrawReferenceValue("Original", oValueV, oValueInt); + ImGui.Text($"Add to '{StageToString(selectedStage)}'"); + ImGui.EndTooltip(); } - - ImGui.PopID(); } } - public class Parameter<T> : Parameter where T : unmanaged + private static string setByLine(Parameter param, Config config, List<Override>? stageOverrides, List<Override>? globalOverrides, bool forSet, bool allowGlobal) { - public Parameter(string name, nint offset, int mask, ParameterType type, T? min, T? max, T? step, int flags) : base(name, offset, mask, type, flags) + // Reverse order of selectedOverrideSuperseded(). + if (overridesContainsParam(externalOverrides.Values, param)) { - if (typeof(T) == typeof(float)) - { - Assert(type == ParameterType.FLOAT || - type == ParameterType.VECTOR2 || - type == ParameterType.VECTOR3 || - type == ParameterType.VECTOR4 || - type == ParameterType.PATCH_FLOAT || - type == ParameterType.COLOR_FACTOR); - this.stepf = (step != null) ? Convert.ToSingle(step) : 0.1f; - this.minf = (min != null) ? Convert.ToSingle(min) : -Single.MaxValue; - this.maxf = (max != null) ? Convert.ToSingle(max) : Single.MaxValue; - } - else - { - this.step = (step != null) ? Convert.ToInt32(step) : 0; - if (typeof(T) == typeof(int)) - { - Assert(type == ParameterType.INT || - type == ParameterType.ALIGNED_INT || - type == ParameterType.HEX || - type == ParameterType.BITS || - type == ParameterType.COLOR || - type == ParameterType.SHADOW_RESOLUTION); - this.min = (min != null) ? Convert.ToInt32(min) : Int32.MinValue; - this.max = (max != null) ? Convert.ToInt32(max) : Int32.MaxValue; - } - else if (typeof(T) == typeof(byte)) - { - Assert(type == ParameterType.BYTE); - this.min = (min != null) ? Convert.ToInt32(min) : 0x0; - this.max = (max != null) ? Convert.ToInt32(max) : 0xFF; - } - else if (typeof(T) == typeof(bool)) - { - Assert(type == ParameterType.BOOL || - type == ParameterType.FLAG); - this.min = 0; - this.max = 1; - } - else - { - Assert(); - } - } + return "Set by 'External'"; } - } - - public static (float?, float?, float?) pStep(float step) { return (null, null, step); } - public static (float?, float?, float?) pMin(float min) { return (min, null, null); } - public static (float?, float?, float?) pMax(float max) { return (null, max, null); } - public static (float?, float?, float?) pMinStep(float min, float step) { return (min, null, step); } - public static (float?, float?, float?) pMaxStep(float max, float step) { return (null, max, step); } - public static (float?, float?, float?) pMinMaxStep(float min, float max, float step) { return (min, max, step); } - public static (int?, int?, int?) pStep(int step) { return (null, null, step); } - public static (int?, int?, int?) pMin(int min) { return (min, null, null); } - public static (int?, int?, int?) pMax(int max) { return (null, max, null); } - public static (int?, int?, int?) pMinStep(int min, int step) { return (min, null, step); } - public static (int?, int?, int?) pMaxStep(int max, int step) { return (null, max, step); } - public static (int?, int?, int?) pMinMaxStep(int min, int max, int step) { return (min, max, step); } - public static (byte?, byte?, byte?) pStep(byte step) { return (null, null, step); } - - public static Parameter<T> P<T>(string name, nint offset, ParameterType type, (T? min, T? max, T? step) m = default) where T : unmanaged - { - return new Parameter<T>(name, offset, 0, type, m.min, m.max, m.step, 0); - } - - public static Parameter<T> P<T>(string name, long offset, ParameterType type, (T? min, T? max, T? step) m = default) where T : unmanaged - { - return new Parameter<T>(name, (nint)offset, 0, type, m.min, m.max, m.step, 0); - } - - public static Parameter<T> V<T>(string name, nint offset, ParameterType type, (T? min, T? max, T? step) m = default) where T : unmanaged - { - return new Parameter<T>(name, offset, 0, type, m.min, m.max, m.step, ParameterFlags.ViewOnly); - } - - public static Parameter<T> M<T>(string name, nint offset, ParameterType type, int mask, (T? min, T? max, T? step) m = default) where T : unmanaged - { - return new Parameter<T>(name, offset, mask, type, m.min, m.max, m.step, 0); - } - - internal static Parameter? getParameterByName(string name) - { - if (OldParameterNames.TryGetValue(name, out string? newName)) + Assert(!forSet); + if (selectedSet != "" && overridesContainsParam(config.Sets[selectedSet], param)) { - Log.Warn($"Paramter '{name}' renamed to '{newName}'."); - name = newName; + return $"Set by '{selectedSet}'"; } - foreach (Parameter param in allParameters) + if ((allowGlobal || globalOverrides == null) && stageOverrides != null && overridesContainsParam(stageOverrides, param)) { - if (param.Name == name) - { - return param; - } + return $"Set by '{StageToString(currentStage)}'"; } - return null; + if (allowGlobal) + { + return "Set by \'Global\'"; + } + // Superseded because inactive. + Assert(globalOverrides != null && selectedStage != currentStage); + return ""; } public class Override @@ -1629,7 +605,7 @@ namespace WorldTuningTool if (superseded) { // Global is the lowest priority so "Set by 'Global'" never applies here. - byLine = Param.SetByLine(config, stageOverrides, globalOverrides, forSet, false); + byLine = setByLine(Param, config, stageOverrides, globalOverrides, forSet, false); } Param.DrawReferenceValue((byLine == "") ? "Current" : byLine, v4, i1); hasStatus = true; @@ -1662,24 +638,6 @@ namespace WorldTuningTool private delegate void UpdateToneMapParams(nint stackOffset, nint toneMapObjectInternal); private Hook<UpdateToneMapParams>? updateToneMapParams; - private static bool InTransition = false; - private static List<Parameter> DirtyParams = new List<Parameter>(); - private static void BeginTransition() - { - Assert(!InTransition && DirtyParams.Count == 0); - InTransition = true; - } - private static void EndTransition() - { - Assert(InTransition); - InTransition = false; - foreach (Parameter param in DirtyParams) - { - param.Resolve(); - } - DirtyParams.Clear(); - } - private static MtObject? sMhScene = null; private static MtObject? sMhMain = null; private static MtObject? sMhRender = null; @@ -2387,6 +1345,18 @@ namespace WorldTuningTool setShadowQuality.Invoke(sMhScene!.Get<int>(0x5530) + 1); } + private static void handleShadowResParameter(int multiplier) + { + if (multiplier > 1) + { + shadowResEnable(multiplier); + } + else + { + shadowResDisable(); + } + } + #if SHADER_FEATURES private bool fullResSSLR = false; #endif @@ -2573,6 +1543,10 @@ namespace WorldTuningTool { Instance = this; + PFuncs.HandleShadowResolution = handleShadowResParameter; + PFuncs.DrawStageOverrideTooltip = drawStageOverrideTooltip; + PFuncs.DrawExtraButtons = drawExtraButtons; + // Assert no duplicate names. foreach (Parameter param in allParameters) { @@ -3685,8 +2659,7 @@ namespace WorldTuningTool private void HandleGlobalChange() { Config config = getConfig(); - - // Unset current Global overrides. + // Unset and drop current Global overrides. List<Override>? stageOverrides = maybeGetStageOverrides(config); List<Override> globalOverrides = config.Overrides[globalStage]; foreach (Override ovG in globalOverrides) @@ -3697,17 +2670,12 @@ namespace WorldTuningTool ovG.Unset(); } } - - // Drop current Global overrides. globalOverrides.Clear(); - // Rebuild from new selections. foreach (Override ovG in config.Globals[selectedGlobal]) { globalOverrides.Add(ovG); } - - // Set new Global overrides. foreach (Override ovG in globalOverrides) { bool superseded = selectedOverrideSuperseded(config, null, stageOverrides, ovG.Param); @@ -4847,7 +3815,7 @@ namespace WorldTuningTool if (ImGui.CollapsingHeader("DEBUG")) { - ImGui.Checkbox("Globally Ignore Parameter Min/Max", ref ignoreMinMax); + ImGui.Checkbox("Globally Ignore Parameter Min/Max", ref IgnoreMinMax); if (ImGui.BeginItemTooltip()) { ImGui.Text("Some min/max values are set to avoid crashes, so be warned."); diff --git a/Utility.cs b/Utility.cs new file mode 100644 index 0000000..e48ee52 --- /dev/null +++ b/Utility.cs @@ -0,0 +1,128 @@ +//#define ENABLE_ASSERTS + +using System.Numerics; +using System.Globalization; +using System.Runtime.CompilerServices; +#if ENABLE_ASSERTS +using System.Diagnostics; +#endif + +using SharpPluginLoader.Core; +using SharpPluginLoader.Core.Memory; + +namespace WorldTuningTool +{ + internal static class Utility + { + internal const float defaultParamWidth = 0.21f; + internal const float fadeAlphaFactor = 0.7f; + internal const float disableAlphaFactor = 0.6f; + + private static bool loggedAssertFailed = false; + internal static void Assert(bool condition = false, [CallerLineNumber] int line = 0) + { + if (!condition && !loggedAssertFailed) + { + Log.Error($"Assert failed on line {line}, further asserts won't be logged."); + loggedAssertFailed = true; + } +#if ENABLE_ASSERTS + Trace.Assert(condition); +#endif + } + + internal static byte ByteFlag(bool f) { return f ? (byte)0x1 : (byte)0x0; } + + internal static T Get<T>(nint addr, nint offset = 0x0) where T : unmanaged + { + return MemoryUtil.Read<T>(addr + offset); + } + + internal static T Get<T>(long addr, nint offset = 0x0) where T : unmanaged + { + return MemoryUtil.Read<T>((nint)addr + offset); + } + + internal static unsafe T *GetPtrInline<T>(nint addr, nint offset = 0x0) where T : unmanaged + { + return (T *)(addr + offset); + } + + internal static ref T GetRef<T>(nint addr, nint offset = 0x0) where T : unmanaged + { + return ref MemoryUtil.GetRef<T>(addr + offset); + } + + internal static int BitIndex(int mask) + { + return BitOperations.Log2((uint)(mask & (-mask))); + } + + internal static Vector4 ColorVectorFromInt(int c) + { + return new Vector4( + ((c ) & 0xFF) / 255.0f, + ((c >> 8 ) & 0xFF) / 255.0f, + ((c >> 16) & 0xFF) / 255.0f, + ((c >> 24) & 0xFF) / 255.0f); + } + + internal static int ColorVectorToInt(Vector4 v) + { + return ((int)MathF.Round(v.X * 255.0f) | + ((int)MathF.Round(v.Y * 255.0f) << 8) | + ((int)MathF.Round(v.Z * 255.0f) << 16) | + ((int)MathF.Round(v.W * 255.0f) << 24)); + } + + internal static float StringToSingle(string s) { return Convert.ToSingle(s, CultureInfo.InvariantCulture); } + internal static bool StringToBoolean(string s) { return Convert.ToBoolean(s, CultureInfo.InvariantCulture); } + internal static int StringToInt32(string s, int fromBase = 10) + { + if (fromBase == 10) return Convert.ToInt32(s, CultureInfo.InvariantCulture); + return Convert.ToInt32(s, fromBase); + } + internal static string SingleToString(float f, string? format = null) { return f.ToString(format, CultureInfo.InvariantCulture); } + internal static string BooleanToString(bool b) { return b.ToString(CultureInfo.InvariantCulture); } + internal static string Int32ToString(int i, string? format = null) { return i.ToString(format, CultureInfo.InvariantCulture); } + + internal static Vector2 StringToVector2(string s, char delim = ',') + { + if (String.IsNullOrEmpty(s)) throw new FormatException(); + string[] sp = s.Split(delim).Select((v) => v.Trim()).ToArray(); + if (sp.Length != 2) throw new FormatException(); + return new(StringToSingle(sp[0]), StringToSingle(sp[1])); + } + + internal static Vector3 StringToVector3(string s, char delim = ',') + { + if (String.IsNullOrEmpty(s)) throw new FormatException(); + string[] sp = s.Split(delim).Select((v) => v.Trim()).ToArray(); + if (sp.Length != 3) throw new FormatException(); + return new(StringToSingle(sp[0]), StringToSingle(sp[1]), StringToSingle(sp[2])); + } + + internal static Vector4 StringToVector4(string s, char delim = ',') + { + if (String.IsNullOrEmpty(s)) throw new FormatException(); + string[] sp = s.Split(delim).Select((v) => v.Trim()).ToArray(); + if (sp.Length != 4) throw new FormatException(); + return new(StringToSingle(sp[0]), StringToSingle(sp[1]), StringToSingle(sp[2]), StringToSingle(sp[3])); + } + + internal static string Vector2ToString(Vector2 v) + { + return string.Format(CultureInfo.InvariantCulture, "{0},{1}", v.X, v.Y); + } + + internal static string Vector3ToString(Vector3 v) + { + return string.Format(CultureInfo.InvariantCulture, "{0},{1},{2}", v.X, v.Y, v.Z); + } + + internal static string Vector4ToString(Vector4 v) + { + return string.Format(CultureInfo.InvariantCulture, "{0},{1},{2},{3}", v.X, v.Y, v.Z, v.W); + } + } +} |