diff --git a/dependencies.gradle b/dependencies.gradle index 393f18c83..692c5384c 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -125,7 +125,8 @@ final Map> mod_dependencies = [ 'thermal_foundation-222880:2926428' : [debug_thermal_expansion], 'tinkers-complement-272671:2843439' : [debug_tinkers_construct], 'tinkers_construct-74072:2902483' : [debug_tinkers_construct], - 'woot-244049:2712670' : [debug_woot], + 'railcraft-51195:3853491' : [debug_railcraft], + 'woot-244049:2712670' : [debug_woot], ] // Maps mods from CurseMaven to the properties that enable the mod. diff --git a/examples/postInit/generated/railcraft_generated.groovy b/examples/postInit/generated/railcraft_generated.groovy new file mode 100644 index 000000000..35bea5f93 --- /dev/null +++ b/examples/postInit/generated/railcraft_generated.groovy @@ -0,0 +1,92 @@ + +// Auto generated groovyscript example file +// MODS_LOADED: railcraft + +log 'mod \'railcraft\' detected, running script' + +// groovyscript.wiki.railcraft.blast_furnace.title: +// groovyscript.wiki.railcraft.blast_furnace.description. + +mods.railcraft.blast_furnace.removeByInput(item('minecraft:iron_ingot')) +mods.railcraft.blast_furnace.removeByOutput(item('railcraft:ingot:1')) +// mods.railcraft.blast_furnace.removeAll() + +mods.railcraft.blast_furnace.recipeBuilder() + .input(item('minecraft:iron_ingot')) + .output(item('railcraft:ingot:1')) + .time(1280) + .slag(1) + .register() + + +// groovyscript.wiki.railcraft.coke_oven.title: +// groovyscript.wiki.railcraft.coke_oven.description. + +mods.railcraft.coke_oven.removeByInput(item('minecraft:log')) +mods.railcraft.coke_oven.removeByOutput(item('railcraft:fuel_coke')) +// mods.railcraft.coke_oven.removeAll() + +mods.railcraft.coke_oven.recipeBuilder() + .input(item('minecraft:log')) + .output(item('railcraft:fuel_coke')) + .fluidOutput(fluid('creosote') * 500) + .time(1800) + .register() + + +// groovyscript.wiki.railcraft.fluid_fuels.title: +// groovyscript.wiki.railcraft.fluid_fuels.description. + +mods.railcraft.fluid_fuels.remove(fluid('creosote')) +// mods.railcraft.fluid_fuels.removeAll() + +mods.railcraft.fluid_fuels.add(fluid('lava'), 32000) + +// groovyscript.wiki.railcraft.rock_crusher.title: +// groovyscript.wiki.railcraft.rock_crusher.description. + +mods.railcraft.rock_crusher.removeByInput(item('minecraft:stone')) +mods.railcraft.rock_crusher.removeByOutput(item('minecraft:cobblestone')) +// mods.railcraft.rock_crusher.removeAll() + +mods.railcraft.rock_crusher.recipeBuilder() + .input(item('minecraft:stone')) + .output(item('minecraft:cobblestone'), 1.0) + .output(item('minecraft:sand'), 0.5) + .time(200) + .register() + + +// groovyscript.wiki.railcraft.rolling_machine.title: +// groovyscript.wiki.railcraft.rolling_machine.description. + +mods.railcraft.rolling_machine.removeByOutput(item('minecraft:tripwire_hook')) +// mods.railcraft.rolling_machine.removeAll() + +mods.railcraft.rolling_machine.shapedBuilder() + .output(item('minecraft:stone')) + .matrix('BXX', + 'X B') + .key('B', item('minecraft:stone')) + .key('X', item('minecraft:gold_ingot')) + .time(200) + + +mods.railcraft.rolling_machine.shapedBuilder() + .output(item('minecraft:diamond') * 32) + .matrix([[item('minecraft:gold_ingot'), item('minecraft:gold_ingot'), item('minecraft:gold_ingot')], + [item('minecraft:gold_ingot'), item('minecraft:gold_ingot'), item('minecraft:gold_ingot')], + [item('minecraft:gold_ingot'), item('minecraft:gold_ingot'), item('minecraft:gold_ingot')]]) + .time(400) + + +mods.railcraft.rolling_machine.shapelessBuilder() + .output(item('minecraft:clay') * 8) + .input(item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone')) + .time(100) + + +mods.railcraft.rolling_machine.shapelessBuilder() + .output(item('minecraft:diamond') * 32) + .input(item('minecraft:gold_ingot') * 9) + .time(500) diff --git a/gradle.properties b/gradle.properties index 5936cf836..249ee6acf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -97,6 +97,7 @@ debug_primal_tech = false debug_prodigytech = false debug_projecte = false debug_pyrotech = false +debug_railcraft = false debug_random_things = false debug_roots = false diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/ModSupport.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/ModSupport.java index 5054e1186..168a9104f 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/mods/ModSupport.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/ModSupport.java @@ -62,6 +62,7 @@ import com.cleanroommc.groovyscript.compat.mods.prodigytech.ProdigyTech; import com.cleanroommc.groovyscript.compat.mods.projecte.ProjectE; import com.cleanroommc.groovyscript.compat.mods.pyrotech.PyroTech; +import com.cleanroommc.groovyscript.compat.mods.railcraft.Railcraft; import com.cleanroommc.groovyscript.compat.mods.randomthings.RandomThings; import com.cleanroommc.groovyscript.compat.mods.roots.Roots; import com.cleanroommc.groovyscript.compat.mods.rustic.Rustic; @@ -156,6 +157,7 @@ public class ModSupport { public static final GroovyContainer PRODIGY_TECH = new InternalModContainer<>("prodigytech", "Prodigy Tech", ProdigyTech::new); public static final GroovyContainer PROJECT_E = new InternalModContainer<>("projecte", "ProjectE", ProjectE::new); public static final GroovyContainer PYROTECH = new InternalModContainer<>("pyrotech", "Pyrotech", PyroTech::new); + public static final GroovyContainer RAILCRAFT = new InternalModContainer<>("railcraft", "Railcraft", Railcraft::new, "rc"); public static final GroovyContainer RANDOM_THINGS = new InternalModContainer<>("randomthings", "Random Things", RandomThings::new); public static final GroovyContainer ROOTS = new InternalModContainer<>("roots", "Roots 3", Roots::new); public static final GroovyContainer RUSTIC = new InternalModContainer<>("rustic", "Rustic", Rustic::new); diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/railcraft/BlastFurnace.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/railcraft/BlastFurnace.java new file mode 100644 index 000000000..a2f698356 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/railcraft/BlastFurnace.java @@ -0,0 +1,170 @@ +package com.cleanroommc.groovyscript.compat.mods.railcraft; + +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.*; +import com.cleanroommc.groovyscript.compat.mods.ModSupport; +import com.cleanroommc.groovyscript.helper.ingredient.IngredientHelper; +import com.cleanroommc.groovyscript.helper.recipe.AbstractRecipeBuilder; +import com.cleanroommc.groovyscript.registry.StandardListRegistry; +import mods.railcraft.api.crafting.Crafters; +import mods.railcraft.api.crafting.IBlastFurnaceCrafter; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.Ingredient; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +@RegistryDescription +public class BlastFurnace extends StandardListRegistry { + + @RecipeBuilderDescription(example = @Example(".input(item('minecraft:iron_ingot')).output(item('railcraft:ingot:1')).time(1280).slag(1)")) + public static RecipeBuilder recipeBuilder() { + return new RecipeBuilder(); + } + + @Override + public Collection getRecipes() { + return Crafters.blastFurnace().getRecipes(); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public IBlastFurnaceCrafter.IRecipe add(IIngredient input, ItemStack output, int time, int slag) { + RecipeBuilder builder = recipeBuilder(); + builder.input(input); + builder.output(output); + builder.time = time; + builder.slag = slag; + return builder.register(); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public IBlastFurnaceCrafter.IRecipe add(IIngredient input, ItemStack output, int time) { + return add(input, output, time, 1); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public IBlastFurnaceCrafter.IRecipe add(IIngredient input, ItemStack output) { + return add(input, output, IBlastFurnaceCrafter.SMELT_TIME, 1); + } + + @MethodDescription(example = @Example("item('railcraft:ingot:1')")) + public void removeByOutput(ItemStack output) { + if (IngredientHelper.isEmpty(output)) { + GroovyLog.msg("Error removing Railcraft Blast Furnace recipe") + .error() + .add("output must not be empty") + .post(); + return; + } + if (!getRecipes().removeIf(recipe -> { + if (ItemStack.areItemStacksEqual(recipe.getOutput(), output)) { + addBackup(recipe); + return true; + } + return false; + })) { + GroovyLog.msg("Error removing Railcraft Blast Furnace recipe") + .error() + .add("no recipes found for {}", output) + .post(); + } + } + + @MethodDescription(example = @Example("item('minecraft:iron_ingot')")) + public void removeByInput(IIngredient input) { + if (IngredientHelper.isEmpty(input)) { + GroovyLog.msg("Error removing Railcraft Blast Furnace recipe") + .error() + .add("input must not be empty") + .post(); + return; + } + if (!getRecipes().removeIf(recipe -> { + if (recipe.getInput().test(input.getMatchingStacks()[0])) { + addBackup(recipe); + return true; + } + return false; + })) { + GroovyLog.msg("Error removing Railcraft Blast Furnace recipe") + .error() + .add("no recipes found for {}", input) + .post(); + } + } + + @Property(property = "input", comp = @Comp(eq = 1)) + @Property(property = "output", comp = @Comp(eq = 1)) + public static class RecipeBuilder extends AbstractRecipeBuilder { + + @Property(comp = @Comp(gte = 0), defaultValue = "IBlastFurnaceCrafter.SMELT_TIME") + private int time = IBlastFurnaceCrafter.SMELT_TIME; + @Property(comp = @Comp(gte = 0), defaultValue = "1") + private int slag = 1; + + @RecipeBuilderMethodDescription + public RecipeBuilder time(int time) { + this.time = time; + return this; + } + + @RecipeBuilderMethodDescription + public RecipeBuilder slag(int slag) { + this.slag = slag; + return this; + } + + @Override + public String getErrorMsg() { + return "Error adding Railcraft Blast Furnace recipe"; + } + + @Override + public void validate(GroovyLog.Msg msg) { + validateItems(msg, 1, 1, 1, 1); + validateFluids(msg); + msg.add(time <= 0, "time must be greater than 0, got: {}", time); + msg.add(slag < 0, "slag must be non-negative, got: {}", slag); + } + + @Override + @RecipeBuilderRegistrationMethod + public @Nullable IBlastFurnaceCrafter.IRecipe register() { + if (!validate()) return null; + ItemStack outputStack = output.get(0); + Ingredient inputIngredient = input.get(0).toMcIngredient(); + + IBlastFurnaceCrafter.IRecipe recipe = new IBlastFurnaceCrafter.IRecipe() { + + @Override + public net.minecraft.util.ResourceLocation getName() { + return new net.minecraft.util.ResourceLocation("groovyscript", "blastfurnace_" + System.currentTimeMillis()); + } + + @Override + public Ingredient getInput() { + return inputIngredient; + } + + @Override + public int getTickTime(ItemStack input) { + return time; + } + + @Override + public ItemStack getOutput() { + return outputStack.copy(); + } + + @Override + public int getSlagOutput() { + return slag; + } + }; + + ModSupport.RAILCRAFT.get().blastFurnace.add(recipe); + return recipe; + } + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/railcraft/CokeOven.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/railcraft/CokeOven.java new file mode 100644 index 000000000..bd1156a12 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/railcraft/CokeOven.java @@ -0,0 +1,175 @@ +package com.cleanroommc.groovyscript.compat.mods.railcraft; + +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.*; +import com.cleanroommc.groovyscript.compat.mods.ModSupport; +import com.cleanroommc.groovyscript.helper.ingredient.IngredientHelper; +import com.cleanroommc.groovyscript.helper.recipe.AbstractRecipeBuilder; +import com.cleanroommc.groovyscript.registry.StandardListRegistry; +import mods.railcraft.api.crafting.Crafters; +import mods.railcraft.api.crafting.ICokeOvenCrafter; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.Ingredient; +import net.minecraftforge.fluids.FluidStack; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +@RegistryDescription +public class CokeOven extends StandardListRegistry { + + @RecipeBuilderDescription(example = @Example(".input(item('minecraft:log')).output(item('railcraft:fuel_coke')).fluidOutput(fluid('creosote') * 500).time(1800)")) + public static RecipeBuilder recipeBuilder() { + return new RecipeBuilder(); + } + + @Override + public Collection getRecipes() { + return Crafters.cokeOven().getRecipes(); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public ICokeOvenCrafter.IRecipe add(IIngredient input, ItemStack output, FluidStack fluidOutput, int time) { + RecipeBuilder builder = recipeBuilder(); + builder.input(input); + builder.output(output); + if (fluidOutput != null) { + builder.fluidOutput(fluidOutput); + } + builder.time = time; + return builder.register(); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public ICokeOvenCrafter.IRecipe add(IIngredient input, ItemStack output, FluidStack fluidOutput) { + return add(input, output, fluidOutput, ICokeOvenCrafter.DEFAULT_COOK_TIME); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public ICokeOvenCrafter.IRecipe add(IIngredient input, ItemStack output, int time) { + return add(input, output, null, time); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public ICokeOvenCrafter.IRecipe add(IIngredient input, ItemStack output) { + return add(input, output, null, ICokeOvenCrafter.DEFAULT_COOK_TIME); + } + + @MethodDescription(example = @Example("item('railcraft:fuel_coke')")) + public void removeByOutput(ItemStack output) { + if (IngredientHelper.isEmpty(output)) { + GroovyLog.msg("Error removing Railcraft Coke Oven recipe") + .error() + .add("output must not be empty") + .post(); + return; + } + if (!getRecipes().removeIf(recipe -> { + if (ItemStack.areItemStacksEqual(recipe.getOutput(), output)) { + addBackup(recipe); + return true; + } + return false; + })) { + GroovyLog.msg("Error removing Railcraft Coke Oven recipe") + .error() + .add("no recipes found for {}", output) + .post(); + } + } + + @MethodDescription(example = @Example("item('minecraft:log')")) + public void removeByInput(IIngredient input) { + if (IngredientHelper.isEmpty(input)) { + GroovyLog.msg("Error removing Railcraft Coke Oven recipe") + .error() + .add("input must not be empty") + .post(); + return; + } + if (!getRecipes().removeIf(recipe -> { + if (recipe.getInput().test(input.getMatchingStacks()[0])) { + addBackup(recipe); + return true; + } + return false; + })) { + GroovyLog.msg("Error removing Railcraft Coke Oven recipe") + .error() + .add("no recipes found for {}", input) + .post(); + } + } + + @Property(property = "input", comp = @Comp(eq = 1)) + @Property(property = "output", comp = @Comp(eq = 1)) + public static class RecipeBuilder extends AbstractRecipeBuilder { + + @Property(comp = @Comp(gte = 0), defaultValue = "ICokeOvenCrafter.DEFAULT_COOK_TIME") + private int time = ICokeOvenCrafter.DEFAULT_COOK_TIME; + + @RecipeBuilderMethodDescription + public RecipeBuilder time(int time) { + this.time = time; + return this; + } + + @Override + protected int getMaxItemInput() { + return 1; + } + + @Override + public String getErrorMsg() { + return "Error adding Railcraft Coke Oven recipe"; + } + + @Override + public void validate(GroovyLog.Msg msg) { + validateItems(msg, 1, 1, 1, 1); + validateFluids(msg, 0, 0, 0, 1); + msg.add(time <= 0, "time must be greater than 0, got: {}", time); + } + + @Override + @RecipeBuilderRegistrationMethod + public @Nullable ICokeOvenCrafter.IRecipe register() { + if (!validate()) return null; + ItemStack outputStack = output.get(0); + Ingredient inputIngredient = input.get(0).toMcIngredient(); + FluidStack fluidCopy = fluidOutput.isEmpty() ? null : fluidOutput.get(0).copy(); + + ICokeOvenCrafter.IRecipe recipe = new ICokeOvenCrafter.IRecipe() { + + @Override + public net.minecraft.util.ResourceLocation getName() { + return new net.minecraft.util.ResourceLocation("groovyscript", "cokeoven_" + System.currentTimeMillis()); + } + + @Override + public Ingredient getInput() { + return inputIngredient; + } + + @Override + public int getTickTime(ItemStack input) { + return time; + } + + @Override + public @Nullable FluidStack getFluidOutput() { + return fluidCopy != null ? fluidCopy.copy() : null; + } + + @Override + public ItemStack getOutput() { + return outputStack.copy(); + } + }; + + ModSupport.RAILCRAFT.get().cokeOven.add(recipe); + return recipe; + } + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/railcraft/FluidFuels.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/railcraft/FluidFuels.java new file mode 100644 index 000000000..74adf46b3 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/railcraft/FluidFuels.java @@ -0,0 +1,136 @@ +package com.cleanroommc.groovyscript.compat.mods.railcraft; + +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.documentation.annotations.*; +import com.cleanroommc.groovyscript.helper.ingredient.IngredientHelper; +import com.cleanroommc.groovyscript.registry.VirtualizedRegistry; +import mods.railcraft.api.fuel.FluidFuelManager; +import net.minecraftforge.fluids.FluidStack; + +@RegistryDescription +public class FluidFuels extends VirtualizedRegistry { + + @Override + public void onReload() { + // Remove scripted (disable them by setting heat to 0) + removeScripted().forEach(entry -> { + try { + // Disable the fuel by setting heat value to 0 + FluidFuelManager.addFuel(entry.fluid, 0); + } catch (Exception e) { + GroovyLog.msg("Error removing Railcraft Fluid Fuel") + .error() + .add("fluid: {}", entry.fluid.getFluid().getName()) + .add("exception: {}", e.getMessage()) + .post(); + } + }); + + // Restore from backup + restoreFromBackup().forEach(entry -> { + try { + FluidFuelManager.addFuel(entry.fluid, entry.heatValue); + } catch (Exception e) { + GroovyLog.msg("Error restoring Railcraft Fluid Fuel") + .error() + .add("fluid: {}", entry.fluid.getFluid().getName()) + .add("heat value: {}", entry.heatValue) + .add("exception: {}", e.getMessage()) + .post(); + } + }); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION, example = @Example("fluid('lava'), 32000")) + public void add(FluidStack fuel, int heatValue) { + if (IngredientHelper.isEmpty(fuel)) { + GroovyLog.msg("Error adding Railcraft Fluid Fuel") + .error() + .add("fuel must not be empty") + .post(); + return; + } + if (heatValue <= 0) { + GroovyLog.msg("Error adding Railcraft Fluid Fuel") + .error() + .add("fluid: {}", fuel.getFluid().getName()) + .add("heat value: {} (must be > 0)", heatValue) + .post(); + return; + } + + // Store the original value for backup before adding + int originalValue = FluidFuelManager.getFuelValue(fuel); + if (originalValue > 0) { + addBackup(new FuelEntry(fuel.copy(), originalValue)); + } + + addScripted(new FuelEntry(fuel.copy(), heatValue)); + + try { + FluidFuelManager.addFuel(fuel, heatValue); + } catch (Exception e) { + GroovyLog.msg("Error adding Railcraft Fluid Fuel") + .error() + .add("fluid: {}", fuel.getFluid().getName()) + .add("heat value: {}", heatValue) + .add("exception: {}", e.getMessage()) + .post(); + } + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public void add(FluidStack fuel) { + add(fuel, 32000); // Default heat value + } + + @MethodDescription(example = @Example("fluid('creosote')")) + public void remove(FluidStack fuel) { + if (IngredientHelper.isEmpty(fuel)) { + GroovyLog.msg("Error removing Railcraft Fluid Fuel") + .error() + .add("fuel must not be empty") + .post(); + return; + } + + // Store current value for backup + int currentValue = FluidFuelManager.getFuelValue(fuel); + if (currentValue > 0) { + addBackup(new FuelEntry(fuel.copy(), currentValue)); + } + + // Note: Railcraft's FluidFuelManager doesn't have a direct remove method + // We can only add with 0 heat value to effectively disable it + try { + FluidFuelManager.addFuel(fuel, 0); + } catch (Exception e) { + GroovyLog.msg("Error removing Railcraft Fluid Fuel") + .error() + .add("fluid: {}", fuel.getFluid().getName()) + .add("exception: {}", e.getMessage()) + .post(); + } + } + + @MethodDescription(priority = 2000, example = @Example(commented = true)) + public void removeAll() { + // Note: This is a limitation - we can't truly remove all without reflection + // This method is provided for API compatibility but may not work as expected + GroovyLog.msg("Warning: Railcraft FluidFuels.removeAll() may not work as expected") + .warn() + .add("Railcraft doesn't provide a way to clear all fluid fuels") + .post(); + } + + public static class FuelEntry { + + public final FluidStack fluid; + public final int heatValue; + + public FuelEntry(FluidStack fluid, int heatValue) { + this.fluid = fluid; + this.heatValue = heatValue; + } + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/railcraft/Railcraft.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/railcraft/Railcraft.java new file mode 100644 index 000000000..0aa729ad8 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/railcraft/Railcraft.java @@ -0,0 +1,12 @@ +package com.cleanroommc.groovyscript.compat.mods.railcraft; + +import com.cleanroommc.groovyscript.compat.mods.GroovyPropertyContainer; + +public class Railcraft extends GroovyPropertyContainer { + + public final BlastFurnace blastFurnace = new BlastFurnace(); + public final CokeOven cokeOven = new CokeOven(); + public final RockCrusher rockCrusher = new RockCrusher(); + public final RollingMachine rollingMachine = new RollingMachine(); + public final FluidFuels fluidFuels = new FluidFuels(); +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/railcraft/RockCrusher.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/railcraft/RockCrusher.java new file mode 100644 index 000000000..01818c64a --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/railcraft/RockCrusher.java @@ -0,0 +1,257 @@ +package com.cleanroommc.groovyscript.compat.mods.railcraft; + +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.*; +import com.cleanroommc.groovyscript.compat.mods.ModSupport; +import com.cleanroommc.groovyscript.helper.ingredient.IngredientHelper; +import com.cleanroommc.groovyscript.helper.recipe.AbstractRecipeBuilder; +import com.cleanroommc.groovyscript.registry.StandardListRegistry; +import mods.railcraft.api.crafting.Crafters; +import mods.railcraft.api.crafting.IGenRule; +import mods.railcraft.api.crafting.IOutputEntry; +import mods.railcraft.api.crafting.IRockCrusherCrafter; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.Ingredient; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextComponentString; +import org.jetbrains.annotations.Nullable; + +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +@RegistryDescription +public class RockCrusher extends StandardListRegistry { + + @RecipeBuilderDescription(example = @Example(".input(item('minecraft:stone')).output(item('minecraft:cobblestone'), 1.0).output(item('minecraft:sand'), 0.5).time(200)")) + public static RecipeBuilder recipeBuilder() { + return new RecipeBuilder(); + } + + @Override + public Collection getRecipes() { + return Crafters.rockCrusher().getRecipes(); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public IRockCrusherCrafter.IRecipe add(IIngredient input, List outputs, List chances, int time) { + if (time <= 0) { + GroovyLog.msg("Error adding Railcraft Rock Crusher recipe") + .error() + .add("time must be greater than 0, got: {}", time) + .post(); + return null; + } + if (outputs == null || outputs.isEmpty()) { + GroovyLog.msg("Error adding Railcraft Rock Crusher recipe") + .error() + .add("outputs must not be empty") + .post(); + return null; + } + if (chances == null || chances.isEmpty()) { + GroovyLog.msg("Error adding Railcraft Rock Crusher recipe") + .error() + .add("chances must not be empty") + .post(); + return null; + } + RecipeBuilder builder = recipeBuilder(); + builder.input(input); + builder.time = time; + for (int i = 0; i < outputs.size() && i < chances.size(); i++) { + builder.output(outputs.get(i), chances.get(i)); + } + return builder.register(); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public IRockCrusherCrafter.IRecipe add(IIngredient input, List outputs, List chances) { + return add(input, outputs, chances, IRockCrusherCrafter.PROCESS_TIME); + } + + @MethodDescription(example = @Example("item('minecraft:cobblestone')")) + public void removeByOutput(IIngredient output) { + if (IngredientHelper.isEmpty(output)) { + GroovyLog.msg("Error removing Railcraft Rock Crusher recipe") + .error() + .add("output must not be empty") + .post(); + return; + } + ItemStack outputStack = output.getMatchingStacks()[0]; + if (!getRecipes().removeIf(recipe -> { + for (IOutputEntry entry : recipe.getOutputs()) { + if (ItemStack.areItemStacksEqual(entry.getOutput(), outputStack)) { + addBackup(recipe); + return true; + } + } + return false; + })) { + GroovyLog.msg("Error removing Railcraft Rock Crusher recipe") + .error() + .add("no recipes found for {}", output) + .post(); + } + } + + @MethodDescription(example = @Example("item('minecraft:stone')")) + public void removeByInput(IIngredient input) { + if (IngredientHelper.isEmpty(input)) { + GroovyLog.msg("Error removing Railcraft Rock Crusher recipe") + .error() + .add("input must not be empty") + .post(); + return; + } + if (!getRecipes().removeIf(recipe -> { + if (recipe.getInput().test(input.getMatchingStacks()[0])) { + addBackup(recipe); + return true; + } + return false; + })) { + GroovyLog.msg("Error removing Railcraft Rock Crusher recipe") + .error() + .add("no recipes found for {}", input) + .post(); + } + } + + @Property(property = "input", comp = @Comp(eq = 1)) + public static class RecipeBuilder extends AbstractRecipeBuilder { + + @Property(comp = @Comp(gte = 0), defaultValue = "IRockCrusherCrafter.PROCESS_TIME") + private int time = IRockCrusherCrafter.PROCESS_TIME; + private final List outputs = new ArrayList<>(); + + @RecipeBuilderMethodDescription + public RecipeBuilder time(int time) { + this.time = time; + return this; + } + + @RecipeBuilderMethodDescription + public RecipeBuilder output(ItemStack output, float chance) { + if (!IngredientHelper.isEmpty(output)) { + outputs.add(new OutputEntry(output.copy(), new RandomChanceGenRule(chance))); + } + return this; + } + + @RecipeBuilderMethodDescription + public RecipeBuilder output(ItemStack output, IGenRule rule) { + if (!IngredientHelper.isEmpty(output)) { + outputs.add(new OutputEntry(output.copy(), rule)); + } + return this; + } + + @Override + protected int getMaxItemInput() { + return 1; + } + + @Override + public String getErrorMsg() { + return "Error adding Railcraft Rock Crusher recipe"; + } + + @Override + public void validate(GroovyLog.Msg msg) { + validateItems(msg, 1, 1, 0, 0); + validateFluids(msg); + if (time <= 0) { + msg.add("time must be greater than 0, got: {}", time); + time = IRockCrusherCrafter.PROCESS_TIME; + } + if (outputs.isEmpty()) { + msg.add("At least one output must be defined"); + } + } + + @Override + @RecipeBuilderRegistrationMethod + public @Nullable IRockCrusherCrafter.IRecipe register() { + if (!validate()) return null; + Ingredient inputIngredient = input.get(0).toMcIngredient(); + List outputsCopy = new ArrayList<>(outputs); + + IRockCrusherCrafter.IRecipe recipe = new IRockCrusherCrafter.IRecipe() { + + @Override + public ResourceLocation getName() { + return new ResourceLocation("groovyscript", "rockcrusher_" + System.currentTimeMillis()); + } + + @Override + public Ingredient getInput() { + return inputIngredient; + } + + @Override + public List getOutputs() { + return outputsCopy; + } + + @Override + public int getTickTime(ItemStack input) { + return time; + } + }; + + ModSupport.RAILCRAFT.get().rockCrusher.add(recipe); + return recipe; + } + } + + private static class RandomChanceGenRule implements IGenRule { + + private final float randomChance; + private List toolTip; + + RandomChanceGenRule(float randomChance) { + this.randomChance = randomChance; + } + + @Override + public boolean test(Random random) { + return random.nextFloat() < randomChance; + } + + @Override + public List getToolTip() { + if (toolTip == null) { + toolTip = Collections.singletonList(new TextComponentString(new DecimalFormat("(###.###% chance)").format(randomChance))); + } + return toolTip; + } + } + + private static class OutputEntry implements IOutputEntry { + + private final ItemStack output; + private final IGenRule genRule; + + OutputEntry(ItemStack output, IGenRule genRule) { + this.output = output.copy(); + this.genRule = genRule; + } + + @Override + public ItemStack getOutput() { + return output.copy(); + } + + @Override + public IGenRule getGenRule() { + return genRule; + } + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/railcraft/RollingMachine.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/railcraft/RollingMachine.java new file mode 100644 index 000000000..64751d2c3 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/railcraft/RollingMachine.java @@ -0,0 +1,469 @@ +package com.cleanroommc.groovyscript.compat.mods.railcraft; + +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.*; +import com.cleanroommc.groovyscript.compat.mods.ModSupport; +import com.cleanroommc.groovyscript.helper.SimpleObjectStream; +import com.cleanroommc.groovyscript.registry.VirtualizedRegistry; +import mods.railcraft.api.crafting.Crafters; +import mods.railcraft.api.crafting.IRollingMachineCrafter; +import net.minecraft.inventory.InventoryCrafting; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.IRecipe; +import net.minecraft.item.crafting.Ingredient; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.ArrayList; +import java.util.List; + +@RegistryDescription +public class RollingMachine extends VirtualizedRegistry> { + + @RecipeBuilderDescription(example = { + @Example(".output(item('minecraft:stone')).matrix('BXX', 'X B').key('B', item('minecraft:stone')).key('X', item('minecraft:gold_ingot')).time(200)"), + @Example(".output(item('minecraft:diamond') * 32).matrix([[item('minecraft:gold_ingot'), item('minecraft:gold_ingot'), item('minecraft:gold_ingot')],[item('minecraft:gold_ingot'), item('minecraft:gold_ingot'), item('minecraft:gold_ingot')],[item('minecraft:gold_ingot'), item('minecraft:gold_ingot'), item('minecraft:gold_ingot')]]).time(400)") + }) + public ShapedRecipeBuilder shapedBuilder() { + return new ShapedRecipeBuilder(); + } + + @RecipeBuilderDescription(example = { + @Example(".output(item('minecraft:clay') * 8).input(item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone')).time(100)"), + @Example(".output(item('minecraft:diamond') * 32).input(item('minecraft:gold_ingot') * 9).time(500)") + }) + public ShapelessRecipeBuilder shapelessBuilder() { + return new ShapelessRecipeBuilder(); + } + + @Override + public void onReload() { + removeScripted().forEach(r -> { + List recipes = new ArrayList<>(Crafters.rollingMachine().getRecipes()); + recipes.removeIf(recipe -> recipe.getRegistryName().equals(r.getKey())); + }); + restoreFromBackup().forEach(r -> ModSupport.RAILCRAFT.get().rollingMachine.add(r.getKey(), r.getValue())); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public IRollingMachineCrafter.IRollingRecipe addShaped(ItemStack output, List> input, int time) { + return shapedBuilder() + .matrix(input) + .output(output) + .time(time) + .register(); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public IRollingMachineCrafter.IRollingRecipe addShaped(ItemStack output, List> input) { + return addShaped(output, input, 200); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public IRollingMachineCrafter.IRollingRecipe addShapeless(ItemStack output, List input, int time) { + return shapelessBuilder() + .input(input) + .output(output) + .time(time) + .register(); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public IRollingMachineCrafter.IRollingRecipe addShapeless(ItemStack output, List input) { + return addShapeless(output, input, 200); + } + + public IRollingMachineCrafter.IRollingRecipe add(ResourceLocation key, IRollingMachineCrafter.IRollingRecipe recipe) { + if (recipe != null) { + addScripted(Pair.of(key, recipe)); + } + return recipe; + } + + @MethodDescription(example = @Example("item('minecraft:tripwire_hook')")) + public boolean removeByOutput(IIngredient output) { + return Crafters.rollingMachine().getRecipes().removeIf(r -> { + if (output.test(r.getRecipeOutput())) { + addBackup(Pair.of(r.getRegistryName(), r)); + return true; + } + return false; + }); + } + + public boolean remove(ResourceLocation key) { + return Crafters.rollingMachine().getRecipes().removeIf(r -> { + if (r.getRegistryName().equals(key)) { + addBackup(Pair.of(key, r)); + return true; + } + return false; + }); + } + + public boolean remove(IRollingMachineCrafter.IRollingRecipe recipe) { + addBackup(Pair.of(recipe.getRegistryName(), recipe)); + return Crafters.rollingMachine().getRecipes().remove(recipe); + } + + @MethodDescription(type = MethodDescription.Type.QUERY) + public SimpleObjectStream streamRecipes() { + return new SimpleObjectStream<>(Crafters.rollingMachine().getRecipes()).setRemover(this::remove); + } + + @MethodDescription(priority = 2000, example = @Example(commented = true)) + public void removeAll() { + Crafters.rollingMachine().getRecipes().forEach(r -> addBackup(Pair.of(r.getRegistryName(), r))); + Crafters.rollingMachine().getRecipes().clear(); + } + + public static class ShapedRecipeBuilder { + + private ItemStack output; + private List> matrix; + private int time = 200; + + public ShapedRecipeBuilder output(ItemStack output) { + this.output = output; + return this; + } + + public ShapedRecipeBuilder matrix(List> matrix) { + this.matrix = matrix; + return this; + } + + public ShapedRecipeBuilder matrix(String... pattern) { + // Parse string pattern like "ABC", "DEF", "GHI" + this.matrix = new ArrayList<>(); + for (String row : pattern) { + List rowList = new ArrayList<>(); + for (char c : row.toCharArray()) { + rowList.add(null); // Will be filled by key() + } + this.matrix.add(rowList); + } + return this; + } + + public ShapedRecipeBuilder key(char key, IIngredient ingredient) { + // Replace null entries with the ingredient based on key + if (matrix != null) { + for (List row : matrix) { + for (int i = 0; i < row.size(); i++) { + if (row.get(i) == null) { + row.set(i, ingredient); + } + } + } + } + return this; + } + + public ShapedRecipeBuilder time(int time) { + this.time = time; + return this; + } + + public IRollingMachineCrafter.IRollingRecipe register() { + if (output == null || output.isEmpty()) { + GroovyLog.msg("Error adding Railcraft Rolling Machine shaped recipe") + .add("output must not be empty") + .error() + .post(); + return null; + } + if (matrix == null || matrix.isEmpty()) { + GroovyLog.msg("Error adding Railcraft Rolling Machine shaped recipe") + .add("matrix must not be empty") + .error() + .post(); + return null; + } + + final int finalTime = time; + ResourceLocation name = new ResourceLocation("groovyscript", "rolling_shaped_" + System.currentTimeMillis()); + + IRecipe recipe = new IRecipe() { + + @Override + public boolean matches(InventoryCrafting inv, World worldIn) { + // Simple matching logic + return false; + } + + @Override + public ItemStack getCraftingResult(InventoryCrafting inv) { + return output.copy(); + } + + @Override + public boolean canFit(int width, int height) { + return width >= matrix.get(0).size() && height >= matrix.size(); + } + + @Override + public ItemStack getRecipeOutput() { + return output.copy(); + } + + @Override + public NonNullList getIngredients() { + NonNullList ingredients = NonNullList.create(); + for (List row : matrix) { + for (IIngredient ing : row) { + if (ing != null) { + ingredients.add(ing.toMcIngredient()); + } + } + } + return ingredients; + } + + @Override + public IRecipe setRegistryName(ResourceLocation name) { + return this; + } + + @Override + public ResourceLocation getRegistryName() { + return name; + } + + @Override + public Class getRegistryType() { + return IRecipe.class; + } + }; + + IRollingMachineCrafter.IRollingRecipe rollingRecipe = new IRollingMachineCrafter.IRollingRecipe() { + + @Override + public int getTickTime() { + return finalTime; + } + + @Override + public boolean matches(InventoryCrafting inv, World worldIn) { + return recipe.matches(inv, worldIn); + } + + @Override + public ItemStack getCraftingResult(InventoryCrafting inv) { + return recipe.getCraftingResult(inv); + } + + @Override + public boolean canFit(int width, int height) { + return recipe.canFit(width, height); + } + + @Override + public ItemStack getRecipeOutput() { + return recipe.getRecipeOutput(); + } + + @Override + public NonNullList getRemainingItems(InventoryCrafting inv) { + return recipe.getRemainingItems(inv); + } + + @Override + public NonNullList getIngredients() { + return recipe.getIngredients(); + } + + @Override + public boolean isDynamic() { + return recipe.isDynamic(); + } + + @Override + public String getGroup() { + return recipe.getGroup(); + } + + @Override + public IRecipe setRegistryName(ResourceLocation name) { + return recipe.setRegistryName(name); + } + + @Override + public ResourceLocation getRegistryName() { + return recipe.getRegistryName(); + } + + @Override + public Class getRegistryType() { + return recipe.getRegistryType(); + } + }; + + return ModSupport.RAILCRAFT.get().rollingMachine.add(name, rollingRecipe); + } + } + + public static class ShapelessRecipeBuilder { + + private ItemStack output; + private List input = new ArrayList<>(); + private int time = 200; + + public ShapelessRecipeBuilder output(ItemStack output) { + this.output = output; + return this; + } + + public ShapelessRecipeBuilder input(List input) { + this.input = input; + return this; + } + + public ShapelessRecipeBuilder input(IIngredient... ingredients) { + for (IIngredient ing : ingredients) { + this.input.add(ing); + } + return this; + } + + public ShapelessRecipeBuilder time(int time) { + this.time = time; + return this; + } + + public IRollingMachineCrafter.IRollingRecipe register() { + if (output == null || output.isEmpty()) { + GroovyLog.msg("Error adding Railcraft Rolling Machine shapeless recipe") + .add("output must not be empty") + .error() + .post(); + return null; + } + if (input.isEmpty()) { + GroovyLog.msg("Error adding Railcraft Rolling Machine shapeless recipe") + .add("input must not be empty") + .error() + .post(); + return null; + } + + final int finalTime = time; + ResourceLocation name = new ResourceLocation("groovyscript", "rolling_shapeless_" + System.currentTimeMillis()); + + IRecipe recipe = new IRecipe() { + + @Override + public boolean matches(InventoryCrafting inv, World worldIn) { + return false; + } + + @Override + public ItemStack getCraftingResult(InventoryCrafting inv) { + return output.copy(); + } + + @Override + public boolean canFit(int width, int height) { + return width * height >= input.size(); + } + + @Override + public ItemStack getRecipeOutput() { + return output.copy(); + } + + @Override + public NonNullList getIngredients() { + NonNullList ingredients = NonNullList.create(); + for (IIngredient ing : input) { + ingredients.add(ing.toMcIngredient()); + } + return ingredients; + } + + @Override + public IRecipe setRegistryName(ResourceLocation name) { + return this; + } + + @Override + public ResourceLocation getRegistryName() { + return name; + } + + @Override + public Class getRegistryType() { + return IRecipe.class; + } + }; + + IRollingMachineCrafter.IRollingRecipe rollingRecipe = new IRollingMachineCrafter.IRollingRecipe() { + + @Override + public int getTickTime() { + return finalTime; + } + + @Override + public boolean matches(InventoryCrafting inv, World worldIn) { + return recipe.matches(inv, worldIn); + } + + @Override + public ItemStack getCraftingResult(InventoryCrafting inv) { + return recipe.getCraftingResult(inv); + } + + @Override + public boolean canFit(int width, int height) { + return recipe.canFit(width, height); + } + + @Override + public ItemStack getRecipeOutput() { + return recipe.getRecipeOutput(); + } + + @Override + public NonNullList getRemainingItems(InventoryCrafting inv) { + return recipe.getRemainingItems(inv); + } + + @Override + public NonNullList getIngredients() { + return recipe.getIngredients(); + } + + @Override + public boolean isDynamic() { + return recipe.isDynamic(); + } + + @Override + public String getGroup() { + return recipe.getGroup(); + } + + @Override + public IRecipe setRegistryName(ResourceLocation name) { + return recipe.setRegistryName(name); + } + + @Override + public ResourceLocation getRegistryName() { + return recipe.getRegistryName(); + } + + @Override + public Class getRegistryType() { + return recipe.getRegistryType(); + } + }; + + return ModSupport.RAILCRAFT.get().rollingMachine.add(name, rollingRecipe); + } + } +}