Jump to content

Trying to modify the temperature settings in a greenhouse. Need help finding the right place to do this.


Recommended Posts

Posted

I did my best to follow the tutorials on the wiki to setup visual studio to connect with vintage story.  I am able to view the game code but can't change anything in it.

I'm trying to create a mod that sets the greenhouse temperature to a constant.  I was able to find at least some of the code that computes this, which I attached.  The tutorials talk about how to change JSON files and create new objects but I think what I'm trying to do might require changing the game code.  I suspect I'm missing some obvious way to change this code but I've not figured it out yet.  Any help would be appreciated.

I also realize the code snippet I attached specifically says Fruit Trees but haven't yet found similar definitions for crops or berry bushes etc.

f5050e4fff3ab6d4155583ed186d7ace.png

Posted
55 minutes ago, The Insanity God said:

@Vexxvididu You probably missed the documentation due to the way it's named https://wiki.vintagestory.at/Modding:Monkey_patching

If you need examples there are some other mods out there that change greenhouse buff like: https://github.com/Kaktur/VS-Mod-GreenhouseBuff/tree/master/GreenhouseBuff/GreenhouseBuff/ModPatches (makes the buff +20 C instead of +5 C)

 

THANKS!!  and yeah... I missed that specific document.  And I saw that mod, but also saw it was dated and I want to do something slightly different.  ....and just to learn it!  thanks!!

Posted

I spent hours fighting with the mod templates in visual studio.  It was very helpful to use that greenhouse mod as an example but I still can't quite get my changes to work.  I suspect my mod isn't quite calling my .cs scripts in my ModPatches folder. Not entirely sure what I'm still doing wrong.  My folder structure looks a lot like that greenhousebuff mod except that I'm not reading from a config .json.  I am obviously missing something that calls all my scripts but am including the: var harmony = new Harmony(Mod.Info.ModID); statement ...if it's even seeing that...  I'm obviously missing something.  Any ideas on what I could be doing wrong?  I also noticed that when I compile my mod, it shows up twice in the game mod screen as shown in the screen shot below:

Untitled.png

Posted (edited)

I can't say much about the actual code without seeing it but when you say "compile" do you mean:

  1. Build and manually create mod zip
  2. Use CakeBuild and grab the mod zip from the "Release" folder
  3. Just pressing the play button in visual studio

(All of those are viable though I wouldn't recommend the first)

In regards to the mod showing up twice, this is something that commonly happens if you install the mod (by putting it in mod folder) and then run from visual studio or if you just have multiple versions of the same mod installed. Usually not an issue as it will just load the one with the latest version number.

The folder structure of the code is irrelevant btw, it's compiled into a single .DLL file. Game searches for `ModSystem` classes and uses those as entry point (assuming "type": "code" in modinfo, but that should be standard with the template) in your case the mod system would look somewhat like this:

public class MyModSystem : ModSystem
{
    public override void Start(ICoreAPI api)
    {
        base.Start(api);
    
        //Prevent duplicate patching in single player (because it creates 2 instances of the ModSystem, 1 for server side and 1 for client side)
        if (!Harmony.HasAnyPatches(Mod.Info.ModID))
        {
            var harmony = new Harmony(Mod.Info.ModID);
            harmony.PatchAllUncategorized();
            //and possibly some category patches based on config
        }
    }

    public override void Dispose()
    {
        new Harmony(Mod.Info.ModID).UnpatchAll(Mod.Info.ModID);
    }
}
Edited by The Insanity God
  • Like 1
Posted (edited)
9 hours ago, The Insanity God said:

I can't say much about the actual code without seeing it but when you say "compile" do you mean:

  1. Build and manually create mod zip
  2. Use CakeBuild and grab the mod zip from the "Release" folder
  3. Just pressing the play button in visual studio

(All of those are viable though I wouldn't recommend the first)

In regards to the mod showing up twice, this is something that commonly happens if you install the mod (by putting it in mod folder) and then run from visual studio or if you just have multiple versions of the same mod installed. Usually not an issue as it will just load the one with the latest version number.

The folder structure of the code is irrelevant btw, it's compiled into a single .DLL file. Game searches for `ModSystem` classes and uses those as entry point (assuming "type": "code" in modinfo, but that should be standard with the template) in your case the mod system would look somewhat like this:

public class MyModSystem : ModSystem
{
    public override void Start(ICoreAPI api)
    {
        base.Start(api);
    
        //Prevent duplicate patching in single player (because it creates 2 instances of the ModSystem, 1 for server side and 1 for client side)
        if (!Harmony.HasAnyPatches(Mod.Info.ModID))
        {
            var harmony = new Harmony(Mod.Info.ModID);
            harmony.PatchAllUncategorized();
            //and possibly some category patches based on config
        }
    }

    public override void Dispose()
    {
        new Harmony(Mod.Info.ModID).UnpatchAll(Mod.Info.ModID);
    }
}

 

I mean just hitting the play button in visual studio.

And I'll gladly post the code I have so far in case you or any other kind soul has the time to tell me what I am doing wrong. The mod system page was recently changed from your suggestion.  The other scripts are modified from the GreenHouseBuff mod.  I am NOT super confident that I'm using the opcodes properly since I've never used those before. ...google AI was giving very inconsistent feedback on what to do with those, haha.

The farmland and berry bush scripts are supposed to set the temperature in the greenhouse to a steady 20 degrees.  The fruit tree script likely isn't right for that so I just set the bonus to a steady 10 degrees.  I can come back to that one later.

SteadyGreenHousesModSystem.cs BerryBush.cs Farmland.cs FruitTree.cs

Edited by Vexxvididu
Clarity.
Posted
  1. The `[HarmonyPatch]` attribute does not need to be on the `SteadyGreenHouses` class (it doesn't contain any patches)
  2. The class containing patches (for instance `BerryBush`) does not need to inherit `ModSystem` and might as well be a static classes (since it only contains static methods)
  3. I'd recommend using the `CodeMatcher`, makes patches a lot more readable
  4. You replaced the `Add` operation with a `Nop` this will result in invalid code as you never removed the variable that was loaded so it's still on the stack (should be putting a big error about this in your log file).

Berry patch would end up looking like this using CodeMatcher and after fixing number 4:

[HarmonyTranspiler]
[HarmonyPatch(typeof(BlockEntityBerryBush), "CheckGrow")]
internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
    var matcher = new CodeMatcher(instructions);

    matcher.MatchStartForward(
        CodeMatch.LoadsLocal(), //match the loading of a local variable
        CodeMatch.LoadsConstant(5f), //match the static 5
        new CodeMatch(OpCodes.Add), //match the addition
        CodeMatch.StoresLocal() // match the storing of a local variable
    );

    if (matcher.IsInvalid)
    {
        throw new Exception("BerryBush transpiler could not find a match, either base game code has changed or another mod has changed this logic");
    }

    matcher.RemoveInstruction(); //We don't need the original temperature (so don't push the value onto the stack)
    matcher.Instruction.operand = 20f;
    matcher.Advance(1);
    matcher.RemoveInstruction(); //No addition is needed anymore

    return matcher.InstructionEnumeration();
}

 

  • Like 1
Posted
6 hours ago, The Insanity God said:
  1. The `[HarmonyPatch]` attribute does not need to be on the `SteadyGreenHouses` class (it doesn't contain any patches)
  2. The class containing patches (for instance `BerryBush`) does not need to inherit `ModSystem` and might as well be a static classes (since it only contains static methods)
  3. I'd recommend using the `CodeMatcher`, makes patches a lot more readable
  4. You replaced the `Add` operation with a `Nop` this will result in invalid code as you never removed the variable that was loaded so it's still on the stack (should be putting a big error about this in your log file).

Berry patch would end up looking like this using CodeMatcher and after fixing number 4:

[HarmonyTranspiler]
[HarmonyPatch(typeof(BlockEntityBerryBush), "CheckGrow")]
internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
    var matcher = new CodeMatcher(instructions);

    matcher.MatchStartForward(
        CodeMatch.LoadsLocal(), //match the loading of a local variable
        CodeMatch.LoadsConstant(5f), //match the static 5
        new CodeMatch(OpCodes.Add), //match the addition
        CodeMatch.StoresLocal() // match the storing of a local variable
    );

    if (matcher.IsInvalid)
    {
        throw new Exception("BerryBush transpiler could not find a match, either base game code has changed or another mod has changed this logic");
    }

    matcher.RemoveInstruction(); //We don't need the original temperature (so don't push the value onto the stack)
    matcher.Instruction.operand = 20f;
    matcher.Advance(1);
    matcher.RemoveInstruction(); //No addition is needed anymore

    return matcher.InstructionEnumeration();
}

 

Thank you so much!  I kind of figured that use of Nop wasn't right but wasn't sure what else to do.  Your approach here makes a lot more sense.

So now I'm pretty sure it's still just not running my scripts and I think (based on the logs) it might not like my modinfo.json.  It keeps saying it's not found.  I have it in the defaulted location created by the project.

modinfo.json

Posted

ModInfo file looks just fine, if it's not found you might want to check the `csproj` file to ensure it's actually including your ModInfo file
image.thumb.png.b7926ba64816d265c9df83ebaf42e1ea.png

Might want to also manually delete the content of the `bin` folder (folder is normally hidden inside visual studio) to do a fresh rebuild.

  • Thanks 1
Posted

THANKS!  I made more progress thanks to you.  What worked for me was to delete the main project out of the mods folder and only move the released bin contents into the mods folder.  This actually appears to be functional and is working for berry bushes!  WOOHOO!

 

Now I just need to fix it for farmland.  I THINK the new problem is this CodeMatch.LoadsLocal() below.  This works for berry bushes, since that is a local variable in that code, but in the farmland script it directly loads climateCondition.Temperature.  ...which is maybe is a "Field"?  I tried using LoadsField() but that seems to expect an argument.  I assume the StoresLocal() also needs to change.

 

    [HarmonyTranspiler]
    [HarmonyPatch(typeof(BlockEntityFarmland), "Update")]
    internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
    {
        var matcher = new CodeMatcher(instructions);

        matcher.MatchStartForward(
            CodeMatch.LoadsLocal(), //match the loading of a local variable
            CodeMatch.LoadsConstant(5f), //match the static 5
            new CodeMatch(OpCodes.Add), //match the addition
            CodeMatch.StoresLocal() // match the storing of a local variable
        );

        if (matcher.IsInvalid)
        {
            throw new Exception("Farmland transpiler could not find a match, either base game code has changed or another mod has changed this logic");
        }



        matcher.RemoveInstruction(); //We don't need the original temperature (so don't push the value onto the stack)
        matcher.Instruction.operand = 20f;
        matcher.Advance(1);
        matcher.RemoveInstruction(); //No addition is needed anymore

        return matcher.InstructionEnumeration();
    }


//Change tooltip description.
[HarmonyPostfix]
    [HarmonyPatch(typeof(BlockEntityFarmland), "GetBlockInfo")]
    static void Postfix(StringBuilder dsc)
    {

        // Replace the '5' in the greenhouse temp bonus string
        string originalString = Lang.Get("greenhousetempbonus");
        string modifiedString = originalString.Replace("5", "X");

        // Modify the description
        if (dsc.ToString().Contains(originalString))
        {
            dsc.Replace(originalString, modifiedString);
        }
    } 

 

Posted (edited)

Wait you had the main project inside of the mods folder? 😅 well that does explain why it was showing up twice, since pressing play normally launches the game with your bin folder as an additional mod path. Meaning it will attempt to load both your actual compiled mod and the non compiled project because it's in the mods folder (which fails because folder structure doesn't match)

But yeah in the case of farmland the IL code looks a bit different:
image.thumb.png.344a796fa3cc2a60f4fd053d7a99eb8a.png

So would end up looking more like this:

[HarmonyTranspiler]
[HarmonyPatch(typeof(BlockEntityFarmland), "Update")]
internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
    var matcher = new CodeMatcher(instructions);
    var temperatureField = AccessTools.Field(typeof(ClimateCondition), nameof(ClimateCondition.Temperature));
    
    matcher.MatchStartForward(
        CodeMatch.LoadsLocal(), //match the loading of a local variable (ClimateCondition)
        new CodeMatch(OpCodes.Dup), //duplicate climate condition (cause it needs to both retrieve and store field
        CodeMatch.LoadsField(temperatureField), //match the retrieval of the field
        CodeMatch.LoadsConstant(5f), //match the static 5
        new CodeMatch(OpCodes.Add), //match the addition
        CodeMatch.StoresField(temperatureField) // match the storing of the field
    );

    if (matcher.IsInvalid)
    {
        throw new Exception("Farmland transpiler could not find a match, either base game code has changed or another mod has changed this logic");
    }


    matcher.Advance(1); //Still need to load the ClimateCondition
    matcher.RemoveInstruction(); //But don't duplicate (we only need it once)
    matcher.RemoveInstruction(); //Also don't try to load the field
    matcher.Instruction.operand = 20f;
    matcher.Advance(1);
    matcher.RemoveInstruction(); //No addition is needed anymore

    return matcher.InstructionEnumeration();
}

 

Edited by The Insanity God
  • Thanks 1
Posted
22 minutes ago, The Insanity God said:

Wait you had the main project inside of the mods folder? 😅 well that does explain why it was showing up twice, since pressing play normally launches the game with your bin folder as an additional mod path. Meaning it will attempt to load both your actual compiled mod and the non compiled project because it's in the mods folder (which fails because folder structure doesn't match)

But yeah in the case of farmland the IL code looks a bit different:
image.thumb.png.344a796fa3cc2a60f4fd053d7a99eb8a.png

So would end up looking more like this:

[HarmonyTranspiler]
[HarmonyPatch(typeof(BlockEntityFarmland), "Update")]
internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
    var matcher = new CodeMatcher(instructions);
    var temperatureField = AccessTools.Field(typeof(ClimateCondition), nameof(ClimateCondition.Temperature));
    
    matcher.MatchStartForward(
        CodeMatch.LoadsLocal(), //match the loading of a local variable (ClimateCondition)
        new CodeMatch(OpCodes.Dup), //duplicate climate condition (cause it needs to both retrieve and store field
        CodeMatch.LoadsField(temperatureField), //match the retrieval of the field
        CodeMatch.LoadsConstant(5f), //match the static 5
        new CodeMatch(OpCodes.Add), //match the addition
        CodeMatch.StoresField(temperatureField) // match the storing of the field
    );

    if (matcher.IsInvalid)
    {
        throw new Exception("Farmland transpiler could not find a match, either base game code has changed or another mod has changed this logic");
    }


    matcher.Advance(1); //Still need to load the ClimateCondition
    matcher.RemoveInstruction(); //But don't duplicate (we only need it once)
    matcher.RemoveInstruction(); //Also don't try to load the field
    matcher.Instruction.operand = 20f;
    matcher.Advance(1);
    matcher.RemoveInstruction(); //No addition is needed anymore

    return matcher.InstructionEnumeration();
}

 

I am indebted to you!  My mod works!  THANKS!  And yeah.. I had the project in the mods folder due to me misinterpreting one of the tutorials.

THANKS SO MUCH!  This is what I wanted.  You did most of the work, but I learned a lot from you!

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.