diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..cdac32c --- /dev/null +++ b/404.html @@ -0,0 +1,2160 @@ + + + +
+ + + + + + + + + + + + + + +This page contains links to external documentation that may be useful for several topics regarding development, as well +as other resources you might find helpful.
+Official Forge / NeoForge Docs
+ +Official Fabric Docs
+ +Other Modding Docs
+ +LDLib is the main library we're using for GTCEu-Modern.
+LDLib Docs
+ +Overview on using Mixins
+ +This is a great resource for getting started on how to use mixins, as well as a good quick reference if you're +looking for how to do something specific.
+Note that this is not exclusive to Fabric, but applies for all platforms instead.
+Unofficial Documentation
+ +Unofficial documentation for Mojang's DataFixerUpper library.
+Using Registrate
+ +Architectury Gradle Plugin
+Architectury-Wiki Gradle Plugin
+Note that we're only using Architectury's build system, not the library itself.
+Shimmer (one of our soft-dependencies) displays information about colored lights in development environments by default.
+You can turn it off using the command /shimmer coloredLightMonitor
ingame.
Alternatively, if you want to disable it by default, put this in your KubeJS client scripts:
+// Disable Shimmer's colored light monitor:
+let LightCounterRender = Java.loadClass("com.lowdragmc.shimmer.client.light.LightCounter$Render")
+LightCounterRender.enable = false
+
Fabric doesn't have a capability system like Forge does, but you can use several utility methods instead:
+FluidTransferHelper.getFluidTransfer(...);
+ItemTransferHelper.getItemTransfer(...);
+GTCapabilityHelper.getRecipeLogic(...)
+GTCapabilityHelper.getControllable(...)
+GTCapabilityHelper.getCoverable(...)
+GTCapabilityHelper.getToolable(...)
+GTCapabilityHelper.getWorkable(...)
+GTCapabilityHelper.getElectricItem(...)
+GTCapabilityHelper.getEnergyContainer(...)
+
Note
+In general, these containers should be created as final
fields (that's what we need for the
+SyncData system).
+Set their base arguments in the constructor (you can pass args for subclasses to modify).
You can create these containers via one of the following classes:
+NotifiableItemStackHandler
NotifiableFluidTank
NotifiableEnergyContainer
In general, you should prefer these classes over other implementations if possible, as they notify all listeners +of internal changes to improve performance.
+IO constructor parameters:
+handlerIO
: Whether the container is regarded as input or output during recipe processingcapabilityIO
: Whether the player can use hoppers, pipes, cables, etc. to interact with the storageIf you don't need to use the storage for recipe processing and/or providing capabilities, you can just use one of the +following classes, as they are more lightweight:
+ItemStackTransfer
FluidStorage
In some cases, you might need to create a custom implementation for either of these containers.
+To do so, use either of the following interfaces:
IItemTransfer
IFluidTransfer
IEnergyContainer
In case you have special requirements to your containers, you may be able to use one of these implementations in
+conjunction with one or more regular containers.
+They generally act as a proxy to the underlying container(s), while also handling these requirements.
ItemTransferList
FluidTransferList
EnergyContainerList
For proxying multiple containers, limited to a specific IO direction.
+IOItemTransferList
IOFluidTransferList
Not merged yet
Branch: mi-ender-link
If you need to proxy any item or fluid container that needs to be rate limited for insertion and extraction, you can +use either of the following classes:
+LimitingItemTransferProxy
LimitingFluidTransferProxy
The transfer limit passed as a constructor parameter will not renew automatically. Your container will therefore stop +transferring anything once this limit is reached.
+If you want to make this a rate limit instead, you will have to schedule a task that regularly resets the transfer +limit to the maximum value for your task's interval:
+public class MyCover extends CoverBehavior {
+ private LimitingFluidTransferProxy transferProxy;
+ private ConditionalSubscriptionHandler rateLimitSubscription;
+
+ public MyCover(IFluidTransfer myFluidTransfer) {
+ super(/* ... */);
+
+ transferProxy = new LimitingFluidTransferProxy(
+ myFluidTransfer,
+ 0L // Initial limit of 0, will be updated regularly in isRateLimitRefreshActive()
+ );
+ rateLimitSubscription = new ConditionalSubscriptionHandler(
+ this,
+ this::resetTransferRateLimit,
+ this::isRateLimitRefreshActive
+ );
+ }
+
+ @Override
+ public void onLoad() {
+ super.onLoad();
+ rateLimitSubscription.initialize(coverHolder.getLevel());
+ }
+
+ private void resetTransferRateLimit() {
+ if (transferProxy == null)
+ return;
+
+ transferProxy.setRemainingTransfer(transferRate.getMilliBuckets() * 20);
+ }
+
+ private boolean isRateLimitRefreshActive() {
+ // ...
+ }
+}
+
In certain cases (e.g. in a cache that holds all currently loaded instances of a machine), you might need to store data +in a global (static and mutable) variable.
+When doing so, you need to ensure that remote and serverside instances don't get mixed up.
+SideLocal<T>
Not yet merged
Branch: mi-ender-link
To make working with this requirement easier, You can use SideLocal<T>
to store your global data.
+It is similar to Java's ThreadLocal
, but operates on the game's sides instead.
If you are currently on the remote side (LDLib.isRemote()
/ on the client's main
thread), it will return the
+remote side's instance of your data. Otherwise, you will get the server side's instance.
public class MyCache {
+ private static SideLocal<Map<UUID, MyData>> cache = new SideLocal<>(HashMap::new);
+
+ public static void cacheData(UUID id, MyData data) {
+ cache.get().put(id, data);
+ }
+
+ public static MyData getData(UUID id) {
+ return cache.get().get(id);
+ }
+}
+
Alternatively to passing an initializer for both instances to SideLocal
's constructor, you can also supply
+separate instances for the remote and server side.
The fluid systems of Forge and Fabric use different units. +Make sure you use the correct units whenever you're handling fluid amounts.
+To get the size of one bucket, use the following method:
+FluidHelper.getBucket(); // returns 1000 on Forge and 81000 on Fabric.
+
Not yet documented
+Due to Minecraft's worldgen limitations (1), GTCEu's ore vein generation does not use the native worldgen feature system.
+Instead, we have our own system of generating ore veins separately from the actual ore placement,
+so that ores are only ever placed for the currently generating chunk.
+This page roughly describes the process of generating, caching and placing ores.
The generation can be (roughly) split up into three steps:
+This document will cover these steps from the bottom up, starting at the chunk generation mixin (ChunkGeneratorMixin.gtceu$applyBiomeDecoration()
).
The ChunkGeneratorMixin
holds a reference to the OrePlacer
(not to be confused with OreBlockPlacer
) - which is used to place the
+generated veins' blocks into the world, limited to the currently generating chunk.
When trying to generate a chunk, the OrePlacer
will query the OreGenCache
for a list of veins surrounding the current chunk.
The radius for querying the surrounding area is determined by the oreVeinRandomOffset
config option, as well as the largest registered vein size.
+It is therefore automatically compatible with any additional (or changed default) veins registered through either KubeJS, or by an addon.
Of course, the ore gen cache can only hold a limited amount of generated veins at once (see the oreGenerationChunkCacheSize
config option).
Because veins may be removed from the cache before all of their chunks are generated, it is extremely important that the ore generation is fully deterministic!
+This ensures that we do not generate ore veins that are either cut off, or have a mismatch in shape or type across chunk borders.
+It also automatically applies across game restarts, keeping continuity even then.
The only situation where ore veins will differ across chunk borders (other than certain internal changes to the generation, of course), is after +the relevant config options have been changed.
+In our case, that means that the RandomSource
s used for world generation must be completely new for generating each vein, so that its type, shape, offset,
+contents, etc. are not influenced by previous queries to the random generator.
+It is completely and exclusively seeded from the world's seed, as well as the chunk position.
For the random ore vein offset, we also include the vein's world generation layer in the random seed.
+This may need to include an additional component in the future, in case we add support for multiple veins per chunk and worldgen-layer.
Whenever the OreGenCache
cannot find a vein for a specific chunk, it will request a list of that chunk's GeneratedVein
s from the OreGenerator
.
The OreGenerator
is responsible for determining a vein's type, its origin (influenced by the oreVeinRandomOffset
config option), as well as providing the appropriate
+randomness source to the used implementation of VeinGenerator
.
Vein Origin vs Center
+A vein's origin is always the chunk it originates in, regardless of the random offset.
+The actual center of a vein is influenced by the random offset and might not be located at the chunk center - or in the same chunk at all.
Once the relevant VeinGenerator
implementation has finished generating the vein's shape, it will be cached per chunk, inside a GeneratedVein
.
VeinGenerator
and OreBlockPlacer
A vein generator is what will generate the actual shape of the vein.
+It should, however, never try to place any blocks directly. Instead, its generate()
method will only return a map of OreBlockPlacer
s by block position, which
+are responsible for actually placing the blocks in the world, as soon as a chunk generates.
+Each OreBlockPlacer
should only place either a single block, or no block.
OreBlockPlacer
sIn certain situations, the process of actually placing the block requires a randomness source (e.g. to determine the chance of its block being placed).
+To keep the ore generation fully deterministic in this case as well, it is recommended to generate a new seed using the supplied RandomSource
at the time of
+vein shape generation. This seed should be passed into the OreBlockPlacer
returned for the each block position.
Inside the OreBlockPlacer
, you can then simply create a new RandomSource
using the precomputed seed.
public class MyVeinGenerator {
+ public Map<BlockPos, OreBlockPlacer> generate(WorldGenLevel level, RandomSource random, GTOreDefinition entry, BlockPos origin) {
+ Map<BlockPos, OreBlockPlacer> generatedBlocks = new Object2ObjectOpenHashMap<>();
+
+ for (BlockPos pos : determineShapePositions()) {
+ final var randomSeed = random.nextLong(); // Fully deterministic regardless of chunk generation order
+ generatedBlocks.put(pos, (access, section) -> placeBlock(access, section, randomSeed, pos, entry));
+ }
+
+ return generatedBlocks;
+ }
+
+ private void placeBlock(BulkSectionAccess level, LevelChunkSection section, long randomSeed, BlockPos pos, GTOreDefinition entry) {
+ RandomSource rand = new XoroshiroRandomSource(randomSeed);
+
+ // ...
+ }
+}
+
To improve its generality, RecipeLogic has been rewritten in GTCEu Modern to support inputs and outputs other than +EU, items and fluids.
+The new RecipeLogic
no longer handles overclocked and parallel logic, but instead delegates it to the
+machine via IRecipeLogicMachine
:
/**
+ * Override it to modify recipe on the fly e.g. applying overclock,
+ * change chance, etc
+ *
+ * @param recipe recipe from detected from GTRecipeType
+ * @return modified recipe.
+ * null -- this recipe is unavailable
+ */
+@Nullable
+GTRecipe modifyRecipe(GTRecipe recipe);
+
In general, a simple electric overclocking can be done this way. For details, see OverclockingLogic
and RecipeHelper
public @Nullable GTRecipe modifyRecipe(GTRecipe recipe) {
+ if (RecipeHelper.getRecipeEUtTier(recipe) > getTier()) {
+ return null;
+ }
+
+ return RecipeHelper.applyOverclock(
+ getDefinition().getOverclockingLogic(),
+ recipe,
+ getMaxVoltage()
+ );
+}
+
Parallel is also not complicated to implement. Let's take the generator
as an example
public @Nullable GTRecipe modifyRecipe(GTRecipe recipe) {
+ var EUt = RecipeHelper.getOutputEUt(recipe); // get the recipe's EU/t
+
+ if (EUt > 0) {
+ // calculate the max parallel limitation.
+ var maxParallel = (int) (Math.min(
+ energyContainer.getOutputVoltage(),
+ GTValues.V[overclockTier]
+ ) / EUt);
+
+ while (maxParallel > 0) {
+ // copy and apply parallel, it will affect all recipes' contents
+ // and the recipe duration.
+ var copied = recipe.copy(ContentModifier.multiplier(maxParallel));
+
+ // If the machine has enough ingredients, return copied recipe.
+ if (copied.matchRecipe(this)) {
+ copied.duration = copied.duration / maxParallel;
+
+ return copied;
+ }
+
+ // Trying to halve the number of parallelism
+ maxParallel /= 2;
+ }
+ }
+ return null;
+}
+
ITickable
/ update()
The client update is always present and you can override the clientTick()
method, which works just as well as in 1.12.
But for the sake of performance, our machines are no longer always in a tickable state.
+We introduced ITickSubscription
for managed tick logic.
+Understand the basic concept of subscribing to periodic updates when they are needed, and unsubscribe them when
+they are not.
Automatic output of our machine requires periodic output of internal items to an adjacent inventory. +But most of the time this logic doesn't need to be executed if any of the following conditions apply:
+Lets look at how we implement it in QuantumChest
.
QuantumChest
@Getter @Persisted @DescSynced
+protected boolean autoOutputItems;
+@Persisted @DropSaved
+protected final NotifiableItemStackHandler cache; // inner inventory
+protected TickableSubscription autoOutputSubs;
+protected ISubscription exportItemSubs;
+
+// update subscription, subscribe if tick logic subscription is required, unsubscribe otherwise.
+protected void updateAutoOutputSubscription() {
+ var outputFacing = getOutputFacingItems(); // get output facing
+ if ((isAutoOutputItems() && !cache.isEmpty()) // inner item non empty
+ && outputFacing != null // has output facing
+ && ItemTransferHelper.getItemTransfer(getLevel(), getPos().relative(outputFacing), outputFacing.getOpposite()) != null) { // adjacent block has inventory.
+ autoOutputSubs = subscribeServerTick(autoOutputSubs, this::checkAutoOutput); // subscribe tick logic
+ } else if (autoOutputSubs != null) { // unsubscribe tick logic
+ autoOutputSubs.unsubscribe();
+ autoOutputSubs = null;
+ }
+}
+
+// output to nearby block.
+protected void checkAutoOutput() {
+ if (getOffsetTimer() % 5 == 0) {
+ if (isAutoOutputItems() && getOutputFacingItems() != null) {
+ cache.exportToNearby(getOutputFacingItems());
+ }
+ updateAutoOutputSubscription(); // dont foget to check if it's still available
+ }
+}
+
+@Override
+public void onLoad() {
+ super.onLoad();
+ if (getLevel() instanceof ServerLevel serverLevel) {
+ // you cant call ItemTransferHelper.getItemTransfer while chunk is loading, so lets defer it next tick.
+ serverLevel.getServer().tell(new TickTask(0, this::updateAutoOutputSubscription));
+ }
+ // add a listener to listen the changes of inner inventory. (for ex, if inventory not empty anymore, we may need to unpdate logic)
+ exportItemSubs = cache.addChangedListener(this::updateAutoOutputSubscription);
+}
+
+@Override
+public void onUnload() {
+ super.onUnload(); //autoOutputSubs will be released automatically when machine unload
+ if (exportItemSubs != null) { //we should mannually release it.
+ exportItemSubs.unsubscribe();
+ exportItemSubs = null;
+ }
+}
+
+// For any change may affect the logic to invoke updateAutoOutputSubscription at a time
+@Override
+public void setAutoOutputItems(boolean allow) {
+ this.autoOutputItems = allow;
+ updateAutoOutputSubscription();
+}
+
+@Override
+public void setOutputFacingItems(Direction outputFacing) {
+ this.outputFacingItems = outputFacing;
+ updateAutoOutputSubscription();
+}
+
+@Override
+public void onNeighborChanged(Block block, BlockPos fromPos, boolean isMoving) {
+ super.onNeighborChanged(block, fromPos, isMoving);
+ updateAutoOutputSubscription();
+}
+
I know the code is a kinda long, but it's for performance, and thanks to the SyncData system, we've eliminated a lot of +synchronization code, so please sacrifice a little for better performance.
+ConditionalSubscriptionHandler
For ease of use in some situations, it is possible to eliminate some of the boilerplate code and delegate management of
+your subscription to a ConditionalSubscriptionHandler
instead.
Using that class, it is possible to simply provide an update method to run every tick while the subscription is active,
+as well as a Supplier<Boolean>
that determines whether it is active.
Whenever the input of its condition changes, you need to call updateSubscription()
on the handler, so that it can
+re-evaluate it and take the necessary steps if it has changed.
+You should also to call this method after executing your tick logic in most cases, to ensure the subscription doesn't
+stay active any longer than it needs to.
ConditionalSubscriptionHandler
class MyMachine extends MetaMachine implements IControllable {
+ @Persisted @Getter
+ private boolean workingEnabled = true;
+
+ private final ConditionalSubscriptionHandler subscriptionHandler;
+
+ public MyMachine() {
+ super(/* ... */);
+
+ this.subscriptionHandler = new ConditionalSubscriptionHandler(
+ this, this::update, this::isSubscriptionActive
+ );
+ }
+
+ private void update() {
+ // Only run once every second
+ if (getOffsetTimer() % 20 != 0)
+ return;
+
+ // ...
+
+ // Now that the update logic has been executed, update the subscription.
+ // This will internally check if the subscription is still active and
+ // unsubscribe otherwise.
+ subscriptionHandler.updateSubscription();
+ }
+
+ private boolean isSubscriptionActive() {
+ return isWorkingEnabled();
+ }
+
+ @Override
+ public void setWorkingEnabled(boolean workingEnabled) {
+ this.workingEnabled = workingEnabled;
+
+ // Whether the subscription is currently active depends on whether working
+ // is enabled for this machine. As soon as any of the condition inputs changes,
+ // you need to update the subscription.
+ subscriptionHandler.updateSubscription();
+ }
+
+ @Override
+ public void onLoad() {
+ super.onLoad();
+
+ // As soon as you can get a reference to the dimension/level you're in,
+ // you need to initialize your subscription handler.
+ subscriptionHandler.initialize(getLevel());
+ }
+}
+
Info
+This is an overview of technical terms that are commonly used in this documentation.
+If you're not sure what something means (or how it applies to the current context), please refer to this page.
The part of the game running on a player's computer.
+It always hosts the Remote Side.
+In singleplayer mode, the Server Side is hosted on the client as well. In multiplayer mode, the client
+connects to a dedicated server instead.
See also: Server Side
+The remote side is the part of the game that is connected to the game's server side.
+It always runs on the client.
This side may not have the same amount of data available to it as the server does (see SyncData if you
+need to automatically synchronize certain data to the remote side).
+It also does not perform any tick update logic.
See also: Remote Side
+The server side is what one or more players connect to.
+In singleplayer mode, it runs on the client. In multiplayer mode, it runs on a dedicated server.
This side usually has all of the world's data available to it and runs tick update logic.
+This is therefore, where TPS impact becomes relevant. In general, use
Short for "ticks per second". Should stay at exactly 20.
+See Tick Updates and Optimization for techniques +on how to reduce performance impact.
+ + + + + + + + + + + + + + + + +@DescSynced
@Persisted
@RequireRerender
@UpdateListener
For serializing and synchronizing fields, LDLib's annotation-based SyncData system is used.
+Please also refer to the LDLib Wiki.
+Here is an overview of the most important annotations:
+public class MyMachine {
+ @Persisted // (1)
+ private String myPersistedField;
+
+ @DescSynced // (2)
+ private String myClientsideRelevantField;
+
+ @DescSynced @RequireRerender // (3)
+ private IO io;
+
+ @DescSynced @UpdateListener(methodName = "runAdditionalUpdate") // (4)
+ private int fieldWithAdditionalClientUpdateLogic;
+
+
+ private void runAdditionalUpdate(int newValue, int oldValue) {
+ // Run additional clientside update code here
+ }
+}
+
This field is automatically serialized to and deserialized from NBT data, that will be stored with its container.
+ By default, @Persisted
only applies on the server side.
For fields that need to be available on the client (or more specifically, remote) side, you can annotate them with
+ @DescSynced
to make them available there as well.
+ Any changes made to the field on the server side will automatically be synchronized to the client side.
If a change of a synced field's value requires rerendering the containing block (e.g. for different overlays based
+ on a cover's IO direction), simply add the @RequireRerender
annotation to it.
+ Its renderer's code will then be called again every time the field changes.
In some cases, a field may require some additional code to run on the client/remote side when it has been synced.
+ The @UpdateListener
annotation allows you to define a method to be called in that case.
For serializing and synchronizing fields, LDLib's annotation-based SyncData system is used.
+Using this system, almost all boilerplate code regarding these topics can be omitted.
For more info on the SyncData annotations, please refer to the following chapters, as well as to the +LDLib Wiki.
+ + + + + + + + + + + + + + + + +If you want to contribute to the development of GTCEu Modern, please feel free to submit a +pull request with your changes.
+The following pages describe a few important concepts that you will likely run into when working with our codebase.
+LDLib Docs
+ +This mod is based on the LDLib library for a lot of comminly used functionalities.
+Please refer to its documentation as well.
Architectury Gradle Plugin
+Architectury-Wiki Gradle Plugin
+This mod is using Architectury's build system, for compatibiliy across multiple moding platforms.
+This is an overview of GTCEu's ore veins and the ore types they contain.
+Please note that these are the default settings and may be different in certain modpacks.
Height range: 10 to 80
+Height range: 10 to 80
+Height range: 10 to 140
+Height range: -10 to 160
+Height range: -15 to 45
+Height range: 30 to 60
+Height range: -10 to 60
+Height range: 0 to 50
+Height range: 10 to 60
+Height range: 15 to 60
+Height range: -10 to 60
+Height range: 30 to 70
+Height range: 30 to 80
+ +Height range: -40 to 10
+Height range: -65 to -30
+Height range: -60 to 10
+Height range: -30 to 0
+Height range: -40 to -10
+Height range: -20 to 10
+Height range: -65 to -10
+Height range: -40 to 0
+Height range: 20 to 40
+Height range: 5 to 30
+Height range: 80 to 120
+Height range: 20 to 30
+Height range: 20 to 50
+Height range: 20 to 40
+Height range: 40 to 80
+Height range: 5 to 40
+Height range: 5 to 45
+Height range: 10 to 30
+Height range: 80 to 120
+Height range: 80 to 120
+Height range: 10 to 80
+Height range: 20 to 80
+Height range: 10 to 90
+Height range: 30 to 60
+Height range: 20 to 60
+Height range: 5 to 50
+ + + + + + + + + + + + + + + + +Bronze is the most important material in the Steam Age.
+To prepare the first batch of it you will need Tin and Copper just crush the ingots with a GT mortar. And then mix them in a crafting table in 3 to 1 proportion. Then smelting to receive the ingot.
++
+ + + + + + + + + + + + + + + + +For early game steam generation you have two options:
+For water you can use a primitive pump +
+or use a water connection from any other mod.
+After unlocking steel (using a primitive blast furnace) you will get access to the high pressure versions of the boilers and allow you to produce more steam.
+Large boilers allow you to create massive amount of steam in the blink of an eye. A boiler can be constructed of many different GT materials, that will allow for different steam generation rates (consult the table below).
+Boiler type | +Low pressure | +High pressure | +
---|---|---|
Solar | +6 L/T | +18 L/T | +
Liquid | +12 L/T | +30 L/T | +
Solid | +6 L/T | +15 L/T | +
Boiler | +Generation | +Boil up | +Max temp | +
---|---|---|---|
Bronze | +800 L/T | +40s | +1074K | +
Steel | +1800 L/T | +90s | +2074K | +
Titanium | +3200 L/T | +160s | +3474K | +
Tungstensteel | +6400 L/T | +160s | +6674K | +
*L/T -> Liters per tick (1L = 1mB)
+ + + + + + + + + + + + + + + + +Steam is the first of many stages that come with the Gregtech mod. It will be your first encounter with GT machines and tools. Be careful things may get explosive.
+So it is important that you learn how to transport and make enough steam for your factory.
+Most important material: Bronze
+End Goal: LV machine hull
+Gated by: Primitive blast furnace
+ + + + + + + + + + + + + + + + +While we try to keep this documentation up to date and as complete as possible, it may not always contain all of the latest information.
+As an additional resource to these docs, you can also reference our KubeJS integration directly in the source code:
+src/main/java/com/gregtechceu/gtceu/integration/kjs
Continue reading for a few important places you may want to check.
+If you're not sure what methods and fields are available on one of our builders, you can find all of them in this directory.
+Material Builder
+src/main/java/com/gregtechceu/gtceu/api/data/chemical/material/Material.java
The material builder is not located in the KJS integration package.
+Please reference the nested Material.Builder
class instead.
GregTechKubeJSPlugin
+src/main/java/com/gregtechceu/gtceu/integration/kjs/GregTechKubeJSPlugin.java
GregTechKubeJSPlugin.registerBindings()
GregTechKubeJSPlugin.registerTypeWrappers()
1.0.x
to 1.1.0
Item tags for circuits have been changed from #forge:circuits/<tier>
to #gtceu:circuits/<tier>
.
+You will need to update any recipes accordingly.
Several recipe IDs have changed. If you are removing/modifying any recipes by ID, please check if they are still valid.
+Due to changes in how ores are rendered, ore stone types will now additionally require a base model location:
+e.create('my_ore', 'ore')
+ .baseModelLocation('some_other_mod:block/my_block')
+ //...
+
1.2.0
to 1.2.1
The coilMaterial
function now uses a supplier instead of taking a material directly.
// Before:
+.coilMaterial(GTMaterials.get('infinity'))
+
+// After:
+.coilMaterial(() => GTMaterials.get('infinity'))
+
If any of your machines had a custom recipe modifier, its syntax has changed slightly.
+More than one recipe modifier can now be applied, making more complex chains of modifiers easier to declare.
+In particular, multiblocks supporting parallel hatches now need to be declared differently:
// Before:
+.recipeModifier(GTRecipeModifiers.PARALLEL_HATCH.apply(OverclockingLogic.PERFECT_OVERCLOCK, GTRecipeModifiers.ELECTRIC_OVERCLOCK))
+
+// After:
+.recipeModifiers(GTRecipeModifiers.PARALLEL_HATCH, GTRecipeModifiers.ELECTRIC_OVERCLOCK.apply(OverclockingLogic.PERFECT_OVERCLOCK))
+
Bedrock ore veins are no longer automatically generated.
+They are now entirely up to modpack developers to define, and offer more flexibility than the previous system.
GTCEuServerEvents.oreVeins(event => {
+ event.add('kubejs:my_custom_bedrock_vein', vein => {
+ // ...
+ })
+ event.modify('kubejs:other_custom_vein', vein => {
+ // ...
+ })
+ event.remove('kubejs:other_custom_vein')
+})
+
The documentation for how to use the add and modify events will follow soon.
+For now, please reference the BedrockOreDefinition.Builder
class in our source code.
GTCEuStartupEvents.registry('gtceu:recipe_type', event => {
+ event.create('basic_alternator')
+ .category('multiblock')
+ .setEUIO('out')
+ .setMaxIOSize(0, 0, 0, 0)
+ .setProgressBar(GuiTextures.PROGRESS_BAR_ARROW, FillDirection.LEFT_TO_RIGHT)
+ .setSound(GTSoundEntries.ARC)
+ .setMaxTooltips(6)
+})
+
GTCEuStartupEvents.registry('gtceu:machine', event => {
+ event.create('basic_alternator', 'multiblock')
+ .rotationState(RotationState.NON_Y_AXIS)
+ .recipeType('basic_alternator')
+ .pattern(definition => FactoryBlockPattern.start()
+ .aisle("CWC", "CWC", "#W#")
+ .aisle("CWC", "K#E", "CWC")
+ .aisle("CWC", "CWA", "#W#")
+ .where('A', Predicates.controller(Predicates.blocks(definition.get())))
+ .where('W', Predicates.blocks(GTBlocks.COIL_CUPRONICKEL.get()))
+ .where("C", Predicates.blocks(GTBlocks.CASING_STEEL_SOLID.get()))
+ .where('#', Predicates.any())
+ .where('K', Predicates.abilities(PartAbility.INPUT_KINETIC).setExactLimit(1))
+ .where('E', Predicates.abilities(PartAbility.OUTPUT_ENERGY).setExactLimit(1))
+ .build()
+ )
+ .workableCasingRenderer(
+ "gtceu:block/casings/solid/machine_casing_solid_steel",
+ "gtceu:block/multiblock/implosion_compressor", false
+ )
+})
+
{
+ "block.gtceu.basic_alternator": "Basic Alternator",
+ "gtceu.basic_alternator": "Basic Alternator"
+}
+
ServerEvents.recipes(event => {
+ function basic_alt(id, su, rpm, eu){
+ event.recipes.gtceu.basic_alternator(id)
+ .inputStress(su)
+ .rpm(rpm)
+ .duration(2)
+ .EUt(eu)
+ }
+ basic_alt('lv_1_amp', 256, 32, -32)
+})
+
GTCEuStartupEvents.registry('gtceu:recipe_type', event => {
+ event.create('greenhouse')
+ .category('drack')
+ .setEUIO('in')
+ .setMaxIOSize(3, 4, 1, 0)
+ .setProgressBar(GuiTextures.PROGRESS_BAR_ARROW, FillDirection.LEFT_TO_RIGHT)
+ .setSound(GTSoundEntries.BATH)
+})
+
GTCEuStartupEvents.registry('gtceu:machine', event => {
+ event.create('greenhouse', 'multiblock')
+ .rotationState(RotationState.NON_Y_AXIS)
+ .recipeType('greenhouse')
+ .appearanceBlock(GTBlocks.CASING_STEEL_SOLID)
+ .pattern(definition => FactoryBlockPattern.start()
+ .aisle('CCC', 'CGC', 'CGC', 'CLC', 'CCC')
+ .aisle('CMC', 'GSG', 'G#G', 'LIL', 'COC')
+ .aisle('CKC', 'CGC', 'CGC', 'CLC', 'CNC')
+ .where('K', Predicates.controller(Predicates.blocks(definition.get())))
+ .where('M', Predicates.blocks('moss_block')
+ .or(Predicates.blocks('dirt'))
+ .or(Predicates.blocks('grass_block'))
+ )
+ .where('G', Predicates.blocks('ae2:quartz_glass'))
+ .where('S', Predicates.blocks('oak_sapling')
+ .or(Predicates.blocks('dark_oak_sapling'))
+ .or(Predicates.blocks('spruce_sapling'))
+ .or(Predicates.blocks('birch_sapling'))
+ .or(Predicates.blocks('jungle_sapling'))
+ .or(Predicates.blocks('acacia_sapling'))
+ .or(Predicates.blocks('azalea'))
+ .or(Predicates.blocks('flowering_azalea'))
+ .or(Predicates.blocks('mangrove_propagule'))
+ .or(Predicates.blocks('gtceu:rubber_sapling'))
+ )
+ .where('I', Predicates.blocks('glowstone'))
+ .where('L', Predicates.blocks(GTBlocks.CASING_GRATE.get()))
+ .where('C', Predicates.blocks(GTBlocks.CASING_STEEL_SOLID.get())
+ .or(Predicates.autoAbilities(definition.getRecipeTypes()))
+ )
+ .where('O', Predicates.abilities(PartAbility.MUFFLER)
+ .setExactLimit(1)
+ )
+ .where('N', Predicates.abilities(PartAbility.MAINTENANCE))
+ .where('#', Predicates.air())
+ .build()
+ )
+ .workableCasingRenderer('gtceu:block/casings/solid/machine_casing_solid_steel', 'gtceu:block/multiblock/implosion_compressor', false)
+})
+
{
+ "block.gtceu.greenhouse": "Greenhouse",
+ "gtceu.greenhouse": "Greenhouse"
+}
+
ServerEvents.recipes(event => {
+
+ ////// Machine Recipe //////
+
+ event.shaped(
+ 'gtceu:greenhouse',
+ ['AWA', 'CSC', 'WCW'],
+ {
+ A: '#forge:circuits/mv',
+ W: 'gtceu:copper_single_cable',
+ C: '#forge:circuits/mv',
+ S: 'gtceu:solid_machine_casing'
+ }
+ ).id('gtceu:shaped/greenhouse')
+
+
+ ////// Greenhouse Recipes //////
+
+ function Greenhouse(id, input, fluid, output, boosted) {
+ if (boosted) {
+ event.recipes.gtceu.greenhouse(id)
+ .circuit(2)
+ .notConsumable(InputItem.of(input))
+ .itemInputs('4x gtceu:fertilizer')
+ .inputFluids(Fluid.of('minecraft:water', fluid))
+ .itemOutputs(output)
+ .duration(320)
+ .EUt(MV)
+ } else {
+ event.recipes.gtceu.greenhouse(id)
+ .circuit(1)
+ .notConsumable(InputItem.of(input))
+ .inputFluids(Fluid.of('minecraft:water', fluid))
+ .itemOutputs(output)
+ .duration(640)
+ .EUt(MV)
+ }
+ }
+
+
+ ////// Trees //////
+
+ // Rubber
+ Greenhouse('rubber_sapling', 'gtceu:rubber_sapling', 1000, ['32x gtceu:rubber_log', '8x gtceu:sticky_resin', '4x gtceu:rubber_sapling'], false)
+ Greenhouse('rubber_sapling_boosted', 'gtceu:rubber_sapling', 1000, ['64x gtceu:rubber_log', '16x gtceu:sticky_resin', '4x gtceu:rubber_sapling'], true)
+
+ // Oak
+ Greenhouse('oak_sapling', 'minecraft:oak_sapling', 1000, ['64x minecraft:oak_log', '4x minecraft:oak_sapling'], false)
+ Greenhouse('oak_sapling_boosted', 'minecraft:oak_sapling', 1000, ['64x minecraft:oak_log', '64x minecraft:oak_log', '4x minecraft:oak_sapling'], true)
+
+ // Dark Oak
+ Greenhouse('dark_oak_sapling', 'minecraft:dark_oak_sapling', 1000, ['64x minecraft:dark_oak_log', '4x minecraft:dark_oak_sapling'], false)
+ Greenhouse('dark_oak_sapling_boosted', 'minecraft:dark_oak_sapling', 1000, ['64x minecraft:dark_oak_log', '64x minecraft:dark_oak_log', '4x minecraft:dark_oak_sapling'], true)
+
+ // Spruce
+ Greenhouse('spruce_sapling', 'minecraft:spruce_sapling', 1000, ['64x minecraft:spruce_log', '4x minecraft:spruce_sapling'], false)
+ Greenhouse('spruce_sapling_boosted', 'minecraft:spruce_sapling', 1000, ['64x minecraft:spruce_log', '64x minecraft:spruce_log', '4x minecraft:spruce_sapling'], true)
+
+ // Birch
+ Greenhouse('birch_sapling', 'minecraft:birch_sapling', 1000, ['64x minecraft:birch_log', '4x minecraft:birch_sapling'], false)
+ Greenhouse('birch_sapling_boosted', 'minecraft:birch_sapling', 1000, ['64x minecraft:birch_log', '64x minecraft:birch_log', '4x minecraft:birch_sapling'], true)
+
+ // Acacia
+ Greenhouse('acacia_sapling', 'minecraft:acacia_sapling', 1000, ['64x minecraft:acacia_log', '4x minecraft:acacia_sapling'], false)
+ Greenhouse('acacia_sapling_boosted', 'minecraft:acacia_sapling', 1000, ['64x minecraft:acacia_log', '64x minecraft:acacia_log', '4x minecraft:acacia_sapling'], true)
+
+ // Jungle
+ Greenhouse('jungle_sapling', 'minecraft:jungle_sapling', 1000, ['64x minecraft:jungle_log', '4x minecraft:jungle_sapling'], false)
+ Greenhouse('jungle_sapling_boosted', 'minecraft:jungle_sapling', 1000, ['64x minecraft:jungle_log', '64x minecraft:jungle_log', '4x minecraft:jungle_sapling'], true)
+
+ // Azalea
+ Greenhouse('azalea_sapling', 'minecraft:azalea', 1000, ['64x minecraft:oak_log', '4x minecraft:azalea'], false)
+ Greenhouse('azalea_boosted', 'minecraft:azalea', 1000, ['64x minecraft:oak_log', '64x minecraft:oak_log', '4x minecraft:azalea'], true)
+
+ // Flowering Azalea
+ Greenhouse('flowering_azalea', 'minecraft:flowering_azalea', 1000, ['64x minecraft:oak_log', '4x minecraft:flowering_azalea'], false)
+ Greenhouse('flowering_azalea_boosted', 'minecraft:flowering_azalea', 1000, ['64x minecraft:oak_log', '64x minecraft:oak_log', '4x minecraft:flowering_azalea'], true)
+
+ // Mangrove
+ Greenhouse('mangrove_propagule', 'minecraft:mangrove_propagule', 1000, ['64x minecraft:mangrove_log', '4x minecraft:mangrove_propagule'], false)
+ Greenhouse('mangrove_propagule_boosted', 'minecraft:mangrove_propagule', 1000, ['64x minecraft:mangrove_log', '64x minecraft:mangrove_log', '4x minecraft:mangrove_propagule'], true)
+
+ ////// Crops //////
+
+ // Sugarcane
+ Greenhouse('sugar_cane', 'minecraft:sugar_cane', 1000, '24x minecraft:sugar_cane', false)
+ Greenhouse('sugar_cane_boosted', 'minecraft:sugar_cane', 1000, '48x minecraft:sugar_cane', true)
+
+ // Kelp
+ Greenhouse('kelp', 'minecraft:kelp', 2000, '24x minecraft:kelp', false)
+ Greenhouse('kelp_boosted', 'minecraft:kelp', 2000, '48x minecraft:kelp', true)
+
+ // Bamboo
+ Greenhouse('bamboo', 'minecraft:bamboo', 1000, '24x minecraft:bamboo', false)
+ Greenhouse('bamboo_boosted', 'minecraft:bamboo', 1000, '48x minecraft:bamboo', true)
+
+ // Cactus
+ Greenhouse('cactus', 'minecraft:cactus', 1000, '24x minecraft:cactus', false)
+ Greenhouse('cactus_boosted', 'minecraft:cactus', 1000, '48x minecraft:cactus', true)
+
+ // Wheat
+ Greenhouse('wheat', 'minecraft:wheat_seeds', 1000, '24x minecraft:wheat', false)
+ Greenhouse('wheat_boosted', 'minecraft:wheat_seeds', 1000, '48x minecraft:wheat', true)
+
+ // Carrot
+ Greenhouse('carrot', 'minecraft:carrot', 1000, '24x minecraft:carrot', false)
+ Greenhouse('carrot_boosted', 'minecraft:carrot', 1000, '48x minecraft:carrot', true)
+
+ // Potato
+ Greenhouse('potato', 'minecraft:potato', 1000, '24x minecraft:potato', false)
+ Greenhouse('potato_boosted', 'minecraft:potato', 1000, '48x minecraft:potato', true)
+
+ // Beetroot
+ Greenhouse('beetroot', 'minecraft:beetroot_seeds', 1000, '24x minecraft:beetroot', false)
+ Greenhouse('beetroot_boosted', 'minecraft:beetroot_seeds', 1000, '48x minecraft:beetroot', true)
+
+ // Mellon
+ Greenhouse('melon', 'minecraft:melon_seeds', 1000, '12x minecraft:melon', false)
+ Greenhouse('melon_boosted', 'minecraft:melon_seeds', 1000, '24x minecraft:melon', true)
+
+ // Pumpkin
+ Greenhouse('pumpkin', 'minecraft:pumpkin_seeds', 1000, '12x minecraft:pumpkin', false)
+ Greenhouse('pumpkin_boosted', 'minecraft:pumpkin_seeds', 1000, '24x minecraft:pumpkin', true)
+
+ // Nether Wart
+ Greenhouse('nether_wart', 'minecraft:nether_wart', 1000, '12x minecraft:nether_wart', false)
+ Greenhouse('nether_wart_boosted', 'minecraft:nether_wart', 1000, '24x minecraft:nether_wart', true)
+
+ // Red Mushroom
+ Greenhouse('red_mushroom', 'minecraft:red_mushroom', 1000, '12x minecraft:red_mushroom', false)
+ Greenhouse('red_mushroom_boosted', 'minecraft:red_mushroom', 1000, '24x minecraft:red_mushroom', true)
+
+ // Brown Mushroom
+ Greenhouse('brown_mushroom', 'minecraft:brown_mushroom', 1000, '12x minecraft:brown_mushroom', false)
+ Greenhouse('brown_mushroom_boosted', 'minecraft:brown_mushroom', 1000, '24x minecraft:brown_mushroom', true)
+})
+
GTCEuStartupEvents.registry('gtceu:recipe_type', event => {
+ event.create('ore_processing_plant')
+ .category('ore_processing_plant')
+ .setEUIO('in')
+ .setMaxIOSize(1, 8, 2, 1)
+ .setSound(GTSoundEntries.BATH);
+});
+
GTCEuStartupEvents.registry('gtceu:machine', event => {
+ event.create('ore_processing_plant', 'multiblock')
+ .rotationState(RotationState.NON_Y_AXIS)
+ .recipeType('ore_processing_plant')
+ .recipeModifiers(GTRecipeModifiers.PARALLEL_HATCH, GTRecipeModifiers.ELECTRIC_OVERCLOCK.apply(OverclockingLogic.PERFECT_OVERCLOCK))
+ .appearanceBlock(GTBlocks.CASING_TUNGSTENSTEEL_ROBUST)
+ .pattern(definition => FactoryBlockPattern.start()
+ .aisle(' AAA ', ' FFF ', ' FFF ', ' F ', ' ', ' ', ' ')
+ .aisle('AFFFA', 'FG GF', 'F F', ' F F ', ' FFF ', ' F ', ' B ')
+ .aisle('AFFFA', 'F P F', 'F P F', 'F P F', ' FPF ', ' FMF ', ' B B ')
+ .aisle('AFFFA', 'FG GF', 'F F', ' F F ', ' FFF ', ' F ', ' B ')
+ .aisle(' AAA ', ' FCF ', ' FFF ', ' F ', ' ', ' ', ' ')
+ .where('C', Predicates.controller(Predicates.blocks(definition.get())))
+ .where('F', Predicates.blocks(GTBlocks.CASING_TUNGSTENSTEEL_ROBUST.get())
+ .or(Predicates.autoAbilities(definition.getRecipeTypes()))
+ .or(Predicates.abilities(PartAbility.MAINTENANCE).setExactLimit(1))
+ .or(Predicates.abilities(PartAbility.PARALLEL_HATCH).setMaxGlobalLimited(1)))
+ .where('M', Predicates.abilities(PartAbility.MUFFLER))
+ .where('P', Predicates.blocks(GTBlocks.CASING_TUNGSTENSTEEL_PIPE.get()))
+ .where('G', Predicates.blocks(GTBlocks.CASING_TUNGSTENSTEEL_GEARBOX.get()))
+ .where('A', Predicates.blocks(GTBlocks.FIREBOX_TUNGSTENSTEEL.get()))
+ .where('B', Predicates.blocks('gtceu:bronze_machine_casing'))
+ .where(' ', Predicates.any())
+ .build())
+ .workableCasingRenderer("gtceu:block/casings/solid/machine_casing_robust_tungstensteel",
+ "gtceu:block/multiblock/primitive_blast_furnace", false);
+
{
+ "block.gtceu.ore_processing_plant": "Ore Processing Plant",
+ "gtceu.ore_processing_plant": "Ore Processing"
+}
+
This sections contains several examples you can use and adapt for your own modpack.
+ + + + + + + + + + + + + + + + +Materials are in-game items or fluids. They can be dusts, ingots, gems, fluids and all their derivatives. +To make a new material (NOTE: to add a material that is present on the periodic table, but doesn't have any in-game items/fluids, look below for how to do it),
+write an event.create()
call in the registering function, like in the examples.
+Write inside the parentheses the name of the material inside ''
or "".
You can change the properties of the material by adding any combination of the following calls:
+.ingot()
will make the material have both an ingot and dust form..dust()
will make the material have a dust form. Don't use this together with .dust()
..gem()
will make the material have both a gem form and a dust form. Don't use those together with .dust()
or .ingot()
.fluid()
will make the material have a fluid form..gas()
will make the material have a gas (fluid) form with gas properties..plasma()
will make the material have a plasma (fluid) form with plasma properties..polymer()
will make the material have a dust form with polymer properties..burnTime(burn time in ticks)
will turn the material into a furnace fuel..fluidBurnTime(burn time in ticks)
defines how long the fluid of the material will burn..components(component1, component2, ...)
describes the composition. The components are a list of elements of the following form: 'Kx material_name'
, where K
is a positive integer..iconSet(set)
gives the material an icon set..color(color code)
gives the material a color. The color must be provided as a hex value in the following form: 0xNNNNNN
, where N
are digits..secondaryColor(color code)
gives the material a secondary color. If this is not being called, the secondary value will default to white(0xffffff)..flags(flag1, flag2, ...)
can be used to select certain properties of the material, like generating gears, or disabling decomposition..element(element)
-> similar to .components()
, but is used when the material represents an element..rotorStats(speed, damage, durability)
-> this will create a turbine rotor from this material..blastTemp()
is meant to be paired together with .ingot()
. Will generate a EBF recipe (and an ABS recipe) based on the parameters you give it:null
for none, 'low'
for nitrogen, 'mid'
for helium, 'high'
for argon, 'higher'
for neon or 'highest'
for krypton..ore()
will create an ore from the material.true
for emissive textures.washedIn()
.separatedIn()
.separatedInto()
.oreSmeltInto()
.polarizesInto()
.arcSmeltInto()
.maceratesInto()
.ingotSmeltInto()
.addOreByproducts()
.cableProperties()
generates wires and cables(if material is not a superconductor). The following parameter sets can be given:.toolProperties()
.fluidPipeProperties()
.itemPipeProperties()
.addDefaultEnchant()
Harvest Level & Burn Time
+For .ingot()
, .dust()
and .gem()
, optionally you can put inside the parentheses any of these sets of parameters:
.ingot(2)
will make the material have the harvest level of iron tools) ingot(2, 2000)
will make the material have the harvest level of iron tools and will burn in furnaces as fuel for 2000 ticks or 100 seconds).Disabling Decomposition
+Depending on the composition, GT will autogenerate an electrolyzer or centrifuge recipe to decompose the material. You can block that by adding the disable decomposition flag.
+Choosing EU/t
+GT has some builtin constants to ease choosing the required EU/t:
+- GTValues.V
for a full amp of power at the selected tier
+- GTValues.VA
for a full amp, adjusted for cable loss
+- GTValues.VH
for half an amp
+- GTValues.VHA
for half an amp, adjusted for cable loss
These values are arrays containing the respective EU/t values for each tier.
+For example, you can get a full amp of EV power, adjusted for cable loss like this:
GTValues.VA[GTValues.EV]
+
To chose a color for your material, you can checkout https://www.w3schools.com/colors/colors_picker.asp +After you select a color with the above tool, copy the 6 digits that follow the # under the color preview.
+GTCEuStartupEvents.registry('gtceu:material', event => {
+ event.create('andesite_alloy')
+ .ingot()
+ .components('1x andesite', '1x iron')
+ .color(0x839689).iconSet(GTMaterialIconSet.DULL)
+ .flags(GTMaterialFlags.GENERATE_PLATE, GTMaterialFlags.GENERATE_GEAR, GTMaterialFlags.GENERATE_SMALL_GEAR)
+})
+
GTCEuStartupEvents.registry('gtceu:material', event => {
+ event.create('purple_coal')
+ .gem(2, 4000)
+ .element(GTElements.C)
+ .ore(2, 3)
+ .color(0x7D2DDB).iconSet(GTMaterialIconSet.LIGNITE)
+})
+
GTCEuStartupEvents.registry('gtceu:material', event => {
+ event.create('mysterious_dust')
+ .dust()
+ .cableProperties(GTValues.V[GTValues.LV], 69, 0, true) // (1)
+})
+
GTCEuStartupEvents.registry('gtceu:material', event => {
+ event.create('mysterious_ooze')
+ .fluid()
+ .color(0x500bbf)
+ .fluidTemp(69420)
+})
+
Elements are the base of GT materials. Registering an element WILL NOT add any items.
+To make a new element(NOTE: you can add only elements that are NOT present on the periodic table),
+write an event.create()
call in the registry function like in the example below.
+Inside the parentheses the following parameters are introduced:
When a material will be created from this element, the above properties will affect the auto-generated recipes.
+GTCEuStartupEvents.registry('gtceu:element', event => {
+ event.create('test_element', 27, 177, -1, null, 'test', false) // (1)
+})
+
Using material flags, you can specify several properties of each material, which +can influence how the material behaves, as well as which items are generated for it.
+Using material Flags
+GTCEuStartupEvents.registry('gtceu:material', event => {
+ event.create('my_material')
+ // ...
+ .flags(GTMaterialFlags.FLAMMABLE)
+})
+
FORCE_GENERATE_BLOCK
GENERATE_BOLT_SCREW
GENERATE_DENSE
GENERATE_FINE_WIRE
GENERATE_FOIL
GENERATE_FRAME
GENERATE_GEAR
GENERATE_LENS
GENERATE_LONG_ROD
GENERATE_PLATE
GENERATE_RING
GENERATE_ROD
GENERATE_ROTOR
GENERATE_ROUND
GENERATE_SMALL_GEAR
GENERATE_SPRING
GENERATE_SPRING_SMALL
BLAST_FURNACE_CALCITE_DOUBLE
BLAST_FURNACE_CALCITE_TRIPLE
DISABLE_ALLOY_BLAST
DISABLE_ALLOY_PROPERTY
CRYSTALLIZABLE
DECOMPOSITION_BY_CENTRIFUGING
DECOMPOSITION_BY_ELECTROLYZING
DISABLE_DECOMPOSITION
EXCLUDE_BLOCK_CRAFTING_BY_HAND_RECIPES
EXCLUDE_BLOCK_CRAFTING_RECIPES
EXCLUDE_PLATE_COMPRESSOR_RECIPES
EXPLOSIVE
FLAMMABLE
HIGH_SIFTER_OUTPUT
IS_MAGNETIC
MORTAR_GRINDABLE
NO_SMASHING
NO_SMELTING
NO_UNIFICATION
NO_WORKING
SOLDER_MATERIAL
SOLDER_MATERIAL_BAD
SOLDER_MATERIAL_GOOD
STICKY
The material system uses icon sets to determine the textures of generated blocks and items.
+The following icon sets are available by default:
+GTMaterialIconSet.BRIGHT
GTMaterialIconSet.CERTUS
GTMaterialIconSet.DIAMOND
GTMaterialIconSet.DULL
GTMaterialIconSet.EMERALD
GTMaterialIconSet.FINE
GTMaterialIconSet.FLINT
GTMaterialIconSet.FLUID
GTMaterialIconSet.GAS
GTMaterialIconSet.GEM_HORIZONTAL
GTMaterialIconSet.GEM_VERTICAL
GTMaterialIconSet.GLASS
GTMaterialIconSet.LAPIS
GTMaterialIconSet.LIGNITE
GTMaterialIconSet.MAGNETIC
GTMaterialIconSet.METALLIC
GTMaterialIconSet.NETHERSTAR
GTMaterialIconSet.OPAL
GTMaterialIconSet.QUARTZ
GTMaterialIconSet.ROUGH
GTMaterialIconSet.RUBY
GTMaterialIconSet.SAND
GTMaterialIconSet.SHINY
GTMaterialIconSet.WOOD
Custom iconsets can be specified as well, for example GTMatericalIconSet.get('infinity')
.
GTMaterialIconSet.get('')
+
BlastProperty.blastTemp() // (1)
+BlastProperty.gasTier() // (2)
+BlastProperty.durationOverride() // (3)
+BlastProperty.eutOverride() // (4)
+- DustProperty:
+ - .dust() // (5)
+- FluidPipeProperty:
+ - .fluidPipeProperties() // (6)
+- FluidProperty:
+ - .fluid() // (7)
+ - .isGas() // (8)
+ - .hasBlock()
+- GemProperty:
+ - .gem()
+- IngotProperty:
+ - .ingot() // (9)
+ - .smeltInto()
+ - .arcSmeltInto()
+ - .magneticMaterial()
+ - .macerateInto()
+- OreProperty:
+ - .ore() // (10)
+
Sets the Blast Furnace Temperature of the material. If the temperature is below 1000K recipes will be generated in the Primitive Blast Furnace. If above 1750K recipes for the Hot Ingot will be created along with the Vacuum Freezer Recipe to cool the ingot. Example: .blastTemp(2750)
Sets the Gas Tier which determins what GAS EBF recipes will be generated. Example: .gasTier(LOW)
Overrides the EBF's default behaviour for recipe durations.
+Overrides the EBF's default behaviour for EU/t.
+Used for creating a dust material. The haverst level and burn time can be specified in the brackets. Example: .dust(2, 4000)
Creates a fluid pipe out of the material it is added to. The possible values are: Max Fluid Temperature, Throughput, Gas Proof, Acid Proof, Cyro Proof, Plasma Proof,
+ Channels. Example: .fluidPipeProperties(9620, 850, false, false, false, false, 1)
All periodic table elements are present in GT, but some of them don't have any properties attached. You can also add a BlastProperty for EBF autogenerated recipes. You can also do this for other materials such as Obsidian. Here is how you can add them:
+ const $IngotProperty = Java.loadClass('com.gregtechceu.gtceu.api.data.chemical.material.properties.IngotProperty');
+ const $DustProperty = Java.loadClass('com.gregtechceu.gtceu.api.data.chemical.material.properties.DustProperty');
+ const $FluidProperty = Java.loadClass('com.gregtechceu.gtceu.api.data.chemical.material.properties.FluidProperty');
+ const $BlastProperty = Java.loadClass('com.gregtechceu.gtceu.api.data.chemical.material.properties.BlastProperty')
+
+ GTCEuStartupEvents.registry('gtceu:material', event => {
+
+ // Ingot
+ GTMaterials.Zirconium.setProperty(PropertyKey.INGOT, new $IngotProperty());
+ GTMaterials.Obsidian.setProperty(PropertyKey.INGOT, new $IngotProperty());
+
+ // Dust
+ GTMaterials.Selenium.setProperty(PropertyKey.DUST, new $DustProperty());
+
+ // Fluid
+ GTMaterials.Iodine.setProperty(PropertyKey.FLUID, new $FluidProperty());
+ GTMaterials.Iodine.getProperty(PropertyKey.FLUID).storage.enqueueRegistration(GTFluidStorageKeys.LIQUID, new GTFluidBuilder());
+ GTMaterials.Oganesson.setProperty(PropertyKey.FLUID, new $FluidProperty());
+ GTMaterials.Iodine.getProperty(PropertyKey.FLUID).storage.enqueueRegistration(GTFluidStorageKeys.GAS, new GTFluidBuilder()); //Can be LIQUID, GAS, PLASMA or MOLTEN
+
+ // Blast Property
+ GTMaterials.Zirconium.setProperty(PropertyKey.BLAST, new $BlastProperty(8000, 'higher', GTValues.VA(GTValues.MV), 8000));
+
+ });
+
You can even add an ore to existing materials:
+```js title="flags.js + GTCEuStartupEvents.registry('gtceu:material', event => {
+const $OreProperty = Java.loadClass('com.gregtechceu.gtceu.api.data.chemical.material.properties.OreProperty');
+
+ //Zinc Ore
+ GTMaterials.Zinc.setProperty(PropertyKey.ORE, new $OreProperty());
+
+});
+
You can also add flags to existing materials:
js title="flags.js
+ GTCEuStartupEvents.registry('gtceu:material', event => {
+
+ GTMaterials.Lead.addFlags(GTMaterialFlags.GENERATE_GEAR); //This is for materials already in GTCEU
+ GTMaterials.get('custom_material_name').addFlags(GTMaterialFlags.GENERATE_FOIL); //This only works for materials added by GTCEU addons
+
+ });
+```
GregTech has its own material system based on chemical elements.
+Materials are composed of chemical elements and/or other materials.
+Each material can have different items (and blocks), such as ingots, dusts, plates, wires, ores, etc.
You can create your own custom ore veins using KJS.
+It is also possible to modify or even delete existing ones.
GTCEuServerEvents.oreVeins(event => {
+ event.add("kubejs:custom_vein", vein => {
+ // Basic vein generation properties
+ vein.weight(200) // [*] (1)
+ vein.clusterSize(40) // [*] (2)
+ vein.density(0.25) // [*] (3)
+ vein.discardChanceOnAirExposure(0) // (4)
+
+ // Define where the vein can generate
+ vein.layer("deepslate") // [*] (5)
+ vein.dimensions("minecraft:overworld") // (6)
+ vein.biomes("#minecraft:is_overworld") // (7)
+
+ // Define a height range:
+ // You must choose EXACTLY ONE of these options! [*]
+ vein.heightRangeUniform(-60, 20) // (8)
+ vein.heightRangeTriangle(-60, 20) // (9)
+ vein.heightRange(/* ... */) // (10)
+
+ // Define the vein's generator:
+ vein.generator(/* ... */) // [*] (11)
+
+ // Add one or more type of surface indicator to the vein:
+ vein.addIndicator(/* ... */) // (12)
+ })
+})
+
0
and 1
.0
#
), or any number of individual biomes.HeightRangePlacement
directly, instead of the above shorthand versions:vein.heightRange(
+ height: {
+ type: "uniform",
+ min_inclusive: {
+ absolute: -60
+ },
+ max_inclusive: {
+ absolute: 20
+ }
+ }
+})
+
In case you want to limit your ore vein to multiple biomes that don't have a common tag yet, you can either specify all biomes manually, or you can create a biome tag:
+ServerEvents.tags('biome', event => {
+ event.add('kubejs:my_biome_tag', 'minecraft:forest')
+ event.add('kubejs:my_biome_tag', 'minecraft:river')
+})
+
You can then use your biome tag by simply calling vein.biomes('#kubejs:my_biome_tag')
in your vein definition.
GTCEuServerEvents.oreVeins(event => {
+ event.remove("gtceu:magnetite_vein_ow")
+})
+
If you want to remove all predefined ore veins (for example if you want to completely change ore generation +in your modpack), you can use the following code:
+GTCEuServerEvents.oreVeins(event => {
+ event.removeAll()
+})
+
You can also filter the veins you want to remove:
+event.removeAll((id, vein) => id.path != "magnetite_vein_ow")
+
GTCEuServerEvents.oreVeins(event => {
+ event.modify("gtceu:cassiterite_vein", vein => {
+ vein.density(1.0)
+ })
+})
+
The API for vein modifications is the same as for creating new veins.
+Moving veins to other dimensions
+When moving one of the default veins to another dimension, keep in mind that you also have to change their biome(s) accordingly.
+You can also modify all existing ore veins at once:
+GTCEuServerEvents.oreVeins(event => {
+ event.modifyAll("gtceu:cassiterite_vein", (id, vein) => {
+ console.log("Modifying vein: " + id)
+ vein.density(1.0)
+ })
+})
+
vein.layeredVeinGenerator(generator => generator
+ .buildLayerPattern(pattern => pattern
+ .layer(l => l.weight(3).mat(GTMaterials.Silver).size(2, 4))
+ .layer(l => l.weight(2).mat(GTMaterials.Gold).size(1, 1))
+ .layer(l => l.weight(1).block(() => Block.getBlock('minecraft:oak_log')).size(1, 1))
+ .layer(l => l.weight(1).state(() => Block.getBlock('minecraft:oak_planks').defaultBlockState()).size(1, 1))
+ )
+)
+
vein.veinedVeinGenerator(generator => generator
+ .oreBlock(GTMaterials.Silver, 4) // (1)
+ .rareBlock(GTMaterials.Gold, 1) // (2)
+ .rareBlockChance(0.25)
+ .veininessThreshold(0.1)
+ .maxRichnessThreshold(0.3)
+ .minRichness(0.3)
+ .maxRichness(0.5)
+ .edgeRoundoffBegin(10) // (3)
+ .maxEdgeRoundoff(0.2) // (4)
+)
+
Noise Parameters
+The vein's noise parameters can be summarized as follows:
+- veininessThreshold
defines how "sharp" the edges of the vein are.
+ Higher values result in more "blurry" edges.
+- maxRichnessThreshold
defines how many ores generate inside the vein (must be >= veininessThreshold
).
+ A higher distance between the values results in less "filled" veins.
+- minRichness
and maxRichness
allow you to limit the output of this calculation to a specific range.
The output of this calculation determines the chance for each block in the vein to generate.
+Height Ranges
+The height range of the generator is automatically inferred if you use heightRangeUniform()
or heightRangeTriangle()
in the vein definition, before setting the generator. Otherwise you need to set the height range manually:
generator.minYLevel(10)
+generator.maxYLevel(90)
+
vein.dikeVeinGenerator(generator => generator
+ .withBlock(GTMaterials.Silver, 3, 20, 60) // (1)
+ .withBlock(GTMaterials.Gold, 1, 20, 40)
+)
+
Height Ranges
+The height range of the generator is automatically inferred if you use heightRangeUniform()
or heightRangeTriangle()
in the vein definition, before setting the generator. Otherwise you need to set the height range manually:
generator.minYLevel(10)
+generator.maxYLevel(90)
+
Not yet documented
+vein.standardVeinGenerator(generator => /* ... */)
+
Not yet documented
+vein.geodeVeinGenerator(generator => /* ... */)
+
vein.surfaceIndicatorGenerator(indicator => indicator
+ .surfaceRock(GTMaterials.Platinum) // [*] (1)
+ .placement("above") // (2)
+ .density(0.4)
+ .radius(5)
+)
+
// Using a block:
+indicator.block(Block.getBlock('minecraft:oak_log'))
+
+// Using a block state:
+indicator.state(Block.getBlock('minecraft:oak_log').defaultBlockState())
+
surface
generates indicators on the world's surfaceabove
generates indicators in the next free space abovebelow
generates indicators in the next free space belowsurface
In a modpack, you may want to add your own stone types in order to integrate GT's ore generation with blocks from other mods.
+To do so, you need to register a tag prefix for your ore, add a language key, as well as allowing your ores to actually generate.
+For example purposes, this guide uses the block "Blockium" (ID: my_mod:blockium
).
+Replace this with the block you want to add ores for.
GTCEuStartupEvents.registry('gtceu:tag_prefix', event => {
+ event.create('blockium', 'ore') // (1)
+ .stateSupplier(() => Block.getBlock('my_mod:blockium').defaultBlockState()) // (2)
+ .baseModelLocation('my_mod:block/blockium') // (3)
+ .unificationEnabled(true)
+ .materialIconType(GTMaterialIconType.ore)
+ .generationCondition(ItemGenerationCondition.hasOreProperty)
+})
+
create()
is the name that corresponds to your stone type. The second parameter is always 'ore'
!Block.getBlock()
you must use the stone type's block ID as a parameter.ResourceLocation
of the base stone type's model. If the base block uses custom rendering, you may need to create your own model.{
+ "tagprefix.blockium": "Blockium %s Ore"
+}
+
To make your ores actually generate in the world, you have several options. +If you just want to stick to ore generation in the default dimensions, the easiest way to achieve this is adding your new ore base blocks to one of the following block tags:
+minecraft:stone_ore_replaceables
or minecraft:deepslate_ore_replaceables
minecraft:nether_carver_replaceables
forge:end_stone_ore_replaceables
on forge / c:end_stone_ore_replaceables
on fabricServerEvents.tags('block', event => {
+ event.add('minecraft:stone_ore_replaceables', 'my_mod:blockium')
+})
+
You can also add ores in other dimensions, but to do so you will have to create a custom World Generation Layer.
+You'll learn how to do so in Layers & Dimensions
Some mods may generate blocks with BlockState
s that differ from their defaultBlockState
.
+In this case you have to specify the actually generated block state in your ore stone type's stateSupplier
:
let UtilsJS = Java.loadClass("dev.latvian.mods.kubejs.util.UtilsJS")
+
+GTCEuStartupEvents.registry('gtceu:tag_prefix', event => {
+ event.create(type.path, 'ore')
+ .stateSupplier(() => UtilsJS.parseBlockState("my_mod:blockium[some_blockstate_property=true]"))
+})
+
To create ore veins in another dimension (or just at the location of certain blocks), you need to create a new worldgen layer.
+You may also need to add a custom stone type for your ores.
GTCEuStartupEvents.registry('gtceu:world_gen_layer', event => {
+ event.create('my_custom_layer')
+ .targets('#minecraft:stone_ore_replaceables', 'minecraft:endstone') // [*] (1)
+ .dimensions('minecraft:overworld', 'minecraft:the_end') // [*]
+})
+
RuleTest
or RuleTestSupplier
in case you need a bit more flexibility.Once the layer is created, you can refer to it by its name when creating or modifying an ore vein:
+GTCEuServerEvents.oreVeins(event => {
+ event.add("kubejs:custom_vein", vein => {
+ vein.layer("my_custom_layer")
+ // ...
+ })
+})
+
Bedrock Fluid Veins are invisable veins that exist under the bedrock, to find Fluid Veins you must have at least a HV tier Prospector. A Fluid Drilling Rig must be used to obtain the fluids out of the vein.
+// In server events
+GTCEuServerEvents.fluidVeins(event => {
+
+ event.add('gtceu:custom_bedrock_fluid_vein', vein => {
+ vein.addSpawnDimension('minecraft:overworld')
+ vein.fluid(() => Fluid.of('gtceu:custom_fluid').fluid)
+ vein.weight(600)
+ vein.minimumYield(120)
+ vein.maximumYield(720)
+ vein.depletionAmount(2)
+ vein.depletionChance(1)
+ vein.depletedYield(50)
+ });
+
GregTech has its own ore generation that is quite different from that of Minecraft's standard system. +Due to technical limitations, this system does not use Minecraft's features and works a bit differently.
+GT's ores generate in large veins that are placed along a grid (and a random offset per vein) throughout the world.
+In this section, you will learn how to customize ore generation for your modpack.
+Warning
+After changing the ore generation you will have to restart the server or re-open your world!
+It is not enough to simply use /reload
in this case.
Removing GTCEu Modern recipes with KubeJS works the same as any other recipe, meaning they can be removed by: ID, Mod, Input, Output, Type or a Mixture.
+ServerEvents.recipes(event => {
+ event.remove({ id: 'gtceu:smelting/sticky_resin_from_slime' }) // (1)
+ event.remove({ mod: 'gtceu' }) // (2)
+ event.remove({ type: 'gtceu:arc_furnace' }) // (3)
+ event.remove({ input: '#forge:ingots/iron' }) // (4)
+ event.remove({ output: 'minecraft:cobblestone' }) // (5)
+ event.remove({ type: 'gtceu:assembler', input: '#forge:plates/steel' }) // (6)
+})
+
#forge:ingots/iron
for removal.minecraft:cobblestone
for removal.#forge:plates/steel
for removal.With KubeJS it is possible to modfiy the Inputs or Outputs of existing GTCEu Modern recipes, which uses the same method of targeting the recipes.
+ServerEvents.recipes(event => {
+ event.replaceInput({ mod: 'gtceu' }, 'minecraft:sand', '#forge:sand') // (1)
+ event.replaceOutput({ type: 'gtceu:arc_furnace' }, 'gtceu:wrought_iron_ingot', 'minecraft:dirt') // (2)
+})
+
minecraft:sand
and replaces it with #forge:sand
.gtceu:wrought_iron_ingot
and replaces it with minecraft:dirt
.Syntax: event.recipes.gtceu.RECIPE_TYPE(string: recipe id)
ServerEvents.recipes(event => {
+ event.recipes.gtceu.assembler('test')
+ .itemInputs(
+ '64x minecraft:dirt',
+ '32x minecraft:diamond'
+ )
+ .inputFluids(
+ Fluid.of('minecraft:lava', 1500)
+ )
+ .itemOutputs(
+ 'minecraft:stick'
+ )
+ .duration(100)
+ .EUt(30)
+})
+
.itemInput()
.itemInputs()
.chancedInput()
.notConsumable()
.inputFluids()
.chancedFluidInput()
.circuit()
.itemOutput()
.itemOutputs()
.chancedOutput()
.outputFluids
.chancedFluidOutput()
Warning
+Due to some Rhino Jank, when adding rock breaker recipes you will need to manually tell Rhino how to interpret .addData()
.
ServerEvents.recipes(event => {
+ const RockBreakerCondition = Java.loadClass("com.gregtechceu.gtceu.common.recipe.RockBreakerCondition")
+
+ event.recipes.gtceu.rock_breaker('rhino_jank')
+ .notConsumable('minecraft:dirt')
+ .itemOutputs('minecraft:dirt')
+ ["addData(java.lang.String,java.lang.String)"]("fluidA", "minecraft:lava")
+ ["addData(java.lang.String,java.lang.String)"]("fluidB", "minecraft:water")
+ .duration(16)
+ .EUt(30)
+ .addCondition(RockBreakerCondition.INSTANCE)
+})
+
StartupEvents.registry('block', event => {
+ event.create('infinity_coil_block', 'gtceu:coil')
+ .temperature(100)
+ .level(0)
+ .energyDiscount(1) // (1)
+ .tier(10)
+ .coilMaterial(() => GTMaterials.get('infinity'))
+ .texture('kubejs:block/example_block')
+ .hardness(5)
+ .requiresTool(true)
+ .material('metal')
+})
+
GTCEuStartupEvents.registry('gtceu:machine', event => {
+ event.create('test_simple_steam_machine', 'steam', true) // (1)
+})
+
GTCEuStartupEvents.registry('gtceu:machine', event => {
+ event.create('test_electric', 'simple', GTValues.LV, GTValues.MV, GTValues.HV) // (1)
+ .rotationState(RotationState.NON_Y_AXIS)
+ .recipeType('test_recipe_type')
+ .tankScalingFunction(tier => tier * 3200)
+})
+
GTCEuStartupEvents.registry('gtceu:machine', event => {
+ event.create('test_kinetic', 'kinetic', GTValues.LV, GTValues.MV, GTValues.HV)
+ .rotationState(RotationState.NON_Y_AXIS)
+ .recipeType('test_kinetic_recipe_type')
+ .tankScalingFunction(tier => tier * 3200)
+})
+
GTCEuStartupEvents.registry('gtceu:machine', event => {
+ event.create('test_generator', 'generator', GTValues.LV, GTValues.MV, GTValues.HV) // (1)
+ .recipeType('test_generator_recipe_type')
+ .tankScalingFunction(tier => tier * 3200)
+})
+
GTCEuStartupEvents.registry('gtceu:machine', event => {
+ event.create('test_generator', 'multiblock')
+ .rotationState(RotationState.NON_Y_AXIS)
+ .appearanceBlock(GTBlocks.CASING_STEEL_SOLID)
+ .recipeTypes(['test_recipe_type_1', 'test_recipe_type_2'])
+ .pattern(definition => FactoryBlockPattern.start()
+ .aisle('CCC', 'GGG', 'CCC')
+ .aisle('CCC', 'GDG', 'CSC')
+ .aisle('CKC', 'GGG', 'CMC')
+ .where('K', Predicates.controller(Predicates.blocks(definition.get())))
+ .where('M', Predicates.abilities(PartAbility.MAINTENANCE))
+ .where('S', Predicates.abilities(PartAbility.MUFFLER))
+ .where('D', Predicates.blocks(GTBlocks.COIL_CUPRONICKEL.get()))
+ .where('G', Predicates.blocks('minecraft:glass'))
+ .where('C', Predicates.blocks(GTBlocks.CASING_STEEL_SOLID.get())
+ .or(Predicates.autoAbilities(definition.getRecipeTypes())))
+ .build())
+ .workableCasingRenderer(
+ "gtceu:block/casings/solid/machine_casing_inert_ptfe",
+ "gtceu:block/multiblock/large_chemical_reactor",
+ false
+ )
+})
+
Shape Info is used to manually define how your multiblock appears in the JEI/REI/EMI multiblock preview tab.
+GTCEuStartupEvents.registry('gtceu:machine', event => {
+ event.create('test_generator', 'multiblock')
+ .rotationState(RotationState.NON_Y_AXIS)
+ .appearanceBlock(GTBlocks.CASING_STEEL_SOLID)
+ .recipeTypes(['test_recipe_type_1', 'test_recipe_type_2'])
+ .pattern(definition => FactoryBlockPattern.start()
+ .aisle('CCC', 'GGG', 'CCC')
+ .aisle('CCC', 'GDG', 'CSC')
+ .aisle('CKC', 'GGG', 'CMC')
+ .where('K', Predicates.controller(Predicates.blocks(definition.get())))
+ .where('M', Predicates.abilities(PartAbility.MAINTENANCE))
+ .where('S', Predicates.abilities(PartAbility.MUFFLER))
+ .where('D', Predicates.blocks(GTBlocks.COIL_CUPRONICKEL.get()))
+ .where('G', Predicates.blocks('minecraft:glass'))
+ .where('C', Predicates.blocks(GTBlocks.CASING_STEEL_SOLID.get())
+ .or(Predicates.autoAbilities(definition.getRecipeTypes())))
+ .build())
+ .shapeInfo(controller => MultiblockShapeInfo.builder()
+ .aisle('eCe', 'GGG', 'CCC')
+ .aisle('CCC', 'GDG', 'CSC')
+ .aisle('iKo', 'GGG', 'CMC')
+ .where('K', controller, Direction.SOUTH)
+ .where('C', GTBlocks.CASING_STEEL_SOLID.get())
+ .where('G', Block.getBlock('minecraft:glass'))
+ .where('D', GTBlocks.COIL_CUPRONICKEL.get())
+ .where('S', GTMachines.MUFFLER_HATCH[1], Direction.UP)
+ .where('M', GTMachines.MAINTENANCE_HATCH[1], Direction.SOUTH)
+ .where('e', GTMachines.ENERGY_INPUT_HATCH[1], Direction.NORTH)
+ .where('i', GTMachines.ITEM_IMPORT_BUS[1], Direction.SOUTH)
+ .where('0', GTMachines.ITEM_EXPORT_BUS[1], Direction.SOUTH)
+ .build())
+ .workableCasingRenderer(
+ "gtceu:block/casings/solid/machine_casing_inert_ptfe",
+ "gtceu:block/multiblock/large_chemical_reactor",
+ false
+ )
+})
+
Recipe Types MUST be registered before the machines or multiblocks
+GTCEuStartupEvents.registry('gtceu:recipe_type', event => {
+ event.create('test_recipe_type')
+ .category('test')
+ .setEUIO('in')
+ .setMaxIOSize(3, 3, 3, 3) // (1)
+ .setSlotOverlay(false, false, GuiTextures.SOLIDIFIER_OVERLAY)
+ .setProgressBar(GuiTextures.PROGRESS_BAR_ARROW, FillDirection.LEFT_TO_RIGHT)
+ .setSound(GTSoundEntries.COOLING)
+})
+
GTCEuStartupEvents.registry('gtceu:recipe_type', event => {
+ event.create('test_recipe_type')
+ .category('test_kinetic')
+ .setEUIO('in')
+ .setMaxIOSize(3, 3, 3, 3)
+ .setSlotOverlay(false, false, GuiTextures.SOLIDIFIER_OVERLAY)
+ .setProgressBar(GuiTextures.PROGRESS_BAR_ARROW, FillDirection.LEFT_TO_RIGHT)
+ .setSound(GTSoundEntries.COOLING)
+ .setMaxTooltip(6) // (1)
+})
+
Low Drag Lib (bundled with GTCEu Modern) provides an in-game UI Editor for creating custom GUI's for
+machines and for Recipe Types. The UI Editor is accessible in-game
+via the command /gtceu ui_editor
.
This section contains other topics that aren't necessarily large enough to be grouped into their own categories.
+ + + + + + + + + + + + + + + + +GTCEu Modern offers extensive integration with KubeJS for customizability.
+Most of our tools for modpack creators revolve around this KubeJS API.
Refer to this section for information on how to use it, as well as for examples.
+Sometimes, calling a specific method is always required when adding (or modifying) something.
+These methods are marked with // [*]
in the docs, like in the following example:
ServerEvents.exampleEvent(event => {
+ event.create('example', builder => {
+ builder.requiredMethod(42) // [*] (1)
+ builder.otherRequiredMethod(42) // [*]
+
+ builder.optionalMethod() // (2)
+ })
+})
+
While we try to keep this documentation up to date and as complete as possible, it may not always contain all of the latest information.
+Please also check the Beyond the Docs page for additional references.
+ + + + + + + + + + + + + + + + +