initial commit for shared version repo of Batteries mod - currently supports 1.21.11 and 1.21.1 of NeoForge.

This commit is contained in:
trunksbomb
2026-03-22 14:18:55 -04:00
commit 8d86c883ae
247 changed files with 18315 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
plugins {
id 'java-library'
id 'maven-publish'
id 'net.neoforged.moddev' version '2.0.140'
id 'idea'
}
apply from: rootProject.file('gradle/neoforge-module.gradle')

View File

@@ -0,0 +1,245 @@
package com.trunksbomb.batteries;
import com.mojang.logging.LogUtils;
import com.trunksbomb.batteries.block.BatteryBlock;
import com.trunksbomb.batteries.block.ChargerBlock;
import com.trunksbomb.batteries.block.CoalGeneratorBlock;
import com.trunksbomb.batteries.block.entity.BatteryBlockEntity;
import com.trunksbomb.batteries.block.entity.CoalGeneratorBlockEntity;
import com.trunksbomb.batteries.block.entity.ChargerBlockEntity;
import com.trunksbomb.batteries.command.BatteryDebugCommands;
import com.trunksbomb.batteries.item.BatteryBlockItem;
import com.trunksbomb.batteries.item.BatteryPoweredAxeItem;
import com.trunksbomb.batteries.item.BatteryPoweredArmorItem;
import com.trunksbomb.batteries.item.BatteryPoweredBowItem;
import com.trunksbomb.batteries.item.BatteryPoweredHoeItem;
import com.trunksbomb.batteries.item.BatteryPoweredPickaxeItem;
import com.trunksbomb.batteries.item.BatteryPoweredShovelItem;
import com.trunksbomb.batteries.item.BatteryPoweredShieldItem;
import com.trunksbomb.batteries.item.BatteryPoweredSwordItem;
import com.trunksbomb.batteries.item.BatteryItem;
import com.trunksbomb.batteries.item.BatteryItemData.Tier;
import com.trunksbomb.batteries.item.PoweredItem;
import com.trunksbomb.batteries.menu.BatteriesMenu;
import com.trunksbomb.batteries.menu.BatteryBlockMenu;
import com.trunksbomb.batteries.menu.CoalGeneratorMenu;
import com.trunksbomb.batteries.recipe.BatteryBlockUpgradeRecipe;
import com.trunksbomb.batteries.recipe.BatteryTierUpgradeRecipe;
import com.trunksbomb.batteries.recipe.PoweredGearUpgradeRecipe;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.CreativeModeTabs;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.CustomRecipe;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.equipment.ArmorMaterials;
import net.minecraft.world.item.equipment.ArmorType;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.inventory.MenuType;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.config.ModConfig;
import net.neoforged.neoforge.capabilities.Capabilities;
import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.common.extensions.IMenuTypeExtension;
import net.neoforged.neoforge.event.RegisterCommandsEvent;
import net.neoforged.neoforge.transfer.access.ItemAccess;
import net.neoforged.neoforge.transfer.energy.EnergyHandler;
import net.neoforged.neoforge.transfer.transaction.Transaction;
import net.neoforged.neoforge.registries.DeferredBlock;
import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredItem;
import net.neoforged.neoforge.registries.DeferredRegister;
import org.slf4j.Logger;
@Mod(Batteries.MODID)
public class Batteries {
public static final String MODID = "batteries";
public static final Logger LOGGER = LogUtils.getLogger();
public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks(MODID);
public static final DeferredRegister.Items ITEMS = DeferredRegister.createItems(MODID);
public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITY_TYPES = DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, MODID);
public static final DeferredRegister<MenuType<?>> MENU_TYPES = DeferredRegister.create(Registries.MENU, MODID);
public static final DeferredRegister<CreativeModeTab> CREATIVE_MODE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, MODID);
public static final DeferredRegister<RecipeSerializer<?>> RECIPE_SERIALIZERS = DeferredRegister.create(Registries.RECIPE_SERIALIZER, MODID);
public static final DeferredItem<Item> BATTERY = registerBattery("battery", Tier.BASIC);
public static final DeferredItem<Item> BATTERY1 = registerBattery("battery1", Tier.ADVANCED);
public static final DeferredItem<Item> BATTERY2 = registerBattery("battery2", Tier.ELITE);
public static final DeferredItem<Item> BATTERY3 = registerBattery("battery3", Tier.ULTIMATE);
public static final DeferredItem<Item> BATTERY_ENDER = registerBattery("battery_ender", Tier.ENDER);
public static final DeferredItem<Item> BATTERY_CREATIVE = registerBattery("battery_creative", Tier.CREATIVE);
public static final DeferredItem<Item> BATTERY_PICKAXE = ITEMS.register("battery_pickaxe",
id -> new BatteryPoweredPickaxeItem(new Item.Properties().setId(ResourceKey.create(Registries.ITEM, id))));
public static final DeferredItem<Item> BATTERY_AXE = ITEMS.register("battery_axe",
id -> new BatteryPoweredAxeItem(new Item.Properties().setId(ResourceKey.create(Registries.ITEM, id))));
public static final DeferredItem<Item> BATTERY_SHOVEL = ITEMS.register("battery_shovel",
id -> new BatteryPoweredShovelItem(new Item.Properties().setId(ResourceKey.create(Registries.ITEM, id))));
public static final DeferredItem<Item> BATTERY_HOE = ITEMS.register("battery_hoe",
id -> new BatteryPoweredHoeItem(new Item.Properties().setId(ResourceKey.create(Registries.ITEM, id))));
public static final DeferredItem<Item> BATTERY_SWORD = ITEMS.register("battery_sword",
id -> new BatteryPoweredSwordItem(new Item.Properties().setId(ResourceKey.create(Registries.ITEM, id))));
public static final DeferredItem<Item> BATTERY_HELMET = ITEMS.register("battery_helmet",
id -> new BatteryPoweredArmorItem(ArmorMaterials.DIAMOND, ArmorType.HELMET, new Item.Properties().setId(ResourceKey.create(Registries.ITEM, id))));
public static final DeferredItem<Item> BATTERY_CHESTPLATE = ITEMS.register("battery_chestplate",
id -> new BatteryPoweredArmorItem(ArmorMaterials.DIAMOND, ArmorType.CHESTPLATE, new Item.Properties().setId(ResourceKey.create(Registries.ITEM, id))));
public static final DeferredItem<Item> BATTERY_LEGGINGS = ITEMS.register("battery_leggings",
id -> new BatteryPoweredArmorItem(ArmorMaterials.DIAMOND, ArmorType.LEGGINGS, new Item.Properties().setId(ResourceKey.create(Registries.ITEM, id))));
public static final DeferredItem<Item> BATTERY_BOOTS = ITEMS.register("battery_boots",
id -> new BatteryPoweredArmorItem(ArmorMaterials.DIAMOND, ArmorType.BOOTS, new Item.Properties().setId(ResourceKey.create(Registries.ITEM, id))));
public static final DeferredItem<Item> BATTERY_SHIELD = ITEMS.register("battery_shield",
id -> new BatteryPoweredShieldItem(new Item.Properties().durability(336).setId(ResourceKey.create(Registries.ITEM, id))));
public static final DeferredItem<Item> BATTERY_BOW = ITEMS.register("battery_bow",
id -> new BatteryPoweredBowItem(new Item.Properties().durability(384).setId(ResourceKey.create(Registries.ITEM, id))));
public static final DeferredBlock<Block> BATTERY_BLOCK = BLOCKS.register("battery_block", id -> new BatteryBlock(BatteryBlock.batteryBlockProperties()
.setId(ResourceKey.create(Registries.BLOCK, id))));
public static final DeferredBlock<Block> COAL_GENERATOR = BLOCKS.register("coal_generator", id -> new CoalGeneratorBlock(CoalGeneratorBlock.generatorProperties()
.setId(ResourceKey.create(Registries.BLOCK, id))));
public static final DeferredBlock<Block> CHARGER = BLOCKS.register("charger", id -> new ChargerBlock(ChargerBlock.chargerProperties()
.setId(ResourceKey.create(Registries.BLOCK, id))));
public static final DeferredBlock<Block> ENDER_CHARGER = BLOCKS.register("ender_charger", id -> new ChargerBlock(ChargerBlock.chargerProperties()
.setId(ResourceKey.create(Registries.BLOCK, id))));
public static final DeferredItem<BlockItem> BATTERY_BLOCK_ITEM = ITEMS.register("battery_block",
id -> new BatteryBlockItem(BATTERY_BLOCK.get(), new Item.Properties().setId(ResourceKey.create(Registries.ITEM, id))));
public static final DeferredItem<BlockItem> COAL_GENERATOR_ITEM = ITEMS.registerSimpleBlockItem("coal_generator", COAL_GENERATOR);
public static final DeferredItem<BlockItem> CHARGER_ITEM = ITEMS.registerSimpleBlockItem("charger", CHARGER);
public static final DeferredItem<BlockItem> ENDER_CHARGER_ITEM = ITEMS.registerSimpleBlockItem("ender_charger", ENDER_CHARGER);
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<BatteryBlockEntity>> BATTERY_BLOCK_ENTITY = BLOCK_ENTITY_TYPES.register("battery_block",
id -> new BlockEntityType<>(BatteryBlockEntity::new, BATTERY_BLOCK.get()));
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<CoalGeneratorBlockEntity>> COAL_GENERATOR_BLOCK_ENTITY = BLOCK_ENTITY_TYPES.register("coal_generator",
id -> new BlockEntityType<>(CoalGeneratorBlockEntity::new, COAL_GENERATOR.get()));
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<ChargerBlockEntity>> CHARGER_BLOCK_ENTITY = BLOCK_ENTITY_TYPES.register("charger",
id -> new BlockEntityType<>(ChargerBlockEntity::new, CHARGER.get(), ENDER_CHARGER.get()));
public static final DeferredHolder<MenuType<?>, MenuType<BatteriesMenu>> BATTERIES_MENU = MENU_TYPES.register("batteries_menu",
() -> IMenuTypeExtension.create(BatteriesMenu::new));
public static final DeferredHolder<MenuType<?>, MenuType<BatteryBlockMenu>> BATTERY_BLOCK_MENU = MENU_TYPES.register("battery_block_menu",
() -> IMenuTypeExtension.create(BatteryBlockMenu::new));
public static final DeferredHolder<MenuType<?>, MenuType<CoalGeneratorMenu>> COAL_GENERATOR_MENU = MENU_TYPES.register("coal_generator_menu",
() -> IMenuTypeExtension.create(CoalGeneratorMenu::new));
public static final DeferredHolder<RecipeSerializer<?>, RecipeSerializer<PoweredGearUpgradeRecipe>> POWERED_GEAR_UPGRADE_RECIPE =
RECIPE_SERIALIZERS.register("powered_gear_upgrade", () -> new CustomRecipe.Serializer<>(PoweredGearUpgradeRecipe::new));
public static final DeferredHolder<RecipeSerializer<?>, RecipeSerializer<BatteryBlockUpgradeRecipe>> BATTERY_BLOCK_UPGRADE_RECIPE =
RECIPE_SERIALIZERS.register("battery_block_upgrade", () -> new CustomRecipe.Serializer<>(BatteryBlockUpgradeRecipe::new));
public static final DeferredHolder<RecipeSerializer<?>, RecipeSerializer<BatteryTierUpgradeRecipe>> BATTERY_TIER_UPGRADE_RECIPE =
RECIPE_SERIALIZERS.register("battery_tier_upgrade", () -> new CustomRecipe.Serializer<>(BatteryTierUpgradeRecipe::new));
@SuppressWarnings("unused")
public static final DeferredHolder<CreativeModeTab, CreativeModeTab> BATTERIES_TAB = CREATIVE_MODE_TABS.register("main", () -> CreativeModeTab.builder()
.title(Component.translatable("itemGroup.batteries"))
.withTabsBefore(CreativeModeTabs.REDSTONE_BLOCKS)
.icon(() -> BATTERY.get().getDefaultInstance())
.displayItems((parameters, output) -> {
output.accept(BATTERY.get());
output.accept(BATTERY1.get());
output.accept(BATTERY2.get());
output.accept(BATTERY3.get());
output.accept(BATTERY_ENDER.get());
output.accept(BATTERY_CREATIVE.get());
if (BatteriesConfig.poweredToolsEnabled()) {
output.accept(BATTERY_PICKAXE.get());
output.accept(BATTERY_AXE.get());
output.accept(BATTERY_SHOVEL.get());
output.accept(BATTERY_HOE.get());
}
if (BatteriesConfig.poweredWeaponsEnabled()) {
output.accept(BATTERY_SWORD.get());
output.accept(BATTERY_SHIELD.get());
output.accept(BATTERY_BOW.get());
}
if (BatteriesConfig.poweredArmorEnabled()) {
output.accept(BATTERY_HELMET.get());
output.accept(BATTERY_CHESTPLATE.get());
output.accept(BATTERY_LEGGINGS.get());
output.accept(BATTERY_BOOTS.get());
}
output.accept(BATTERY_BLOCK_ITEM.get());
output.accept(COAL_GENERATOR_ITEM.get());
output.accept(CHARGER_ITEM.get());
output.accept(ENDER_CHARGER_ITEM.get());
})
.build());
public Batteries(IEventBus modEventBus, ModContainer modContainer) {
BLOCKS.register(modEventBus);
ITEMS.register(modEventBus);
BLOCK_ENTITY_TYPES.register(modEventBus);
MENU_TYPES.register(modEventBus);
CREATIVE_MODE_TABS.register(modEventBus);
RECIPE_SERIALIZERS.register(modEventBus);
modEventBus.addListener(Batteries::registerCapabilities);
NeoForge.EVENT_BUS.addListener(Batteries::registerCommands);
modContainer.registerConfig(ModConfig.Type.SERVER, BatteriesConfig.SPEC);
LOGGER.info("Loading Batteries MVP for Minecraft 1.21");
}
private static void registerCapabilities(RegisterCapabilitiesEvent event) {
event.registerItem(Capabilities.Energy.ITEM, (stack, context) -> BatteryItem.createEnergyHandler(stack),
BATTERY.get(), BATTERY1.get(), BATTERY2.get(), BATTERY3.get(), BATTERY_ENDER.get(), BATTERY_CREATIVE.get());
event.registerItem(Capabilities.Energy.ITEM, (stack, context) -> stack.getItem() instanceof PoweredItem poweredItem ? poweredItem.createEnergyHandler(stack) : null,
BATTERY_PICKAXE.get(), BATTERY_AXE.get(), BATTERY_SHOVEL.get(), BATTERY_HOE.get(), BATTERY_SWORD.get(),
BATTERY_HELMET.get(), BATTERY_CHESTPLATE.get(), BATTERY_LEGGINGS.get(), BATTERY_BOOTS.get(), BATTERY_SHIELD.get(), BATTERY_BOW.get());
event.registerItem(Capabilities.Energy.ITEM, (stack, context) -> stack.getItem() instanceof BatteryBlockItem batteryBlockItem ? batteryBlockItem.createEnergyHandler(stack) : null,
BATTERY_BLOCK_ITEM.get());
event.registerBlockEntity(Capabilities.Energy.BLOCK, BATTERY_BLOCK_ENTITY.get(), BatteryBlockEntity::getEnergyHandler);
event.registerBlockEntity(Capabilities.Energy.BLOCK, COAL_GENERATOR_BLOCK_ENTITY.get(), CoalGeneratorBlockEntity::getEnergyHandler);
event.registerBlockEntity(Capabilities.Item.BLOCK, COAL_GENERATOR_BLOCK_ENTITY.get(), CoalGeneratorBlockEntity::getItemHandler);
event.registerBlockEntity(Capabilities.Energy.BLOCK, CHARGER_BLOCK_ENTITY.get(), ChargerBlockEntity::getEnergyHandler);
}
private static void registerCommands(RegisterCommandsEvent event) {
BatteryDebugCommands.register(event.getDispatcher(), new BatteryDebugCommands.EnergyAdapter<EnergyHandler>() {
@Override
public EnergyHandler resolve(ItemStack stack) {
return stack.getCapability(Capabilities.Energy.ITEM, ItemAccess.forStack(stack));
}
@Override
public int stored(EnergyHandler handler) {
return handler.getAmountAsInt();
}
@Override
public int capacity(EnergyHandler handler) {
return handler.getCapacityAsInt();
}
@Override
public int charge(EnergyHandler handler, int amount) {
try (Transaction transaction = Transaction.openRoot()) {
int accepted = handler.insert(amount, transaction);
if (accepted > 0) {
transaction.commit();
}
return accepted;
}
}
@Override
public int drain(EnergyHandler handler, int amount) {
try (Transaction transaction = Transaction.openRoot()) {
int extracted = handler.extract(amount, transaction);
if (extracted > 0) {
transaction.commit();
}
return extracted;
}
}
});
}
private static DeferredItem<Item> registerBattery(String name, Tier tier) {
return ITEMS.register(name, id -> new BatteryItem(tier, new Item.Properties()
.stacksTo(1)
.setId(ResourceKey.create(Registries.ITEM, id))));
}
}

View File

@@ -0,0 +1,32 @@
package com.trunksbomb.batteries;
import com.trunksbomb.batteries.client.screen.BatteryBlockScreen;
import com.trunksbomb.batteries.client.screen.BatteriesScreen;
import com.trunksbomb.batteries.client.screen.CoalGeneratorScreen;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent;
// This class will not load on dedicated servers. Accessing client side code from here is safe.
@Mod(value = Batteries.MODID, dist = Dist.CLIENT)
@EventBusSubscriber(modid = Batteries.MODID, value = Dist.CLIENT)
public class BatteriesClient {
public BatteriesClient() {
// TODO: Register battery screens, renderers, and client-only behavior here.
}
@SubscribeEvent
static void onClientSetup(FMLClientSetupEvent event) {
Batteries.LOGGER.info("Batteries client setup complete");
}
@SubscribeEvent
static void registerScreens(RegisterMenuScreensEvent event) {
event.register(Batteries.BATTERIES_MENU.get(), BatteriesScreen::new);
event.register(Batteries.BATTERY_BLOCK_MENU.get(), BatteryBlockScreen::new);
event.register(Batteries.COAL_GENERATOR_MENU.get(), CoalGeneratorScreen::new);
}
}

View File

@@ -0,0 +1,135 @@
package com.trunksbomb.batteries.block;
import com.mojang.serialization.MapCodec;
import com.trunksbomb.batteries.Batteries;
import com.trunksbomb.batteries.block.entity.BatteryBlockEntity;
import com.trunksbomb.batteries.menu.BatteryBlockMenu;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NonNull;
public class BatteryBlock extends BaseEntityBlock {
public static final MapCodec<BatteryBlock> CODEC = simpleCodec(BatteryBlock::new);
public static final net.minecraft.world.level.block.state.properties.EnumProperty<Direction> FACING = HorizontalDirectionalBlock.FACING;
public static final IntegerProperty CHARGE = IntegerProperty.create("charge", 0, 4);
public BatteryBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any().setValue(FACING, Direction.NORTH).setValue(CHARGE, 0));
}
@Override
protected @NonNull MapCodec<? extends BaseEntityBlock> codec() {
return CODEC;
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<net.minecraft.world.level.block.Block, BlockState> builder) {
builder.add(FACING, CHARGE);
}
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
return this.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getOpposite());
}
@Override
protected @NonNull BlockState rotate(BlockState state, Rotation rotation) {
return state.setValue(FACING, rotation.rotate(state.getValue(FACING)));
}
@Override
protected @NonNull BlockState mirror(BlockState state, Mirror mirror) {
return state.rotate(mirror.getRotation(state.getValue(FACING)));
}
@Override
protected @NonNull RenderShape getRenderShape(@NonNull BlockState state) {
return RenderShape.MODEL;
}
@Override
protected @NonNull InteractionResult useWithoutItem(@NonNull BlockState state, @NonNull Level level, @NonNull BlockPos pos, @NonNull Player player, net.minecraft.world.phys.@NonNull BlockHitResult hitResult) {
return openMenu(level, pos, player);
}
@Override
protected @NonNull InteractionResult useItemOn(@NonNull ItemStack stack, @NonNull BlockState state, @NonNull Level level, @NonNull BlockPos pos, @NonNull Player player, @NonNull InteractionHand hand, net.minecraft.world.phys.@NonNull BlockHitResult hitResult) {
return openMenu(level, pos, player);
}
@Nullable
@Override
public BlockEntity newBlockEntity(@NonNull BlockPos pos, @NonNull BlockState state) {
return new BatteryBlockEntity(pos, state);
}
@Nullable
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, @NonNull BlockState state, @NonNull BlockEntityType<T> blockEntityType) {
return level.isClientSide() ? null : createTickerHelper(blockEntityType, Batteries.BATTERY_BLOCK_ENTITY.get(), BatteryBlockEntity::serverTick);
}
@Override
public void setPlacedBy(@NonNull Level level, @NonNull BlockPos pos, @NonNull BlockState state, @Nullable LivingEntity placer, @NonNull ItemStack stack) {
super.setPlacedBy(level, pos, state, placer, stack);
if (level.getBlockEntity(pos) instanceof BatteryBlockEntity batteryBlockEntity) {
batteryBlockEntity.loadFromItem(stack);
}
}
@Override
public void playerDestroy(Level level, @NonNull Player player, @NonNull BlockPos pos, @NonNull BlockState state, @Nullable BlockEntity blockEntity, @NonNull ItemStack tool) {
if (!level.isClientSide() && !player.isCreative() && blockEntity instanceof BatteryBlockEntity batteryBlockEntity) {
popResource(level, pos, batteryBlockEntity.createDropStack());
}
super.playerDestroy(level, player, pos, state, blockEntity, tool);
}
@Override
public void onPlace(@NonNull BlockState state, @NonNull Level level, @NonNull BlockPos pos, @NonNull BlockState oldState, boolean movedByPiston) {
super.onPlace(state, level, pos, oldState, movedByPiston);
level.invalidateCapabilities(pos);
}
public static BlockBehaviour.Properties batteryBlockProperties() {
return BlockBehaviour.Properties.of()
.strength(2.0F)
.requiresCorrectToolForDrops();
}
private static InteractionResult openMenu(Level level, BlockPos pos, Player player) {
if (!level.isClientSide() && player instanceof ServerPlayer serverPlayer && level.getBlockEntity(pos) instanceof BatteryBlockEntity blockEntity) {
MenuProvider provider = new SimpleMenuProvider(
(containerId, inventory, menuPlayer) -> BatteryBlockMenu.forBlock(containerId, inventory, blockEntity),
Component.translatable("container.batteries.battery_block")
);
serverPlayer.openMenu(provider, buffer -> BatteryBlockMenu.writeBlockPos(buffer, pos));
}
return InteractionResult.SUCCESS;
}
}

View File

@@ -0,0 +1,166 @@
package com.trunksbomb.batteries.block;
import com.mojang.serialization.MapCodec;
import com.trunksbomb.batteries.Batteries;
import com.trunksbomb.batteries.block.ChargerBlockData.BatteryState;
import com.trunksbomb.batteries.block.entity.ChargerBlockEntity;
import com.trunksbomb.batteries.item.BatteryItem;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.material.MapColor;
import org.jspecify.annotations.NonNull;
import org.jetbrains.annotations.Nullable;
public class ChargerBlock extends BaseEntityBlock {
public static final EnumProperty<Direction> FACING = HorizontalDirectionalBlock.FACING;
public static final EnumProperty<BatteryState> BATTERY = EnumProperty.create("battery", BatteryState.class);
public static final MapCodec<ChargerBlock> CODEC = simpleCodec(ChargerBlock::new);
public ChargerBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any().setValue(FACING, Direction.NORTH).setValue(BATTERY, BatteryState.NONE));
}
@Override
protected @NonNull MapCodec<? extends BaseEntityBlock> codec() {
return CODEC;
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(FACING, BATTERY);
}
@Override
public @NonNull BlockState getStateForPlacement(@NonNull BlockPlaceContext context) {
return this.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getOpposite());
}
@Override
protected @NonNull BlockState rotate(@NonNull BlockState state, @NonNull Rotation rotation) {
return state.setValue(FACING, rotation.rotate(state.getValue(FACING)));
}
@Override
protected @NonNull BlockState mirror(@NonNull BlockState state, @NonNull Mirror mirror) {
return this.rotate(state, mirror.getRotation(state.getValue(FACING)));
}
@Override
protected @NonNull RenderShape getRenderShape(@NonNull BlockState state) {
return RenderShape.MODEL;
}
@Override
protected @NonNull InteractionResult useWithoutItem(@NonNull BlockState state, @NonNull Level level, @NonNull BlockPos pos, @NonNull Player player, net.minecraft.world.phys.@NonNull BlockHitResult hitResult) {
return tryRetrieveBattery(state, level, pos, player);
}
@Override
protected @NonNull InteractionResult useItemOn(@NonNull ItemStack stack, @NonNull BlockState state, @NonNull Level level, @NonNull BlockPos pos, @NonNull Player player, @NonNull InteractionHand hand, net.minecraft.world.phys.@NonNull BlockHitResult hitResult) {
BlockEntity blockEntity = level.getBlockEntity(pos);
if (!(blockEntity instanceof ChargerBlockEntity chargerBlockEntity)) {
return InteractionResult.PASS;
}
if (chargerBlockEntity.hasBattery()) {
return tryRetrieveBattery(state, level, pos, player);
}
if (!(stack.getItem() instanceof BatteryItem batteryItem) || !canAcceptBattery(state, batteryItem)) {
return InteractionResult.PASS;
}
if (!level.isClientSide()) {
if (state.is(Batteries.ENDER_CHARGER.get())) {
UUID batteryUuid = BatteryItem.ensureBatteryId(stack);
chargerBlockEntity.linkBattery(batteryUuid, player.getUUID());
updateBatteryState(level, pos, state, BatteryState.fromTier(batteryItem.tier()));
} else {
ItemStack remainingStack = chargerBlockEntity.insertBattery(stack);
player.setItemInHand(hand, remainingStack);
updateBatteryState(level, pos, state, BatteryState.fromTier(batteryItem.tier()));
}
}
return InteractionResult.SUCCESS;
}
@Nullable
@Override
public BlockEntity newBlockEntity(@NonNull BlockPos pos, @NonNull BlockState state) {
return new ChargerBlockEntity(pos, state);
}
@Nullable
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(@NonNull Level level, @NonNull BlockState state, @NonNull BlockEntityType<T> blockEntityType) {
return level.isClientSide() ? null : createTickerHelper(blockEntityType, Batteries.CHARGER_BLOCK_ENTITY.get(), ChargerBlockEntity::serverTick);
}
@Override
public void playerDestroy(@NonNull Level level, @NonNull Player player, @NonNull BlockPos pos, @NonNull BlockState state, @Nullable BlockEntity blockEntity, @NonNull ItemStack tool) {
if (blockEntity instanceof ChargerBlockEntity chargerBlockEntity && chargerBlockEntity.hasStoredBattery() && level instanceof ServerLevel serverLevel) {
popResource(serverLevel, pos, chargerBlockEntity.extractBattery());
}
super.playerDestroy(level, player, pos, state, blockEntity, tool);
}
public static BlockBehaviour.Properties chargerProperties() {
return BlockBehaviour.Properties.of()
.mapColor(MapColor.METAL)
.strength(2.0F)
.requiresCorrectToolForDrops();
}
private static boolean canAcceptBattery(BlockState state, BatteryItem batteryItem) {
return ChargerBlockData.canAcceptBattery(state.is(Batteries.ENDER_CHARGER.get()), batteryItem.tier());
}
private static void updateBatteryState(Level level, BlockPos pos, BlockState state, BatteryState batteryState) {
level.setBlock(pos, state.setValue(BATTERY, batteryState), Block.UPDATE_ALL);
}
private static InteractionResult tryRetrieveBattery(BlockState state, Level level, BlockPos pos, Player player) {
BlockEntity blockEntity = level.getBlockEntity(pos);
if (!(blockEntity instanceof ChargerBlockEntity chargerBlockEntity) || !chargerBlockEntity.hasBattery()) {
return InteractionResult.PASS;
}
if (!level.isClientSide()) {
if (state.is(Batteries.ENDER_CHARGER.get())) {
chargerBlockEntity.clearLinkedBattery();
updateBatteryState(level, pos, state, BatteryState.NONE);
} else {
ItemStack extractedBattery = chargerBlockEntity.extractBattery();
updateBatteryState(level, pos, state, BatteryState.NONE);
if (!player.addItem(extractedBattery)) {
player.drop(extractedBattery, false);
}
}
}
return InteractionResult.SUCCESS;
}
}

View File

@@ -0,0 +1,121 @@
package com.trunksbomb.batteries.block;
import com.mojang.serialization.MapCodec;
import com.trunksbomb.batteries.Batteries;
import com.trunksbomb.batteries.block.entity.CoalGeneratorBlockEntity;
import com.trunksbomb.batteries.menu.CoalGeneratorMenu;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.material.MapColor;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NonNull;
public class CoalGeneratorBlock extends BaseEntityBlock {
public static final MapCodec<CoalGeneratorBlock> CODEC = simpleCodec(CoalGeneratorBlock::new);
public static final net.minecraft.world.level.block.state.properties.EnumProperty<Direction> FACING = HorizontalDirectionalBlock.FACING;
public static final BooleanProperty LIT = BlockStateProperties.LIT;
public CoalGeneratorBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any().setValue(FACING, Direction.NORTH).setValue(LIT, false));
}
@Override
protected @NonNull MapCodec<? extends BaseEntityBlock> codec() {
return CODEC;
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<net.minecraft.world.level.block.Block, BlockState> builder) {
builder.add(FACING, LIT);
}
@Override
public @NonNull BlockState getStateForPlacement(@NonNull BlockPlaceContext context) {
return this.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getOpposite());
}
@Override
protected @NonNull BlockState rotate(@NonNull BlockState state, @NonNull Rotation rotation) {
return state.setValue(FACING, rotation.rotate(state.getValue(FACING)));
}
@Override
protected @NonNull BlockState mirror(@NonNull BlockState state, @NonNull Mirror mirror) {
return this.rotate(state, mirror.getRotation(state.getValue(FACING)));
}
@Override
protected @NonNull RenderShape getRenderShape(@NonNull BlockState state) {
return RenderShape.MODEL;
}
@Override
protected @NonNull InteractionResult useWithoutItem(@NonNull BlockState state, @NonNull Level level, @NonNull BlockPos pos, @NonNull Player player, net.minecraft.world.phys.@NonNull BlockHitResult hitResult) {
return openMenu(level, pos, player);
}
@Override
protected @NonNull InteractionResult useItemOn(@NonNull ItemStack stack, @NonNull BlockState state, @NonNull Level level, @NonNull BlockPos pos, @NonNull Player player, @NonNull InteractionHand hand, net.minecraft.world.phys.@NonNull BlockHitResult hitResult) {
return openMenu(level, pos, player);
}
@Nullable
@Override
public BlockEntity newBlockEntity(@NonNull BlockPos pos, @NonNull BlockState state) {
return new CoalGeneratorBlockEntity(pos, state);
}
@Nullable
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(@NonNull Level level, @NonNull BlockState state, @NonNull BlockEntityType<T> blockEntityType) {
return level.isClientSide() ? null : createTickerHelper(blockEntityType, Batteries.COAL_GENERATOR_BLOCK_ENTITY.get(), CoalGeneratorBlockEntity::serverTick);
}
public static BlockBehaviour.Properties generatorProperties() {
return BlockBehaviour.Properties.of()
.mapColor(MapColor.WOOD)
.strength(2.0F)
.requiresCorrectToolForDrops();
}
public static void setLit(Level level, BlockPos pos, BlockState state, boolean lit) {
if (state.hasProperty(LIT) && state.getValue(LIT) != lit) {
level.setBlock(pos, state.setValue(LIT, lit), net.minecraft.world.level.block.Block.UPDATE_CLIENTS);
}
}
private static InteractionResult openMenu(Level level, BlockPos pos, Player player) {
if (!level.isClientSide() && player instanceof ServerPlayer serverPlayer && level.getBlockEntity(pos) instanceof CoalGeneratorBlockEntity blockEntity) {
MenuProvider provider = new SimpleMenuProvider(
(containerId, inventory, menuPlayer) -> CoalGeneratorMenu.forBlock(containerId, inventory, blockEntity),
Component.translatable("container.batteries.coal_generator")
);
serverPlayer.openMenu(provider, buffer -> CoalGeneratorMenu.writeBlockPos(buffer, pos));
}
return InteractionResult.SUCCESS;
}
}

View File

@@ -0,0 +1,17 @@
package com.trunksbomb.batteries.block.entity;
import net.neoforged.neoforge.transfer.energy.SimpleEnergyHandler;
final class BatteryBlockEnergyHandler extends SimpleEnergyHandler {
private final BatteryBlockEntity blockEntity;
BatteryBlockEnergyHandler(BatteryBlockEntity blockEntity) {
super(blockEntity.getEnergyCapacity(), blockEntity.getMaxTransfer(), blockEntity.getMaxTransfer(), blockEntity.getStoredEnergy());
this.blockEntity = blockEntity;
}
@Override
protected void onEnergyChanged(int previousAmount) {
this.blockEntity.setStoredEnergyInternal(this.energy);
}
}

View File

@@ -0,0 +1,191 @@
package com.trunksbomb.batteries.block.entity;
import com.trunksbomb.batteries.Batteries;
import com.trunksbomb.batteries.block.BatteryBlock;
import com.trunksbomb.batteries.block.BatteryBlockData;
import com.trunksbomb.batteries.item.BatteryBlockItem;
import com.trunksbomb.batteries.item.EnergyTierHelper;
import com.trunksbomb.batteries.item.PoweredItemEnergy;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.world.level.storage.ValueOutput;
import net.neoforged.neoforge.capabilities.Capabilities;
import net.neoforged.neoforge.transfer.energy.EnergyHandler;
import net.neoforged.neoforge.transfer.transaction.Transaction;
import org.jspecify.annotations.NonNull;
public class BatteryBlockEntity extends net.minecraft.world.level.block.entity.BlockEntity {
private static final String ENERGY_KEY = "energy";
private static final String CAPACITY_KEY = "capacity";
private int storedEnergy;
private int energyCapacity = BatteryBlockItem.BASE_CAPACITY;
private BatteryBlockEnergyHandler internalEnergyHandler;
private final BatteryBlockData.SideMode[] sideModes = new BatteryBlockData.SideMode[] {
BatteryBlockData.SideMode.BOTH, BatteryBlockData.SideMode.BOTH, BatteryBlockData.SideMode.BOTH,
BatteryBlockData.SideMode.BOTH, BatteryBlockData.SideMode.BOTH, BatteryBlockData.SideMode.BOTH
};
public BatteryBlockEntity(BlockPos pos, BlockState blockState) {
super(Batteries.BATTERY_BLOCK_ENTITY.get(), pos, blockState);
}
public static void serverTick(Level level, BlockPos pos, BlockState state, BatteryBlockEntity blockEntity) {
if (blockEntity.storedEnergy <= 0) {
return;
}
int remainingTransfer = Math.min(blockEntity.storedEnergy, blockEntity.getMaxTransfer());
if (remainingTransfer <= 0) {
return;
}
EnergyHandler source = blockEntity.getInternalEnergyHandler();
for (Direction direction : Direction.values()) {
if (!blockEntity.canExtract(direction)) {
continue;
}
if (remainingTransfer <= 0) {
break;
}
BlockPos targetPos = pos.relative(direction);
if (!level.isLoaded(targetPos)) {
continue;
}
EnergyHandler target = level.getCapability(Capabilities.Energy.BLOCK, targetPos, direction.getOpposite());
if (target == null) {
continue;
}
try (Transaction transaction = Transaction.openRoot()) {
int accepted = target.insert(remainingTransfer, transaction);
if (accepted <= 0) {
continue;
}
int extracted = source.extract(accepted, transaction);
if (extracted != accepted) {
continue;
}
transaction.commit();
remainingTransfer -= accepted;
}
}
}
public EnergyHandler getEnergyHandler(@org.jspecify.annotations.Nullable Direction side) {
return new BatteryBlockSidedEnergyHandler(this.getInternalEnergyHandler(), side, this);
}
private BatteryBlockEnergyHandler getInternalEnergyHandler() {
if (this.internalEnergyHandler == null) {
this.internalEnergyHandler = new BatteryBlockEnergyHandler(this);
}
return this.internalEnergyHandler;
}
public int getStoredEnergy() {
return this.storedEnergy;
}
public int getEnergyCapacity() {
return this.energyCapacity;
}
public int getMaxTransfer() {
return EnergyTierHelper.transferRateForCapacity(this.energyCapacity);
}
public BatteryBlockData.SideMode getSideMode(Direction direction) {
return this.sideModes[direction.ordinal()];
}
public void setSideMode(Direction direction, BatteryBlockData.SideMode mode) {
this.sideModes[direction.ordinal()] = mode;
this.setChanged();
if (this.level != null) {
this.level.invalidateCapabilities(this.worldPosition);
}
}
public void cycleSideMode(Direction direction) {
this.setSideMode(direction, this.getSideMode(direction).next());
}
public boolean canInsert(Direction direction) {
BatteryBlockData.SideMode mode = this.getSideMode(direction);
return mode == BatteryBlockData.SideMode.INPUT || mode == BatteryBlockData.SideMode.BOTH;
}
public boolean canExtract(Direction direction) {
BatteryBlockData.SideMode mode = this.getSideMode(direction);
return mode == BatteryBlockData.SideMode.OUTPUT || mode == BatteryBlockData.SideMode.BOTH;
}
public static Direction directionForIndex(int index) {
return BatteryBlockData.directionForIndex(index);
}
public void loadFromItem(ItemStack stack) {
BatteryBlockItem.initializeDefaults(stack);
this.energyCapacity = BatteryBlockItem.getEnergyCapacity(stack);
this.storedEnergy = Math.min(BatteryBlockItem.getStoredEnergy(stack), this.energyCapacity);
this.internalEnergyHandler = null;
this.refreshState();
}
public ItemStack createDropStack() {
ItemStack stack = new ItemStack(Batteries.BATTERY_BLOCK_ITEM.get());
PoweredItemEnergy.setEnergyCapacity(stack, this.energyCapacity);
PoweredItemEnergy.setStoredEnergy(stack, this.storedEnergy);
return stack;
}
void setStoredEnergyInternal(int energy) {
this.storedEnergy = Math.max(0, Math.min(energy, this.energyCapacity));
this.refreshState();
}
private void refreshState() {
this.setChanged();
if (this.level == null) {
return;
}
BlockState state = this.getBlockState();
int targetCharge = BatteryBlockData.chargeStage(this.storedEnergy, this.energyCapacity);
if (state.hasProperty(BatteryBlock.CHARGE) && state.getValue(BatteryBlock.CHARGE) != targetCharge) {
this.level.setBlock(this.worldPosition, state.setValue(BatteryBlock.CHARGE, targetCharge), net.minecraft.world.level.block.Block.UPDATE_CLIENTS);
}
this.level.invalidateCapabilities(this.worldPosition);
}
@Override
protected void loadAdditional(@NonNull ValueInput input) {
super.loadAdditional(input);
this.energyCapacity = Math.max(BatteryBlockItem.BASE_CAPACITY, input.getIntOr(CAPACITY_KEY, BatteryBlockItem.BASE_CAPACITY));
this.storedEnergy = Math.max(0, Math.min(input.getIntOr(ENERGY_KEY, 0), this.energyCapacity));
for (Direction direction : Direction.values()) {
int ordinal = input.getIntOr("side_" + direction.getSerializedName(), BatteryBlockData.SideMode.BOTH.ordinal());
this.sideModes[direction.ordinal()] = BatteryBlockData.SideMode.values()[Math.max(0, Math.min(BatteryBlockData.SideMode.values().length - 1, ordinal))];
}
this.internalEnergyHandler = null;
}
@Override
protected void saveAdditional(@NonNull ValueOutput output) {
super.saveAdditional(output);
output.putInt(CAPACITY_KEY, this.energyCapacity);
output.putInt(ENERGY_KEY, this.storedEnergy);
for (Direction direction : Direction.values()) {
output.putInt("side_" + direction.getSerializedName(), this.sideModes[direction.ordinal()].ordinal());
}
}
}

View File

@@ -0,0 +1,54 @@
package com.trunksbomb.batteries.block.entity;
import net.minecraft.core.Direction;
import net.neoforged.neoforge.transfer.energy.EnergyHandler;
import net.neoforged.neoforge.transfer.transaction.TransactionContext;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
final class BatteryBlockSidedEnergyHandler implements EnergyHandler {
private final EnergyHandler delegate;
@Nullable
private final Direction side;
private final BatteryBlockEntity blockEntity;
BatteryBlockSidedEnergyHandler(EnergyHandler delegate, @Nullable Direction side, BatteryBlockEntity blockEntity) {
this.delegate = delegate;
this.side = side;
this.blockEntity = blockEntity;
}
@Override
public long getAmountAsLong() {
return this.delegate.getAmountAsLong();
}
@Override
public long getCapacityAsLong() {
return this.delegate.getCapacityAsLong();
}
@Override
public int insert(int maxAmount, @NonNull TransactionContext transaction) {
if (maxAmount <= 0) {
return 0;
}
if (this.side != null && !this.blockEntity.canInsert(this.side)) {
return 0;
}
return this.delegate.insert(maxAmount, transaction);
}
@Override
public int extract(int maxAmount, @NonNull TransactionContext transaction) {
if (maxAmount <= 0) {
return 0;
}
if (this.side != null && !this.blockEntity.canExtract(this.side)) {
return 0;
}
return this.delegate.extract(maxAmount, transaction);
}
}

View File

@@ -0,0 +1,172 @@
package com.trunksbomb.batteries.block.entity;
import com.trunksbomb.batteries.Batteries;
import com.trunksbomb.batteries.BatteriesConfig;
import com.trunksbomb.batteries.item.BatteryItem;
import java.util.UUID;
import net.minecraft.core.Direction;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.world.level.storage.ValueOutput;
import net.neoforged.neoforge.transfer.transaction.Transaction;
import org.jspecify.annotations.NonNull;
public class ChargerBlockEntity extends BlockEntity {
public static final UUID DEFAULT_UUID = new UUID(0L, 0L);
private ItemStack battery = ItemStack.EMPTY;
private UUID linkedBatteryUuid = DEFAULT_UUID;
private UUID linkedPlayerUuid = DEFAULT_UUID;
private ChargerEnergyHandler energyHandler;
public ChargerBlockEntity(BlockPos pos, BlockState blockState) {
super(Batteries.CHARGER_BLOCK_ENTITY.get(), pos, blockState);
}
public boolean hasBattery() {
return this.hasStoredBattery() || this.hasLinkedBattery();
}
public boolean hasStoredBattery() {
return !this.battery.isEmpty();
}
public boolean hasLinkedBattery() {
return !DEFAULT_UUID.equals(this.linkedBatteryUuid) && !DEFAULT_UUID.equals(this.linkedPlayerUuid);
}
public ItemStack getBattery() {
return this.battery.copy();
}
public net.neoforged.neoforge.transfer.energy.EnergyHandler getEnergyHandler(Direction side) {
if (this.energyHandler == null) {
this.energyHandler = new ChargerEnergyHandler(this);
}
return this.energyHandler;
}
public ItemStack getChargeTarget() {
if (this.hasStoredBattery()) {
return this.battery;
}
if (this.level instanceof net.minecraft.server.level.ServerLevel serverLevel && this.hasLinkedBattery()) {
ServerPlayer player = serverLevel.getServer().getPlayerList().getPlayer(this.linkedPlayerUuid);
if (player != null) {
return BatteryItem.findLinkedEnderBattery(player, this.linkedBatteryUuid);
}
}
return ItemStack.EMPTY;
}
public boolean canInsert(ItemStack stack) {
return !this.hasBattery() && stack.getItem() instanceof BatteryItem;
}
public ItemStack insertBattery(ItemStack stack) {
if (!canInsert(stack)) {
return stack;
}
this.battery = stack.copyWithCount(1);
stack.shrink(1);
this.onBatteryStateChanged();
return stack;
}
public ItemStack extractBattery() {
if (this.battery.isEmpty()) {
return ItemStack.EMPTY;
}
ItemStack extracted = this.battery;
this.battery = ItemStack.EMPTY;
this.onBatteryStateChanged();
return extracted;
}
public void linkBattery(UUID batteryUuid, UUID playerUuid) {
this.linkedBatteryUuid = batteryUuid;
this.linkedPlayerUuid = playerUuid;
this.onBatteryStateChanged();
}
public void clearLinkedBattery() {
this.linkedBatteryUuid = DEFAULT_UUID;
this.linkedPlayerUuid = DEFAULT_UUID;
this.onBatteryStateChanged();
}
@SuppressWarnings("unused")
public static void serverTick(net.minecraft.world.level.Level level, BlockPos pos, BlockState state, ChargerBlockEntity blockEntity) {
if (!BatteriesConfig.chargerGeneratesCreativePower()) {
return;
}
ItemStack chargeTarget = blockEntity.getChargeTarget();
if (!chargeTarget.isEmpty()) {
chargeBattery(chargeTarget, blockEntity);
}
}
private static void chargeBattery(ItemStack battery, ChargerBlockEntity blockEntity) {
var energyHandler = BatteryItem.getEnergyHandler(battery);
if (energyHandler == null) {
return;
}
try (var transaction = Transaction.openRoot()) {
int charged = energyHandler.insert(BatteryItem.getMaxTransfer(battery), transaction);
if (charged > 0) {
transaction.commit();
blockEntity.onBatteryChargeChanged();
}
}
}
@Override
protected void loadAdditional(@NonNull ValueInput input) {
super.loadAdditional(input);
this.battery = input.read("battery", ItemStack.CODEC).orElse(ItemStack.EMPTY);
this.linkedBatteryUuid = parseUuid(input.getStringOr("linked_battery_uuid", ""));
this.linkedPlayerUuid = parseUuid(input.getStringOr("linked_player_uuid", ""));
this.energyHandler = null;
}
@Override
protected void saveAdditional(@NonNull ValueOutput output) {
super.saveAdditional(output);
if (!this.battery.isEmpty()) {
output.store("battery", ItemStack.CODEC, this.battery);
}
if (this.hasLinkedBattery()) {
output.putString("linked_battery_uuid", this.linkedBatteryUuid.toString());
output.putString("linked_player_uuid", this.linkedPlayerUuid.toString());
}
}
private void onBatteryStateChanged() {
this.setChanged();
this.invalidateEnergyCapability();
}
private void onBatteryChargeChanged() {
this.setChanged();
this.invalidateEnergyCapability();
}
private void invalidateEnergyCapability() {
if (this.level != null) {
this.level.invalidateCapabilities(this.worldPosition);
}
}
private static UUID parseUuid(String value) {
return value.isBlank() ? DEFAULT_UUID : UUID.fromString(value);
}
}

View File

@@ -0,0 +1,47 @@
package com.trunksbomb.batteries.block.entity;
import com.trunksbomb.batteries.item.BatteryItem;
import net.minecraft.world.item.ItemStack;
import net.neoforged.neoforge.transfer.energy.EnergyHandler;
import net.neoforged.neoforge.transfer.transaction.TransactionContext;
import org.jspecify.annotations.NonNull;
final class ChargerEnergyHandler implements EnergyHandler {
private final ChargerBlockEntity blockEntity;
ChargerEnergyHandler(ChargerBlockEntity blockEntity) {
this.blockEntity = blockEntity;
}
@Override
public long getAmountAsLong() {
ItemStack battery = this.blockEntity.getChargeTarget();
return battery.isEmpty() ? 0L : BatteryItem.getStoredEnergy(battery);
}
@Override
public long getCapacityAsLong() {
ItemStack battery = this.blockEntity.getChargeTarget();
return battery.isEmpty() ? 0L : BatteryItem.getEnergyCapacity(battery);
}
@Override
public int insert(int maxAmount, @NonNull TransactionContext transaction) {
ItemStack battery = this.blockEntity.getChargeTarget();
if (battery.isEmpty()) {
return 0;
}
EnergyHandler batteryHandler = BatteryItem.getEnergyHandler(battery);
if (batteryHandler == null || batteryHandler.getAmountAsInt() >= batteryHandler.getCapacityAsInt()) {
return 0;
}
return batteryHandler.insert(maxAmount, transaction);
}
@Override
public int extract(int maxAmount, @NonNull TransactionContext transaction) {
return 0;
}
}

View File

@@ -0,0 +1,210 @@
package com.trunksbomb.batteries.block.entity;
import com.trunksbomb.batteries.Batteries;
import com.trunksbomb.batteries.BatteriesConfig;
import com.trunksbomb.batteries.block.CoalGeneratorBlock;
import com.trunksbomb.batteries.item.BatteryBlockItem;
import com.trunksbomb.batteries.item.EnergyTierHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.world.level.storage.ValueOutput;
import net.neoforged.neoforge.capabilities.Capabilities;
import net.neoforged.neoforge.transfer.energy.EnergyHandler;
import net.neoforged.neoforge.transfer.item.ItemResource;
import net.neoforged.neoforge.transfer.ResourceHandler;
import net.neoforged.neoforge.transfer.transaction.Transaction;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
public class CoalGeneratorBlockEntity extends net.minecraft.world.level.block.entity.BlockEntity {
private static final String ENERGY_KEY = "energy";
private static final String BURN_TIME_KEY = "burn_time";
private static final String MAX_BURN_TIME_KEY = "max_burn_time";
private final SimpleContainer fuelSlot = new SimpleContainer(1) {
@Override
public void setChanged() {
super.setChanged();
CoalGeneratorBlockEntity.this.setChanged();
}
};
private int storedEnergy;
private int burnTimeRemaining;
private int maxBurnTime;
private CoalGeneratorEnergyHandler internalEnergyHandler;
private GeneratorOutputEnergyHandler outputEnergyHandler;
private CoalGeneratorItemHandler itemHandler;
public CoalGeneratorBlockEntity(BlockPos pos, BlockState blockState) {
super(Batteries.COAL_GENERATOR_BLOCK_ENTITY.get(), pos, blockState);
}
public static void serverTick(Level level, BlockPos pos, BlockState state, CoalGeneratorBlockEntity blockEntity) {
boolean wasBurning = blockEntity.isBurning();
blockEntity.tickGeneration(level);
if (wasBurning != blockEntity.isBurning()) {
CoalGeneratorBlock.setLit(level, pos, state, blockEntity.isBurning());
}
blockEntity.tickOutput(level, pos);
}
public SimpleContainer fuelSlot() {
return this.fuelSlot;
}
public EnergyHandler getEnergyHandler(@Nullable Direction side) {
if (this.outputEnergyHandler == null) {
this.outputEnergyHandler = new GeneratorOutputEnergyHandler(this.getInternalEnergyHandler());
}
return this.outputEnergyHandler;
}
public ResourceHandler<ItemResource> getItemHandler(@Nullable Direction side) {
if (this.itemHandler == null) {
this.itemHandler = new CoalGeneratorItemHandler(this);
}
return this.itemHandler;
}
public int getStoredEnergy() {
return this.storedEnergy;
}
public int getEnergyCapacity() {
return BatteryBlockItem.BASE_CAPACITY;
}
public int getBurnTimeRemaining() {
return this.burnTimeRemaining;
}
public int getMaxBurnTime() {
return this.maxBurnTime;
}
public boolean isBurning() {
return this.burnTimeRemaining > 0;
}
private CoalGeneratorEnergyHandler getInternalEnergyHandler() {
if (this.internalEnergyHandler == null) {
this.internalEnergyHandler = new CoalGeneratorEnergyHandler(this);
}
return this.internalEnergyHandler;
}
private void tickGeneration(Level level) {
if (this.burnTimeRemaining <= 0 && this.storedEnergy < this.getEnergyCapacity()) {
ItemStack fuel = this.fuelSlot.getItem(0);
int burnTime = getFuelBurnTime(level, fuel);
if (burnTime > 0) {
this.burnTimeRemaining = burnTime;
this.maxBurnTime = burnTime;
fuel.shrink(1);
if (fuel.isEmpty()) {
this.fuelSlot.setItem(0, ItemStack.EMPTY);
}
this.setChanged();
}
}
if (this.burnTimeRemaining > 0 && this.storedEnergy < this.getEnergyCapacity()) {
this.getInternalEnergyHandler().set(Math.min(this.getEnergyCapacity(), this.storedEnergy + BatteriesConfig.generatorEnergyPerTick()));
this.burnTimeRemaining--;
this.setChanged();
}
}
private void tickOutput(Level level, BlockPos pos) {
if (this.storedEnergy <= 0) {
return;
}
int remainingTransfer = Math.min(this.storedEnergy, this.getMaxTransfer());
EnergyHandler source = this.getInternalEnergyHandler();
for (Direction direction : Direction.values()) {
if (remainingTransfer <= 0) {
break;
}
BlockPos targetPos = pos.relative(direction);
if (!level.isLoaded(targetPos)) {
continue;
}
EnergyHandler target = level.getCapability(Capabilities.Energy.BLOCK, targetPos, direction.getOpposite());
if (target == null) {
continue;
}
try (Transaction transaction = Transaction.openRoot()) {
int accepted = target.insert(remainingTransfer, transaction);
if (accepted <= 0) {
continue;
}
int extracted = source.extract(accepted, transaction);
if (extracted != accepted) {
continue;
}
transaction.commit();
remainingTransfer -= accepted;
}
}
}
public int getMaxTransfer() {
return EnergyTierHelper.transferRateForCapacity(this.getEnergyCapacity());
}
@Override
protected void loadAdditional(@NonNull ValueInput input) {
super.loadAdditional(input);
this.storedEnergy = Math.max(0, Math.min(input.getIntOr(ENERGY_KEY, 0), this.getEnergyCapacity()));
this.burnTimeRemaining = Math.max(0, input.getIntOr(BURN_TIME_KEY, 0));
this.maxBurnTime = Math.max(0, input.getIntOr(MAX_BURN_TIME_KEY, 0));
this.fuelSlot.setItem(0, input.read("fuel", ItemStack.CODEC).orElse(ItemStack.EMPTY));
this.internalEnergyHandler = null;
this.outputEnergyHandler = null;
this.itemHandler = null;
}
@Override
protected void saveAdditional(@NonNull ValueOutput output) {
super.saveAdditional(output);
output.putInt(ENERGY_KEY, this.storedEnergy);
output.putInt(BURN_TIME_KEY, this.burnTimeRemaining);
output.putInt(MAX_BURN_TIME_KEY, this.maxBurnTime);
if (!this.fuelSlot.getItem(0).isEmpty()) {
output.store("fuel", ItemStack.CODEC, this.fuelSlot.getItem(0));
}
}
void setStoredEnergyInternal(int energy) {
this.storedEnergy = Math.max(0, Math.min(energy, this.getEnergyCapacity()));
this.setChanged();
if (this.level != null) {
this.level.invalidateCapabilities(this.worldPosition);
}
}
private static int getFuelBurnTime(Level level, ItemStack stack) {
if (!isFuel(level, stack)) {
return 0;
}
return level.fuelValues().burnDuration(stack);
}
public static boolean isFuel(Level level, ItemStack stack) {
return !stack.isEmpty() && level.fuelValues() != null && level.fuelValues().isFuel(stack);
}
}

View File

@@ -0,0 +1,17 @@
package com.trunksbomb.batteries.block.entity;
import net.neoforged.neoforge.transfer.energy.SimpleEnergyHandler;
final class CoalGeneratorEnergyHandler extends SimpleEnergyHandler {
private final CoalGeneratorBlockEntity blockEntity;
CoalGeneratorEnergyHandler(CoalGeneratorBlockEntity blockEntity) {
super(blockEntity.getEnergyCapacity(), blockEntity.getMaxTransfer(), blockEntity.getMaxTransfer(), blockEntity.getStoredEnergy());
this.blockEntity = blockEntity;
}
@Override
protected void onEnergyChanged(int previousAmount) {
this.blockEntity.setStoredEnergyInternal(this.energy);
}
}

View File

@@ -0,0 +1,112 @@
package com.trunksbomb.batteries.block.entity;
import net.minecraft.world.item.ItemStack;
import net.neoforged.neoforge.transfer.ResourceHandler;
import net.neoforged.neoforge.transfer.TransferPreconditions;
import net.neoforged.neoforge.transfer.item.ItemResource;
import net.neoforged.neoforge.transfer.transaction.SnapshotJournal;
import net.neoforged.neoforge.transfer.transaction.TransactionContext;
public class CoalGeneratorItemHandler extends SnapshotJournal<ItemStack> implements ResourceHandler<ItemResource> {
private final CoalGeneratorBlockEntity blockEntity;
public CoalGeneratorItemHandler(CoalGeneratorBlockEntity blockEntity) {
this.blockEntity = blockEntity;
}
@Override
public int size() {
return 1;
}
@Override
public ItemResource getResource(int index) {
validateIndex(index);
return ItemResource.of(this.blockEntity.fuelSlot().getItem(index));
}
@Override
public long getAmountAsLong(int index) {
validateIndex(index);
return this.blockEntity.fuelSlot().getItem(index).getCount();
}
@Override
public long getCapacityAsLong(int index, ItemResource resource) {
validateIndex(index);
ItemStack current = this.blockEntity.fuelSlot().getItem(index);
if (resource.isEmpty()) {
return current.isEmpty() ? 64 : current.getMaxStackSize();
}
return isValid(index, resource) ? resource.getMaxStackSize() : 0;
}
@Override
public boolean isValid(int index, ItemResource resource) {
validateIndex(index);
if (resource.isEmpty() || this.blockEntity.getLevel() == null) {
return false;
}
return CoalGeneratorBlockEntity.isFuel(this.blockEntity.getLevel(), resource.toStack());
}
@Override
public int insert(int index, ItemResource resource, int amount, TransactionContext transaction) {
validateIndex(index);
TransferPreconditions.checkNonEmptyNonNegative(resource, amount);
if (amount == 0 || !this.isValid(index, resource)) {
return 0;
}
ItemStack current = this.blockEntity.fuelSlot().getItem(index);
if (!current.isEmpty() && !resource.matches(current)) {
return 0;
}
int maxStackSize = resource.getMaxStackSize();
int inserted = Math.min(amount, maxStackSize - current.getCount());
if (inserted <= 0) {
return 0;
}
this.updateSnapshots(transaction);
if (current.isEmpty()) {
this.blockEntity.fuelSlot().setItem(index, resource.toStack(inserted));
} else {
current.grow(inserted);
this.blockEntity.fuelSlot().setItem(index, current);
}
return inserted;
}
@Override
public int extract(int index, ItemResource resource, int amount, TransactionContext transaction) {
validateIndex(index);
TransferPreconditions.checkNonEmptyNonNegative(resource, amount);
return 0;
}
@Override
protected ItemStack createSnapshot() {
return this.blockEntity.fuelSlot().getItem(0).copy();
}
@Override
protected void revertToSnapshot(ItemStack snapshot) {
this.blockEntity.fuelSlot().setItem(0, snapshot);
}
@Override
protected void onRootCommit(ItemStack originalState) {
this.blockEntity.setChanged();
if (this.blockEntity.getLevel() != null) {
this.blockEntity.getLevel().invalidateCapabilities(this.blockEntity.getBlockPos());
}
}
private static void validateIndex(int index) {
if (index != 0) {
throw new IndexOutOfBoundsException("Coal generator only has one slot");
}
}
}

View File

@@ -0,0 +1,32 @@
package com.trunksbomb.batteries.block.entity;
import net.neoforged.neoforge.transfer.energy.EnergyHandler;
import net.neoforged.neoforge.transfer.transaction.TransactionContext;
final class GeneratorOutputEnergyHandler implements EnergyHandler {
private final EnergyHandler delegate;
GeneratorOutputEnergyHandler(EnergyHandler delegate) {
this.delegate = delegate;
}
@Override
public long getAmountAsLong() {
return this.delegate.getAmountAsLong();
}
@Override
public long getCapacityAsLong() {
return this.delegate.getCapacityAsLong();
}
@Override
public int insert(int maxAmount, TransactionContext transaction) {
return 0;
}
@Override
public int extract(int maxAmount, TransactionContext transaction) {
return this.delegate.extract(maxAmount, transaction);
}
}

View File

@@ -0,0 +1,263 @@
package com.trunksbomb.batteries.client.screen;
import com.trunksbomb.batteries.Batteries;
import com.trunksbomb.batteries.item.BatteryItem;
import com.trunksbomb.batteries.menu.BatteriesMenu;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.input.MouseButtonEvent;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.entity.player.Inventory;
public class BatteriesScreen extends AbstractContainerScreen<BatteriesMenu> {
private static final Identifier TEXTURE = Identifier.fromNamespaceAndPath(Batteries.MODID, "textures/battery_gui.png");
private static final int WIDTH = 200;
private static final int HEIGHT = 164;
private static final int BUTTON_WIDTH = 20;
private static final int BUTTON_HEIGHT = 18;
private static final int BUTTON_SCREEN_GAP_X = 7;
private static final int BUTTON_SCREEN_GAP_Y = 3;
private static final int BUTTON_START_X = 48;
private static final int BUTTON_START_Y = 39;
private static final int INVENTORY_START_X = 32;
private static final int INVENTORY_START_Y = 19;
private static final int PLAYER_INVENTORY_START_Y = 82;
private static final int ARMOR_START_X = 7;
private static final int ARMOR_START_Y = 19;
private static final int BUTTON_TEXTURE_GAP = 1;
private static final int BUTTON_TEXTURE_START_X = 201;
private static final int BUTTON_TEXTURE_START_Y = 0;
private static final int ARMOR_TEXTURE_X = 42;
private static final int ARMOR_TEXTURE_Y = 170;
private static final int CHECK_TEXTURE_X = 25;
private static final int CHECK_TEXTURE_Y = 170;
private static final int PLUS_TEXTURE_X = 144;
private static final int PLUS_TEXTURE_Y = 170;
public BatteriesScreen(BatteriesMenu menu, Inventory playerInventory, Component title) {
super(menu, playerInventory, title);
this.imageWidth = WIDTH;
this.imageHeight = HEIGHT;
this.inventoryLabelX = INVENTORY_START_X;
this.inventoryLabelY = PLAYER_INVENTORY_START_Y - 11;
}
@Override
protected void init() {
super.init();
}
@Override
protected void renderBg(GuiGraphics guiGraphics, float partialTick, int mouseX, int mouseY) {
guiGraphics.blit(RenderPipelines.GUI_TEXTURED, TEXTURE, this.leftPos, this.topPos, 0, 0, this.imageWidth, this.imageHeight, 256, 256);
drawButtons(guiGraphics, mouseX, mouseY);
drawOverlays(guiGraphics);
}
@Override
protected void renderLabels(GuiGraphics guiGraphics, int mouseX, int mouseY) {
renderTitle(guiGraphics);
}
@Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
this.renderBackground(guiGraphics, mouseX, mouseY, partialTick);
super.render(guiGraphics, mouseX, mouseY, partialTick);
this.renderTooltip(guiGraphics, mouseX, mouseY);
renderCustomTooltip(guiGraphics, mouseX, mouseY);
}
@Override
public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
double mouseX = event.x();
double mouseY = event.y();
for (int index = 0; index < this.menu.buttonCount(); index++) {
if (isBatteryModeToggle(index)) {
if (buttonBounds(0, 1).contains(mouseX, mouseY)) {
pressMenuButton(0);
return true;
}
continue;
}
Bounds bounds = buttonBounds(indexToRow(index), indexToColumn(index));
if (bounds.contains(mouseX, mouseY)) {
pressMenuButton(index);
return true;
}
}
return super.mouseClicked(event, doubleClick);
}
private void drawButtons(GuiGraphics guiGraphics, int mouseX, int mouseY) {
drawBatteryModeButton(guiGraphics, mouseX, mouseY);
for (int index = 2; index < this.menu.buttonCount(); index++) {
Bounds bounds = buttonBounds(indexToRow(index), indexToColumn(index));
drawButton(guiGraphics, bounds, index, bounds.contains(mouseX, mouseY));
}
}
private void drawBatteryModeButton(GuiGraphics guiGraphics, int mouseX, int mouseY) {
Bounds bounds = buttonBounds(0, 1);
boolean hovering = bounds.contains(mouseX, mouseY);
int textureIndex = this.menu.isButtonEnabled(0) ? 0 : 1;
int offset = hovering ? BUTTON_WIDTH + BUTTON_TEXTURE_GAP : 0;
guiGraphics.blit(RenderPipelines.GUI_TEXTURED, TEXTURE, bounds.x, bounds.y,
BUTTON_TEXTURE_START_X + offset,
BUTTON_TEXTURE_START_Y + textureIndex * (BUTTON_HEIGHT + BUTTON_TEXTURE_GAP),
BUTTON_WIDTH, BUTTON_HEIGHT, 256, 256);
}
private void drawButton(GuiGraphics guiGraphics, Bounds bounds, int index, boolean hovering) {
int textureIndex = index;
int offset = hovering ? BUTTON_WIDTH + BUTTON_TEXTURE_GAP : 0;
guiGraphics.blit(RenderPipelines.GUI_TEXTURED, TEXTURE, bounds.x, bounds.y,
BUTTON_TEXTURE_START_X + offset,
BUTTON_TEXTURE_START_Y + textureIndex * (BUTTON_HEIGHT + BUTTON_TEXTURE_GAP),
BUTTON_WIDTH, BUTTON_HEIGHT, 256, 256);
}
private void drawOverlays(GuiGraphics guiGraphics) {
for (int index = 2; index < this.menu.buttonCount(); index++) {
if (!this.menu.isButtonEnabled(index)) {
continue;
}
Bounds bounds = buttonBounds(indexToRow(index), indexToColumn(index));
guiGraphics.blit(RenderPipelines.GUI_TEXTURED, TEXTURE, bounds.x + 4, bounds.y + 4,
CHECK_TEXTURE_X, CHECK_TEXTURE_Y, 16, 16, 256, 256);
}
for (int slot = 0; slot < BatteriesMenu.FILTER_SLOT_COUNT; slot++) {
if (this.menu.isFilterSlotEmpty(slot)) {
guiGraphics.blit(RenderPipelines.GUI_TEXTURED, TEXTURE,
this.leftPos + INVENTORY_START_X + 18 * slot,
this.topPos + INVENTORY_START_Y,
PLUS_TEXTURE_X, PLUS_TEXTURE_Y, 16, 16, 256, 256);
}
}
for (int armorIndex = 0; armorIndex < 4; armorIndex++) {
int slot = 39 - armorIndex;
ItemStack stack = this.menu.playerInventoryItem(slot);
if (stack.isEmpty()) {
guiGraphics.blit(RenderPipelines.GUI_TEXTURED, TEXTURE,
this.leftPos + ARMOR_START_X,
this.topPos + ARMOR_START_Y + armorIndex * 18,
ARMOR_TEXTURE_X + armorIndex * 17,
ARMOR_TEXTURE_Y,
16, 16, 256, 256);
}
}
if (this.menu.playerInventoryItem(40).isEmpty()) {
guiGraphics.blit(RenderPipelines.GUI_TEXTURED, TEXTURE,
this.leftPos + ARMOR_START_X,
this.topPos + 91,
110, 170, 16, 16, 256, 256);
}
}
private String hoveredButtonTranslationKey(int mouseX, int mouseY) {
if (buttonBounds(0, 1).contains(mouseX, mouseY)) {
return this.menu.isButtonEnabled(0)
? "batteries.gui.button.whitelist"
: "batteries.gui.button.blacklist";
}
for (int index = 2; index < this.menu.buttonCount(); index++) {
Bounds bounds = buttonBounds(indexToRow(index), indexToColumn(index));
if (bounds.contains(mouseX, mouseY)) {
return BatteriesMenu.buttonTranslationKey(index);
}
}
return null;
}
private void pressMenuButton(int index) {
if (this.minecraft.gameMode == null) {
return;
}
this.minecraft.gameMode.handleInventoryButtonClick(this.menu.containerId, index);
this.menu.toggleClientPreview(index);
}
private void renderCustomTooltip(GuiGraphics guiGraphics, int mouseX, int mouseY) {
String hoverKey = hoveredButtonTranslationKey(mouseX, mouseY);
if (hoverKey != null) {
List<Component> tooltip = new ArrayList<>();
tooltip.add(Component.translatable(hoverKey));
guiGraphics.setComponentTooltipForNextFrame(this.font, tooltip, mouseX, mouseY);
}
}
private Component titleText() {
ItemStack battery = this.menu.batteryStack();
if (!(battery.getItem() instanceof BatteryItem)) {
return this.title;
}
return Component.literal(battery.getHoverName().getString()
+ " - "
+ BatteryItem.formatCompactAmount(BatteryItem.getStoredEnergy(battery))
+ " / "
+ BatteryItem.formatCompactAmount(BatteryItem.getEnergyCapacity(battery))
+ " E");
}
private void renderTitle(GuiGraphics guiGraphics) {
Component title = titleText();
int leftAlignedX = INVENTORY_START_X;
int titleY = 8;
int maxWidth = this.imageWidth - INVENTORY_START_X - 8;
int textWidth = this.font.width(title);
float scale = textWidth > maxWidth ? maxWidth / (float) textWidth : 1.0F;
guiGraphics.pose().pushMatrix();
guiGraphics.pose().scale(scale, scale);
guiGraphics.drawString(this.font, title, Math.round(leftAlignedX / scale), Math.round(titleY / scale), 0xFF202020, false);
guiGraphics.pose().popMatrix();
}
private Bounds buttonBounds(int rowIndex, int colIndex) {
return new Bounds(
this.leftPos + BUTTON_START_X + colIndex * (BUTTON_WIDTH + BUTTON_SCREEN_GAP_X),
this.topPos + BUTTON_START_Y + rowIndex * (BUTTON_HEIGHT + BUTTON_SCREEN_GAP_Y),
BUTTON_WIDTH,
BUTTON_HEIGHT
);
}
private static boolean isBatteryModeToggle(int index) {
return index == 0;
}
private static int indexToRow(int index) {
return index <= 3 ? 0 : 1;
}
private static int indexToColumn(int index) {
return switch (index) {
case 0, 1, 4 -> 1;
case 2, 5 -> 2;
case 3, 6 -> 3;
default -> 1;
};
}
private record Bounds(int x, int y, int width, int height) {
private boolean contains(double mouseX, double mouseY) {
return mouseX >= this.x && mouseX < this.x + this.width && mouseY >= this.y && mouseY < this.y + this.height;
}
}
}

View File

@@ -0,0 +1,150 @@
package com.trunksbomb.batteries.client.screen;
import com.trunksbomb.batteries.block.BatteryBlockData;
import com.trunksbomb.batteries.block.entity.BatteryBlockEntity;
import com.trunksbomb.batteries.item.BatteryItem;
import com.trunksbomb.batteries.menu.BatteryBlockMenu;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.Tooltip;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import net.minecraft.world.entity.player.Inventory;
import org.jspecify.annotations.NonNull;
public class BatteryBlockScreen extends AbstractContainerScreen<BatteryBlockMenu> {
private static final Identifier TEXTURE = Identifier.fromNamespaceAndPath("batteries", "textures/battery_block_gui.png");
private static final int TEXTURE_WIDTH = 176;
private static final int TEXTURE_HEIGHT = 164;
private static final int BUTTON_WIDTH = 50;
private static final int BUTTON_HEIGHT = 20;
private static final int BUTTON_START_X = 8;
private static final int BUTTON_START_Y = 18;
private static final int BUTTON_GAP_X = 5;
private static final int BUTTON_GAP_Y = 4;
public BatteryBlockScreen(BatteryBlockMenu menu, Inventory playerInventory, Component title) {
super(menu, playerInventory, title);
this.imageWidth = TEXTURE_WIDTH;
this.imageHeight = TEXTURE_HEIGHT;
this.inventoryLabelY = this.imageHeight - 94;
}
@Override
protected void init() {
super.init();
for (int index = 0; index < BatteryBlockMenu.SIDE_BUTTON_COUNT; index++) {
int buttonIndex = index;
this.addRenderableWidget(Button.builder(buttonLabel(index), button -> pressButton(buttonIndex))
.bounds(this.leftPos + buttonX(index), this.topPos + buttonY(index), BUTTON_WIDTH, BUTTON_HEIGHT)
.tooltip(buttonTooltip(index))
.build());
}
}
@Override
public void containerTick() {
super.containerTick();
int widgetIndex = 0;
for (AbstractWidget widget : this.renderables.stream().filter(AbstractWidget.class::isInstance).map(AbstractWidget.class::cast).toList()) {
if (widgetIndex >= BatteryBlockMenu.SIDE_BUTTON_COUNT) {
break;
}
widget.setMessage(buttonLabel(widgetIndex));
widget.setTooltip(buttonTooltip(widgetIndex));
widgetIndex++;
}
}
@Override
protected void renderBg(GuiGraphics guiGraphics, float partialTick, int mouseX, int mouseY) {
guiGraphics.blit(RenderPipelines.GUI_TEXTURED, TEXTURE, this.leftPos, this.topPos, 0, 0, this.imageWidth, this.imageHeight, TEXTURE_WIDTH, TEXTURE_HEIGHT);
}
@Override
protected void renderLabels(GuiGraphics guiGraphics, int mouseX, int mouseY) {
Component title = Component.literal(this.title.getString()
+ " - "
+ BatteryItem.formatCompactAmount(this.menu.storedEnergy())
+ " / "
+ BatteryItem.formatCompactAmount(this.menu.energyCapacity())
+ " E");
guiGraphics.drawString(this.font, title, 8, 6, 0xFF404040, false);
guiGraphics.drawString(this.font, this.playerInventoryTitle, this.inventoryLabelX, this.inventoryLabelY, 0xFF404040, false);
}
@Override
public void render(@NonNull GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
this.renderBackground(guiGraphics, mouseX, mouseY, partialTick);
super.render(guiGraphics, mouseX, mouseY, partialTick);
this.renderTooltip(guiGraphics, mouseX, mouseY);
}
private void pressButton(int index) {
if (this.minecraft.gameMode == null) {
return;
}
this.minecraft.gameMode.handleInventoryButtonClick(this.menu.containerId, index);
this.menu.cycleClientPreview(index);
}
private Component buttonLabel(int index) {
return Component.literal(shortDirection(index) + ": " + shortMode(this.menu.sideMode(index)));
}
private Tooltip buttonTooltip(int index) {
return Tooltip.create(Component.literal(fullDirection(index) + ": " + fullMode(this.menu.sideMode(index))));
}
private static int buttonX(int index) {
return BUTTON_START_X + (index % 3) * (BUTTON_WIDTH + BUTTON_GAP_X);
}
private static int buttonY(int index) {
return BUTTON_START_Y + (index / 3) * (BUTTON_HEIGHT + BUTTON_GAP_Y);
}
private static String shortDirection(int index) {
return switch (BatteryBlockEntity.directionForIndex(index)) {
case UP -> "U";
case DOWN -> "D";
case NORTH -> "N";
case SOUTH -> "S";
case WEST -> "W";
case EAST -> "E";
};
}
private static String fullDirection(int index) {
return switch (BatteryBlockEntity.directionForIndex(index)) {
case UP -> "Up";
case DOWN -> "Down";
case NORTH -> "North";
case SOUTH -> "South";
case WEST -> "West";
case EAST -> "East";
};
}
private static String shortMode(BatteryBlockData.SideMode mode) {
return switch (mode) {
case INPUT -> "In";
case OUTPUT -> "Out";
case BOTH -> "Both";
case NONE -> "None";
};
}
private static String fullMode(BatteryBlockData.SideMode mode) {
return switch (mode) {
case INPUT -> "Input";
case OUTPUT -> "Output";
case BOTH -> "Both";
case NONE -> "None";
};
}
}

View File

@@ -0,0 +1,73 @@
package com.trunksbomb.batteries.client.screen;
import com.trunksbomb.batteries.item.BatteryItem;
import com.trunksbomb.batteries.menu.CoalGeneratorMenu;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import net.minecraft.world.entity.player.Inventory;
public class CoalGeneratorScreen extends AbstractContainerScreen<CoalGeneratorMenu> {
private static final Identifier TEXTURE = Identifier.fromNamespaceAndPath("batteries", "textures/coal_generator_gui.png");
private static final int FLAME_X = 81;
private static final int FLAME_Y = 61;
private static final int FLAME_WIDTH = 14;
private static final int FLAME_HEIGHT = 14;
private static final int FLAME_BORDER = 0xFF3A2A17;
private static final int FLAME_BG = 0xFF5B4A2C;
private static final int FLAME_FILL = 0xFFFFB347;
private static final int FLAME_CORE = 0xFFFFE08A;
public CoalGeneratorScreen(CoalGeneratorMenu menu, Inventory playerInventory, Component title) {
super(menu, playerInventory, title);
this.imageWidth = 176;
this.imageHeight = 164;
this.inventoryLabelY = this.imageHeight - 94;
}
@Override
protected void renderBg(GuiGraphics guiGraphics, float partialTick, int mouseX, int mouseY) {
guiGraphics.blit(RenderPipelines.GUI_TEXTURED, TEXTURE, this.leftPos, this.topPos, 0, 0, this.imageWidth, this.imageHeight, this.imageWidth, this.imageHeight);
renderFlame(guiGraphics);
}
@Override
protected void renderLabels(GuiGraphics guiGraphics, int mouseX, int mouseY) {
Component title = Component.literal(this.title.getString()
+ " - "
+ BatteryItem.formatCompactAmount(this.menu.storedEnergy())
+ " / "
+ BatteryItem.formatCompactAmount(this.menu.energyCapacity())
+ " E");
guiGraphics.drawString(this.font, title, 8, 6, 0xFF404040, false);
guiGraphics.drawString(this.font, this.playerInventoryTitle, this.inventoryLabelX, this.inventoryLabelY, 0x404040, false);
}
@Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
this.renderBackground(guiGraphics, mouseX, mouseY, partialTick);
super.render(guiGraphics, mouseX, mouseY, partialTick);
this.renderTooltip(guiGraphics, mouseX, mouseY);
}
private void renderFlame(GuiGraphics guiGraphics) {
int maxBurn = this.menu.maxBurnTime();
int burn = this.menu.burnTimeRemaining();
int x = this.leftPos + FLAME_X;
int y = this.topPos + FLAME_Y;
guiGraphics.fill(x, y, x + FLAME_WIDTH, y + FLAME_HEIGHT, FLAME_BORDER);
guiGraphics.fill(x + 1, y + 1, x + FLAME_WIDTH - 1, y + FLAME_HEIGHT - 1, FLAME_BG);
if (maxBurn <= 0 || burn <= 0) {
return;
}
int flameHeight = Math.max(1, burn * (FLAME_HEIGHT - 2) / maxBurn);
int top = y + FLAME_HEIGHT - 1 - flameHeight;
guiGraphics.fill(x + 2, top, x + FLAME_WIDTH - 2, y + FLAME_HEIGHT - 2, FLAME_FILL);
guiGraphics.fill(x + 4, Math.max(y + 2, top + 2), x + FLAME_WIDTH - 4, y + FLAME_HEIGHT - 4, FLAME_CORE);
}
}

View File

@@ -0,0 +1,80 @@
package com.trunksbomb.batteries.integration.jei;
import com.trunksbomb.batteries.Batteries;
import java.util.List;
import mezz.jei.api.IModPlugin;
import mezz.jei.api.JeiPlugin;
import mezz.jei.api.constants.RecipeTypes;
import mezz.jei.api.recipe.vanilla.IVanillaRecipeFactory;
import mezz.jei.api.registration.IRecipeRegistration;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.CraftingBookCategory;
import net.minecraft.world.item.crafting.CraftingRecipe;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.display.SlotDisplay;
import net.minecraft.world.level.ItemLike;
import org.jspecify.annotations.NonNull;
@JeiPlugin
public class BatteriesJeiPlugin implements IModPlugin {
private static final Identifier PLUGIN_UID = Identifier.parse(Batteries.MODID + ":jei_plugin");
@Override
public @NonNull Identifier getPluginUid() {
return PLUGIN_UID;
}
@Override
public void registerRecipes(IRecipeRegistration registration) {
IVanillaRecipeFactory recipeFactory = registration.getVanillaRecipeFactory();
registration.addRecipes(RecipeTypes.CRAFTING, List.of(
twoItemRecipe("battery_pickaxe", recipeFactory, Batteries.BATTERY_PICKAXE.get(), CraftingBookCategory.EQUIPMENT, Items.DIAMOND_PICKAXE, Batteries.BATTERY.get()),
twoItemRecipe("battery_axe", recipeFactory, Batteries.BATTERY_AXE.get(), CraftingBookCategory.EQUIPMENT, Items.DIAMOND_AXE, Batteries.BATTERY.get()),
twoItemRecipe("battery_shovel", recipeFactory, Batteries.BATTERY_SHOVEL.get(), CraftingBookCategory.EQUIPMENT, Items.DIAMOND_SHOVEL, Batteries.BATTERY.get()),
twoItemRecipe("battery_hoe", recipeFactory, Batteries.BATTERY_HOE.get(), CraftingBookCategory.EQUIPMENT, Items.DIAMOND_HOE, Batteries.BATTERY.get()),
twoItemRecipe("battery_sword", recipeFactory, Batteries.BATTERY_SWORD.get(), CraftingBookCategory.EQUIPMENT, Items.DIAMOND_SWORD, Batteries.BATTERY.get()),
twoItemRecipe("battery_helmet", recipeFactory, Batteries.BATTERY_HELMET.get(), CraftingBookCategory.EQUIPMENT, Items.DIAMOND_HELMET, Batteries.BATTERY.get()),
twoItemRecipe("battery_chestplate", recipeFactory, Batteries.BATTERY_CHESTPLATE.get(), CraftingBookCategory.EQUIPMENT, Items.DIAMOND_CHESTPLATE, Batteries.BATTERY.get()),
twoItemRecipe("battery_leggings", recipeFactory, Batteries.BATTERY_LEGGINGS.get(), CraftingBookCategory.EQUIPMENT, Items.DIAMOND_LEGGINGS, Batteries.BATTERY.get()),
twoItemRecipe("battery_boots", recipeFactory, Batteries.BATTERY_BOOTS.get(), CraftingBookCategory.EQUIPMENT, Items.DIAMOND_BOOTS, Batteries.BATTERY.get()),
twoItemRecipe("battery_shield", recipeFactory, Batteries.BATTERY_SHIELD.get(), CraftingBookCategory.EQUIPMENT, Items.SHIELD, Batteries.BATTERY.get()),
twoItemRecipe("battery_bow", recipeFactory, Batteries.BATTERY_BOW.get(), CraftingBookCategory.EQUIPMENT, Items.BOW, Batteries.BATTERY.get()),
recipe("battery1_upgrade", recipeFactory, Batteries.BATTERY1.get(), Items.REDSTONE, Items.GOLD_BLOCK, Batteries.BATTERY.get(), Items.REDSTONE_BLOCK),
recipe("battery2_upgrade", recipeFactory, Batteries.BATTERY2.get(), Items.REDSTONE, Items.DIAMOND, Batteries.BATTERY1.get(), Items.REDSTONE_BLOCK),
recipe("battery3_upgrade", recipeFactory, Batteries.BATTERY3.get(), Items.REDSTONE, Items.DIAMOND_BLOCK, Batteries.BATTERY2.get(), Items.REDSTONE_BLOCK),
recipe("battery_ender_upgrade", recipeFactory, Batteries.BATTERY_ENDER.get(), Items.END_STONE, Items.DIAMOND_BLOCK, Batteries.BATTERY3.get(), Items.ENDER_PEARL)
));
}
private static RecipeHolder<CraftingRecipe> recipe(String name, IVanillaRecipeFactory recipeFactory, ItemLike result,
ItemLike corner, ItemLike topBottom, ItemLike center, ItemLike middleSide) {
CraftingRecipe recipe = recipeFactory.createShapedRecipeBuilder(CraftingBookCategory.REDSTONE, new SlotDisplay.ItemStackSlotDisplay(new ItemStack(result)))
.define('r', Ingredient.of(corner))
.define('g', Ingredient.of(topBottom))
.define('i', Ingredient.of(center))
.define('b', Ingredient.of(middleSide))
.pattern("rgr")
.pattern("bib")
.pattern("rgr")
.build();
ResourceKey<Recipe<?>> recipeId = ResourceKey.create(Registries.RECIPE, Identifier.parse(Batteries.MODID + ":jei/" + name));
return new RecipeHolder<>(recipeId, recipe);
}
private static RecipeHolder<CraftingRecipe> twoItemRecipe(String name, IVanillaRecipeFactory recipeFactory, ItemLike result,
CraftingBookCategory category, ItemLike first, ItemLike second) {
CraftingRecipe recipe = recipeFactory.createShapedRecipeBuilder(category, new SlotDisplay.ItemStackSlotDisplay(new ItemStack(result)))
.define('a', Ingredient.of(first))
.define('b', Ingredient.of(second))
.pattern("ab")
.build();
ResourceKey<Recipe<?>> recipeId = ResourceKey.create(Registries.RECIPE, Identifier.parse(Batteries.MODID + ":jei/" + name));
return new RecipeHolder<>(recipeId, recipe);
}
}

View File

@@ -0,0 +1,70 @@
package com.trunksbomb.batteries.item;
import java.util.function.Consumer;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.block.Block;
import net.neoforged.neoforge.transfer.energy.EnergyHandler;
import org.jspecify.annotations.NonNull;
public class BatteryBlockItem extends BlockItem implements PoweredItem {
public static final int BASE_CAPACITY = 50_000;
public BatteryBlockItem(Block block, Properties properties) {
super(block, properties.stacksTo(1));
}
@Override
public EnergyHandler createEnergyHandler(ItemStack stack) {
return new PoweredEnergyHandler(stack, getEnergyCapacity(stack), this.getMaxTransfer(stack), getStoredEnergy(stack));
}
@Override
public int getMaxTransfer(ItemStack stack) {
return EnergyTierHelper.transferRateForCapacity(getEnergyCapacity(stack));
}
@Override
public boolean isBarVisible(@NonNull ItemStack stack) {
return getEnergyCapacity(stack) > 0;
}
@Override
public int getBarWidth(@NonNull ItemStack stack) {
int capacity = getEnergyCapacity(stack);
return capacity <= 0 ? 0 : Math.round(13.0F * getStoredEnergy(stack) / (float) capacity);
}
@Override
public int getBarColor(@NonNull ItemStack stack) {
return this.getEnergyBarColor(stack);
}
@Override
public void appendHoverText(@NonNull ItemStack stack, @NonNull TooltipContext context, net.minecraft.world.item.component.@NonNull TooltipDisplay tooltipDisplay, @NonNull Consumer<Component> tooltipAdder, @NonNull TooltipFlag flag) {
this.addEnergyTooltip(stack, tooltipAdder, flag);
}
@Override
public boolean shouldCauseReequipAnimation(@NonNull ItemStack oldStack, @NonNull ItemStack newStack, boolean slotChanged) {
return this.shouldCauseEnergyReequipAnimation(oldStack, newStack, slotChanged);
}
public static int getEnergyCapacity(ItemStack stack) {
int capacity = PoweredItemEnergy.getEnergyCapacity(stack);
return capacity > 0 ? capacity : BASE_CAPACITY;
}
public static int getStoredEnergy(ItemStack stack) {
return Math.min(PoweredItemEnergy.getStoredEnergy(stack), getEnergyCapacity(stack));
}
public static void initializeDefaults(ItemStack stack) {
if (PoweredItemEnergy.getEnergyCapacity(stack) <= 0) {
PoweredItemEnergy.setEnergyCapacity(stack, BASE_CAPACITY);
PoweredItemEnergy.setStoredEnergy(stack, 0);
}
}
}

View File

@@ -0,0 +1,45 @@
package com.trunksbomb.batteries.item;
import net.minecraft.world.item.ItemStack;
import net.neoforged.neoforge.transfer.energy.SimpleEnergyHandler;
import net.neoforged.neoforge.transfer.transaction.TransactionContext;
import org.jspecify.annotations.NonNull;
final class BatteryEnergyHandler extends SimpleEnergyHandler {
private final ItemStack stack;
private final boolean creative;
BatteryEnergyHandler(ItemStack stack, int capacity, int maxTransfer, int energy, boolean creative) {
super(capacity, maxTransfer, maxTransfer, creative ? capacity : energy);
this.stack = stack;
this.creative = creative;
}
@Override
public long getAmountAsLong() {
return this.creative ? this.capacity : super.getAmountAsLong();
}
@Override
public int insert(int amount, @NonNull TransactionContext transaction) {
if (this.creative) {
return amount;
}
return super.insert(amount, transaction);
}
@Override
public int extract(int amount, @NonNull TransactionContext transaction) {
if (this.creative) {
return amount;
}
return super.extract(amount, transaction);
}
@Override
protected void onEnergyChanged(int previousAmount) {
if (!this.creative) {
BatteryItem.setStoredEnergy(this.stack, this.energy);
}
}
}

View File

@@ -0,0 +1,379 @@
package com.trunksbomb.batteries.item;
import com.trunksbomb.batteries.BatteriesConfig;
import com.trunksbomb.batteries.item.BatteryItemData.Tier;
import com.trunksbomb.batteries.menu.BatteriesMenu;
import java.util.UUID;
import java.util.function.Consumer;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.Level;
import net.neoforged.neoforge.capabilities.Capabilities;
import net.neoforged.neoforge.transfer.access.ItemAccess;
import net.neoforged.neoforge.transfer.energy.EnergyHandler;
import net.neoforged.neoforge.transfer.transaction.Transaction;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
public class BatteryItem extends Item {
private static final String BATTERY_UUID_KEY = "battery_uuid";
private static final String WHITELIST_KEY = "whitelist";
private static final String CHARGE_HOTBAR_KEY = "charge_hotbar";
private static final String CHARGE_INVENTORY_KEY = "charge_inventory";
private static final String CHARGE_ARMOR_KEY = "charge_armor";
private static final String CHARGE_FAIRLY_KEY = "charge_fairly";
private static final String CHARGE_MACHINE_KEY = "charge_machine";
private static final String FILTER_SLOT_KEY_PREFIX = "filter_slot_";
private final Tier tier;
public BatteryItem(Tier tier, Properties properties) {
super(properties.stacksTo(1));
this.tier = tier;
}
public Tier tier() {
return this.tier;
}
@Override
public @NonNull InteractionResult useOn(@NonNull UseOnContext context) {
return InteractionResult.PASS;
}
@Override
public @NonNull InteractionResult use(@NonNull Level level, @NonNull Player player, @NonNull InteractionHand usedHand) {
if (player instanceof ServerPlayer serverPlayer) {
ItemStack stack = player.getItemInHand(usedHand);
MenuProvider provider = new SimpleMenuProvider(
(containerId, inventory, menuPlayer) -> BatteriesMenu.forBattery(containerId, inventory, usedHand),
stack.getHoverName()
);
serverPlayer.openMenu(provider, buffer -> BatteriesMenu.writeBatteryMenu(buffer, usedHand));
}
return InteractionResult.SUCCESS;
}
@Override
public void inventoryTick(@NonNull ItemStack stack, @NonNull ServerLevel level, @NonNull Entity entity, @Nullable EquipmentSlot slot) {
if (!(entity instanceof ServerPlayer player) || !(stack.getItem() instanceof BatteryItem batteryItem)) {
return;
}
if (batteryItem.tier != Tier.CREATIVE && getStoredEnergy(stack) <= 0) {
return;
}
if (isChargeHotbarEnabled(stack)) {
for (int i = 0; i < 9; i++) {
chargeItemIfValid(stack, player.getInventory().getItem(i));
}
}
if (isChargeInventoryEnabled(stack)) {
for (int i = 9; i < 36; i++) {
chargeItemIfValid(stack, player.getInventory().getItem(i));
}
}
if (isChargeWornEnabled(stack)) {
for (int i = 36; i <= 40; i++) {
chargeItemIfValid(stack, player.getInventory().getItem(i));
}
}
if (isChargeMachineEnabled(stack)) {
chargeNearbyMachines(stack, player, level);
}
}
@Override
public boolean shouldCauseReequipAnimation(@NonNull ItemStack oldStack, @NonNull ItemStack newStack, boolean slotChanged) {
return slotChanged || !newStack.is(oldStack.getItem());
}
@Override
public boolean isBarVisible(@NonNull ItemStack stack) {
return true;
}
@Override
public int getBarWidth(@NonNull ItemStack stack) {
int capacity = getEnergyCapacity(stack);
if (capacity <= 0) {
return 0;
}
return Math.round(13.0F * getStoredEnergy(stack) / (float) capacity);
}
@Override
public int getBarColor(@NonNull ItemStack stack) {
return 0x55CC55;
}
@Override
@SuppressWarnings("deprecation")
public void appendHoverText(@NonNull ItemStack stack, @NonNull TooltipContext context, net.minecraft.world.item.component.@NonNull TooltipDisplay tooltipDisplay, @NonNull Consumer<Component> tooltipAdder, @NonNull TooltipFlag flag) {
tooltipAdder.accept(Component.translatable("batteries.tooltip.energy", formatCompactAmount(getStoredEnergy(stack)), formatCompactAmount(getEnergyCapacity(stack)))
.withStyle(ChatFormatting.GREEN));
}
public static EnergyHandler createEnergyHandler(ItemStack stack) {
if (!(stack.getItem() instanceof BatteryItem batteryItem)) {
return null;
}
int capacity = BatteryItemData.getEnergyCapacity(batteryItem.tier);
int maxTransfer = BatteryItemData.getMaxTransfer(batteryItem.tier);
return new BatteryEnergyHandler(stack, capacity, maxTransfer, getStoredEnergy(stack), batteryItem.tier == Tier.CREATIVE);
}
public static int getStoredEnergy(ItemStack stack) {
if (stack.getItem() instanceof BatteryItem batteryItem) {
return BatteryItemData.getStoredEnergy(stack, batteryItem.tier);
}
return 0;
}
static void setStoredEnergy(ItemStack stack, int energy) {
BatteryItemData.setStoredEnergy(stack, energy);
}
public static int getEnergyCapacity(ItemStack stack) {
if (stack.getItem() instanceof BatteryItem batteryItem) {
return BatteryItemData.getEnergyCapacity(batteryItem.tier);
}
return 0;
}
public static int getMaxTransfer(ItemStack stack) {
if (stack.getItem() instanceof BatteryItem batteryItem) {
return BatteryItemData.getMaxTransfer(batteryItem.tier);
}
return 0;
}
public static EnergyHandler getEnergyHandler(ItemStack stack) {
return stack.getCapability(Capabilities.Energy.ITEM, ItemAccess.forStack(stack));
}
public static UUID ensureBatteryId(ItemStack stack) {
CompoundTag tag = stack.getOrDefault(DataComponents.CUSTOM_DATA, net.minecraft.world.item.component.CustomData.EMPTY).copyTag();
String existingUuid = tag.getStringOr(BATTERY_UUID_KEY, "");
if (!existingUuid.isBlank()) {
return UUID.fromString(existingUuid);
}
UUID uuid = UUID.randomUUID();
net.minecraft.world.item.component.CustomData.update(DataComponents.CUSTOM_DATA, stack, data -> data.putString(BATTERY_UUID_KEY, uuid.toString()));
return uuid;
}
public static UUID getBatteryId(ItemStack stack) {
CompoundTag tag = stack.getOrDefault(DataComponents.CUSTOM_DATA, net.minecraft.world.item.component.CustomData.EMPTY).copyTag();
String uuid = tag.getStringOr(BATTERY_UUID_KEY, "");
return uuid.isBlank() ? null : UUID.fromString(uuid);
}
public static ItemStack findLinkedEnderBattery(Player player, UUID batteryId) {
if (batteryId == null) {
return ItemStack.EMPTY;
}
for (int slot = 0; slot < player.getInventory().getContainerSize(); slot++) {
ItemStack stack = player.getInventory().getItem(slot);
if (!(stack.getItem() instanceof BatteryItem batteryItem) || batteryItem.tier() != Tier.ENDER) {
continue;
}
UUID stackId = getBatteryId(stack);
if (batteryId.equals(stackId)) {
return stack;
}
}
return ItemStack.EMPTY;
}
private static void chargeItemIfValid(ItemStack battery, ItemStack receivingStack) {
if (receivingStack.isEmpty() || receivingStack == battery || receivingStack.getItem() instanceof BatteryItem || !matchesFilter(battery, receivingStack)) {
return;
}
EnergyHandler receivingEnergy = receivingStack.getCapability(Capabilities.Energy.ITEM, ItemAccess.forStack(receivingStack));
if (receivingEnergy == null) {
return;
}
transferEnergyToTarget(battery, receivingEnergy, isFairChargingEnabled(battery));
}
private static void chargeNearbyMachines(ItemStack battery, ServerPlayer player, ServerLevel level) {
int range = BatteriesConfig.machineChargeRange();
BlockPos center = player.blockPosition();
boolean fairCharging = isFairChargingEnabled(battery);
for (BlockPos pos : BlockPos.betweenClosed(center.offset(-range, -range, -range), center.offset(range, range, range))) {
if (!level.isLoaded(pos)) {
continue;
}
Direction side = getFacingFromBlockToPlayer(player, pos);
EnergyHandler energyHandler = level.getCapability(Capabilities.Energy.BLOCK, pos.immutable(), side);
if (energyHandler == null) {
continue;
}
transferEnergyToTarget(battery, energyHandler, fairCharging);
if (((BatteryItem) battery.getItem()).tier != Tier.CREATIVE && getStoredEnergy(battery) <= 0) {
return;
}
}
}
private static Direction getFacingFromBlockToPlayer(Player player, BlockPos pos) {
double dx = player.getX() - (pos.getX() + 0.5D);
double dy = player.getEyeY() - (pos.getY() + 0.5D);
double dz = player.getZ() - (pos.getZ() + 0.5D);
double absX = Math.abs(dx);
double absY = Math.abs(dy);
double absZ = Math.abs(dz);
if (absY >= absX && absY >= absZ) {
return dy >= 0.0D ? Direction.UP : Direction.DOWN;
}
if (absX >= absZ) {
return dx >= 0.0D ? Direction.EAST : Direction.WEST;
}
return dz >= 0.0D ? Direction.SOUTH : Direction.NORTH;
}
private static void transferEnergyToTarget(ItemStack battery, EnergyHandler target, boolean fairCharging) {
EnergyHandler batteryEnergy = getEnergyHandler(battery);
if (batteryEnergy == null) {
return;
}
int targetStored = target.getAmountAsInt();
int targetCapacity = target.getCapacityAsInt();
if (targetCapacity <= targetStored) {
return;
}
int batteryStored = getStoredEnergy(battery);
if (fairCharging && batteryStored <= targetStored) {
return;
}
int transferLimit = Math.min(getMaxTransfer(battery), targetCapacity - targetStored);
if (transferLimit <= 0) {
return;
}
if (((BatteryItem) battery.getItem()).tier == Tier.CREATIVE) {
try (Transaction transaction = Transaction.openRoot()) {
if (target.insert(transferLimit, transaction) > 0) {
transaction.commit();
}
}
return;
}
try (Transaction transaction = Transaction.openRoot()) {
int accepted = target.insert(transferLimit, transaction);
if (accepted <= 0) {
return;
}
int extracted = batteryEnergy.extract(accepted, transaction);
if (extracted != accepted) {
return;
}
transaction.commit();
}
}
private static boolean matchesFilter(ItemStack battery, ItemStack candidate) {
CompoundTag tag = battery.getOrDefault(DataComponents.CUSTOM_DATA, net.minecraft.world.item.component.CustomData.EMPTY).copyTag();
boolean whitelist = tag.getBooleanOr(WHITELIST_KEY, false);
boolean hasFilterEntries = false;
boolean matched = false;
for (int i = 0; i < BatteriesMenu.FILTER_SLOT_COUNT; i++) {
String itemId = tag.getStringOr(filterSlotKey(i), "");
if (itemId.isBlank()) {
continue;
}
hasFilterEntries = true;
Item item = BuiltInRegistries.ITEM.getValue(Identifier.parse(itemId));
if (candidate.is(item)) {
matched = true;
break;
}
}
if (!hasFilterEntries) {
return !whitelist;
}
return whitelist == matched;
}
private static boolean isChargeHotbarEnabled(ItemStack stack) {
return getToggle(stack, CHARGE_HOTBAR_KEY);
}
private static boolean isChargeInventoryEnabled(ItemStack stack) {
return getToggle(stack, CHARGE_INVENTORY_KEY);
}
private static boolean isChargeWornEnabled(ItemStack stack) {
return getToggle(stack, CHARGE_ARMOR_KEY);
}
private static boolean isFairChargingEnabled(ItemStack stack) {
return getToggle(stack, CHARGE_FAIRLY_KEY);
}
private static boolean isChargeMachineEnabled(ItemStack stack) {
return getToggle(stack, CHARGE_MACHINE_KEY);
}
private static boolean getToggle(ItemStack stack, String key) {
CompoundTag tag = stack.getOrDefault(DataComponents.CUSTOM_DATA, net.minecraft.world.item.component.CustomData.EMPTY).copyTag();
return tag.getBooleanOr(key, false);
}
private static String filterSlotKey(int index) {
return FILTER_SLOT_KEY_PREFIX + index;
}
public static String formatWithCommas(int amount) {
return BatteryItemData.formatWithCommas(amount);
}
public static String formatCompactAmount(int amount) {
return BatteryItemData.formatCompactAmount(amount);
}
}

View File

@@ -0,0 +1,272 @@
package com.trunksbomb.batteries.menu;
import com.trunksbomb.batteries.Batteries;
import com.trunksbomb.batteries.item.BatteryItem;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.resources.Identifier;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ClickType;
import net.minecraft.world.inventory.ContainerData;
import net.minecraft.world.inventory.SimpleContainerData;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.CustomData;
public class BatteriesMenu extends AbstractContainerMenu {
public static final int BATTERY_BUTTON_COUNT = 7;
public static final int FILTER_SLOT_COUNT = 9;
private static final String BATTERY_WHITELIST_KEY = "whitelist";
private static final String FILTER_SLOT_KEY_PREFIX = "filter_slot_";
private static final String[] BATTERY_BUTTON_KEYS = new String[] {
"whitelist",
"blacklist",
"charge_hotbar",
"charge_inventory",
"charge_armor",
"charge_fairly",
"charge_machine"
};
private final InteractionHand hand;
private final ContainerData toggleData;
private final SimpleContainer filterSlots;
private final Inventory playerInventory;
public BatteriesMenu(int containerId, Inventory inventory, RegistryFriendlyByteBuf buffer) {
this(containerId, inventory, readHand(buffer), new SimpleContainerData(BATTERY_BUTTON_COUNT));
}
private BatteriesMenu(int containerId, Inventory inventory, InteractionHand hand, ContainerData toggleData) {
super(Batteries.BATTERIES_MENU.get(), containerId);
this.hand = hand;
this.toggleData = toggleData;
this.playerInventory = inventory;
this.filterSlots = new SimpleContainer(FILTER_SLOT_COUNT);
this.addDataSlots(toggleData);
addSlots(inventory);
}
public static BatteriesMenu forBattery(int containerId, Inventory inventory, InteractionHand hand) {
SimpleContainerData data = new SimpleContainerData(BATTERY_BUTTON_COUNT);
ItemStack stack = inventory.player.getItemInHand(hand);
for (int i = 0; i < BATTERY_BUTTON_COUNT; i++) {
data.set(i, getBatteryToggle(stack, resolveStateIndex(i)) ? 1 : 0);
}
BatteriesMenu menu = new BatteriesMenu(containerId, inventory, hand, data);
menu.loadFilterSlots(stack);
return menu;
}
@Override
public boolean clickMenuButton(Player player, int buttonId) {
if (buttonId < 0 || buttonId >= this.toggleData.getCount()) {
return false;
}
int stateIndex = resolveStateIndex(buttonId);
int newValue = this.toggleData.get(stateIndex) == 0 ? 1 : 0;
this.toggleData.set(stateIndex, newValue);
if (!player.level().isClientSide() && this.hand != null) {
ItemStack stack = player.getItemInHand(this.hand);
setBatteryToggle(stack, stateIndex, newValue == 1);
}
return true;
}
@Override
public void clicked(int slotId, int button, ClickType clickType, Player player) {
if (slotId >= 0 && slotId < FILTER_SLOT_COUNT) {
Slot slot = this.slots.get(slotId);
ItemStack carried = this.getCarried();
if (!carried.isEmpty() && slot.mayPlace(carried)) {
ItemStack ghostStack = carried.copy();
ghostStack.setCount(1);
slot.set(ghostStack);
} else {
slot.set(ItemStack.EMPTY);
}
if (!player.level().isClientSide() && this.hand != null) {
saveFilterSlots(player.getItemInHand(this.hand));
}
this.broadcastChanges();
return;
}
super.clicked(slotId, button, clickType, player);
}
@Override
public ItemStack quickMoveStack(Player player, int index) {
return ItemStack.EMPTY;
}
@Override
public boolean stillValid(Player player) {
return true;
}
public int buttonCount() {
return BATTERY_BUTTON_COUNT;
}
public boolean isButtonEnabled(int index) {
return this.toggleData.get(resolveStateIndex(index)) == 1;
}
public void toggleClientPreview(int index) {
if (index < 0 || index >= this.buttonCount()) {
return;
}
int stateIndex = resolveStateIndex(index);
this.toggleData.set(stateIndex, this.toggleData.get(stateIndex) == 0 ? 1 : 0);
}
public static String buttonTranslationKey(int index) {
return "batteries.gui.button." + BATTERY_BUTTON_KEYS[index];
}
private static InteractionHand readHand(RegistryFriendlyByteBuf buffer) {
return buffer.readEnum(InteractionHand.class);
}
public static void writeBatteryMenu(RegistryFriendlyByteBuf buffer, InteractionHand hand) {
buffer.writeEnum(hand);
}
private static boolean getBatteryToggle(ItemStack stack, int index) {
CustomData customData = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
CompoundTag tag = customData.copyTag();
return tag.getBooleanOr(stateKey(index), false);
}
private static void setBatteryToggle(ItemStack stack, int index, boolean enabled) {
CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> tag.putBoolean(stateKey(index), enabled));
}
private void addSlots(Inventory inventory) {
for (int i = 0; i < FILTER_SLOT_COUNT; i++) {
this.addSlot(new BatteryFilterSlot(this.filterSlots, i, 32 + i * 18, 19));
}
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 9; col++) {
int x = 32 + col * 18;
int y = 82 + row * 18;
int index = col + row * 9 + 9;
this.addSlot(new Slot(inventory, index, x, y));
}
}
for (int col = 0; col < 9; col++) {
int x = 32 + col * 18;
int y = 140;
this.addSlot(new Slot(inventory, col, x, y));
}
for (int slotIndex = 39; slotIndex >= 36; slotIndex--) {
int armorOffset = -slotIndex + 39;
this.addSlot(new Slot(inventory, slotIndex, 7, 19 + armorOffset * 18));
}
this.addSlot(new Slot(inventory, 40, 7, 91));
}
private static int resolveStateIndex(int buttonId) {
if (buttonId == 1) {
return 0;
}
return buttonId;
}
private static String stateKey(int index) {
return switch (index) {
case 0 -> BATTERY_WHITELIST_KEY;
case 2 -> "charge_hotbar";
case 3 -> "charge_inventory";
case 4 -> "charge_armor";
case 5 -> "charge_fairly";
case 6 -> "charge_machine";
default -> throw new IllegalArgumentException("Unexpected battery state index: " + index);
};
}
public boolean isFilterSlotEmpty(int index) {
return this.filterSlots != null && this.filterSlots.getItem(index).isEmpty();
}
public ItemStack playerInventoryItem(int index) {
return this.playerInventory.getItem(index);
}
public ItemStack batteryStack() {
return this.hand == null ? ItemStack.EMPTY : this.playerInventory.player.getItemInHand(this.hand);
}
private void loadFilterSlots(ItemStack stack) {
if (this.filterSlots == null) {
return;
}
CustomData customData = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
CompoundTag tag = customData.copyTag();
for (int i = 0; i < FILTER_SLOT_COUNT; i++) {
this.filterSlots.setItem(i, decodeFilterStack(tag.getStringOr(filterSlotKey(i), "")));
}
}
private void saveFilterSlots(ItemStack stack) {
if (this.filterSlots == null) {
return;
}
CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> {
for (int i = 0; i < FILTER_SLOT_COUNT; i++) {
ItemStack filterStack = this.filterSlots.getItem(i);
if (filterStack.isEmpty()) {
tag.remove(filterSlotKey(i));
} else {
tag.putString(filterSlotKey(i), BuiltInRegistries.ITEM.getKey(filterStack.getItem()).toString());
}
}
});
}
private static ItemStack decodeFilterStack(String itemId) {
if (itemId.isBlank()) {
return ItemStack.EMPTY;
}
ItemStack stack = new ItemStack(BuiltInRegistries.ITEM.getValue(Identifier.parse(itemId)));
return stack.isEmpty() ? ItemStack.EMPTY : stack;
}
private static String filterSlotKey(int index) {
return FILTER_SLOT_KEY_PREFIX + index;
}
private static class BatteryFilterSlot extends Slot {
public BatteryFilterSlot(SimpleContainer container, int slot, int x, int y) {
super(container, slot, x, y);
}
@Override
public boolean mayPlace(ItemStack stack) {
return !(stack.getItem() instanceof BatteryItem);
}
@Override
public int getMaxStackSize() {
return 1;
}
}
}

View File

@@ -0,0 +1,141 @@
package com.trunksbomb.batteries.menu;
import com.trunksbomb.batteries.Batteries;
import com.trunksbomb.batteries.block.BatteryBlockData;
import com.trunksbomb.batteries.block.entity.BatteryBlockEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ContainerData;
import net.minecraft.world.inventory.ContainerLevelAccess;
import net.minecraft.world.inventory.Slot;
import org.jspecify.annotations.NonNull;
public class BatteryBlockMenu extends AbstractContainerMenu {
public static final int SIDE_BUTTON_COUNT = 6;
private static final int ENERGY_INDEX = 6;
private static final int CAPACITY_INDEX = 7;
private final ContainerLevelAccess access;
private final ContainerData data;
public BatteryBlockMenu(int containerId, Inventory inventory, RegistryFriendlyByteBuf buffer) {
this(containerId, inventory, buffer.readBlockPos(), createPlaceholderData());
}
private BatteryBlockMenu(int containerId, Inventory inventory, BlockPos blockPos, ContainerData data) {
super(Batteries.BATTERY_BLOCK_MENU.get(), containerId);
this.access = ContainerLevelAccess.create(inventory.player.level(), blockPos);
this.data = data;
this.addDataSlots(data);
this.addPlayerSlots(inventory);
}
public static BatteryBlockMenu forBlock(int containerId, Inventory inventory, BatteryBlockEntity blockEntity) {
return new BatteryBlockMenu(containerId, inventory, blockEntity.getBlockPos(), new ContainerData() {
@Override
public int get(int index) {
if (index >= 0 && index < SIDE_BUTTON_COUNT) {
return blockEntity.getSideMode(BatteryBlockEntity.directionForIndex(index)).ordinal();
}
if (index == ENERGY_INDEX) {
return blockEntity.getStoredEnergy();
}
if (index == CAPACITY_INDEX) {
return blockEntity.getEnergyCapacity();
}
return 0;
}
@Override
public void set(int index, int value) {
if (index >= 0 && index < SIDE_BUTTON_COUNT) {
BatteryBlockData.SideMode[] modes = BatteryBlockData.SideMode.values();
blockEntity.setSideMode(BatteryBlockEntity.directionForIndex(index), modes[Math.max(0, Math.min(modes.length - 1, value))]);
}
}
@Override
public int getCount() {
return 8;
}
});
}
@Override
public boolean clickMenuButton(@NonNull Player player, int buttonId) {
if (buttonId < 0 || buttonId >= SIDE_BUTTON_COUNT) {
return false;
}
this.data.set(buttonId, (this.data.get(buttonId) + 1) % BatteryBlockData.SideMode.values().length);
return true;
}
@Override
public net.minecraft.world.item.@NonNull ItemStack quickMoveStack(@NonNull Player player, int index) {
return net.minecraft.world.item.ItemStack.EMPTY;
}
@Override
public boolean stillValid(@NonNull Player player) {
return stillValid(this.access, player, Batteries.BATTERY_BLOCK.get());
}
public BatteryBlockData.SideMode sideMode(int index) {
return BatteryBlockData.SideMode.values()[this.data.get(index)];
}
public int storedEnergy() {
return this.data.get(ENERGY_INDEX);
}
public int energyCapacity() {
return this.data.get(CAPACITY_INDEX);
}
public void cycleClientPreview(int index) {
if (index >= 0 && index < SIDE_BUTTON_COUNT) {
this.data.set(index, (this.data.get(index) + 1) % BatteryBlockData.SideMode.values().length);
}
}
public static void writeBlockPos(RegistryFriendlyByteBuf buffer, BlockPos blockPos) {
buffer.writeBlockPos(blockPos);
}
private void addPlayerSlots(Inventory inventory) {
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 9; col++) {
this.addSlot(new Slot(inventory, col + row * 9 + 9, 8 + col * 18, 82 + row * 18));
}
}
for (int col = 0; col < 9; col++) {
this.addSlot(new Slot(inventory, col, 8 + col * 18, 140));
}
}
private static ContainerData createPlaceholderData() {
return new ContainerData() {
private final int[] values = new int[8];
@Override
public int get(int index) {
return this.values[index];
}
@Override
public void set(int index, int value) {
this.values[index] = value;
}
@Override
public int getCount() {
return this.values.length;
}
};
}
}

View File

@@ -0,0 +1,123 @@
package com.trunksbomb.batteries.menu;
import com.trunksbomb.batteries.Batteries;
import com.trunksbomb.batteries.block.entity.CoalGeneratorBlockEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ContainerData;
import net.minecraft.world.inventory.ContainerLevelAccess;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
public class CoalGeneratorMenu extends AbstractContainerMenu {
private static final int ENERGY_INDEX = 0;
private static final int CAPACITY_INDEX = 1;
private static final int BURN_TIME_INDEX = 2;
private static final int MAX_BURN_TIME_INDEX = 3;
private final ContainerLevelAccess access;
private final ContainerData data;
public CoalGeneratorMenu(int containerId, Inventory inventory, RegistryFriendlyByteBuf buffer) {
this(containerId, inventory, buffer.readBlockPos(), createPlaceholderData(), new net.minecraft.world.SimpleContainer(1));
}
private CoalGeneratorMenu(int containerId, Inventory inventory, BlockPos blockPos, ContainerData data, net.minecraft.world.Container fuelContainer) {
super(Batteries.COAL_GENERATOR_MENU.get(), containerId);
this.access = ContainerLevelAccess.create(inventory.player.level(), blockPos);
this.data = data;
this.addDataSlots(data);
this.addSlot(new FuelSlot(fuelContainer, 0, 80, 42));
addPlayerSlots(inventory);
}
public static CoalGeneratorMenu forBlock(int containerId, Inventory inventory, CoalGeneratorBlockEntity blockEntity) {
ContainerData data = new ContainerData() {
@Override
public int get(int index) {
return switch (index) {
case ENERGY_INDEX -> blockEntity.getStoredEnergy();
case CAPACITY_INDEX -> blockEntity.getEnergyCapacity();
case BURN_TIME_INDEX -> blockEntity.getBurnTimeRemaining();
case MAX_BURN_TIME_INDEX -> blockEntity.getMaxBurnTime();
default -> 0;
};
}
@Override
public void set(int index, int value) {
}
@Override
public int getCount() {
return 4;
}
};
return new CoalGeneratorMenu(containerId, inventory, blockEntity.getBlockPos(), data, blockEntity.fuelSlot());
}
@Override
public ItemStack quickMoveStack(Player player, int index) {
return ItemStack.EMPTY;
}
@Override
public boolean stillValid(Player player) {
return stillValid(this.access, player, Batteries.COAL_GENERATOR.get());
}
public int storedEnergy() {
return this.getData(ENERGY_INDEX);
}
public int energyCapacity() {
return this.getData(CAPACITY_INDEX);
}
public int burnTimeRemaining() {
return this.getData(BURN_TIME_INDEX);
}
public int maxBurnTime() {
return this.getData(MAX_BURN_TIME_INDEX);
}
private int getData(int index) {
return this.data.get(index);
}
public static void writeBlockPos(RegistryFriendlyByteBuf buffer, BlockPos blockPos) {
buffer.writeBlockPos(blockPos);
}
private void addPlayerSlots(Inventory inventory) {
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 9; col++) {
this.addSlot(new Slot(inventory, col + row * 9 + 9, 8 + col * 18, 82 + row * 18));
}
}
for (int col = 0; col < 9; col++) {
this.addSlot(new Slot(inventory, col, 8 + col * 18, 140));
}
}
private static ContainerData createPlaceholderData() {
return new net.minecraft.world.inventory.SimpleContainerData(4);
}
private final class FuelSlot extends Slot {
private FuelSlot(net.minecraft.world.Container container, int slot, int x, int y) {
super(container, slot, x, y);
}
@Override
public boolean mayPlace(ItemStack stack) {
return CoalGeneratorMenu.this.access.evaluate(
(level, pos) -> CoalGeneratorBlockEntity.isFuel(level, stack),
false
);
}
}
}

View File

@@ -0,0 +1,97 @@
package com.trunksbomb.batteries.recipe;
import com.trunksbomb.batteries.Batteries;
import com.trunksbomb.batteries.item.BatteryBlockItem;
import com.trunksbomb.batteries.item.BatteryItem;
import com.trunksbomb.batteries.item.PoweredItemEnergy;
import net.minecraft.core.HolderLookup;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.CraftingBookCategory;
import net.minecraft.world.item.crafting.CraftingInput;
import net.minecraft.world.item.crafting.CustomRecipe;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.level.Level;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
public class BatteryBlockUpgradeRecipe extends CustomRecipe {
public BatteryBlockUpgradeRecipe(CraftingBookCategory category) {
super(category);
}
@Override
public boolean matches(CraftingInput input, @NonNull Level level) {
return this.findMatch(input) != null;
}
@Override
public @NonNull ItemStack assemble(CraftingInput input, HolderLookup.@NonNull Provider registries) {
Match match = this.findMatch(input);
if (match == null) {
return ItemStack.EMPTY;
}
ItemStack result = new ItemStack(Batteries.BATTERY_BLOCK_ITEM.get());
int newCapacity = PoweredItemEnergy.clampToInt((long) match.baseCapacity() + match.addedCapacity());
int newEnergy = PoweredItemEnergy.clampToInt((long) match.baseEnergy() + match.addedEnergy());
PoweredItemEnergy.setEnergyCapacity(result, newCapacity);
PoweredItemEnergy.setStoredEnergy(result, Math.min(newCapacity, newEnergy));
return result;
}
@Override
public @NonNull RecipeSerializer<? extends CustomRecipe> getSerializer() {
return Batteries.BATTERY_BLOCK_UPGRADE_RECIPE.get();
}
public ItemStack getResultItem(HolderLookup.Provider registries) {
ItemStack result = new ItemStack(Batteries.BATTERY_BLOCK_ITEM.get());
BatteryBlockItem.initializeDefaults(result);
return result;
}
@Nullable
private Match findMatch(CraftingInput input) {
ItemStack baseStack = ItemStack.EMPTY;
long addedCapacity = 0L;
long addedEnergy = 0L;
for (ItemStack stack : input.items()) {
if (stack.isEmpty()) {
continue;
}
if (stack.getItem() instanceof BatteryBlockItem) {
if (baseStack.isEmpty()) {
baseStack = stack;
} else {
addedCapacity += BatteryBlockItem.getEnergyCapacity(stack);
addedEnergy += BatteryBlockItem.getStoredEnergy(stack);
}
continue;
}
if (stack.getItem() instanceof BatteryItem) {
addedCapacity += BatteryItem.getEnergyCapacity(stack);
addedEnergy += BatteryItem.getStoredEnergy(stack);
continue;
}
return null;
}
if (baseStack.isEmpty() || addedCapacity <= 0L) {
return null;
}
return new Match(
BatteryBlockItem.getEnergyCapacity(baseStack),
BatteryBlockItem.getStoredEnergy(baseStack),
PoweredItemEnergy.clampToInt(addedCapacity),
PoweredItemEnergy.clampToInt(addedEnergy)
);
}
private record Match(int baseCapacity, int baseEnergy, int addedCapacity, int addedEnergy) {
}
}

View File

@@ -0,0 +1,92 @@
package com.trunksbomb.batteries.recipe;
import com.trunksbomb.batteries.Batteries;
import com.trunksbomb.batteries.item.BatteryItem;
import net.minecraft.core.HolderLookup;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.CraftingBookCategory;
import net.minecraft.world.item.crafting.CraftingInput;
import net.minecraft.world.item.crafting.CustomRecipe;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.level.Level;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
public class BatteryTierUpgradeRecipe extends CustomRecipe {
public BatteryTierUpgradeRecipe(CraftingBookCategory category) {
super(category);
}
@Override
public boolean matches(CraftingInput input, @NonNull Level level) {
return this.findMatch(input) != null;
}
@Override
public @NonNull ItemStack assemble(CraftingInput input, HolderLookup.@NonNull Provider registries) {
Match match = this.findMatch(input);
if (match == null) {
return ItemStack.EMPTY;
}
ItemStack result = match.baseStack().transmuteCopy(match.resultItem());
int storedEnergy = BatteryItem.getStoredEnergy(match.baseStack());
int capacity = BatteryItem.getEnergyCapacity(result);
BatteryItem.createEnergyHandler(result);
setStoredEnergy(result, Math.min(storedEnergy, capacity));
return result;
}
@Override
public RecipeSerializer<? extends CustomRecipe> getSerializer() {
return Batteries.BATTERY_TIER_UPGRADE_RECIPE.get();
}
public ItemStack getResultItem(HolderLookup.Provider registries) {
return new ItemStack(Batteries.BATTERY1.get());
}
@Nullable
private Match findMatch(CraftingInput input) {
if (input.width() != 3 || input.height() != 3) {
return null;
}
if (matchesPattern(input, Items.REDSTONE, Items.GOLD_BLOCK, Batteries.BATTERY.get(), Items.REDSTONE_BLOCK)) {
return new Match(input.getItem(4), Batteries.BATTERY1.get());
}
if (matchesPattern(input, Items.REDSTONE, Items.DIAMOND, Batteries.BATTERY1.get(), Items.REDSTONE_BLOCK)) {
return new Match(input.getItem(4), Batteries.BATTERY2.get());
}
if (matchesPattern(input, Items.REDSTONE, Items.DIAMOND_BLOCK, Batteries.BATTERY2.get(), Items.REDSTONE_BLOCK)) {
return new Match(input.getItem(4), Batteries.BATTERY3.get());
}
if (matchesPattern(input, Items.END_STONE, Items.DIAMOND_BLOCK, Batteries.BATTERY3.get(), Items.ENDER_PEARL)) {
return new Match(input.getItem(4), Batteries.BATTERY_ENDER.get());
}
return null;
}
private static boolean matchesPattern(CraftingInput input, Item cornerItem, Item topBottomItem, Item centerItem, Item middleSideItem) {
return input.getItem(0).is(cornerItem)
&& input.getItem(1).is(topBottomItem)
&& input.getItem(2).is(cornerItem)
&& input.getItem(3).is(middleSideItem)
&& input.getItem(4).is(centerItem)
&& input.getItem(5).is(middleSideItem)
&& input.getItem(6).is(cornerItem)
&& input.getItem(7).is(topBottomItem)
&& input.getItem(8).is(cornerItem);
}
private static void setStoredEnergy(ItemStack stack, int energy) {
net.minecraft.world.item.component.CustomData.update(net.minecraft.core.component.DataComponents.CUSTOM_DATA, stack,
tag -> tag.putInt("energy", Math.max(0, energy)));
}
private record Match(ItemStack baseStack, Item resultItem) {
}
}

View File

@@ -0,0 +1,156 @@
package com.trunksbomb.batteries.recipe;
import com.trunksbomb.batteries.Batteries;
import com.trunksbomb.batteries.BatteriesConfig;
import com.trunksbomb.batteries.item.BatteryItem;
import com.trunksbomb.batteries.item.PoweredItem;
import com.trunksbomb.batteries.item.PoweredItemEnergy;
import net.minecraft.core.HolderLookup;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.CraftingBookCategory;
import net.minecraft.world.item.crafting.CraftingInput;
import net.minecraft.world.item.crafting.CustomRecipe;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.level.Level;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
public class PoweredGearUpgradeRecipe extends CustomRecipe {
public PoweredGearUpgradeRecipe(CraftingBookCategory category) {
super(category);
}
@Override
public boolean matches(CraftingInput input, @NonNull Level level) {
return this.findMatch(input) != null;
}
@Override
public @NonNull ItemStack assemble(CraftingInput input, HolderLookup.@NonNull Provider registries) {
Match match = this.findMatch(input);
if (match == null) {
return ItemStack.EMPTY;
}
ItemStack result = match.baseStack().transmuteCopy(match.resultItem());
int baseCapacity = match.basePowered() ? PoweredItemEnergy.getEnergyCapacity(match.baseStack()) : 0;
int baseEnergy = match.basePowered() ? PoweredItemEnergy.getStoredEnergy(match.baseStack()) : 0;
int newCapacity = PoweredItemEnergy.clampToInt((long) baseCapacity + match.addedCapacity());
int newEnergy = PoweredItemEnergy.clampToInt((long) baseEnergy + match.addedEnergy());
PoweredItemEnergy.setEnergyCapacity(result, newCapacity);
PoweredItemEnergy.setStoredEnergy(result, Math.min(newCapacity, newEnergy));
return result;
}
@Override
public @NonNull RecipeSerializer<? extends CustomRecipe> getSerializer() {
return Batteries.POWERED_GEAR_UPGRADE_RECIPE.get();
}
public ItemStack getResultItem(HolderLookup.Provider registries) {
return new ItemStack(Batteries.BATTERY_PICKAXE.get());
}
@Nullable
private Match findMatch(CraftingInput input) {
ItemStack baseStack = ItemStack.EMPTY;
Item resultItem = null;
boolean basePowered = false;
long addedCapacity = 0L;
long addedEnergy = 0L;
int batteries = 0;
for (ItemStack stack : input.items()) {
if (stack.isEmpty()) {
continue;
}
if (stack.getItem() instanceof BatteryItem) {
batteries++;
addedCapacity += BatteryItem.getEnergyCapacity(stack);
addedEnergy += BatteryItem.getStoredEnergy(stack);
continue;
}
if (!baseStack.isEmpty()) {
return null;
}
resultItem = this.toPoweredResult(stack);
if (resultItem == null) {
return null;
}
baseStack = stack;
basePowered = stack.getItem() instanceof PoweredItem;
}
if (baseStack.isEmpty() || resultItem == null || batteries == 0) {
return null;
}
return new Match(baseStack, resultItem, basePowered, PoweredItemEnergy.clampToInt(addedCapacity), PoweredItemEnergy.clampToInt(addedEnergy));
}
@Nullable
private Item toPoweredResult(ItemStack stack) {
Item item = stack.getItem();
if (BatteriesConfig.poweredToolsEnabled() && item == Items.DIAMOND_PICKAXE) {
return Batteries.BATTERY_PICKAXE.get();
}
if (BatteriesConfig.poweredToolsEnabled() && item == Items.DIAMOND_AXE) {
return Batteries.BATTERY_AXE.get();
}
if (BatteriesConfig.poweredToolsEnabled() && item == Items.DIAMOND_SHOVEL) {
return Batteries.BATTERY_SHOVEL.get();
}
if (BatteriesConfig.poweredToolsEnabled() && item == Items.DIAMOND_HOE) {
return Batteries.BATTERY_HOE.get();
}
if (BatteriesConfig.poweredWeaponsEnabled() && item == Items.DIAMOND_SWORD) {
return Batteries.BATTERY_SWORD.get();
}
if (BatteriesConfig.poweredArmorEnabled() && item == Items.DIAMOND_HELMET) {
return Batteries.BATTERY_HELMET.get();
}
if (BatteriesConfig.poweredArmorEnabled() && item == Items.DIAMOND_CHESTPLATE) {
return Batteries.BATTERY_CHESTPLATE.get();
}
if (BatteriesConfig.poweredArmorEnabled() && item == Items.DIAMOND_LEGGINGS) {
return Batteries.BATTERY_LEGGINGS.get();
}
if (BatteriesConfig.poweredArmorEnabled() && item == Items.DIAMOND_BOOTS) {
return Batteries.BATTERY_BOOTS.get();
}
if (BatteriesConfig.poweredWeaponsEnabled() && item == Items.SHIELD) {
return Batteries.BATTERY_SHIELD.get();
}
if (BatteriesConfig.poweredWeaponsEnabled() && item == Items.BOW) {
return Batteries.BATTERY_BOW.get();
}
if (BatteriesConfig.poweredToolsEnabled() && (item == Batteries.BATTERY_PICKAXE.get()
|| item == Batteries.BATTERY_AXE.get()
|| item == Batteries.BATTERY_SHOVEL.get()
|| item == Batteries.BATTERY_HOE.get())) {
return item;
}
if (BatteriesConfig.poweredWeaponsEnabled() && (item == Batteries.BATTERY_SWORD.get()
|| item == Batteries.BATTERY_SHIELD.get()
|| item == Batteries.BATTERY_BOW.get())) {
return item;
}
if (BatteriesConfig.poweredArmorEnabled() && (item == Batteries.BATTERY_HELMET.get()
|| item == Batteries.BATTERY_CHESTPLATE.get()
|| item == Batteries.BATTERY_LEGGINGS.get()
|| item == Batteries.BATTERY_BOOTS.get())) {
return item;
}
return null;
}
private record Match(ItemStack baseStack, Item resultItem, boolean basePowered, int addedCapacity, int addedEnergy) {
}
}

View File

@@ -0,0 +1,19 @@
{
"type": "minecraft:crafting_shaped",
"category": "redstone",
"pattern": [
"rgr",
"bib",
"rgr"
],
"key": {
"r": "minecraft:redstone",
"g": "minecraft:gold_ingot",
"i": "minecraft:iron_ingot",
"b": "minecraft:redstone_block"
},
"result": {
"id": "batteries:battery",
"count": 1
}
}

View File

@@ -0,0 +1,4 @@
{
"type": "batteries:battery_tier_upgrade",
"category": "redstone"
}

View File

@@ -0,0 +1,4 @@
{
"type": "batteries:battery_tier_upgrade",
"category": "redstone"
}

View File

@@ -0,0 +1,4 @@
{
"type": "batteries:battery_tier_upgrade",
"category": "redstone"
}

View File

@@ -0,0 +1,18 @@
{
"type": "minecraft:crafting_shaped",
"category": "redstone",
"pattern": [
"RGR",
"LIL",
"RGR"
],
"key": {
"R": "minecraft:redstone_block",
"L": "#minecraft:logs",
"G": "minecraft:gold_ingot",
"I": "minecraft:iron_block"
},
"result": {
"id": "batteries:battery_block"
}
}

View File

@@ -0,0 +1,4 @@
{
"type": "batteries:battery_block_upgrade",
"category": "redstone"
}

View File

@@ -0,0 +1,4 @@
{
"type": "batteries:battery_tier_upgrade",
"category": "redstone"
}

View File

@@ -0,0 +1,19 @@
{
"type": "minecraft:crafting_shaped",
"category": "redstone",
"pattern": [
"rgr",
"bib",
"rgr"
],
"key": {
"r": "minecraft:white_concrete",
"g": "minecraft:gold_ingot",
"i": "minecraft:iron_ingot",
"b": "minecraft:redstone_block"
},
"result": {
"id": "batteries:charger",
"count": 1
}
}

View File

@@ -0,0 +1,18 @@
{
"type": "minecraft:crafting_shaped",
"category": "redstone",
"pattern": [
"RGR",
"LFL",
"RGR"
],
"key": {
"R": "minecraft:redstone_block",
"G": "minecraft:gold_ingot",
"L": "#minecraft:logs",
"F": "minecraft:furnace"
},
"result": {
"id": "batteries:coal_generator"
}
}

View File

@@ -0,0 +1,18 @@
{
"type": "minecraft:crafting_shaped",
"category": "redstone",
"pattern": [
"ede",
"ece",
"ede"
],
"key": {
"e": "minecraft:ender_pearl",
"d": "minecraft:diamond",
"c": "batteries:charger"
},
"result": {
"id": "batteries:ender_charger",
"count": 1
}
}

View File

@@ -0,0 +1,89 @@
# This is an example neoforge.mods.toml file. It contains the data relating to the loading mods.
# There are several mandatory fields (#mandatory), and many more that are optional (#optional).
# The overall format is standard TOML format, v0.5.0.
# Note that there are a couple of TOML lists in this file.
# Find more information on toml format here: https://github.com/toml-lang/toml
# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties.
# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here.
license="${mod_license}"
# A URL to refer people to when problems occur with this mod
#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional
# A list of mods - how many allowed here is determined by the individual mod loader
[[mods]] #mandatory
# The modid of the mod
modId="${mod_id}" #mandatory
# The version number of the mod
version="${mod_version}" #mandatory
# A display name for the mod
displayName="${mod_name}" #mandatory
# A URL to query for updates for this mod. See the JSON update specification https://docs.neoforged.net/docs/misc/updatechecker/
#updateJSONURL="https://change.me.example.invalid/updates.json" #optional
# A URL for the "homepage" for this mod, displayed in the mod UI
#displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional
# A file name (in the root of the mod JAR) containing a logo for display
#logoFile="examplemod.png" #optional
# A text field displayed in the mod UI
#credits="" #optional
# The authors of the mod, displayed in the mod UI (optional)
authors="trunksbomb"
# The description text for the mod (multi line!) (#mandatory)
description='''
Adds portable batteries and chargers for NeoForge. This 1.21 branch currently includes an MVP battery item and charger block while the gameplay systems are being ported.
'''
# The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded.
#[[mixins]]
#config="${mod_id}.mixins.json"
# The [[accessTransformers]] block allows you to declare where your AT file is.
# If this block is omitted, a fallback attempt will be made to load an AT from META-INF/accesstransformer.cfg
#[[accessTransformers]]
#file="META-INF/accesstransformer.cfg"
# The coremods config file path is not configurable and is always loaded from META-INF/coremods.json
# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional.
[[dependencies.${mod_id}]] #optional
# the modid of the dependency
modId="neoforge" #mandatory
# The type of the dependency. Can be one of "required", "optional", "incompatible" or "discouraged" (case insensitive).
# 'required' requires the mod to exist, 'optional' does not
# 'incompatible' will prevent the game from loading when the mod exists, and 'discouraged' will show a warning
type="required" #mandatory
# Optional field describing why the dependency is required or why it is incompatible
# reason="..."
# The version range of the dependency
versionRange="[${neo_version},)" #mandatory
# An ordering relationship for the dependency.
# BEFORE - This mod is loaded BEFORE the dependency
# AFTER - This mod is loaded AFTER the dependency
ordering="NONE"
# Side this dependency is applied on - BOTH, CLIENT, or SERVER
side="BOTH"
# Here's another dependency
[[dependencies.${mod_id}]]
modId="minecraft"
type="required"
# This version range declares a minimum of the current minecraft version up to but not including the next major version
versionRange="${minecraft_version_range}"
ordering="NONE"
side="BOTH"
# Features are specific properties of the game environment, that you may want to declare you require. This example declares
# that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't
# stop your mod loading on the server for example.
#[features.${mod_id}]
#openGLVersion="[3.2,)"