Skip to content
Merged
15 changes: 15 additions & 0 deletions docs/content/Modpacks/Changes/v7.5.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,22 @@ title: "Version 7.5.0"


# Updating from `7.4.1` to `7.5.0`

## MachineBuilder Generics
We have added a second Generic argument to our (Multiblock)MachineBuilder. This effectively means that anywhere where you used to store a partially finished `MachineBuilder<?>`, you now need to store a `MachineBuilder<?, ?>`. The same holds for `MultiblockMachineBuilder<?,?>`.

## RecipeCondition Generics
We have added a Generic argument to `RecipeCondition` describing the condition.
This means that your custom recipe conditions now need to extend `RecipeCondition<MyCondition>` (whereas before they just extended `RecipeCondition`).
For example, if you have a custom recipe condition class like so: `public class ExampleCondition extends RecipeCondition`, it should now be `public class ExampleCondition extends RecipeCondition<ExampleCondition>`.
You also need to adjust the generics of `getType()` and `createTemplate()` to match this change, like so:
```patch
- public RecipeConditionType<?> getType() {
+ public RecipeConditionType<ExampleCondition> getType() {

- public RecipeCondition createTemplate() {
+ public ExampleCondition createTemplate() {
```

## Machine & Cover Copy/Paste System
A new system for copying machines using the Machine Memory Card has been added, see [this page](../Other-Topics/Cover-Machine-Copy-Paste-Support.md) if you want to add extra copy/paste behaviour to your own machines and covers.
78 changes: 44 additions & 34 deletions docs/content/Modpacks/Examples/Custom-Recipe-Condition.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,50 @@ They are registered using
@Mod(ExampleMod.MOD_ID)
public class ExampleMod {

public ExampleMod(FMLJavaModLoadingContext context) {
var bus = context.getModEventBus();
bus.addGenericListener(RecipeConditionType.class, this::registerConditions);
}

// in 1.20.1
public static RecipeConditionType<ExampleCondition> EXAMPLE_CONDITION;

public ExampleMod() {
IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus();
modBus.addGenericListener(RecipeConditionType.class, this::registerConditions);
}

public void registerConditions(GTCEuAPI.RegisterEvent<String, RecipeConditionType<?>> event) {
EXAMPLE_CONDITION = GTRegistries.RECIPE_CONDITIONS.register("example_condition", // (1)
new RecipeConditionType<>(ExampleCondition::new, ExampleCondition.CODEC));
}
// end 1.20.1

// in 1.21.1
public static final RecipeConditionType<ExampleCondition> EXAMPLE_CONDITION = GTRegistries.register(GTRegistries.RECIPE_CONDITIONS,
ResourceLocation.fromNamespaceAndPath(ExampleMod.MOD_ID, "example_condition"), // (2)
new RecipeConditionType<>(ExampleCondition::new, ExampleCondition.CODEC));

public ExampleMod(IEventBus modBus, FMLModContainer container) {
modBus.addListener(CommonInit::onRegister);
bus.addListener(RecipeConditionType.class, this::registerConditions);
}

public void registerConditions(GTCEuAPI.RegisterEvent<String, RecipeConditionType<?>> event) {
EXAMPLE_CONDITION = GTRegistries.RECIPE_CONDITIONS.register("example_condition",
new RecipeConditionType<>(
ExampleCondition::new,
ExampleCondition.CODEC
)
);
new RecipeConditionType<>(ExampleCondition::new, ExampleCondition.CODEC));
}
// end 1.21.1
}
```

1. The 1.20.1 version doesn't require a namespace, so make sure you don't use the same ID as someone else!
2. You may use a helper method akin to `GTCEu.id` for creating the ResourceLocation, but you **must** use your own namespace for it.

We will set up a condition that requires that the power buffer of the machine is above a certain Y level.
```java
public class ExampleCondition extends RecipeCondition {
public class ExampleCondition extends RecipeCondition<ExampleCondition> {

public int height;

public static final Codec<ExampleCondition> CODEC = RecordCodecBuilder
.create(instance -> RecipeCondition.isReverse(instance)
.and(Codec.INT.fieldOf("height").forGetter(val -> val.height))
.apply(instance, ExampleCondition::new));
public static final Codec<ExampleCondition> CODEC = RecordCodecBuilder.create(instance -> RecipeCondition.isReverse(instance)
.and(Codec.INT.fieldOf("height").forGetter(val -> val.height)
).apply(instance, ExampleCondition::new));

public int height;

public ExampleCondition(boolean isReverse, int height) {
this.isReverse = isReverse;
Expand All @@ -52,14 +67,14 @@ public class ExampleCondition extends RecipeCondition {

public ExampleCondition(int height) {
this(false, height);
}
}

public ExampleCondition() {
this(false, 0);
}

@Override
public RecipeConditionType<?> getType() {
public RecipeConditionType<ExampleCondition> getType() {
return ExampleMod.EXAMPLE_CONDITION;
}

Expand All @@ -74,7 +89,7 @@ public class ExampleCondition extends RecipeCondition {
}

@Override
public RecipeCondition createTemplate() {
public ExampleCondition createTemplate() {
return new ExampleCondition(0);
}
}
Expand All @@ -85,7 +100,7 @@ Lets step through this example. This will not be in order as it is in the file,
Starting with:
```java
@Override
public RecipeConditionType<?> getType() {
public RecipeConditionType<ExampleCondition> getType() {
return ExampleMod.EXAMPLE_CONDITION;
}

Expand All @@ -105,16 +120,12 @@ This part is quite simple, and just returns the type and tooltip for the conditi
public ExampleCondition(int height) {
this(false, height);
}

public ExampleCondition() {
this(false, 0);
}
```
These are the constructors. We need the `isReverse`, as it is part of the overarching `RecipeCondition` type. `isReverse` means that if the condition is met, your recipe won't be run. Furthermore, a no-arg constructor is required for (de)serialization.
These are the constructors. We need the `isReverse`, as it is part of the overarching `RecipeCondition` type. `isReverse` means that if the condition is met, your recipe won't be run. Furthermore, a constructor with all arguments is required for (de)serialization.

```java
@Override
public RecipeCondition createTemplate() {
public ExampleCondition createTemplate() {
return new ExampleCondition(0);
}
```
Expand All @@ -131,21 +142,20 @@ This creates the basic "template" that might be used for serialization. This sho
This is the actual condition.

```java
public static final Codec<ExampleCondition> CODEC = RecordCodecBuilder.create(instance -> RecipeCondition.isReverse(instance).and(
Codec.INT.fieldOf("height").forGetter(val -> val.height)
).apply(instance, ExampleCondition::new));

public int height;

public static final Codec<ExampleCondition> CODEC = RecordCodecBuilder
.create(instance -> RecipeCondition.isReverse(instance)
.and(Codec.INT.fieldOf("height").forGetter(val -> val.height))
.apply(instance, ExampleCondition::new));
```

The CODEC is how java knows how to serialize/deserialize your condition. This is needed for syncing between client/server, and storing it to json to load when the world loads.
It consists of a few parts:

- `RecordCodecBuilder.create(instance -> ` means we will start a RecordCodecBuilder, or a builder that only consists of simple types.
- `RecipeCondition.isReverse(instance)` is a helper codec that serializes the isReverse boolean of your codec.
- `.and(` means this is the next field in the record.
- `Codec.INT.fieldOf("height").forGetter(val -> val.height)` means we want to serialize an INT, we want to call it "height" in the json, and to get the value you call `.height`.
- `.and(` allows adding additional fields to the codec.
- `Codec.INT.fieldOf("height").forGetter(val -> val.height)` means we want to serialize an integer, we want to call it "height" in the JSON, and to get the value to serialize you use `ExampleCondition#height`.
- `.apply(instance, ExampleCondition::new)` means when deserializing back to an object, you apply these steps to get the values (in this case `bool isReverse, int height`) and call the constructor with those arguments.
In this case, this would call our `new ExampleCondition(isReverse, height)` constructor we have defined earlier.

Expand Down
72 changes: 44 additions & 28 deletions docs/content/Modpacks/Recipes/Recipe-Conditions.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,62 @@ title: Recipe Conditions

Recipe Conditions are recipe properties that can prevent a recipe from starting based on certain criteria, like for example Biome, Weather, Quest Completions, or self-made custom Conditions.

These conditions can be used in both java and kubejs recipes. However, custom conditons can only be done in java. If you want to see how to make these, check out the [Custom Recipe Condition](../Examples/Custom-Recipe-Condition.md) example page.
These conditions can be used in both Java and KubeJS recipes. However, custom conditions can only be done in Java addons. If you want to see how to make these, check out the [Custom Recipe Condition](../Examples/Custom-Recipe-Condition.md) example page.

!!! Note
The condition is run after recipe matching and before recipe execution. If the recipe condition doesn't match, the machine will be suspended and won't be updated again until something in the inputs/outputs changes.

### Base Conditons

- Biome: `.biome("namespace:biome_id")`
- Locks a recipe behind being inside a certain biome, works with any biome a pack has loaded. For example, you could use `minecraft:plains`.
- Locks a recipe behind being inside a certain biome, works with any biome a pack has loaded.
For example, you could do `.biome("minecraft:plains")`.
- Dimension: `.dimension("namespace:dimension_id")`
- Locks a recipe being behind a certain dimension, the gas collector is a good example of this. For example, you could use `minecraft:the_end`
- Position_Y: `.posY(int min, int max)`
- Locks a recipe behind a certain y level in-world. For example, you could use `.posY(120, 130)` to have a recipe require a machine to be in between y 120 and y 130.
- Locks a recipe being behind a certain dimension, the gas collector is a good example of this.
- For example, you could do `.dimension("minecraft:the_end")`
- Y Position: `.posY(int min, int max)`
- Locks a recipe behind a certain y level in-world.
- For example, you could use `.posY(120, 130)` to have a recipe require a machine to be in between y 120 and y 130.
- Rain: `.rain(float level)`
- Locks a recipe behind a certain level of rain. For example, you could use `.rain(1.0)` to make a recipe need full rain.
- Adjacent_Fluids: `adjacentFluids("minecraft:water","minecraft:lava")`
- You can pass through any amount of fluids into the array. Moreover, any fluid passed into the array will make the recipe require a full source block touching the machine. We also have `adjacentFluidTag("forge:water", "forge:lava")`.
- Adjacent_Blocks: `adjacentBlocks("minecraft:stone", "minecraft:iron_block")`
- Much like the fluid condition, you can pass blocks into the array that lock the recipe behind needing the machine to touch these blocks. We also have `adjacentBlockTag("forge:stone", "forge:storage_blocks/iron")`.
- Locks a recipe behind a certain level of rain.
- For example, you could use `.rain(1.0)` to make a recipe need full rain.
- Adjacent Fluids: `.adjacentFluids("namespace:fluid_id", ...)`
- You can pass any amount of fluids into the array. Moreover, any fluid passed into the array will make the recipe require a full source block touching the machine.
- For example, you could use `.adjacentFluids("minecraft:water", "minecraft:lava")` to make a recipe require BOTH a water source and a lava source next to the machine.
- We also have `.adjacentFluidTag("forge:water", "forge:lava")`, which does the same, but allows fluid _tags_ to be used.
- Adjacent Blocks: `.adjacentBlocks("namespace:block_id", ...)`
- Much like the fluid condition, you can pass blocks into the array that lock the recipe behind needing the machine to touch these blocks.
- For example, you could use `.adjacentBlocks("minecraft:stone", "minecraft:iron_block")` to make a recipe require a Stone block and a Block of Iron.
- We also have `.adjacentBlockTag("forge:stone", "forge:storage_blocks/iron")`, which does the same, but allows block _tags_ to be used.
- Thunder: `.thunder(float level)`
- Locks a recipe behind a certain level of rain. For example, you could use `.thunder(1.0)` to make a recipe need a strong thunderstorm.
- Vent: This condition is auto added to any steam single block, it blocks recipes from running if the vent is obstructed.
- Locks a recipe behind a certain level of rain.
- For example, you could use `.thunder(1.0)` to make a recipe need a strong thunderstorm.
- Vent: This condition is automatically added to any recipes ran in a single block steam machine. It blocks recipes from running if the machine's vent is obstructed.
- Cleanroom: `.cleanroom(CleanroomType.CLEANROOM)`
- Locks a recipe to being inside a cleanroom. You can also use STERILE_CLEANROOM as well as your own custom cleanroom type.
- Fusion_Start_EU: `.fusionStartEU(long eu)`
- Locks a recipe behind the amount of stored power in a fusion machine. To use this, the machine must use the FusionReactorMachine class. For example, you could use `.fusionStartEU(600000)`
- Station_Research: `.stationResearch(b => b.researchStack("namespace:item_id").EUt(long eu).CWUt(int minCWUPerTick, int TotalCWU))`
- Locks a recipe behind having a certain research stack. For this condition to be properly seen, you will either need a base machine recipe type with the research ui component, or make your own. For example, you could do `.stationResearch(b => b.researchStack("gtceu:lv_motor").EUt(131000).CWUt(24, 12000))` which would lock a recipe behind needing a data orb with the lv motor research. It will also generate you a research station recipe.
- Scanner_Research: `.scannerResearch(b => b.researchStack("namespace:item_id").EUt(long eu))`
- Much like station research, this condition locks a recipe behind needing a research stack. However, in this case it will default to a data stick. For example, you could do `.scannerResearch(b => b.researchStack("gtceu:lv_motor").EUt(8192))`, which would make the recipe need a data stick with the lv motor research, and generates a scanner recipe.
- Enviromental_Hazard: `.environmentalHazard(GTMedicalConditions.CARBON_MONOXIDE_POISONING)`
- Locks a recipe into needing a certain environmental hazard to run. For now, carbon monoxide is the only one. An example of a machine using this condition is the air scrubber.
- Daytime: `.daytime(boolean notNight)`
- Locks recipe behind whether it is day or night. For example, you could do `.daytime(true)`, to make the recipe need it to be daytime.
- Locks a recipe to being inside a cleanroom. You can also use `STERILE_CLEANROOM` as well as your own custom cleanroom type(s).
- Fusion Start EU: `.fusionStartEU(long eu)`
- Locks a recipe behind the amount of stored power in a fusion machine. To use this, the machine must use the FusionReactorMachine class.
- For example, you could use `.fusionStartEU(600000)`
- Station Research: `.stationResearch(b => b.researchStack("namespace:item_id").EUt(long eu).CWUt(int minCWUPerTick, int TotalCWU))`
- Locks a recipe behind having a certain research stack. For this condition to be properly seen, you will either need a base machine recipe type with the research ui component, or make your own.
- For example, you could do `.stationResearch(b => b.researchStack("gtceu:lv_motor").EUt(131000).CWUt(24, 12000))` which would lock a recipe behind needing a data orb with the lv motor research. It will also generate you a research station recipe.
- Scanner Research: `.scannerResearch(b => b.researchStack("namespace:item_id").EUt(long eu))`
- Much like station research, this condition locks a recipe behind needing a research stack. However, in this case it will default to a data stick.
- For example, you could do `.scannerResearch(b => b.researchStack("gtceu:lv_motor").EUt(8192))`, which would make the recipe need a data stick with the lv motor research, and generates a scanner recipe.
- Environmental Hazard: `.environmentalHazard("medical_condition_name")`
- Locks a recipe into needing a certain environmental hazard to run. For now, `"carbon_monoxide_poisoning"` is the only one that's added to the world (by default). An example of a machine using this condition is the air scrubber.
- For example, you could do `.environmentalHazard("carcinogen")` (if you have something that creates radiation, as if you don't, the recipe would never run.)
- Daytime: `.daytime(boolean isNight)`
- Locks recipe behind whether it is day or night.
- For example, you could do `.daytime(true)` to make the recipe require nighttime to run.

### Mod Dependent Conditions
- Ftb_Quests: `.ftbQuest(quest_id)`
- Locks a recipe behind the owner of a machine completing a ftb quest. An example can't be easily given since every quest book is different.
- Gamestage: `.gameStage(gameStage_id)`
- FTB Quests: `.ftbQuest("quest_id")`
- Locks a recipe behind the owner of a machine completing a quest with FTB Quests.
- An example can't be easily given since every quest book is different.
- Game Stages: `.gameStage("gamestage_id")`
- Locks a recipe behind a certain game stage.
- Heracles_Quests: `.heraclesQuest(quest_id)`
- Locks a recipe behind the owner of a machine completing a heracles quest. An example can't be easily given since every quest book is different.
- Odyssey Quests (Heracles): `.heraclesQuest("quest_id")`
- Locks a recipe behind the owner of a machine completing a quest with Heracles.
- An example can't be easily given since every quest book is different.

Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package com.gregtechceu.gtceu.api.codec;

import com.gregtechceu.gtceu.utils.memoization.GTMemoizer;

import net.minecraft.util.ExtraCodecs;

import com.mojang.datafixers.util.Either;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.DynamicOps;

import java.util.function.Function;
import java.util.function.Supplier;

public final class GTCodecUtils {

public class GTCodecUtils {
private GTCodecUtils() {}

public static final Codec<Long> NON_NEGATIVE_LONG = longRangeWithMessage(0, Long.MAX_VALUE,
(val) -> "Value must be non-negative: " + val);
Expand All @@ -32,4 +39,28 @@ public static Codec<Long> longRange(long min, long max) {
public static <T> T unboxEither(Either<T, T> either) {
return either.map(Function.identity(), Function.identity());
}

public static <T> Codec<Supplier<T>> lazyParsingCodec(Codec<T> delegate) {
return new LazyParsingCodec<>(delegate);
}

private record LazyParsingCodec<A>(Codec<A> codec) implements Codec<Supplier<A>> {

@Override
public <T> DataResult<Pair<Supplier<A>, T>> decode(DynamicOps<T> ops, T input) {
return DataResult.success(Pair.of(GTMemoizer.memoize(() -> deferredDecode(ops, input)), input));
}

@Override
public <T> DataResult<T> encode(Supplier<A> input, DynamicOps<T> ops, T prefix) {
return input.get() == null ? DataResult.success(prefix) : this.codec.encode(input.get(), ops, prefix);
}

private <T> A deferredDecode(DynamicOps<T> ops, T input) {
return this.codec.decode(ops, input).get()
.map(Pair::getFirst, partial -> {
throw new IllegalStateException("Unable to parse deferred value: " + partial.message());
});
}
}
}
Loading