diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 15d6e97..1984106 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,10 +18,11 @@ jobs: steps: - name: Check out repository code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Call 7D2D compiler action uses: OCB7D2D/OcbModCompiler@master with: + v7d2d: 'V1.0' name: "OcbRemoteTurretControl" version: "${{ github.ref_name }}" token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/Harmony/OcbRemoteTurret.cs b/Harmony/OcbRemoteTurret.cs index 36843bf..35a5d1a 100644 --- a/Harmony/OcbRemoteTurret.cs +++ b/Harmony/OcbRemoteTurret.cs @@ -177,20 +177,20 @@ public static bool Prefix(XUiC_PowerRangedAmmoSlots __instance) } } - /*************************************************************************/ - // Below is a few advanced transpiler patches - // Inserting `UpgradeVariantHelper` into handler - /*************************************************************************/ + /*************************************************************************/ + // Below is a few advanced transpiler patches + // Inserting `UpgradeVariantHelper` into handler + /*************************************************************************/ - // Used by patched function below - static void UpgradeVariantHelper(ItemStack stack) + // Used by patched function below + private static void UpgradeVariantHelper(ItemStack stack) { // Check if we are dealing with a block if (stack.itemValue.type < Block.ItemsStartHere) { // Check if the block has `ReturnVariantHelper` set if (Block.list[stack.itemValue.type].Properties.Values - .TryGetString("ReturnVariantHelper", out string variant)) + .TryGetValue("ReturnVariantHelper", out string variant)) { // Upgrade `itemValue` to variant helper block type if (Block.GetBlockByName(variant) is Block helper) diff --git a/Library/BlockRemoteTurret.cs b/Library/BlockRemoteTurret.cs index 650abb5..382dd4b 100644 --- a/Library/BlockRemoteTurret.cs +++ b/Library/BlockRemoteTurret.cs @@ -35,9 +35,9 @@ public readonly static List public static TileEntityPowered LastPanelOpen = null; // public BlockRemoteTurret() => IsRandomlyTick = false; - private Dictionary> AmmoPerks + private readonly Dictionary> AmmoPerks = new Dictionary>(); - private Dictionary> PlayerPerks + private readonly Dictionary> PlayerPerks = new Dictionary>(); // Key-Mappings to switch turret cameras @@ -95,12 +95,12 @@ public override void Init() KeyMapPrev = KeyCode.A; KeyMapNext = KeyCode.D; Properties.ParseEnum("ScreenKeyMapPrev", ref KeyMapPrev); Properties.ParseEnum("ScreenKeyMapNext", ref KeyMapNext); - if (Properties.Values.TryGetString("AmmoPerks", out string ammos)) + if (Properties.Values.TryGetValue("AmmoPerks", out string ammos)) { // Note: we don't allow whitespace!? foreach (var ammo in ammos.Split(',')) { - if (Properties.Values.TryGetString(ammo + "Perk", out string skill)) + if (Properties.Values.TryGetValue(ammo + "Perk", out string skill)) { int minLevel = 0; Properties.ParseInt(ammo + "PerkLevel", ref minLevel); @@ -160,31 +160,33 @@ public static void AddEntityToEntries( public static TileEntityPowered PanelToOpen = null; public override bool OnBlockActivated( - string _commandName, + string _commandName, WorldBase world, int cIdx, Vector3i position, BlockValue _blockValue, - EntityAlive player) + EntityPlayerLocal player) { if (_commandName == "activate") { CurrentOpenBlock = null; CurrentOpenPanel = null; - RemoteTurretUtils.CollectTurrets(world, cIdx, + RemoteTurretUtils.CollectTurrets(world, position, ControlPanels, RemoteTurrets); var te = world.GetTileEntity(cIdx, position); if (!(te is TileEntityPowered tep)) { - GameManager.ShowTooltip(player as EntityPlayerLocal, - Localization.Get("ttNoTurretConnected"), string.Empty, "ui_denied"); + GameManager.ShowTooltip(player, + Localization.Get("ttNoTurretConnected"), + string.Empty, "ui_denied"); return false; } // Check if panel is powered and has turrets if (!tep.IsPowered || RemoteTurrets.Count == 0) { - GameManager.ShowTooltip(player as EntityPlayerLocal, - Localization.Get("ttNoTurretConnected"), string.Empty, "ui_denied"); + GameManager.ShowTooltip(player, + Localization.Get("ttNoTurretConnected"), + string.Empty, "ui_denied"); return false; } // Set `OnOpen` state @@ -237,7 +239,7 @@ public static bool OnPanelOpen(LocalPlayerUI ui) return true; } - private BlockActivationCommand[] cmds = new BlockActivationCommand[2] + private new readonly BlockActivationCommand[] cmds = new BlockActivationCommand[2] { new BlockActivationCommand("activate", "tool", true), new BlockActivationCommand("take", "hand", false) @@ -279,7 +281,7 @@ public override string GetActivationText( /****************************************************************************/ IEnumerator UpdateCoroutine = null; - Dictionary Loaded + readonly Dictionary Loaded = new Dictionary(); private IEnumerator UpdateNext() @@ -312,15 +314,15 @@ private int Vector3DistSquared(Vector3i v3i) } public override void OnBlockEntityTransformBeforeActivated(WorldBase _world, - Vector3i _blockPos, int _cIdx, BlockValue _blockValue, BlockEntityData _ebcd) + Vector3i _blockPos, BlockValue _blockValue, BlockEntityData _ebcd) { - base.OnBlockEntityTransformBeforeActivated(_world, _blockPos, _cIdx, _blockValue, _ebcd); + base.OnBlockEntityTransformBeforeActivated(_world, _blockPos, _blockValue, _ebcd); if (Loaded.Count == 0) { UpdateCoroutine = UpdateNext(); GameManager.Instance.StartCoroutine(UpdateCoroutine); } - Loaded.Add(_blockPos, new RemoteTurretPanel(_world, _cIdx, _blockPos, + Loaded.Add(_blockPos, new RemoteTurretPanel(_world, _blockPos, _ebcd.transform.Find("Screen"), this)); } diff --git a/Library/RemoteTurretControlPanel.cs b/Library/RemoteTurretControlPanel.cs index 5b04f76..21d6e34 100644 --- a/Library/RemoteTurretControlPanel.cs +++ b/Library/RemoteTurretControlPanel.cs @@ -14,9 +14,6 @@ public class RemoteTurretPanel : PoweredScreenPanel // Just a reference to our world private readonly WorldBase World; - // Never know what this does!? - private readonly int ClrIdx = 0; - // Position of the remote turret control panel private readonly Vector3i Position; @@ -96,12 +93,12 @@ private void SetRenderersActive(bool enabled) // Constructor - public RemoteTurretPanel(WorldBase world, int clrIdx, Vector3i pos, Transform monitor, BlockRemoteTurret block) + public RemoteTurretPanel(WorldBase world, Vector3i pos, Transform monitor, BlockRemoteTurret block) { Block = block; // Set as already as possible if (block == null) throw new Exception("No Block"); LastTick = GetRandomCamIntervalOffset(0.3f); - World = world; ClrIdx = clrIdx; Position = pos; + World = world; Position = pos; if (World == null) throw new Exception("No World"); Renderers = monitor?.GetComponentsInChildren(); if (MainRenderer == null) throw new Exception("No Monitor"); @@ -162,7 +159,7 @@ public void Tick(bool active) // Gather all connected turrets // Does so by following wires RemoteTurretUtils.CollectTurrets( - World, ClrIdx, Position, + World, Position, ControlPanels, RemoteTurrets); // Reset view index CurrentCam = 0; diff --git a/Library/RemoteTurretUtils.cs b/Library/RemoteTurretUtils.cs index a3bf1f3..acea020 100644 --- a/Library/RemoteTurretUtils.cs +++ b/Library/RemoteTurretUtils.cs @@ -23,7 +23,6 @@ private static List PoweredChildren(TileEntityPowered te) public static void CollectTurrets( WorldBase world, - int cIdx, Vector3i blockPos, List ControlPanels, List RemoteTurrets, @@ -37,7 +36,7 @@ public static void CollectTurrets( var queued = queue.Dequeue(); var position = queued.Item1; var depth = queued.Item2; - if (world.GetTileEntity(cIdx, position) is TileEntityPowered tep) + if (world.GetTileEntity(position) is TileEntityPowered tep) { // Skip other remote turret blocks (only collect local turrets) if (tep.blockValue.Block is BlockRemoteTurret) diff --git a/Library/XUI_PowerRemoteTurretControlPanel.cs b/Library/XUI_PowerRemoteTurretControlPanel.cs index 3dea19b..ac96587 100644 --- a/Library/XUI_PowerRemoteTurretControlPanel.cs +++ b/Library/XUI_PowerRemoteTurretControlPanel.cs @@ -27,7 +27,7 @@ public class XUI_PowerRemoteTurretPanel : XUiC_PowerRangedTrapWindowGroup public TileEntityPowered LastPanel => BlockRemoteTurret.LastPanelOpen; // Reference to the camera preview window - private XUiC_CameraWindow cameraWindowPreview; + // private XUiC_CameraWindow cameraWindowPreview; // Current slot we are viewing // We always have a lock on it @@ -36,13 +36,6 @@ public class XUI_PowerRemoteTurretPanel : XUiC_PowerRangedTrapWindowGroup private int LastSlotLocked = -1; private bool RequestPending = false; - public override void Init() - { - base.Init(); - var preview = GetChildById("windowPowerCameraControlPreview"); - cameraWindowPreview = preview as XUiC_CameraWindow; - } - // Coroutine to close instantly // Needed to give a little timeout private IEnumerator CloseLater() @@ -185,7 +178,7 @@ private void Destroy() ControlPanels.Clear(); } - private void TileEntityDestroyed(TileEntity te) + private void TileEntityDestroyed(ITileEntity te) { Destroy(); } @@ -300,9 +293,9 @@ public void SetCurrentTurretSlot(int slot) static readonly FieldInfo FieldLockedTileEntities = AccessTools.Field(typeof(GameManager), "lockedTileEntities"); - public static Dictionary GetServerLocks() + public static Dictionary GetServerLocks() { - return (Dictionary)FieldLockedTileEntities.GetValue(GameManager.Instance); + return (Dictionary)FieldLockedTileEntities.GetValue(GameManager.Instance); } // Call as a remote function call to acquire new and release old lock diff --git a/ModInfo.xml b/ModInfo.xml index 7ac528d..5609467 100644 --- a/ModInfo.xml +++ b/ModInfo.xml @@ -5,5 +5,5 @@ - + \ No newline at end of file diff --git a/README.md b/README.md index d3b6150..3dd9073 100644 --- a/README.md +++ b/README.md @@ -157,8 +157,23 @@ which means that screens the player stands closer are updated more frequently. When very close, up to same fps as the main game runs. Further away you may only get an update each 1 or 2 seconds. +## Download and Install + +End-Users are encouraged to download my mods from [NexusMods][5]. +Every download there helps me to buy stuff for mod development. + +Otherwise please use one of the [official releases][6] here. +Only clone or download the repo if you know what you do! + +[5]: https://www.nexusmods.com/7daystodie/mods/2279 +[6]: https://github.com/OCB7D2D/OcbRemoteTurretControl/releases + ## Changelog +### Version 0.3.0 + +- First compatibility with V1.0 (exp) + ### Version 0.2.1 - Add fog and sky to all electric cameras @@ -187,6 +202,5 @@ Further away you may only get an update each 1 or 2 seconds. - Initial working version -## Compatibility - -Developed initially for version a20.5(b2), updated through A21.0(b324). +[1]: +[2]: \ No newline at end of file diff --git a/RemoteTurretControl.csproj b/RemoteTurretControl.csproj index 648bc1f..04a01f9 100644 --- a/RemoteTurretControl.csproj +++ b/RemoteTurretControl.csproj @@ -112,7 +112,6 @@ - diff --git a/RemoteTurretControl.dll b/RemoteTurretControl.dll index 94a2f39..92ac429 100644 Binary files a/RemoteTurretControl.dll and b/RemoteTurretControl.dll differ diff --git a/Resources/RemoteTurretPanels.unity3d b/Resources/RemoteTurretPanels.unity3d index 9967f59..87b1194 100644 Binary files a/Resources/RemoteTurretPanels.unity3d and b/Resources/RemoteTurretPanels.unity3d differ diff --git a/Unity/TurretPanel/ProjectSettings/ProjectVersion.txt b/Unity/TurretPanel/ProjectSettings/ProjectVersion.txt index 8e3af85..629f7d4 100644 --- a/Unity/TurretPanel/ProjectSettings/ProjectVersion.txt +++ b/Unity/TurretPanel/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2021.3.19f1 -m_EditorVersionWithRevision: 2021.3.19f1 (c9714fde33b6) +m_EditorVersion: 2022.3.29f1 +m_EditorVersionWithRevision: 2022.3.29f1 (8d510ca76d2b) diff --git a/Utils/ModXmlPatcher.cs b/Utils/ModXmlPatcher.cs deleted file mode 100644 index eea8245..0000000 --- a/Utils/ModXmlPatcher.cs +++ /dev/null @@ -1,382 +0,0 @@ -/* MIT License - -Copyright (c) 2022 OCB7D2D - -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. - -*/ - -using HarmonyLib; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Xml; -using System.Xml.Linq; -using System.Xml.XPath; - -static class ModXmlPatcher -{ - - // Must be set from outside first, otherwise not much happens - public static Dictionary> Conditions = null; - - // Evaluates one single condition (can be negated) - private static bool EvaluateCondition(string condition) - { - // Try to get optional condition from global dictionary - if (Conditions != null && Conditions.TryGetValue(condition, out Func callback)) - { - // Just call the function - // We don't cache anything - return callback(); - } - // Otherwise check if a mod with that name exists - // ToDo: maybe do something with ModInfo.version? - else if (ModManager.GetMod(condition) != null) - { - return true; - } - // Otherwise it's false - // Unknown tests too - return false; - } - - // Evaluate a comma separated list of conditions - // The results are logically `and'ed` together - private static bool EvaluateConditions(string conditions, XmlFile xml) - { - // Ignore if condition is empty or null - if (string.IsNullOrEmpty(conditions)) return false; - // Split comma separated list (no whitespace allowed yet) - - if (conditions.StartsWith("xpath:")) - { - conditions = conditions.Substring(6); - foreach (string xpath in conditions.Split(',')) - { - bool negate = false; - List xmlNodeList; - if (xpath.StartsWith("!")) - { - negate = true; - xmlNodeList = xml.XmlDoc.XPathSelectElements( - xpath.Substring(1)).ToList(); - } - else - { - xmlNodeList = xml.XmlDoc.XPathSelectElements(xpath).ToList(); - } - bool result = true; - if (xmlNodeList == null) result = false; - if (xmlNodeList.Count == 0) result = false; - if (negate) result = !result; - if (!result) return false; - } - } - else - { - foreach (string condition in conditions.Split(',')) - { - bool result = true; - // Try to find version comparator - int notpos = condition[0] == '!' ? 1 : 0; - int ltpos = condition.IndexOf("<"); - int gtpos = condition.IndexOf(">"); - int lepos = condition.IndexOf("≤"); - int gepos = condition.IndexOf("≥"); - int length = condition.Length - notpos; - if (ltpos != -1) length = ltpos - notpos; - else if (gtpos != -1) length = gtpos - notpos; - else if (lepos != -1) length = lepos - notpos; - else if (gepos != -1) length = gepos - notpos; - string name = condition.Substring(notpos, length); - if (length != condition.Length - notpos) - { - if (ModManager.GetMod(name) is Mod mod) - { - string version = condition.Substring(notpos + length + 1); - Version having = mod.Version; - Version testing = Version.Parse(version); - if (ltpos != -1) result = having < testing; - if (gtpos != -1) result = having > testing; - if (lepos != -1) result = having <= testing; - if (gepos != -1) result = having >= testing; - } - else - { - result = false; - } - } - else if (!EvaluateCondition(name)) - { - result = false; - } - - if (notpos == 1) result = !result; - if (result == false) return false; - } - } - - // Something was true - return true; - } - - // 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) - { - bool result = true; - foreach (XAttribute attr in element.Attributes()) - { - // Skip unknown attributes - if (attr.Name != "path") continue; - // Load path relative to previous XML include - string prev = Path.Combine(parent.Directory, parent.Filename); - string path = Path.Combine(Path.GetDirectoryName(prev), attr.Value); - if (File.Exists(path)) - { - try - { - string _text = File.ReadAllText(path, Encoding.UTF8) - .Replace("@modfolder:", "@modfolder(" + modName + "):"); - XmlFile _patchXml; - try - { - _patchXml = new XmlFile(_text, - Path.GetDirectoryName(path), - Path.GetFileName(path), - true); - } - catch (Exception ex) - { - Log.Error("XML loader: Loading XML patch include '{0}' from mod '{1}' failed.", path, modName); - Log.Exception(ex); - result = false; - continue; - } - result &= XmlPatcher.PatchXml( - target, _patchXml, modName); - } - catch (Exception ex) - { - Log.Error("XML loader: Patching '" + target.Filename + "' from mod '" + modName + "' failed."); - Log.Exception(ex); - result = false; - } - } - else - { - Log.Error("XML loader: Can't find XML include '{0}' from mod '{1}'.", path, modName); - } - } - return result; - } - - // Basically the same function as `XmlPatcher.PatchXml` - // Patched to support `include` and `modif` XML elements - - static int count = 0; - - public static bool PatchXml(XmlFile xmlFile, XmlFile patchXml, XElement node, string patchName) - { - bool result = true; - count++; - ParserStack stack = new ParserStack(); - stack.count = count; - foreach (XElement child in node.Elements()) - { - if (child.NodeType == XmlNodeType.Element) - { - 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)) - { - 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; - } - } - } - return result; - } - - // Flags for consecutive mod-if parsing - public struct ParserStack - { - public int count; - public bool IfClauseParsed; - public bool PreviousResult; - } - - // 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) - { - - // Only support root level - switch (_patchElement.Name.ToString()) - { - - case "include": - - // Call out to our include handler - return IncludeAnotherDocument(_xmlFile, _patchXml, - _patchElement, _patchName); - - case "modif": - - // Reset flags first - stack.IfClauseParsed = true; - stack.PreviousResult = false; - - // Check if we have true conditions - foreach (XAttribute attr in _patchElement.Attributes()) - { - // Ignore unknown attributes for now - if (attr.Name != "condition") - { - Log.Warning("Ignoring unknown attribute {0}", attr.Name); - continue; - } - // Evaluate one or'ed condition - if (EvaluateConditions(attr.Value, _xmlFile)) - { - stack.PreviousResult = true; - return PatchXml(_xmlFile, _patchXml, - _patchElement, _patchName); - } - } - - // Nothing failed!? - return true; - - case "modelsif": - - // Check for correct parser state - if (!stack.IfClauseParsed) - { - Log.Error("Found clause out of order"); - return false; - } - - // Abort else when last result was true - if (stack.PreviousResult) return true; - - // Check if we have true conditions - foreach (XAttribute attr in _patchElement.Attributes()) - { - // Ignore unknown attributes for now - if (attr.Name != "condition") - { - Log.Warning("Ignoring unknown attribute {0}", attr.Name); - continue; - } - // Evaluate one or'ed condition - if (EvaluateConditions(attr.Value, _xmlFile)) - { - stack.PreviousResult = true; - return PatchXml(_xmlFile, _patchXml, - _patchElement, _patchName); - } - } - - // Nothing failed!? - return true; - - case "modelse": - - // Reset flags first - stack.IfClauseParsed = false; - // Abort else when last result was true - if (stack.PreviousResult) return true; - return PatchXml(_xmlFile, _patchXml, - _patchElement, _patchName); - - default: - // Reset flags first - stack.IfClauseParsed = false; - stack.PreviousResult = true; - // Dispatch to original function - return (bool)MethodSinglePatch.Invoke(null, - new object[] { _xmlFile, _patchElement, _patchName }); - } - } - - // Hook into vanilla XML Patcher - [HarmonyPatch(typeof(XmlPatcher))] - [HarmonyPatch("PatchXml")] - public class XmlPatcher_PatchXml - { - static bool Prefix( - ref XmlFile _xmlFile, - ref XmlFile _patchXml, - ref string _patchName, - ref bool __result) - { - // According to Harmony docs, returning false on a prefix - // should skip the original and all other prefixers, but - // it seems that it only skips the original. The other - // prefixers are still called. The reason for this is - // unknown, but could be because the game uses HarmonyX. - // 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 (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; - } - // Call out to static helper function - __result = PatchXml( - _xmlFile, _patchXml, - element, _patchName); - // First one wins - _patchXml = null; - return false; - } - } - -}