KubeJS compat

This commit is contained in:
2025-04-13 13:02:59 +02:00
parent f93f35b67a
commit c5412e404c
23 changed files with 502 additions and 37 deletions

View File

@@ -125,10 +125,10 @@ repositories {
}
maven {
url "https://maven.saps.dev/minecraft"
url "https://maven.latvian.dev/releases"
content {
includeGroup "dev.latvian.mods"
includeGroup "dev.ftb.mods"
includeGroup "dev.latvian.apps"
}
}
maven {
@@ -157,6 +157,10 @@ dependencies {
implementation("curse.maven:jade-324717:${jade_id}")
compileOnly("dev.architectury:architectury-neoforge:$architectury_version")
api("dev.latvian.mods:kubejs-neoforge:$kubejs_version")
interfaceInjectionData("dev.latvian.mods:kubejs-neoforge:$kubejs_version") // optional
//runtimeOnly("dev.architectury:architectury-neoforge:13.0.8")
//implementation("dev.ftb.mods:ftb-chunks-neoforge:2101.1.1")
//implementation("dev.ftb.mods:ftb-teams-neoforge:2101.1.0")

View File

@@ -14,15 +14,15 @@ parchment_version = 2024.11.17
mod_id=mechanicals
mod_name=Mechanicals Lib
mod_license=LGPL3
mod_version=0.1.23
mod_version=0.2.11
mod_group_id=com.oierbravo
mod_author=oierbravo
mod_description=Utility Library for Create Addons.
# dependency versions
create_version = 6.0.2-32
ponder_version = 1.0.44
flywheel_version = 1.0.1-11
create_version = 6.0.4-53
ponder_version = 1.0.46
flywheel_version = 1.0.2
registrate_version = MC1.21-1.3.0+62
jei_minecraft_version = 1.21.1
jei_version = 19.21.0.247
@@ -31,3 +31,9 @@ curios_version = 9.2.2
sodium_version = 0.6.9
jade_id = 6198776
kubejs_version=2101.7.1-build.181
#kubejs_version=2101.7.2-build.230
rhino_version=2101.2.7-build.74
architectury_version=13.0.2
mixin_extras_version = 0.4.1

View File

@@ -1,4 +1,4 @@
package com.oierbravo.mechanicals.jade;
package com.oierbravo.mechanicals.compat.jade;
public interface IHavePercent {
int getProgressPercent();

View File

@@ -1,4 +1,4 @@
package com.oierbravo.mechanicals.jade;
package com.oierbravo.mechanicals.compat.jade;
import com.oierbravo.mechanicals.utility.LibLang;
import net.minecraft.nbt.CompoundTag;

View File

@@ -1,4 +1,4 @@
package com.oierbravo.mechanicals.jei;
package com.oierbravo.mechanicals.compat.jei;
public class CreateRecipeCategoryBuilder {
}

View File

@@ -0,0 +1,30 @@
package com.oierbravo.mechanicals.compat.kubejs;
import com.oierbravo.mechanicals.compat.kubejs.bindings.BlockPredicateBuilder;
import com.oierbravo.mechanicals.compat.kubejs.bindings.ProcessingOutputBuilder;
import com.oierbravo.mechanicals.compat.kubejs.components.*;
import com.oierbravo.mechanicals.compat.kubejs.bindings.RecipeRequirementBuilder;
import dev.latvian.mods.kubejs.plugin.KubeJSPlugin;
import dev.latvian.mods.kubejs.recipe.schema.RecipeComponentFactoryRegistry;
import dev.latvian.mods.kubejs.script.BindingRegistry;
public class MechanicalsJsPlugin implements KubeJSPlugin {
@Override
public void registerRecipeComponents(RecipeComponentFactoryRegistry registry) {
registry.register(ProcessingOutputComponent.OUTPUT);
registry.register(BlockPredicateComponent.BLOCK_PREDICATE);
registry.register(RecipeRequirementsComponent.RECIPE_REQUIREMENT);
registry.register(ResourceLocationComponent.RESOURCE_LOCATION);
registry.register(CreateFluidIngredientComponent.FLUID_INGREDIENT);
}
@Override
public void registerBindings(BindingRegistry registry) {
if (registry.type().isServer()) {
registry.add("Output", ProcessingOutputBuilder.class);
registry.add("BlockPredicate", BlockPredicateBuilder.class);
registry.add("RecipeRequirement", RecipeRequirementBuilder.class);
}
}
}

View File

@@ -0,0 +1,10 @@
package com.oierbravo.mechanicals.compat.kubejs.bindings;
import com.oierbravo.mechanicals.foundation.util.BlockPredicateUtils;
import net.minecraft.advancements.critereon.BlockPredicate;
public class BlockPredicateBuilder {
public static BlockPredicate of(String id){
return BlockPredicateUtils.Builder.build(id);
}
}

View File

@@ -0,0 +1,14 @@
package com.oierbravo.mechanicals.compat.kubejs.bindings;
import com.simibubi.create.content.processing.recipe.ProcessingOutput;
import net.minecraft.world.item.ItemStack;
public class ProcessingOutputBuilder {
public static ProcessingOutput of(ItemStack itemStack){
return of(itemStack, 1);
}
public static ProcessingOutput of(ItemStack itemStack, float chance){
return new ProcessingOutput(itemStack, chance);
}
}

View File

@@ -0,0 +1,29 @@
package com.oierbravo.mechanicals.compat.kubejs.bindings;
import com.oierbravo.mechanicals.foundation.recipe.requirements.*;
import net.minecraft.resources.ResourceLocation;
public class RecipeRequirementBuilder {
public static MinYRequirement minY(int value){
return MinYRequirement.of(value);
}
public static MaxYRequirement maxY(int value){
return MaxYRequirement.of(value);
}
public static MinSpeedRequirement minSpeed(float value){
return MinSpeedRequirement.of(value);
}
public static MaxSpeedRequirement maxSpeed(float value){
return MaxSpeedRequirement.of(value);
}
public static BiomeRequirement biome(String id){
return BiomeRequirement.of(ResourceLocation.parse(id));
}
public static BiomeTagRequirement biomeTag(String id){
return BiomeTagRequirement.of(ResourceLocation.parse(id));
}
}

View File

@@ -0,0 +1,74 @@
package com.oierbravo.mechanicals.compat.kubejs.components;
import com.mojang.serialization.Codec;
import com.oierbravo.mechanicals.foundation.util.BlockPredicateUtils;
import dev.latvian.mods.kubejs.recipe.KubeRecipe;
import dev.latvian.mods.kubejs.recipe.component.RecipeComponent;
import dev.latvian.mods.rhino.Context;
import dev.latvian.mods.rhino.NativeArray;
import dev.latvian.mods.rhino.type.TypeInfo;
import net.createmod.catnip.data.Couple;
import net.minecraft.advancements.critereon.BlockPredicate;
import java.util.List;
public record BlockPredicateComponent() implements RecipeComponent<BlockPredicate> {
public static final RecipeComponent<BlockPredicate> BLOCK_PREDICATE = new BlockPredicateComponent();
@Override
public Codec<BlockPredicate> codec() {
return BlockPredicate.CODEC;
}
@Override
public TypeInfo typeInfo() {
return TypeInfo.of(BlockPredicate.class);
}
@Override
public RecipeComponent<List<BlockPredicate>> asList() {
return RecipeComponent.super.asList();
}
@Override
public BlockPredicate wrap(Context cx, KubeRecipe recipe, Object from) {
if(from instanceof String id)
return BlockPredicateUtils.Builder.build(id);
if(from instanceof BlockPredicate blockPredicate)
return blockPredicate;
return RecipeComponent.super.wrap(cx, recipe, from);
}
public static final RecipeComponent<Couple<BlockPredicate>> BLOCK_PREDICATE_COUPLE = new RecipeComponent<>() {
private static final TypeInfo WRAP_TYPE = TypeInfo.OBJECT_ARRAY;
//private static final TypeInfo WRAP_TYPE = TypeInfo.of(Couple.class).withParams(TypeInfo.of(BlockPredicate.class));
@Override
public Codec<Couple<BlockPredicate>> codec() {
return Couple.codec(BlockPredicate.CODEC);
}
@Override
public TypeInfo typeInfo() {
return WRAP_TYPE;
}
@Override
public Couple<BlockPredicate> wrap(Context cx, KubeRecipe recipe, Object from) {
if(from instanceof NativeArray nativeArray){
return Couple.create((BlockPredicate) nativeArray.getFirst(),(BlockPredicate) nativeArray.get(1));
}
return Couple.create(BlockPredicate.Builder.block().build(),BlockPredicate.Builder.block().build());
}
@Override
public String toString() {
return "couple_block_predicate";
}
};
}

View File

@@ -0,0 +1,31 @@
package com.oierbravo.mechanicals.compat.kubejs.components;
import com.mojang.serialization.Codec;
import com.simibubi.create.foundation.fluid.FluidIngredient;
import dev.latvian.mods.kubejs.recipe.KubeRecipe;
import dev.latvian.mods.kubejs.recipe.component.RecipeComponent;
import dev.latvian.mods.rhino.Context;
import dev.latvian.mods.rhino.type.TypeInfo;
import net.neoforged.neoforge.fluids.FluidStack;
public record CreateFluidIngredientComponent() implements RecipeComponent<FluidIngredient> {
public static final RecipeComponent<FluidIngredient> FLUID_INGREDIENT = new CreateFluidIngredientComponent();
@Override
public Codec<FluidIngredient> codec() {
return FluidIngredient.CODEC;
}
@Override
public TypeInfo typeInfo() {
return TypeInfo.of(FluidIngredient.class);
}
@Override
public FluidIngredient wrap(Context cx, KubeRecipe recipe, Object from) {
if(from instanceof FluidStack fluidStack)
return FluidIngredient.fromFluidStack(fluidStack);
return RecipeComponent.super.wrap(cx, recipe, from);
}
}

View File

@@ -0,0 +1,42 @@
package com.oierbravo.mechanicals.compat.kubejs.components;
import com.mojang.serialization.Codec;
import com.simibubi.create.content.processing.recipe.ProcessingOutput;
import dev.latvian.mods.kubejs.item.ItemStackJS;
import dev.latvian.mods.kubejs.recipe.KubeRecipe;
import dev.latvian.mods.kubejs.recipe.component.RecipeComponent;
import dev.latvian.mods.kubejs.script.KubeJSContext;
import dev.latvian.mods.kubejs.util.RegistryAccessContainer;
import dev.latvian.mods.rhino.Context;
import dev.latvian.mods.rhino.type.TypeInfo;
import net.minecraft.world.item.ItemStack;
public record ProcessingOutputComponent() implements RecipeComponent<ProcessingOutput> {
public static final RecipeComponent<ProcessingOutput> OUTPUT = new ProcessingOutputComponent();
@Override
public Codec<ProcessingOutput> codec() {
return ProcessingOutput.CODEC;
}
@Override
public TypeInfo typeInfo() {
return TypeInfo.of(ProcessingOutput.class).or(ItemStackJS.TYPE_INFO);
}
@Override
public ProcessingOutput wrap(Context cx, KubeRecipe recipe, Object from) {
if (from instanceof ProcessingOutput o) {
return o;
}
RegistryAccessContainer registryAccess = ((KubeJSContext) cx).getRegistries();
ItemStack itemStack = ItemStackJS.wrap(registryAccess, from);
if (itemStack.isEmpty()) {
throw new IllegalArgumentException("empty processing output: " + from);
}
return new ProcessingOutput(itemStack,1);
}
}

View File

@@ -0,0 +1,27 @@
package com.oierbravo.mechanicals.compat.kubejs.components;
import com.mojang.serialization.Codec;
import com.oierbravo.mechanicals.foundation.recipe.IRecipeRequirement;
import dev.latvian.mods.kubejs.recipe.KubeRecipe;
import dev.latvian.mods.kubejs.recipe.component.RecipeComponent;
import dev.latvian.mods.rhino.Context;
import dev.latvian.mods.rhino.type.TypeInfo;
public record RecipeRequirementsComponent() implements RecipeComponent<IRecipeRequirement> {
public static final RecipeComponent<IRecipeRequirement> RECIPE_REQUIREMENT = new RecipeRequirementsComponent();
@Override
public Codec<IRecipeRequirement> codec() {
return IRecipeRequirement.CODEC;
}
@Override
public TypeInfo typeInfo() {
return TypeInfo.of(IRecipeRequirement.class);
}
@Override
public IRecipeRequirement wrap(Context cx, KubeRecipe recipe, Object from) {
return RecipeComponent.super.wrap(cx, recipe, from);
}
}

View File

@@ -0,0 +1,31 @@
package com.oierbravo.mechanicals.compat.kubejs.components;
import com.mojang.serialization.Codec;
import dev.latvian.mods.kubejs.recipe.KubeRecipe;
import dev.latvian.mods.kubejs.recipe.component.RecipeComponent;
import dev.latvian.mods.rhino.Context;
import dev.latvian.mods.rhino.type.TypeInfo;
import net.minecraft.resources.ResourceLocation;
public record ResourceLocationComponent() implements RecipeComponent<ResourceLocation> {
public static final RecipeComponent<ResourceLocation> RESOURCE_LOCATION = new ResourceLocationComponent();
@Override
public Codec<ResourceLocation> codec() {
return ResourceLocation.CODEC;
}
@Override
public TypeInfo typeInfo() {
return TypeInfo.of(ResourceLocation.class);
}
@Override
public ResourceLocation wrap(Context cx, KubeRecipe recipe, Object from) {
if(from instanceof String id)
return ResourceLocation.parse(id);
if(from instanceof ResourceLocation resourceLocation)
return resourceLocation;
return RecipeComponent.super.wrap(cx, recipe, from);
}
}

View File

@@ -22,16 +22,6 @@ public class CycleBehavior extends BlockEntityBehaviour {
private int numCycles;
private int currentCycle;
public interface CycleBehaviourSpecifics {
void onCycleCompleted();
void onOperationCompletd();
float getKineticSpeed();
boolean tryProcess(boolean simulate);
void playSound();
int getCycles();
}
public <T extends SmartBlockEntity & CycleBehaviourSpecifics> CycleBehavior(T te, int pCycle, boolean pActuateHalfCycle) {
super(te);
@@ -105,7 +95,6 @@ public class CycleBehavior extends BlockEntityBehaviour {
if (runningTicks == cycleTime / cycleDivider && specifics.getKineticSpeed() != 0) {
apply();
specifics.playSound();
if (!level.isClientSide)
blockEntity.sendData();
}
@@ -117,6 +106,7 @@ public class CycleBehavior extends BlockEntityBehaviour {
finished = true;
running = false;
specifics.onOperationCompletd();
specifics.playCompletionSound();
}
blockEntity.sendData();
return;
@@ -124,6 +114,12 @@ public class CycleBehavior extends BlockEntityBehaviour {
prevRunningTicks = runningTicks;
runningTicks += getRunningTickSpeed();
if (level.isClientSide){
specifics.playSound();
specifics.showParticles();
}
if (prevRunningTicks < cycleTime / cycleDivider && runningTicks >= cycleTime / cycleDivider) {
runningTicks = cycleTime / cycleDivider;
// Pause the ticks until a packet is received
@@ -179,5 +175,18 @@ public class CycleBehavior extends BlockEntityBehaviour {
public int getRunningTicks() {
return runningTicks;
}
public interface CycleBehaviourSpecifics {
default void onCycleCompleted(){};
default void onOperationCompletd(){};
default void playSound(){};
default void showParticles(){};
default void playCompletionSound(){};
int getCycles();
float getKineticSpeed();
boolean tryProcess(boolean simulate);
}
}

View File

@@ -18,14 +18,7 @@ public class DynamicCycleBehavior extends BlockEntityBehaviour {
private boolean running;
private boolean finished;
public interface DynamicCycleBehaviorSpecifics {
void onOperationCompleted();
float getKineticSpeed();
boolean tryProcess(boolean simulate);
void playCompletionSound();
int getProcessingTime();
}
public <T extends SmartBlockEntity & DynamicCycleBehaviorSpecifics> DynamicCycleBehavior(T te) {
super(te);
@@ -104,11 +97,16 @@ public class DynamicCycleBehavior extends BlockEntityBehaviour {
prevRunningTicks = runningTicks;
runningTicks += getRunningTickSpeed();
if (level.isClientSide){
specifics.playSound();
specifics.showParticles();
}
if (prevRunningTicks < cycleTime && runningTicks >= cycleTime) {
runningTicks = cycleTime;
// Pause the ticks until a packet is received
if (level.isClientSide && !blockEntity.isVirtual())
if (level.isClientSide && !blockEntity.isVirtual()){
runningTicks = -(cycleTime);
}
}
}
@@ -170,4 +168,15 @@ public class DynamicCycleBehavior extends BlockEntityBehaviour {
return 1;
return 1 - (float) (getCycleTime() - prevRunningTicks) / getCycleTime();
}
public interface DynamicCycleBehaviorSpecifics {
default void onOperationCompleted(){};
default void playSound(){};
default void showParticles(){};
default void playCompletionSound(){};
float getKineticSpeed();
boolean tryProcess(boolean simulate);
int getProcessingTime();
}
}

View File

@@ -58,11 +58,6 @@ public class RecipeRequirementsBehaviour<R extends IRecipeWithRequirements> exte
return false;
}
//meetsRequirements = meetsRequirements(pRecipe.get().getRecipeRequirements(), specifics);
//if(!pRecipe.get().checkRequirements((BlockEntity) specifics)){
//}
blockEntity.sendData();
return true;
}

View File

@@ -5,14 +5,19 @@ import com.mojang.serialization.codecs.RecordCodecBuilder;
import com.oierbravo.mechanicals.foundation.recipe.IRecipeRequirement;
import com.oierbravo.mechanicals.foundation.recipe.RecipeRequirementType;
import com.oierbravo.mechanicals.register.MechanicalRecipeRequirementTypes;
import net.minecraft.client.Minecraft;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredRegister;
import java.util.Optional;
@@ -33,6 +38,12 @@ public record BiomeRequirement(ResourceKey<Biome> biomeResourceKey) implements I
public static BiomeRequirement of(ResourceKey<Biome> key) {
return new BiomeRequirement( key);
}
public static BiomeRequirement of(String id) {
return of(ResourceLocation.parse(id));
}
public static BiomeRequirement of(ResourceLocation resourceLocation) {
return of(ResourceKey.create(Registries.BIOME, resourceLocation));
}
public boolean test(Level pLevel, BlockEntity pBlockEntity) {
if(biomeResourceKey == null)

View File

@@ -5,6 +5,7 @@ import com.mojang.serialization.codecs.RecordCodecBuilder;
import com.oierbravo.mechanicals.foundation.recipe.IRecipeRequirement;
import com.oierbravo.mechanicals.foundation.recipe.RecipeRequirementType;
import com.oierbravo.mechanicals.register.MechanicalRecipeRequirementTypes;
import net.minecraft.client.Minecraft;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.RegistryFriendlyByteBuf;
@@ -35,14 +36,16 @@ public record BiomeTagRequirement(TagKey<Biome> tag) implements IRecipeRequireme
public ResourceLocation getResourceLocation(){
return tag.location();
}
/* public static final StreamCodec<RegistryFriendlyByteBuf, BiomeTagRequirement> STREAM_CODEC = StreamCodec.composite(
ResourceKey.streamCodec(Registries.BIOME), BiomeTagRequirement::tag,
BiomeTagRequirement::new
);*/
public static BiomeTagRequirement of(TagKey<Biome> tag) {
return new BiomeTagRequirement(tag);
}
public static BiomeTagRequirement of(ResourceLocation resourceLocation) {
return of(TagKey.create(Registries.BIOME, resourceLocation));
}
public static BiomeTagRequirement of(String id) {
return new BiomeTagRequirement(ResourceLocation.parse(id));
}
public boolean test(Level pLevel, BlockEntity pBlockEntity) {
if(tag == null)

View File

@@ -0,0 +1,113 @@
package com.oierbravo.mechanicals.foundation.util;
import net.minecraft.advancements.critereon.BlockPredicate;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.LiquidBlock;
import net.minecraft.world.level.material.Fluid;
import net.neoforged.neoforge.fluids.FluidStack;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
public class BlockPredicateUtils {
public static class Builder {
public static BlockPredicate.Builder from(Block block){
return BlockPredicate.Builder.block().of(block);
}
public static BlockPredicate.Builder from(ResourceLocation resourceLocation){
return from(BuiltInRegistries.BLOCK.get(resourceLocation));
}
public static BlockPredicate build(Block block){
return from(block).build();
}
public static BlockPredicate build(ResourceLocation resourceLocation){
return build(BuiltInRegistries.BLOCK.get(resourceLocation));
}
public static BlockPredicate build(String id){
return build(ResourceLocation.parse(id));
}
}
public static class Matcher {
public static final BlockPredicate EMPTY = new BlockPredicate(Optional.empty(), Optional.empty(), Optional.empty());
final BlockPredicate blockPredicate;
public Matcher(BlockPredicate blockPredicate){
this.blockPredicate = blockPredicate;
}
public static Matcher of(BlockPredicate blockPredicate) {
return new Matcher(blockPredicate);
}
public boolean isEmpty() {
if (blockPredicate == EMPTY) return true;
return hasEmptyBlocks() && hasEmptyProperties() && hasEmptyNbt();
}
public boolean hasEmptyBlocks() {
return blockPredicate.blocks().isEmpty();
}
public boolean hasEmptyProperties() {
return blockPredicate.properties().isEmpty();
}
public boolean hasEmptyNbt() {
return blockPredicate.nbt().isEmpty();
}
public List<Block> blocks() {
if (isEmpty())
return List.of();
final List<Block> blocks = new java.util.ArrayList<>();
if (blockPredicate.blocks().isPresent()) {
for (Holder<Block> block : blockPredicate.blocks().get().unwrap().map(BuiltInRegistries.BLOCK::getOrCreateTag, Function.identity())) {
blocks.add(block.value());
}
}
return blocks;
}
public List<Fluid> fluids() {
return blocks().stream()
.filter(block -> block instanceof LiquidBlock)
.map(block -> ((LiquidBlock) block).fluid.getSource())
.toList();
}
public List<FluidStack> fluidStacks() {
return fluids().stream()
.map(fluid -> new FluidStack(fluid, 1000))
.toList();
}
public List<Item> items() {
if (isEmpty()) {
return List.of();
}
return blocks().stream()
.map(Block::asItem)
.filter(item ->
!item.equals(Items.AIR))
.toList();
}
public List<ItemStack> itemStacks() {
if (isEmpty()) {
return List.of();
}
List<Item> items = items();
return items().stream()
.map(Item::getDefaultInstance)
.toList();
}
}
}

View File

@@ -0,0 +1,18 @@
package com.oierbravo.mechanicals.utility;
import com.simibubi.create.AllShapes;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.phys.shapes.VoxelShape;
public class MechanicalShapes {
private static AllShapes.Builder shape(VoxelShape shape) {
return new AllShapes.Builder(shape);
}
private static AllShapes.Builder shape(double x1, double y1, double z1, double x2, double y2, double z2) {
return shape(cuboid(x1, y1, z1, x2, y2, z2));
}
private static VoxelShape cuboid(double x1, double y1, double z1, double x2, double y2, double z2) {
return Block.box(x1, y1, z1, x2, y2, z2);
}
}

View File

@@ -18,4 +18,12 @@ public class RegistrateLangBuilder {
registrate.addRawLang(literal,defaultTranslation);
return this;
}
public RegistrateLangBuilder addBlockTooltip(String id, String defaultTranslation){
registrate.addRawLang("block." + namespace + "." + id + ".tooltip",defaultTranslation);
return this;
}
public RegistrateLangBuilder addBlockTooltipSummary(String id, String defaultTranslation){
registrate.addRawLang("block." + namespace + "." + id + ".tooltip.summary",defaultTranslation);
return this;
}
}

View File

@@ -0,0 +1 @@
com.oierbravo.mechanicals.compat.kubejs.MechanicalsJsPlugin