void GameLocation_performAction() { var code = FindCode( Instructions.Call_get(typeof(Game1), nameof(Game1.player)), Instructions.Ldfld(typeof(Farmer), nameof(Farmer.maxItems)), OpCodes.Call, Instructions.Ldc_I4_S(36), OpCodes.Bge ); code.Extend( Instructions.Ldstr("Backpack"), OpCodes.Call, OpCodes.Br ); var len = code.length; code.Append( Instructions.Call_get(typeof(Game1), nameof(Game1.player)), Instructions.Ldfld(typeof(Farmer), nameof(Farmer.maxItems)), code[2], Instructions.Ldc_I4_S(48), code[4], Instructions.Call(GetType(), "clickBackpack"), Instructions.Br((Label)code[len - 1].operand) ); code[4] = Instructions.Bge(AttachLabel(code[len])); }
// Support harvesting of spring onions with scythe private void Crop_harvest_support_spring_onion(LocalBuilder var_vector) { if (config.HarvestMode.SpringOnion == HarvestModeEnum.HAND) { return; } // Note: the branch // if (this.forageCrop) // refers mainly to the crop spring union. // Find the lines: var AddItem = FindCode( // if (Game1.player.addItemToInventoryBool (@object, false)) { Instructions.Call_get(typeof(Game1), nameof(Game1.player)), InstructionMatcher.AnyOf( // @object OpCodes.Ldloc_0, OpCodes.Ldloc_1 ), OpCodes.Ldc_I4_0, Instructions.Callvirt(typeof(Farmer), nameof(Farmer.addItemToInventoryBool), typeof(Item), typeof(bool)), OpCodes.Brfalse ); var ldarg_0 = Instructions.Ldarg_0(); var Ldloc_object = AddItem[1]; var tail = AttachLabel(AddItem.FindNext( OpCodes.Ldarg_0, Instructions.Ldfld(typeof(Crop), nameof(Crop.regrowAfterHarvest)) )[0]); // Insert check for harvesting with scythe and act accordingly. AddItem.ReplaceJump(0, ldarg_0); AddItem.Prepend( // if (this.harvestMethod != 0) { ldarg_0, Instructions.Ldfld(typeof(Crop), nameof(Crop.harvestMethod)), Instructions.Call_get(typeof(NetInt), nameof(NetInt.Value)), Instructions.Brfalse(AttachLabel(AddItem[0])), // Game1.createItemDebris (@object, vector, -1, null, -1) Ldloc_object, // @object Instructions.Ldloc_S(var_vector), // vector Instructions.Ldc_I4_M1(), // -1 Instructions.Ldnull(), // null Instructions.Ldc_I4_M1(), // -1 Instructions.Call(typeof(Game1), nameof(Game1.createItemDebris), typeof(Item), typeof(Vector2), typeof(int), typeof(GameLocation), typeof(int)), Instructions.Pop(), // For SDV 1.4 // Jump to tail Instructions.Br(tail) ); }
void Crop_harvest() { // >>> Fix harvesting of spring onions. // Find the line: // if (Game1.player.addItemToInventoryBool (@object, false)) { var AddItem = FindCode( Instructions.Call_get(typeof(StardewValley.Game1), "player"), OpCodes.Ldloc_0, OpCodes.Ldc_I4_0, Instructions.Callvirt(typeof(StardewValley.Farmer), "addItemToInventoryBool", typeof(StardewValley.Item), typeof(bool)), OpCodes.Brfalse ); // Make jumps to the start of AddItem jump to the start of our new code instead. var ldarg0 = Instructions.Ldarg_0(); AddItem.ReplaceJump(0, ldarg0); // Insert check for harvesting with scythe and act accordingly. AddItem.Prepend( // if (this.harvestMethod != 0) { ldarg0, Instructions.Ldfld(typeof(StardewValley.Crop), "harvestMethod"), Instructions.Call_get(typeof(Netcode.NetInt), "Value"), // this.indexOfHarvest Instructions.Brfalse(AttachLabel(AddItem[0])), // Game1.createObjectDebris (@object.ParentSheetIndex, xTile, yTile, -1, @object.Quality, 1.0, null); Instructions.Ldloc_0(), Instructions.Callvirt_get(typeof(StardewValley.Item), "ParentSheetIndex"), // @object.ParentSheetIndex Instructions.Ldarg_1(), // xTile Instructions.Ldarg_2(), // yTile Instructions.Ldc_I4_M1(), // -1 Instructions.Ldloc_0(), Instructions.Callvirt_get(typeof(StardewValley.Object), "Quality"), // @object.Quality Instructions.Ldc_R4(1), // 1.0 Instructions.Ldnull(), // null Instructions.Call(typeof(StardewValley.Game1), "createObjectDebris", typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(float), typeof(StardewValley.GameLocation)), // Game1.player.gainExperience (2, howMuch); Instructions.Call_get(typeof(StardewValley.Game1), "player"), Instructions.Ldc_I4_2(), Instructions.Ldloc_1(), Instructions.Callvirt(typeof(StardewValley.Farmer), "gainExperience", typeof(int), typeof(int)), // return true Instructions.Ldc_I4_1(), Instructions.Ret() // } ); // >>> Patch code to drop sunflower seeds when harvesting with scythe. // Patch is configurable, so it can be disabled in case it breaks in the future. if (config.HarvestSeeds) { // Find tail of harvestMethod==1 branch var ScytheBranchTail = FindCode( OpCodes.Ldarg_0, Instructions.Ldfld(typeof(StardewValley.Crop), "harvestMethod"), OpCodes.Call, // Netcode OpCodes.Ldc_I4_1, OpCodes.Bne_Un ).Follow(4); // Select starting from the exp code. ScytheBranchTail.ExtendBackwards( Instructions.Ldsfld(typeof(StardewValley.Game1), "objectInformation"), OpCodes.Ldarg_0, Instructions.Ldfld(typeof(StardewValley.Crop), "indexOfHarvest"), OpCodes.Call, // Netcode OpCodes.Callvirt, OpCodes.Ldc_I4_1 ); // Monitor.Log(ScytheBranchTail.ToString()); if (ScytheBranchTail.length > 60) { throw new Exception("Too many operations in tail of harvestMethod branch"); } // Find the start of the 'drop sunflower seeds' part. var DropSunflowerSeeds = FindCode( OpCodes.Ldarg_0, Instructions.Ldfld(typeof(StardewValley.Crop), "indexOfHarvest"), OpCodes.Call, // Netcode Instructions.Ldc_I4(421), // 421 = Item ID of Sunflower. OpCodes.Bne_Un ); // Find the local variable that stores the amount being dropped. var DropAmount = DropSunflowerSeeds.FindNext( Instructions.Callvirt(typeof(System.Random), "Next", typeof(int), typeof(int)), OpCodes.Stloc_S ); // Rewrite the tail of the Scythe harvest branch. ScytheBranchTail.Replace( // Set num2 = 0. Instructions.Ldc_I4_0(), Instructions.Stloc_S((LocalBuilder)DropAmount[1].operand), // Jump to the 'drop subflower seeds' part. Instructions.Br(AttachLabel(DropSunflowerSeeds[0])) ); } }
/** * Patch code to drop sunflower seeds when harvesting with scythe. * Patch code to let harvesting with scythe drop only 1 item. * The other item drops are handled by the plucking code. */ void Crop_harvest_sunflower_drops(LocalBuilder var_quality) { // Remove start of loop var start_loop = FindCode( // for (int i = 0 OpCodes.Ldc_I4_0, OpCodes.Stloc_S, OpCodes.Br, // junimoHarvester != null Instructions.Ldarg_S(4), OpCodes.Brfalse ); // Get a reference to the 'i' variable. var var_i = start_loop[1].operand as LocalBuilder; // Remove the head of the loop. start_loop.length = 3; start_loop.Remove(); // Find the start of the 'drop sunflower seeds' part. var DropSunflowerSeeds = FindCode( OpCodes.Ldarg_0, Instructions.Ldfld(typeof(Crop), nameof(Crop.indexOfHarvest)), OpCodes.Call, // Netcode Instructions.Ldc_I4(421), // 421 = Item ID of Sunflower. OpCodes.Bne_Un ); // Set quality for seeds to 0. DropSunflowerSeeds.Append( Instructions.Ldc_I4_0(), Instructions.Stloc_S(var_quality) ); // Remove end of loop and everything after that until the end of the harvest==1 branch. var ScytheBranchTail = FindCode( OpCodes.Ldarg_0, Instructions.Ldfld(typeof(Crop), nameof(Crop.harvestMethod)), OpCodes.Call, // Netcode OpCodes.Ldc_I4_1, OpCodes.Bne_Un ).Follow(4); ScytheBranchTail.ExtendBackwards( Instructions.Ldloc_S(var_i), OpCodes.Ldc_I4_1, OpCodes.Add, Instructions.Stloc_S(var_i), Instructions.Ldloc_S(var_i), OpCodes.Ldloc_S, // num2 OpCodes.Blt ); // Change jump to end of loop into jump to drop sunflower seeds. ScytheBranchTail.ReplaceJump(0, DropSunflowerSeeds[0]); // Rewrite the tail of the Scythe harvest branch. ScytheBranchTail.Replace( // Jump to the 'drop subflower seeds' part. Instructions.Br(AttachLabel(DropSunflowerSeeds[0])) ); }
void Crop_harvest() { #region Fix vector Harmony.CodeInstruction ins = null; // Remove line (2x) // Vector2 vector = new Vector2 ((float)xTile, (float)yTile); for (int i = 0; i < 2; i++) { var vec = FindCode( OpCodes.Ldloca_S, OpCodes.Ldarg_1, OpCodes.Conv_R4, OpCodes.Ldarg_2, OpCodes.Conv_R4, OpCodes.Call ); ins = vec[5]; vec.Remove(); } // Add to begin of function // Vector2 vector = new Vector2 ((float)xTile*64., (float)yTile*64.); BeginCode().Append( Instructions.Ldloca_S(3), Instructions.Ldarg_1(), Instructions.Conv_R4(), Instructions.Ldc_R4(64), Instructions.Mul(), Instructions.Ldarg_2(), Instructions.Conv_R4(), Instructions.Ldc_R4(64), Instructions.Mul(), ins ); // Replace (4x): // from: new Vector2 (vector.X * 64f, vector.Y * 64f) // to: vector for (int i = 0; i < 4; i++) { FindCode( null, OpCodes.Ldfld, Instructions.Ldc_R4(64), OpCodes.Mul, null, OpCodes.Ldfld, Instructions.Ldc_R4(64), OpCodes.Mul, OpCodes.Newobj ).Replace( Instructions.Ldloc_3() // vector ); } #endregion #region Support harvesting of spring onions with scythe // Note: the branch // if (this.forageCrop) // refers mainly to the crop spring union. // Find the lines: var AddItem = FindCode( // if (Game1.player.addItemToInventoryBool (@object, false)) { Instructions.Call_get(typeof(Game1), nameof(Game1.player)), OpCodes.Ldloc_0, OpCodes.Ldc_I4_0, Instructions.Callvirt(typeof(Farmer), nameof(Farmer.addItemToInventoryBool), typeof(Item), typeof(bool)), OpCodes.Brfalse ); // Swap the lines (add '*64' to vector) & // Insert check for harvesting with scythe and act accordingly. AddItem.Prepend( // if (this.harvestMethod != 0) { Instructions.Ldarg_0(), Instructions.Ldfld(typeof(Crop), nameof(Crop.harvestMethod)), Instructions.Call_get(typeof(NetInt), nameof(NetInt.Value)), Instructions.Brfalse(AttachLabel(AddItem[0])), // Game1.createItemDebris (@object, vector, -1, null, -1) Instructions.Ldloc_0(), // @object Instructions.Ldloc_3(), // vector Instructions.Ldc_I4_M1(), // -1 Instructions.Ldnull(), // null Instructions.Ldc_I4_M1(), // -1 Instructions.Call(typeof(Game1), nameof(Game1.createItemDebris), typeof(Item), typeof(Vector2), typeof(int), typeof(GameLocation), typeof(int)), // Game1.player.gainExperience (2, howMuch); Instructions.Call_get(typeof(Game1), nameof(Game1.player)), Instructions.Ldc_I4_2(), Instructions.Ldloc_1(), Instructions.Callvirt(typeof(Farmer), nameof(Farmer.gainExperience), typeof(int), typeof(int)), // return true Instructions.Ldc_I4_1(), Instructions.Ret() // } ); #endregion #region Sunflower drops // >>> Patch code to drop sunflower seeds when harvesting with scythe. // >>> Patch code to let harvesting with scythe drop only 1 item. // >>> The other item drops are handled by the plucking code. // Remove start of loop var start_loop = FindCode( // for (int i = 0 OpCodes.Ldc_I4_0, OpCodes.Stloc_S, OpCodes.Br, // junimoHarvester != null Instructions.Ldarg_S(4), OpCodes.Brfalse ); // Get a reference to the 'i' variable. var var_i = (LocalBuilder)start_loop[1].operand; // Remove the head of the loop. start_loop.length = 3; start_loop.Remove(); // Find the start of the 'drop sunflower seeds' part. var DropSunflowerSeeds = FindCode( OpCodes.Ldarg_0, Instructions.Ldfld(typeof(Crop), nameof(Crop.indexOfHarvest)), OpCodes.Call, // Netcode Instructions.Ldc_I4(421), // 421 = Item ID of Sunflower. OpCodes.Bne_Un ); // Set quality for seeds to 0. DropSunflowerSeeds.Append( Instructions.Ldc_I4_0(), Instructions.Stloc_S(5) ); // Remove end of loop and everything after that until the end of the harvest==1 branch. var ScytheBranchTail = FindCode( OpCodes.Ldarg_0, Instructions.Ldfld(typeof(Crop), nameof(Crop.harvestMethod)), OpCodes.Call, // Netcode OpCodes.Ldc_I4_1, OpCodes.Bne_Un ).Follow(4); ScytheBranchTail.ExtendBackwards( Instructions.Ldloc_S(var_i), OpCodes.Ldc_I4_1, OpCodes.Add, Instructions.Stloc_S(var_i), Instructions.Ldloc_S(var_i), Instructions.Ldloc_S(4), OpCodes.Blt ); // Change jump to end of loop into jump to drop sunflower seeds. ScytheBranchTail.ReplaceJump(0, DropSunflowerSeeds[0]); // Rewrite the tail of the Scythe harvest branch. ScytheBranchTail.Replace( // Jump to the 'drop subflower seeds' part. Instructions.Br(AttachLabel(DropSunflowerSeeds[0])) ); #endregion #region Colored flowers // For colored flowers we need to call createItemDebris instead of createObjectDebris FindCode( // Game1.createObjectDebris (indexOfHarvest, xTile, yTile, -1, num3, 1f, null); OpCodes.Ldarg_0, OpCodes.Ldfld, OpCodes.Call, OpCodes.Ldarg_1, OpCodes.Ldarg_2, OpCodes.Ldc_I4_M1, OpCodes.Ldloc_S, OpCodes.Ldc_R4, OpCodes.Ldnull, OpCodes.Call ).Replace( // var tmp = CreateObject(this, num3); Instructions.Ldarg_0(), // this Instructions.Ldloc_S(5), // num3 Instructions.Call(typeof(ModEntry), nameof(CreateObject), typeof(Crop), typeof(int)), // Game1.createItemDebris(tmp, vector, -1, null, -1); Instructions.Ldloc_3(), // vector Instructions.Ldc_I4_M1(), // -1 Instructions.Ldnull(), // null Instructions.Ldc_I4_M1(), // -1 Instructions.Call(typeof(Game1), nameof(Game1.createItemDebris), typeof(Item), typeof(Vector2), typeof(int), typeof(GameLocation), typeof(int)) ); #endregion if (config.AllHaveQuality) { // Patch function calls for additional harvest to pass on the harvest quality. FindCode( OpCodes.Ldc_I4_M1, OpCodes.Ldc_I4_0, Instructions.Ldc_R4(1.0f), OpCodes.Ldnull, Instructions.Call(typeof(Game1), nameof(Game1.createObjectDebris), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(float), typeof(GameLocation)) )[1] = Instructions.Ldloc_S(5); FindCode( OpCodes.Ldc_I4_1, OpCodes.Ldc_I4_0, OpCodes.Ldc_I4_M1, OpCodes.Ldc_I4_0, OpCodes.Newobj, Instructions.Callvirt(typeof(JunimoHarvester), nameof(JunimoHarvester.tryToAddItemToHut), typeof(Item)) )[3] = Instructions.Ldloc_S(5); } if (!config.ScytheHarvestFlowers) { var lbl = AttachLabel(instructions[0]); BeginCode().Append( // if (harvestMethod==1 && programColored) { Instructions.Ldarg_0(), Instructions.Ldfld(typeof(Crop), nameof(Crop.harvestMethod)), Instructions.Call_get(typeof(NetInt), nameof(NetInt.Value)), Instructions.Brfalse(lbl), Instructions.Ldarg_0(), Instructions.Ldfld(typeof(Crop), nameof(Crop.programColored)), Instructions.Call_get(typeof(NetBool), nameof(NetBool.Value)), Instructions.Brfalse(lbl), // return false Instructions.Ldc_I4_0(), Instructions.Ret() // } ); } }
// Support harvesting of spring onions with scythe private void Crop_harvest_support_spring_onion(LocalBuilder var_vector) { if (config.HarvestMode.SpringOnion == HarvestModeEnum.HAND) { return; } // Note: the branch // if (this.forageCrop) // refers mainly to the crop spring union. InstructionMatcher addItemToInventoryBool = Instructions.Callvirt(typeof(Farmer), nameof(Farmer.addItemToInventoryBool), typeof(Item), typeof(bool)); if (helper.ModRegistry.IsLoaded("spacechase0.MoreRings")) { try { addItemToInventoryBool = InstructionMatcher.AnyOf( addItemToInventoryBool, Instructions.Callvirt(AccessTools.TypeByName("MoreRings.Patches.CropPatcher"), "Farmer_AddItemToInventoryBool", typeof(Farmer), typeof(Item), typeof(bool)) ); } catch (Exception e) { Monitor.Log("The More Rings mod was loaded, but MoreRings.Patches.CropPatcher.Farmer_AddItemToInventoryBool(...) was not found."); LogException(e, LogLevel.Warn); } } // Find the lines: InstructionRange AddItem; AddItem = FindCode( // if (Game1.player.addItemToInventoryBool (@object, false)) { Instructions.Call_get(typeof(Game1), nameof(Game1.player)), InstructionMatcher.AnyOf( // @object OpCodes.Ldloc_0, OpCodes.Ldloc_1 ), OpCodes.Ldc_I4_0, addItemToInventoryBool, OpCodes.Brfalse ); var ldarg_0 = Instructions.Ldarg_0(); var Ldloc_object = AddItem[1]; var tail = AttachLabel(AddItem.FindNext( OpCodes.Ldarg_0, Instructions.Ldfld(typeof(Crop), nameof(Crop.regrowAfterHarvest)) )[0]); // Insert check for harvesting with scythe and act accordingly. AddItem.ReplaceJump(0, ldarg_0); AddItem.Prepend( // if (this.harvestMethod != 0) { ldarg_0, Instructions.Ldfld(typeof(Crop), nameof(Crop.harvestMethod)), Instructions.Call_get(typeof(NetInt), nameof(NetInt.Value)), Instructions.Brfalse(AttachLabel(AddItem[0])), // Game1.createItemDebris (@object, vector, -1, null, -1) Ldloc_object, // @object Instructions.Ldloc_S(var_vector), // vector Instructions.Ldc_I4_M1(), // -1 Instructions.Ldnull(), // null Instructions.Ldc_I4_M1(), // -1 Instructions.Call(typeof(Game1), nameof(Game1.createItemDebris), typeof(Item), typeof(Vector2), typeof(int), typeof(GameLocation), typeof(int)), Instructions.Pop(), // For SDV 1.4 // Jump to tail Instructions.Br(tail) ); }