AmethystZhou Posted February 24, 2025 Report Posted February 24, 2025 Hi! I am very new to coding and learning as I try to make a small mod to change the world generation behavior for surface ore bits. This is the relevant part of code in VSSurvivalMod.dll: if (shouldGenSurfaceDeposit) { int surfaceY = (int)heremapchunk.RainHeightMap[lz * 32 + lx]; int depth = surfaceY - posy; float chance = this.SurfaceBlockChance * Math.Max(0f, 1.11f - (float)depth / 9f); ... It calculates a chance for surface ore bits to generate, which decreases by 11.1% per block and reaches 0% by the 10th block. This means that if the ore deposit generates deeper than 10 blocks, no surface ore bits would be generated. I tried creating a simple .dll mod using Visual Studio and Harmony patching. At first I tested it with only changing the "9" in the calculation to a very large number, which works in a test world. But when I modified the code so that both variables are modified (1.11 and 9), the game gets stuck on loading. What am I doing wrong in this code? using Vintagestory.API.Client; using Vintagestory.API.Server; using Vintagestory.API.Config; using Vintagestory.API.Common; using HarmonyLib; using System.Collections.Generic; using System.Reflection.Emit; using Vintagestory.ServerMods; namespace SurfaceBits; [HarmonyPatch] public class MyModSystem : ModSystem { public static ICoreAPI api; public Harmony harmony; public override void Start(ICoreAPI api) { MyModSystem.api = api; // The mod is started once for the server and once for the client. // Prevent the patches from being applied by both in the same process. if (!Harmony.HasAnyPatches(Mod.Info.ModID)) { harmony = new Harmony(Mod.Info.ModID); harmony.PatchAll(); // Applies all harmony patches } } [HarmonyPatch(typeof(DiscDepositGenerator), nameof(DiscDepositGenerator.GenDeposit))] public static class SurfaceOrePatch { static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { var matcher = new CodeMatcher(instructions) .MatchStartForward(new CodeMatch(OpCodes.Ldc_R4, 9.0f)) .ThrowIfInvalid("Could not match 9.0") .SetOperandAndAdvance(10000.0f) .MatchStartForward(new CodeMatch(OpCodes.Ldc_R4, 1.11f)) .ThrowIfInvalid("Could not match 1.11") .SetOperandAndAdvance(0.0f); return matcher.Instructions(); } } public override void Dispose() { harmony?.UnpatchAll(Mod.Info.ModID); } } Thank you very much!
DArkHekRoMaNT Posted February 24, 2025 Report Posted February 24, 2025 I haven't used Matcher, but I think it iterates the instructions only once. So you need to create a new CodeMatcher for the second value or just reverse the order (look for 1.11f before 9f).
DArkHekRoMaNT Posted February 24, 2025 Report Posted February 24, 2025 Although this is a test example, I do not recommend doing patches this way. It is better to bind to some clear labels, for example if(shouldGenSurfaceDeposit) and this.SurfaceBlockChance. Searching and patching a constant in this way is very fragile
AmethystZhou Posted February 24, 2025 Author Report Posted February 24, 2025 20 minutes ago, DArkHekRoMaNT said: Although this is a test example, I do not recommend doing patches this way. It is better to bind to some clear labels, for example if(shouldGenSurfaceDeposit) and this.SurfaceBlockChance. Searching and patching a constant in this way is very fragile That makes a lot of sense. I don't know how to use the CodeMatcher to replace the entire function though. Apparently the reason my code didn't work is because I'm searching for the two variables in the wrong order, since the 1.11 appears before 9, the second search can't find anything..
BearWrestler Posted March 6, 2025 Report Posted March 6, 2025 From what I understand, a good technique when patching is to inject your own method call, so you can then work with a regular method instead of just IL. So perhaps, after this line: float chance = this.SurfaceBlockChance * Math.Max(0f, 1.11f - (float)depth / 9f); You can inject a line like this to override the value: chance = M; After which you are free to implement whatever computation you like. You can even pass this.SurfaceBlockChance as a parameter to your injected method if it's not easily accessible from your mod (haven't checked).
Recommended Posts