diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31bf9aa..23f50c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,7 @@ jobs: - name: Call 7D2D compiler action uses: OCB7D2D/OcbModCompiler@master with: + v7d2d: 'V1.0' name: "OcbInventoryMouseWheel" version: "${{ github.ref_name }}" token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/Harmony/ModXmlPatcher.cs b/Harmony/ModXmlPatcher.cs index 390a508..13d82de 100644 --- a/Harmony/ModXmlPatcher.cs +++ b/Harmony/ModXmlPatcher.cs @@ -74,7 +74,7 @@ private static bool EvaluateConditions(string conditions, XmlFile xml) foreach (string xpath in conditions.Split(',')) { bool negate = false; - List xmlNodeList; + List xmlNodeList; if (xpath.StartsWith("!")) { negate = true; @@ -140,13 +140,59 @@ private static bool EvaluateConditions(string conditions, XmlFile xml) return true; } + // Static dictionary for templating + static Dictionary dict = + new Dictionary(); + + // Helper class to keep correct template dict scoping + // Add new keys when initialized, restores old on close + private class DictScopeHelper : IDisposable + { + Dictionary prev = null; + public DictScopeHelper(XElement element) => + dict = GetTemplateValues(prev = dict, element); + public void Dispose() => dict = prev; + } + + // Add placeholder keys to our template dictionary + private static Dictionary GetTemplateValues( + Dictionary parent, XElement child) + { + // Make a copy of the parent dictionary + Dictionary dict = + new Dictionary(parent); + // Add new template values to dictionary + foreach (var attr in child.Attributes()) + { + if (!attr.Name.LocalName.StartsWith("tmpl-")) continue; + dict[attr.Name.LocalName.Substring(5)] = attr.Value; + } + // Return new dict + return dict; + } + + // Replace all placeholder the dictionary contains + private static void ReplaceTemplateOccurences( + Dictionary dict, ref string text) + { + foreach (var kv in dict) + text = text.Replace( + "{{" + kv.Key + "}}", + kv.Value); + } + // We need to call into the private function to proceed with XML patching private static readonly MethodInfo MethodSinglePatch = AccessTools.Method(typeof(XmlPatcher), "singlePatch"); // Function to load another XML file and basically call the same PatchXML function again - private static bool IncludeAnotherDocument(XmlFile target, XmlFile parent, XElement element, string modName) + private static bool IncludeAnotherDocument(XmlFile target, XmlFile parent, XElement element, Mod mod) { bool result = true; + + var modName = mod.Name; + + // Add template values to dictionary + using (var tmpls = new DictScopeHelper(element)) foreach (XAttribute attr in element.Attributes()) { // Skip unknown attributes @@ -160,6 +206,7 @@ private static bool IncludeAnotherDocument(XmlFile target, XmlFile parent, XElem { string _text = File.ReadAllText(path, Encoding.UTF8) .Replace("@modfolder:", "@modfolder(" + modName + "):"); + ReplaceTemplateOccurences(dict, ref _text); XmlFile _patchXml; try { @@ -176,7 +223,7 @@ private static bool IncludeAnotherDocument(XmlFile target, XmlFile parent, XElem continue; } result &= XmlPatcher.PatchXml( - target, _patchXml, modName); + target, element, _patchXml, mod); } catch (Exception ex) { @@ -198,7 +245,8 @@ private static bool IncludeAnotherDocument(XmlFile target, XmlFile parent, XElem static int count = 0; - public static bool PatchXml(XmlFile xmlFile, XmlFile patchXml, XElement node, string patchName) + public static bool PatchXml(XmlFile xmlFile, + XmlFile patchXml, XElement node, Mod mod) { bool result = true; count++; @@ -206,35 +254,31 @@ public static bool PatchXml(XmlFile xmlFile, XmlFile patchXml, XElement node, st stack.count = count; foreach (XElement child in node.Elements()) { - if (child.NodeType == XmlNodeType.Element) + // Patched to support includes + if (child.Name == "modinc") { - if (!(child is XElement element)) continue; - // Patched to support includes - if (child.Name == "include") - { - // Will do the magic by calling our functions again - IncludeAnotherDocument(xmlFile, patchXml, element, patchName); - } - else if (child.Name == "echo") - { - foreach (XAttribute attr in child.Attributes()) - { - if (attr.Name == "log") Log.Out("{1}: {0}", attr.Value, xmlFile.Filename); - if (attr.Name == "warn") Log.Warning("{1}: {0}", attr.Value, xmlFile.Filename); - if (attr.Name == "error") Log.Error("{1}: {0}", attr.Value, xmlFile.Filename); - if (attr.Name != "log" && attr.Name != "warn" && attr.Name != "error") - Log.Warning("Echo has no valid name (log, warn or error)"); - } - } - // Otherwise try to apply the patches found in child element - else if (!ApplyPatchEntry(xmlFile, patchXml, element, patchName, ref stack)) + // Will do the magic by calling our functions again + IncludeAnotherDocument(xmlFile, patchXml, child, mod); + } + else if (child.Name == "echo") + { + foreach (XAttribute attr in child.Attributes()) { - IXmlLineInfo lineInfo = (IXmlLineInfo)element; - Log.Warning(string.Format("XML patch for \"{0}\" from mod \"{1}\" did not apply: {2} (line {3} at pos {4})", - xmlFile.Filename, patchName, element.ToString(), lineInfo.LineNumber, lineInfo.LinePosition)); - result = false; + if (attr.Name == "log") Log.Out("{1}: {0}", attr.Value, xmlFile.Filename); + if (attr.Name == "warn") Log.Warning("{1}: {0}", attr.Value, xmlFile.Filename); + if (attr.Name == "error") Log.Error("{1}: {0}", attr.Value, xmlFile.Filename); + if (attr.Name != "log" && attr.Name != "warn" && attr.Name != "error") + Log.Warning("Echo has no valid name (log, warn or error)"); } } + // Otherwise try to apply the patches found in child element + else if (!ApplyPatchEntry(xmlFile, child, patchXml, mod, ref stack)) + { + IXmlLineInfo lineInfo = child; + Log.Warning(string.Format("XML patch for \"{0}\" from mod \"{1}\" did not apply: {2} (line {3} at pos {4})", + xmlFile.Filename, mod.Name, child.ToString(), lineInfo.LineNumber, lineInfo.LinePosition)); + result = false; + } } return result; } @@ -249,18 +293,19 @@ public struct ParserStack // Entry point instead of (private) `XmlPatcher.singlePatch` // Implements conditional patching and also allows includes - private static bool ApplyPatchEntry(XmlFile _xmlFile, XmlFile _patchXml, XElement _patchElement, string _patchName, ref ParserStack stack) + private static bool ApplyPatchEntry(XmlFile _targetFile, XElement _patchElement, + XmlFile _patchFile, Mod _patchingMod, ref ParserStack stack) { // Only support root level switch (_patchElement.Name.ToString()) { - case "include": + case "modinc": // Call out to our include handler - return IncludeAnotherDocument(_xmlFile, _patchXml, - _patchElement, _patchName); + return IncludeAnotherDocument(_targetFile, _patchFile, + _patchElement, _patchingMod); case "modif": @@ -278,11 +323,11 @@ private static bool ApplyPatchEntry(XmlFile _xmlFile, XmlFile _patchXml, XElemen continue; } // Evaluate one or'ed condition - if (EvaluateConditions(attr.Value, _xmlFile)) + if (EvaluateConditions(attr.Value, _targetFile)) { stack.PreviousResult = true; - return PatchXml(_xmlFile, _patchXml, - _patchElement, _patchName); + return PatchXml(_targetFile, _patchFile, + _patchElement, _patchingMod); } } @@ -311,11 +356,11 @@ private static bool ApplyPatchEntry(XmlFile _xmlFile, XmlFile _patchXml, XElemen continue; } // Evaluate one or'ed condition - if (EvaluateConditions(attr.Value, _xmlFile)) + if (EvaluateConditions(attr.Value, _targetFile)) { stack.PreviousResult = true; - return PatchXml(_xmlFile, _patchXml, - _patchElement, _patchName); + return PatchXml(_targetFile, _patchFile, + _patchElement, _patchingMod); } } @@ -328,8 +373,8 @@ private static bool ApplyPatchEntry(XmlFile _xmlFile, XmlFile _patchXml, XElemen stack.IfClauseParsed = false; // Abort else when last result was true if (stack.PreviousResult) return true; - return PatchXml(_xmlFile, _patchXml, - _patchElement, _patchName); + return PatchXml(_targetFile, _patchFile, + _patchElement, _patchingMod); default: // Reset flags first @@ -337,7 +382,7 @@ private static bool ApplyPatchEntry(XmlFile _xmlFile, XmlFile _patchXml, XElemen stack.PreviousResult = true; // Dispatch to original function return (bool)MethodSinglePatch.Invoke(null, - new object[] { _xmlFile, _patchElement, _patchName }); + new object[] { _targetFile, _patchElement, _patchFile, _patchingMod }); } } @@ -348,8 +393,9 @@ public class XmlPatcher_PatchXml { static bool Prefix( ref XmlFile _xmlFile, - ref XmlFile _patchXml, - ref string _patchName, + ref XmlFile _patchFile, + XElement _containerElement, + ref Mod _patchingMod, ref bool __result) { // According to Harmony docs, returning false on a prefix @@ -360,21 +406,21 @@ static bool Prefix( // Might also be something solved with latest versions, // as the game uses a rather old HarmonyX version (2.2). // To address this we simply "consume" one of the args. - if (_patchXml == null) return false; - XElement element = _patchXml.XmlDoc.Root; + if (_patchFile == null) return false; + XElement element = _patchFile.XmlDoc.Root; if (element == null) return false; string version = element.GetAttribute("patcher-version"); if (!string.IsNullOrEmpty(version)) { // Check if version is too new for us - if (int.Parse(version) > 4) return true; + if (int.Parse(version) > 5) return true; } // Call out to static helper function __result = PatchXml( - _xmlFile, _patchXml, - element, _patchName); + _xmlFile, _patchFile, + element, _patchingMod); // First one wins - _patchXml = null; + _patchFile = null; return false; } } diff --git a/Harmony/WheelItemStack.cs b/Harmony/WheelItemStack.cs index b5f4cae..1b6ce88 100644 --- a/Harmony/WheelItemStack.cs +++ b/Harmony/WheelItemStack.cs @@ -3,22 +3,6 @@ public static class WheelItemStack { - public static UIScrollView GetParentScrollView(XUiController ctr) - { - var parent = ctr.Parent; - while (parent != null) - { - var vp = parent.ViewComponent; - if (vp != null && vp.EventOnScroll) - { - var ui = vp.UiTransform?.GetComponent(); - if (ui != null) return ui; - } - parent = parent.Parent; - } - return null; - } - public static int TransferItems(int amount, ItemStack src, Action setSrc, ItemStack dst, Action setDst) diff --git a/Harmony/XUiC_WheelItemStack.cs b/Harmony/XUiC_WheelItemStack.cs index d4fa8ed..339258b 100644 --- a/Harmony/XUiC_WheelItemStack.cs +++ b/Harmony/XUiC_WheelItemStack.cs @@ -7,13 +7,9 @@ public class XUiC_WheelItemStack : XUiC_ItemStack protected bool IsOver = false; - protected UIScrollView ScrollView = null; - public override void Init() { base.Init(); - ScrollView = WheelItemStack - .GetParentScrollView(this); } private ItemStack DnDStack => xui.dragAndDrop.CurrentStack; @@ -43,22 +39,14 @@ protected virtual int TransferItems(int amount, return amount; } - protected override void OnHovered(bool _isOver) + public override void OnHovered(bool _isOver) { IsOver = _isOver; base.OnHovered(_isOver); } - protected override void OnScrolled(float _delta) + public override void OnScrolled(float _delta) { - // Check for edge case where we are actually inside another - // Scrollable View (enforce shift key in that situation) - if (ScrollView != null && !Input.GetKey(KeyCode.LeftShift)) - { - // Otherwise "bubble" event up - ScrollView.Scroll(_delta); - return; - } // Process the item transfer if (IsLocked || StackLock) { diff --git a/Harmony/XUiC_WheelRequiredItemStack.cs b/Harmony/XUiC_WheelRequiredItemStack.cs index f79d7ac..b712bbc 100644 --- a/Harmony/XUiC_WheelRequiredItemStack.cs +++ b/Harmony/XUiC_WheelRequiredItemStack.cs @@ -7,15 +7,6 @@ public class XUiC_WheelRequiredItemStack : XUiC_RequiredItemStack protected bool IsOver = false; - protected UIScrollView ScrollView = null; - - public override void Init() - { - base.Init(); - ScrollView = WheelItemStack - .GetParentScrollView(this); - } - private ItemStack DnDStack => xui.dragAndDrop.CurrentStack; private void SetSlot(ItemStack stack) => ItemStack = stack; @@ -43,22 +34,14 @@ protected virtual int TransferItems(int amount, return amount; } - protected override void OnHovered(bool _isOver) + public override void OnHovered(bool _isOver) { IsOver = _isOver; base.OnHovered(_isOver); } - protected override void OnScrolled(float _delta) + public override void OnScrolled(float _delta) { - // Check for edge case where we are actually inside another - // Scrollable View (enforce shift key in that situation) - if (ScrollView != null && !Input.GetKey(KeyCode.LeftShift)) - { - // Otherwise "bubble" event up - ScrollView.Scroll(_delta); - return; - } // Process the item transfer if (IsLocked || StackLock) { diff --git a/README.md b/README.md index 7564f9e..4fc3ea3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# OCB Inventory Mouse Wheel Mod - 7 Days to Die (A21) Addon +# OCB Inventory Mouse Wheel Mod - 7 Days to Die (V1.0 exp) Addon A small harmony mod to enable mouse scroll wheel to move inventory items. @@ -6,12 +6,15 @@ A small harmony mod to enable mouse scroll wheel to move inventory items. ## Download and Install -Simply [download here from GitHub][1] and put into your A20 Mods folder: - -- https://github.com/OCB7D2D/OcbInventoryMouseWheel/archive/master.zip (master branch) +[Download from GitHub releases][1] and extract into your Mods folder! +Ensure you don't have double nested folders and ModInfo.xml is at right place! ## Changelog +### Version 0.3.0 + +- First compatibility with V1.0 (exp) + ### Version 0.2.2 - Update for Darkness Falls A21 Edition @@ -42,10 +45,6 @@ Simply [download here from GitHub][1] and put into your A20 Mods folder: - Initial test version -## Compatibility - -Developed initially for A20(b42), updated through A21.2(b14). - [1]: https://github.com/OCB7D2D/OcbInventoryMouseWheel/releases [2]: https://github.com/OCB7D2D/OcbInventoryMouseWheel/actions/workflows/ci.yml [3]: https://github.com/OCB7D2D/OcbInventoryMouseWheel/actions/workflows/ci.yml/badge.svg