Skip to content

Commit

Permalink
lightning rods
Browse files Browse the repository at this point in the history
  • Loading branch information
aedenthorn committed Jan 12, 2022
1 parent 4e94b7e commit 1a4846e
Show file tree
Hide file tree
Showing 19 changed files with 743 additions and 6 deletions.
20 changes: 20 additions & 0 deletions BetterLightningRods/BetterLightningRods.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>1.0.0</Version>
<TargetFramework>net5.0</TargetFramework>
<EnableHarmony>true</EnableHarmony>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Pathoschild.Stardew.ModBuildConfig" Version="4.0.0" />
</ItemGroup>

<ItemGroup>
<None Update="assets\numbers.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="manifest.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
62 changes: 62 additions & 0 deletions BetterLightningRods/CodePatches.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace BetterLightningRods
{
/// <summary>The mod entry point.</summary>
public partial class ModEntry
{
public static IEnumerable<CodeInstruction> Utility_performLightningUpdate_Transpiler(IEnumerable<CodeInstruction> instructions)
{
SMonitor.Log($"Transpiling Utility.performLightningUpdate");

var codes = new List<CodeInstruction>(instructions);
var newCodes = new List<CodeInstruction>();
bool rodsFound = false;
bool derandomFound = false;
bool shuffleFound = false;
bool chanceFound = false;
CodeInstruction indexCode = null;
for (int i = 0; i < codes.Count; i++)
{
if (!rodsFound && i < codes.Count - 2 && codes[i + 2].opcode == OpCodes.Blt && codes[i + 1].opcode == OpCodes.Ldc_I4_2 && codes[i].opcode == OpCodes.Ldloc_S)
{
SMonitor.Log($"Setting number of lightning rods to check to {Config.RodsToCheck}");
codes[i + 1] = new CodeInstruction(OpCodes.Ldc_I4, Config.RodsToCheck);
rodsFound = true;
}
else if (!chanceFound && codes[i].opcode == OpCodes.Ldc_R8 && (double)codes[i].operand == 0.125)
{
SMonitor.Log($"Setting lightning chance to {Config.LightningChance}%");
codes[i].operand = (double)(Config.LightningChance / 100f);
chanceFound = true;
}
else if (!shuffleFound && Config.UniqueCheck && i < codes.Count - 2 && codes[i + 2].opcode == OpCodes.Ble && codes[i + 1].opcode == OpCodes.Ldc_I4_0 && codes[i].opcode == OpCodes.Callvirt && codes[i - 1].opcode == OpCodes.Ldloc_3)
{
SMonitor.Log($"Shuffling lightning rod list");
indexCode = new CodeInstruction(OpCodes.Ldloc_S, codes[i + 4].operand);
newCodes.Add(new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(ModEntry), nameof(ModEntry.ShuffleRodList))));
shuffleFound = true;
}
else if (!derandomFound && Config.UniqueCheck && indexCode != null && i < codes.Count - 4 && codes[i + 4].opcode == OpCodes.Callvirt && (MethodInfo)codes[i + 4].operand == AccessTools.Method(typeof(Random), nameof(Random.Next), new Type[] { typeof(int) }) && codes[i + 2].opcode == OpCodes.Ldloc_3 && codes[i + 1].opcode == OpCodes.Ldloc_0 && codes[i].opcode == OpCodes.Ldloc_3)
{
SMonitor.Log($"Setting check to indexed rod");
newCodes.Add(codes[i]);
newCodes.Add(codes[i]);
newCodes.Add(indexCode);
newCodes.Add(new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(ModEntry), nameof(ModEntry.GetLightningRod))));
i += 5;
derandomFound = true;
}
newCodes.Add(codes[i]);
}

return newCodes.AsEnumerable();
}

}
}
75 changes: 75 additions & 0 deletions BetterLightningRods/IGenericModConfigMenuApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using StardewModdingAPI;
using System;

namespace BetterLightningRods
{
/// <summary>The API which lets other mods add a config UI through Generic Mod Config Menu.</summary>
public interface IGenericModConfigMenuApi
{
/*********
** Methods
*********/
/****
** Must be called first
****/
/// <summary>Register a mod whose config can be edited through the UI.</summary>
/// <param name="mod">The mod's manifest.</param>
/// <param name="reset">Reset the mod's config to its default values.</param>
/// <param name="save">Save the mod's current config to the <c>config.json</c> file.</param>
/// <param name="titleScreenOnly">Whether the options can only be edited from the title screen.</param>
/// <remarks>Each mod can only be registered once, unless it's deleted via <see cref="Unregister"/> before calling this again.</remarks>
void Register(IManifest mod, Action reset, Action save, bool titleScreenOnly = false);

/// <summary>Add a section title at the current position in the form.</summary>
/// <param name="mod">The mod's manifest.</param>
/// <param name="text">The title text shown in the form.</param>
/// <param name="tooltip">The tooltip text shown when the cursor hovers on the title, or <c>null</c> to disable the tooltip.</param>
void AddSectionTitle(IManifest mod, Func<string> text, Func<string> tooltip = null);

/// <summary>Add a key binding at the current position in the form.</summary>
/// <param name="mod">The mod's manifest.</param>
/// <param name="getValue">Get the current value from the mod config.</param>
/// <param name="setValue">Set a new value in the mod config.</param>
/// <param name="name">The label text to show in the form.</param>
/// <param name="tooltip">The tooltip text shown when the cursor hovers on the field, or <c>null</c> to disable the tooltip.</param>
/// <param name="fieldId">The unique field ID for use with <see cref="OnFieldChanged"/>, or <c>null</c> to auto-generate a randomized ID.</param>
void AddKeybind(IManifest mod, Func<SButton> getValue, Action<SButton> setValue, Func<string> name, Func<string> tooltip = null, string fieldId = null);

/// <summary>Add a boolean option at the current position in the form.</summary>
/// <param name="mod">The mod's manifest.</param>
/// <param name="getValue">Get the current value from the mod config.</param>
/// <param name="setValue">Set a new value in the mod config.</param>
/// <param name="name">The label text to show in the form.</param>
/// <param name="tooltip">The tooltip text shown when the cursor hovers on the field, or <c>null</c> to disable the tooltip.</param>
/// <param name="fieldId">The unique field ID for use with <see cref="OnFieldChanged"/>, or <c>null</c> to auto-generate a randomized ID.</param>
void AddBoolOption(IManifest mod, Func<bool> getValue, Action<bool> setValue, Func<string> name, Func<string> tooltip = null, string fieldId = null);


/// <summary>Add an integer option at the current position in the form.</summary>
/// <param name="mod">The mod's manifest.</param>
/// <param name="getValue">Get the current value from the mod config.</param>
/// <param name="setValue">Set a new value in the mod config.</param>
/// <param name="name">The label text to show in the form.</param>
/// <param name="tooltip">The tooltip text shown when the cursor hovers on the field, or <c>null</c> to disable the tooltip.</param>
/// <param name="min">The minimum allowed value, or <c>null</c> to allow any.</param>
/// <param name="max">The maximum allowed value, or <c>null</c> to allow any.</param>
/// <param name="interval">The interval of values that can be selected.</param>
/// <param name="fieldId">The unique field ID for use with <see cref="OnFieldChanged"/>, or <c>null</c> to auto-generate a randomized ID.</param>
void AddNumberOption(IManifest mod, Func<int> getValue, Action<int> setValue, Func<string> name, Func<string> tooltip = null, int? min = null, int? max = null, int? interval = null, string fieldId = null);

/// <summary>Add a string option at the current position in the form.</summary>
/// <param name="mod">The mod's manifest.</param>
/// <param name="getValue">Get the current value from the mod config.</param>
/// <param name="setValue">Set a new value in the mod config.</param>
/// <param name="name">The label text to show in the form.</param>
/// <param name="tooltip">The tooltip text shown when the cursor hovers on the field, or <c>null</c> to disable the tooltip.</param>
/// <param name="allowedValues">The values that can be selected, or <c>null</c> to allow any.</param>
/// <param name="formatAllowedValue">Get the display text to show for a value from <paramref name="allowedValues"/>, or <c>null</c> to show the values as-is.</param>
/// <param name="fieldId">The unique field ID for use with <see cref="OnFieldChanged"/>, or <c>null</c> to auto-generate a randomized ID.</param>
void AddTextOption(IManifest mod, Func<string> getValue, Action<string> setValue, Func<string> name, Func<string> tooltip = null, string[] allowedValues = null, Func<string, string> formatAllowedValue = null, string fieldId = null);

/// <summary>Remove a mod from the config UI and delete all its options and pages.</summary>
/// <param name="mod">The mod's manifest.</param>
void Unregister(IManifest mod);
}
}
16 changes: 16 additions & 0 deletions BetterLightningRods/ModConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

using StardewModdingAPI;

namespace BetterLightningRods
{
public class ModConfig
{
public bool EnableMod { get; set; } = true;
public bool UniqueCheck { get; set; } = true;
public bool OnlyCheckEmpty { get; set; } = false;
public int RodsToCheck { get; set; } = 2;
public float LightningChance { get; set; } = 13f;
public SButton LightningButton { get; set; } = SButton.F15;

}
}
136 changes: 136 additions & 0 deletions BetterLightningRods/ModEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using HarmonyLib;
using Microsoft.Xna.Framework;
using StardewModdingAPI;
using StardewValley;
using StardewValley.Characters;
using StardewValley.Objects;
using System;
using System.Collections.Generic;
using System.Linq;

namespace BetterLightningRods
{
/// <summary>The mod entry point.</summary>
public partial class ModEntry : Mod
{

public static IMonitor SMonitor;
public static IModHelper SHelper;
public static ModConfig Config;

public static ModEntry context;

/// <summary>The mod entry point, called after the mod is first loaded.</summary>
/// <param name="helper">Provides simplified APIs for writing mods.</param>
public override void Entry(IModHelper helper)
{
Config = Helper.ReadConfig<ModConfig>();

if (!Config.EnableMod)
return;

context = this;

SMonitor = Monitor;
SHelper = helper;
helper.Events.GameLoop.GameLaunched += GameLoop_GameLaunched;
helper.Events.Input.ButtonPressed += Input_ButtonPressed;


var harmony = new Harmony(ModManifest.UniqueID);

harmony.Patch(
original: AccessTools.Method(typeof(Utility), nameof(Utility.performLightningUpdate)),
transpiler: new HarmonyMethod(typeof(ModEntry), nameof(ModEntry.Utility_performLightningUpdate_Transpiler))
);

}

private void Input_ButtonPressed(object sender, StardewModdingAPI.Events.ButtonPressedEventArgs e)
{
if(e.Button == Config.LightningButton)
{
Monitor.Log("Lightning strike!");
Utility.performLightningUpdate(Game1.timeOfDay);

}
}

private void GameLoop_GameLaunched(object sender, StardewModdingAPI.Events.GameLaunchedEventArgs e)
{
// get Generic Mod Config Menu's API (if it's installed)
var configMenu = Helper.ModRegistry.GetApi<IGenericModConfigMenuApi>("spacechase0.GenericModConfigMenu");
if (configMenu is null)
return;

// register mod
configMenu.Register(
mod: ModManifest,
reset: () => Config = new ModConfig(),
save: () => Helper.WriteConfig(Config)
);

configMenu.AddBoolOption(
mod: ModManifest,
name: () => "Mod Enabled?",
getValue: () => Config.EnableMod,
setValue: value => Config.EnableMod = value
);
configMenu.AddBoolOption(
mod: ModManifest,
name: () => "Unique Check?",
tooltip: () => "Don't check the same rod twice per strike.",
getValue: () => Config.UniqueCheck,
setValue: value => Config.UniqueCheck = value
);
configMenu.AddNumberOption(
mod: ModManifest,
name: () => "Rods To Check",
tooltip: () => "Each time lightning strikes, check this many rods to see if they can receive the charge.",
getValue: () => Config.RodsToCheck,
setValue: value => Config.RodsToCheck = value
);
configMenu.AddNumberOption(
mod: ModManifest,
name: () => "Base Lightning Chance",
getValue: () => (int)Config.LightningChance,
setValue: value => Config.LightningChance = value,
min: 0,
max: 100
);
}
private static int GetLightningRod(List<Vector2> rods, int index)
{
int rod = Math.Min(index, rods.Count - 1);
return rod;
}
private static List<Vector2> ShuffleRodList(List<Vector2> rods)
{
//SMonitor.Log($"Shuffling {rods.Count} rods");
ShuffleList(rods);
if (Config.OnlyCheckEmpty)
{
for(int i = rods.Count - 1; i >= 0; i--)
{
if (Game1.getFarm().objects[rods[i]].heldObject.Value != null)
rods.RemoveAt(i);
}
}
return rods;
}
public static List<T> ShuffleList<T>(List<T> list)
{
int n = list.Count;
while (n > 1)
{
n--;
int k = Game1.random.Next(n + 1);
var value = list[k];
list[k] = list[n];
list[n] = value;
}
return list;
}
}

}
22 changes: 22 additions & 0 deletions BetterLightningRods/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"Name": "Better Lightning Rods",
"Author": "aedenthorn",
"Version": "0.1.0",
"Description": "Better Lightning Rods.",
"UniqueID": "aedenthorn.BetterLightningRods",
"EntryDll": "BetterLightningRods.dll",
"MinimumApiVersion": "3.13.0",
"ModUpdater": {
"Repository": "StardewValleyMods",
"User": "aedenthorn",
"Directory": "_releases",
"ModFolder": "BetterLightningRods"
},
"UpdateKeys": [ "Nexus:10631" ],
"Dependencies": [
{
"UniqueID": "Platonymous.ModUpdater",
"IsRequired": false
}
]
}
6 changes: 3 additions & 3 deletions FarmCaveFramework/CodePatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,16 +219,16 @@ private static bool FarmCave_DayUpdate_Prefix(FarmCave __instance, int dayOfMont
totalWeight += r.weight;
}
int spawned = 0;
while (Game1.random.NextDouble() < Math.Min(0.99, caveChoice.resourceChance / 100f))
while (Game1.random.NextDouble() < Math.Min(0.99f, caveChoice.resourceChance / 100f))
{
int currentWeight = 0;
float currentWeight = 0;
double chance = Game1.random.NextDouble();
foreach (var r in caveChoice.resources)
{
currentWeight += r.weight;
if(chance < currentWeight / totalWeight)
{
Vector2 v = new Vector2((float)Game1.random.Next(1, __instance.map.Layers[0].LayerWidth - 1), (float)Game1.random.Next(1, __instance.map.Layers[0].LayerHeight - 4));
Vector2 v = new Vector2(Game1.random.Next(1, __instance.map.Layers[0].LayerWidth - 1), Game1.random.Next(1, __instance.map.Layers[0].LayerHeight - 4));
if (__instance.isTileLocationTotallyClearAndPlaceable(v))
{
spawned++;
Expand Down
37 changes: 37 additions & 0 deletions MeteoriteDefenceModule/CodePatches.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using StardewValley;
using StardewValley.Characters;

namespace PetBed
{
/// <summary>The mod entry point.</summary>
public partial class ModEntry
{

private static bool Pet_warpToFarmHouse_Prefix(Pet __instance, Farmer who, ref int ____currentBehavior)
{
SMonitor.Log("Warping pet to farmhouse");
return !Config.EnableMod || Game1.random.NextDouble() > Config.BedChance / 100f || !WarpPetToBed(__instance, Utility.getHomeOfFarmer(who), ref ____currentBehavior, false);
}

private static bool Pet_setAtFarmPosition_Prefix(Pet __instance, ref int ____currentBehavior)
{
SMonitor.Log("Setting pet to farm position");
return !Config.EnableMod || Game1.random.NextDouble() > Config.BedChance / 100f || !Game1.IsMasterGame || Game1.isRaining || !WarpPetToBed(__instance, Game1.getFarm(), ref ____currentBehavior, true);
}
private static void Pet_dayUpdate_Prefix(Pet __instance, ref bool __state)
{
if(Config.EnableMod && __instance.currentLocation is Farm && !Game1.isRaining && Game1.random.NextDouble() < Config.BedChance / 100f && Game1.IsMasterGame)
{
__state = true;
}
}
private static void Pet_dayUpdate_Postfix(Pet __instance, bool __state, ref int ____currentBehavior)
{
if(__state)
{
SMonitor.Log("Setting pet to farm position");
WarpPetToBed(__instance, Game1.getFarm(), ref ____currentBehavior, true);
}
}
}
}
Loading

0 comments on commit 1a4846e

Please sign in to comment.