diff --git a/.editorconfig b/.editorconfig
index c825f67..edce4f4 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -2,3 +2,8 @@
# CA1416: Validate platform compatibility
dotnet_diagnostic.CA1416.severity = silent
+
+# Indentation and spacing
+indent_size = 4
+indent_style = space
+tab_width = 4
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index ba92f36..5f0e18a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -9,21 +9,21 @@ jobs:
name: Build Mod
runs-on: ubuntu-latest
env:
- MODKIT_VERSION: 0.11.0.2-beta-release-707
+ MODKIT_VERSION: 0.11.1.3-beta-release-795
ECO_BRANCH: staging
steps:
- - uses: actions/checkout@v2
- - name: Setup .NET Core 7.0
- uses: actions/setup-dotnet@v1
+ - uses: actions/checkout@v4
+ - name: Setup .NET Core 8.0
+ uses: actions/setup-dotnet@v4
with:
- dotnet-version: '7.0.x'
+ dotnet-version: '8.0.x'
- name: Fetch dependencies
run: dotnet restore ./EcoLawExtensionsMod/EcoLawExtensionsMod.csproj
env:
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: 1
- name: Cache Eco dlls
id: cache-eco-dlls
- uses: actions/cache@v2
+ uses: actions/cache@v4
with:
path: ./eco-dlls
key: ${{ env.MODKIT_VERSION }}-ref-dlls
@@ -35,10 +35,10 @@ jobs:
env:
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: 1
- name: Upload build artifact
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: mod-binaries-${{github.event.release.tag_name}}
- path: EcoLawExtensionsMod/bin/Release/net7.0/EcoLawExtensionsMod.*
+ path: EcoLawExtensionsMod/bin/Release/net8.0/EcoLawExtensionsMod.*
deploy:
name: Upload Release Assets
needs:
@@ -47,7 +47,7 @@ jobs:
steps:
- name: Download build artifact (mod)
id: download-mod
- uses: actions/download-artifact@v2
+ uses: actions/download-artifact@v4
with:
name: mod-binaries-${{github.event.release.tag_name}}
- name: Upload release asset (mod)
diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml
index 0f30a88..e7f667d 100644
--- a/.github/workflows/staging.yml
+++ b/.github/workflows/staging.yml
@@ -7,23 +7,24 @@ on:
jobs:
build-mod:
+ name: Build Mod
runs-on: ubuntu-latest
env:
- MODKIT_VERSION: 0.11.0.2-beta-release-707
+ MODKIT_VERSION: 0.11.1.3-beta-release-795
ECO_BRANCH: staging
steps:
- - uses: actions/checkout@v2
- - name: Setup .NET Core 7.0
- uses: actions/setup-dotnet@v1
+ - uses: actions/checkout@v4
+ - name: Setup .NET Core 8.0
+ uses: actions/setup-dotnet@v4
with:
- dotnet-version: '7.0.x'
+ dotnet-version: '8.0.x'
- name: Fetch dependencies
run: dotnet restore ./EcoLawExtensionsMod/EcoLawExtensionsMod.csproj
env:
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: 1
- name: Cache Eco dlls
id: cache-eco-dlls
- uses: actions/cache@v2
+ uses: actions/cache@v4
with:
path: ./eco-dlls
key: ${{ env.MODKIT_VERSION }}-ref-dlls
@@ -35,7 +36,7 @@ jobs:
env:
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: 1
- name: Upload build artifact
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: mod-binaries-staging
- path: EcoLawExtensionsMod/bin/Release/net7.0/EcoLawExtensionsMod.*
+ path: EcoLawExtensionsMod/bin/Release/net8.0/EcoLawExtensionsMod.*
diff --git a/EcoLawExtensionsMod/EcoLawExtensionsMod.csproj b/EcoLawExtensionsMod/EcoLawExtensionsMod.csproj
index fdb433e..50108d8 100644
--- a/EcoLawExtensionsMod/EcoLawExtensionsMod.csproj
+++ b/EcoLawExtensionsMod/EcoLawExtensionsMod.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
Eco.Mods.LawExtensions
@@ -38,16 +38,9 @@
-
- all
-
-
- all
-
-
-
-
+
+
diff --git a/EcoLawExtensionsMod/FodyWeavers.xml b/EcoLawExtensionsMod/FodyWeavers.xml
deleted file mode 100644
index bf60e66..0000000
--- a/EcoLawExtensionsMod/FodyWeavers.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
- 0Harmony
- MonoMod.Common
- Mono.Cecil
-
-
-
\ No newline at end of file
diff --git a/EcoLawExtensionsMod/FodyWeavers.xsd b/EcoLawExtensionsMod/FodyWeavers.xsd
deleted file mode 100644
index af5be0b..0000000
--- a/EcoLawExtensionsMod/FodyWeavers.xsd
+++ /dev/null
@@ -1,141 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks
-
-
-
-
- A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.
-
-
-
-
- A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks
-
-
-
-
- A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.
-
-
-
-
- A list of unmanaged 32 bit assembly names to include, delimited with line breaks.
-
-
-
-
- A list of unmanaged 64 bit assembly names to include, delimited with line breaks.
-
-
-
-
- The order of preloaded assemblies, delimited with line breaks.
-
-
-
-
-
- This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file.
-
-
-
-
- Controls if .pdbs for reference assemblies are also embedded.
-
-
-
-
- Controls if runtime assemblies are also embedded.
-
-
-
-
- Controls whether the runtime assemblies are embedded with their full path or only with their assembly name.
-
-
-
-
- Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option.
-
-
-
-
- As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off.
-
-
-
-
- Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code.
-
-
-
-
- Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior.
-
-
-
-
- A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with |
-
-
-
-
- A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |.
-
-
-
-
- A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with |
-
-
-
-
- A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |.
-
-
-
-
- A list of unmanaged 32 bit assembly names to include, delimited with |.
-
-
-
-
- A list of unmanaged 64 bit assembly names to include, delimited with |.
-
-
-
-
- The order of preloaded assemblies, delimited with |.
-
-
-
-
-
-
-
- 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
-
-
-
-
- A comma-separated list of error codes that can be safely ignored in assembly verification.
-
-
-
-
- 'false' to turn off automatic generation of the XML Schema file.
-
-
-
-
-
\ No newline at end of file
diff --git a/EcoLawExtensionsMod/GameValues/CoordAt.cs b/EcoLawExtensionsMod/GameValues/CoordAt.cs
new file mode 100644
index 0000000..f470270
--- /dev/null
+++ b/EcoLawExtensionsMod/GameValues/CoordAt.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Linq;
+
+namespace Eco.Mods.LawExtensions
+{
+ using Core.Controller;
+ using Core.Utils;
+ using Core.Utils.PropertyScanning;
+
+ using Shared.Localization;
+ using Shared.Networking;
+ using Shared.Utils;
+ using Shared.Math;
+
+ using Gameplay.Civics.GameValues;
+
+ [Eco, LocCategory("World"), LocDescription("The X coord of a location.")]
+ public class XCoordAt : GameValue
+ {
+ [Eco, Advanced, LocDescription("The position to read the X coord of.")] public GameValue Location { get; set; }
+
+ private Eval FailNullSafeFloat(Eval eval, string paramName) =>
+ eval != null ? Eval.Make($"Invalid {Localizer.DoStr(paramName)} specified on {GetType().GetLocDisplayName()}: {eval.Message}", float.MinValue)
+ : Eval.Make($"{Localizer.DoStr(paramName)} not set on {GetType().GetLocDisplayName()}.", float.MinValue);
+
+ public override Eval Value(IContextObject action)
+ {
+ var location = this.Location?.Value(action); if (location?.Val == null) return this.FailNullSafeFloat(location, nameof(this.Location));
+
+ var value = location.Val.X;
+ return Eval.Make($"{Text.StyledNum(value)} (X coord)", (float)value);
+ }
+ public override LocString Description() => Localizer.Do($"X coord");
+ }
+
+ [Eco, LocCategory("World"), LocDescription("The Z coord of a location.")]
+ public class ZCoordAt : GameValue
+ {
+ [Eco, Advanced, LocDescription("The position to read the Z coord of.")] public GameValue Location { get; set; }
+
+ private Eval FailNullSafeFloat(Eval eval, string paramName) =>
+ eval != null ? Eval.Make($"Invalid {Localizer.DoStr(paramName)} specified on {GetType().GetLocDisplayName()}: {eval.Message}", float.MinValue)
+ : Eval.Make($"{Localizer.DoStr(paramName)} not set on {GetType().GetLocDisplayName()}.", float.MinValue);
+
+ public override Eval Value(IContextObject action)
+ {
+ var location = this.Location?.Value(action); if (location?.Val == null) return this.FailNullSafeFloat(location, nameof(this.Location));
+
+ var value = location.Val.Z;
+ return Eval.Make($"{Text.StyledNum(value)} (Z coord)", (float)value);
+ }
+ public override LocString Description() => Localizer.Do($"Z coord");
+ }
+}
diff --git a/EcoLawExtensionsMod/GameValues/DistanceToClosestPlant.cs b/EcoLawExtensionsMod/GameValues/DistanceToClosestPlant.cs
index ccef82d..e2fd460 100644
--- a/EcoLawExtensionsMod/GameValues/DistanceToClosestPlant.cs
+++ b/EcoLawExtensionsMod/GameValues/DistanceToClosestPlant.cs
@@ -25,6 +25,8 @@ public class DistanceToClosestPlant : GameValue
[Eco, AllowNullInView, AllowEmpty, LocDescription("The plant to search for.")] public GamePickerList PlantType { get; set; } = new ();
+ [Eco, Advanced, LocDescription("Whether to ignore plants at the target location.")] public GameValue IgnoreAtLocation { get; set; } = new No();
+
private Eval FailNullSafeFloat(Eval eval, string paramName) =>
eval != null ? Eval.Make($"Invalid {Localizer.DoStr(paramName)} specified on {GetType().GetLocDisplayName()}: {eval.Message}", float.MinValue)
: Eval.Make($"{Localizer.DoStr(paramName)} not set on {GetType().GetLocDisplayName()}.", float.MinValue);
@@ -32,6 +34,7 @@ private Eval FailNullSafeFloat(Eval eval, string paramName) =>
public override Eval Value(IContextObject action)
{
var location = this.Location?.Value(action); if (location?.Val == null) return this.FailNullSafeFloat(location, nameof(this.Location));
+ var ignoreAtLocation = this.IgnoreAtLocation?.Value(action); if (ignoreAtLocation?.Val == null) return this.FailNullSafeFloat(ignoreAtLocation, nameof(this.IgnoreAtLocation));
var allRelevantPlants = EcoSim.PlantSim.All
.Where(x => PlantType.ContainsType(x.Species.GetType()));
@@ -40,10 +43,17 @@ public override Eval Value(IContextObject action)
return Eval.Make($"{Text.Style(Text.Styles.Currency, "infinite")} (distance to nearest {PlantType.DescribeEntries(Localizer.DoStr(","))})", float.MaxValue);
}
- var nearest = allRelevantPlants
- .Select(x => (x, World.WrappedDistance(x.Position, location.Val)))
- .OrderBy(x => x.Item2)
- .FirstOrDefault();
+ var plantsWithDistances = allRelevantPlants
+ .Select(x => (x, World.WrappedDistance(x.Position, location.Val)));
+
+ if (ignoreAtLocation.Val)
+ {
+ plantsWithDistances = plantsWithDistances
+ .Where(x => x.Item2 > 0.0f);
+ }
+
+ var nearest = plantsWithDistances
+ .MinBy(x => x.Item2);
return Eval.Make($"{Text.StyledNum(nearest.Item2)} (distance to {nearest.x.Species.DisplayName})", nearest.Item2);
}
diff --git a/EcoLawExtensionsMod/GameValues/DistanceToClosestWorldObject.cs b/EcoLawExtensionsMod/GameValues/DistanceToClosestWorldObject.cs
index ae462f3..a800243 100644
--- a/EcoLawExtensionsMod/GameValues/DistanceToClosestWorldObject.cs
+++ b/EcoLawExtensionsMod/GameValues/DistanceToClosestWorldObject.cs
@@ -12,11 +12,12 @@ namespace Eco.Mods.LawExtensions
using Shared.Utils;
using Shared.Math;
using Shared.IoC;
+ using Shared.Voxel;
using Gameplay.Civics.GameValues;
using Gameplay.Systems.TextLinks;
- using Gameplay.Players;
using Gameplay.Objects;
+
[Eco, LocCategory("World"), LocDescription("How close the nearest world object of a particular type is.")]
public class DistanceToClosestWorldObject : GameValue
@@ -25,6 +26,8 @@ public class DistanceToClosestWorldObject : GameValue
[Eco, AllowNullInView, AllowEmpty, LocDescription("The object to search for.")] public GamePickerList ObjectType { get; set; } = new ();
+ [Eco, Advanced, LocDescription("Whether to ignore world objects at the target location.")] public GameValue IgnoreAtLocation { get; set; } = new No();
+
private Eval FailNullSafeFloat(Eval eval, string paramName) =>
eval != null ? Eval.Make($"Invalid {Localizer.DoStr(paramName)} specified on {GetType().GetLocDisplayName()}: {eval.Message}", float.MinValue)
: Eval.Make($"{Localizer.DoStr(paramName)} not set on {GetType().GetLocDisplayName()}.", float.MinValue);
@@ -32,6 +35,7 @@ private Eval FailNullSafeFloat(Eval eval, string paramName) =>
public override Eval Value(IContextObject action)
{
var location = this.Location?.Value(action); if (location?.Val == null) return this.FailNullSafeFloat(location, nameof(this.Location));
+ var ignoreAtLocation = this.IgnoreAtLocation?.Value(action); if (ignoreAtLocation?.Val == null) return this.FailNullSafeFloat(ignoreAtLocation, nameof(this.IgnoreAtLocation));
var allRelevantObjects = ServiceHolder.Obj.All
.Where(x => ObjectType.ContainsType(x.GetType()));
@@ -40,10 +44,17 @@ public override Eval Value(IContextObject action)
return Eval.Make($"{Text.Style(Text.Styles.Currency, "infinite")} (distance to nearest {ObjectType.DescribeEntries(Localizer.DoStr(","))})", float.MaxValue);
}
- var nearest = allRelevantObjects
- .Select(x => (x, x.Position3i.WrappedDistance(location.Val)))
- .OrderBy(x => x.Item2)
- .FirstOrDefault();
+ var objectsWithDistances = allRelevantObjects
+ .Select(x => (x, World.WrappedDistance(x.Position, location.Val)));
+
+ if (ignoreAtLocation.Val)
+ {
+ objectsWithDistances = objectsWithDistances
+ .Where(x => x.Item2 > 0.0f);
+ }
+
+ var nearest = objectsWithDistances
+ .MinBy(x => x.Item2);
return Eval.Make($"{Text.StyledNum(nearest.Item2)} (distance to {nearest.x.UILink()})", nearest.Item2);
}
diff --git a/EcoLawExtensionsMod/HarmonyPatches/PowerGridComponentTickPatch.cs b/EcoLawExtensionsMod/HarmonyPatches/PowerGridComponentTickPatch.cs
deleted file mode 100644
index 745b102..0000000
--- a/EcoLawExtensionsMod/HarmonyPatches/PowerGridComponentTickPatch.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using HarmonyLib;
-
-namespace Eco.Mods.LawExtensions.HarmonyPatches
-{
- using Gameplay.PowerGrids;
-
- [HarmonyPatch(typeof(PowerGridManager), "Tick")]
- internal class PowerGridManagerTickPatch
- {
- internal static void Prefix(PowerGridManager __instance)
- {
- PowerGridLawManager.Obj.PowerGridManagerPreTick(__instance);
- }
-
- internal static void Postfix(PowerGridManager __instance)
- {
- PowerGridLawManager.Obj.PowerGridManagerPostTick(__instance);
- }
- }
-}
diff --git a/EcoLawExtensionsMod/LawExtensionsPlugin.cs b/EcoLawExtensionsMod/LawExtensionsPlugin.cs
index 57b6ea8..18e365a 100644
--- a/EcoLawExtensionsMod/LawExtensionsPlugin.cs
+++ b/EcoLawExtensionsMod/LawExtensionsPlugin.cs
@@ -1,12 +1,11 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
+using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using System.Collections.Generic;
-using HarmonyLib;
-
namespace Eco.Mods.LawExtensions
{
using Core.Plugins.Interfaces;
@@ -24,7 +23,7 @@ namespace Eco.Mods.LawExtensions
using Gameplay.Items;
using Gameplay.Components;
using Gameplay.Objects;
-
+ using Gameplay.PowerGrids;
[Serialized]
public class LawExtensionsData : Singleton, IStorage
@@ -60,7 +59,6 @@ public class LawExtensionsPlugin : Singleton, IModKitPlugin
static LawExtensionsPlugin()
{
- CosturaUtility.Initialize();
var dynamicTagsField = typeof(TagManager).GetField("dynamicTags", BindingFlags.Static | BindingFlags.NonPublic);
if (dynamicTagsField != null)
{
@@ -92,8 +90,14 @@ public LawExtensionsPlugin()
public void Initialize(TimedTask timer)
{
data.Initialize();
- var harmony = new Harmony("Eco.Mods.LawExtensions");
- harmony.PatchAll();
+ try
+ {
+ SetupPowerGridManagerDetour();
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"Failed to setup power grid manager detour ({ex.Message}) - power related law triggers will not work!");
+ }
// Modded tags as filters for law trigger parameters don't seem to be supported for now (or maybe it's just my crazy way of making a tag...)
// SetupPoweredTag();
}
@@ -114,6 +118,24 @@ public void OnEditObjectChanged(object o, string param)
this.SaveConfig();
}
+ private void SetupPowerGridManagerDetour()
+ {
+ var tickWorkerField = typeof(PowerGridManager).GetField("tickWorker", BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new Exception($"Failed to reflect PowerGridManager.tickWorker");
+ var tickWorker = tickWorkerField.GetValue(PowerGridManager.Obj) as EventDrivenWorker ?? throw new Exception($"Failed to retrieve PowerGridManager.tickWorker");
+ var repeatableActionField = typeof(EventDrivenWorker).GetField("repeatableAction", BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new Exception($"Failed to reflect EventDrivenWorker.repeatableAction");
+ var oldRepeatableAction = repeatableActionField.GetValue(tickWorker) as Func> ?? throw new Exception($"Failed to retrieve EventDrivenWorker.repeatableAction");
+ Func> newRepeatableAction = (token) =>
+ {
+ PowerGridLawManager.PowerGridManagerPreTick(PowerGridManager.Obj);
+ return oldRepeatableAction(token).ContinueWith(t =>
+ {
+ PowerGridLawManager.PowerGridManagerPostTick(PowerGridManager.Obj);
+ return t.Result;
+ });
+ };
+ repeatableActionField.SetValue(tickWorker, newRepeatableAction);
+ }
+
private void SetupPoweredTag()
{
TagDefinition.Register(poweredTagDefinition);
diff --git a/EcoLawExtensionsMod/Registration.cs b/EcoLawExtensionsMod/Registration.cs
new file mode 100644
index 0000000..25981e8
--- /dev/null
+++ b/EcoLawExtensionsMod/Registration.cs
@@ -0,0 +1,14 @@
+namespace Eco.Mods.LawExtensions
+{
+ using Core.Plugins.Interfaces;
+
+ public class LawExtensionsMod : IModInit
+ {
+ public static ModRegistration Register() => new()
+ {
+ ModName = "LawExtensions",
+ ModDescription = "Extends the law system with a number of helpful utility game values and legal actions.",
+ ModDisplayName = "Law Extensions",
+ };
+ }
+}
diff --git a/README.md b/README.md
index a2c0998..f509719 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# Eco Law Extensions Mod
-A server mod for Eco 11.0 that extends the law system with a number of helpful utility game values and legal actions.
+A server mod for Eco 11.1 that extends the law system with a number of helpful utility game values and legal actions.
Added game values:
- Citizen Population - the current citizen count of a title or demographic
@@ -79,6 +79,7 @@ Finds the closest world object matching a filter to a location and gets the dist
| - | - | - |
| Location | Vector3 | The location to test. Usually this is passed in context from the law trigger. |
| ObjectType | Object Picker | A filter for objects to search for. |
+| IgnoreAtLocation | Yes/No | Ignore any world objects directly at the location, e.g. with a distance of 0. This is useful to find the next nearest world object from a world object. Defaults to No. |
#### Distance to Closest Plant
@@ -88,6 +89,7 @@ Finds the closest plant matching a filter to a location and gets the distance to
| - | - | - |
| Location | Vector3 | The location to test. Usually this is passed in context from the law trigger. |
| PlantType | Plant Species Picker | A filter for plants to search for. |
+| IgnoreAtLocation | Yes/No | Ignore any plants directly at the location, e.g. with a distance of 0. This is useful to find the next nearest plant from a plant. Defaults to No. |
#### Layer Value At
@@ -106,6 +108,14 @@ Extracts the Y coordinate from a location. This will be an integer in whole bloc
| - | - | - |
| Location | Vector3 | The location to get the height of. Usually this is passed in context from the law trigger. |
+#### X/Z Coord At
+
+Extracts the X/Z coordinate from a location. This will be an integer in whole blocks.
+
+| Property Name | Type | Description |
+| - | - | - |
+| Location | Vector3 | The location to get the X/Z coord of. Usually this is passed in context from the law trigger. |
+
#### Turn On Machines
Tries to turn on machines belonging to a citizen or group that are currently turned off. The filter can specify how the machines were turned off - for example, only try to turn on machines that were turned off legally (e.g. via prevent on Pollute Air).
@@ -170,7 +180,7 @@ Note that the `PowerAvailable` and `PowerProduced` values are measured in Joules
2. Extract the modkit and copy the dlls from `ReferenceAssemblies` to `eco-dlls` in the root directory (create the folder if it doesn't exist)
3. Open `EcoLawExtensionsMod.sln` in Visual Studio 2019/2022
4. Build the `EcoLawExtensionsMod` project in Visual Studio
-5. Find the artifact in `EcoLawExtensionsMod\bin\{Debug|Release}\net7.0`
+5. Find the artifact in `EcoLawExtensionsMod\bin\{Debug|Release}\net8.0`
### Linux
@@ -178,7 +188,7 @@ Note that the `PowerAvailable` and `PowerProduced` values are measured in Joules
2. Enter the `EcoLawExtensionsMod` directory and run:
`dotnet restore`
`dotnet build`
-3. Find the artifact in `EcoLawExtensionsMod/bin/{Debug|Release}/net7.0`
+3. Find the artifact in `EcoLawExtensionsMod/bin/{Debug|Release}/net8.0`
## License
[MIT](https://choosealicense.com/licenses/mit/)
\ No newline at end of file