first commit

This commit is contained in:
Soph :3 2025-10-05 13:52:42 +03:00
commit e5bdff9721
32 changed files with 2100 additions and 0 deletions

View file

@ -0,0 +1,67 @@
package ovh.sad.growglobe;
import com.mojang.serialization.MapCodec;
import net.minecraft.block.Block;
import net.minecraft.block.BlockEntityProvider;
import net.minecraft.block.BlockState;
import net.minecraft.block.BlockWithEntity;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityTicker;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.screen.NamedScreenHandlerFactory;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
public class GrowGlobeBlock extends BlockWithEntity {
public GrowGlobeBlock(Settings settings) {
super(settings);
}
public static final MapCodec<GrowGlobeBlock> CODEC = createCodec(GrowGlobeBlock::new);
@Override
protected ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit) {
if (!world.isClient) {
BlockEntity blockEntity = world.getBlockEntity(pos);
System.out.println("one");
if (blockEntity instanceof GrowGlobeBlockEntity screenHandlerFactory) {
System.out.println("two");
if (player instanceof ServerPlayerEntity serverPlayer) {
System.out.println("three");
serverPlayer.openHandledScreen(screenHandlerFactory);
}
}
}
return ActionResult.SUCCESS;
}
@Override
protected MapCodec<? extends BlockWithEntity> getCodec() {
return CODEC;
}
@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return new GrowGlobeBlockEntity(pos, state);
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(World world, BlockState state, BlockEntityType<T> type) {
return world.isClient ? null :
(type == GrowGlobeBlockEntities.GROW_GLOBE
? (world1, pos, state1, be) -> GrowGlobeBlockEntity.tick(world1, pos, state1, (GrowGlobeBlockEntity)be)
: null);
}
}

View file

@ -0,0 +1,22 @@
package ovh.sad.growglobe;
import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.util.Identifier;
import team.reborn.energy.api.EnergyStorage;
public class GrowGlobeBlockEntities {
public static BlockEntityType<GrowGlobeBlockEntity> GROW_GLOBE;
public static void register() {
GROW_GLOBE = Registry.register(
Registries.BLOCK_ENTITY_TYPE,
Identifier.of(Growglobe.MOD_ID, "growglobe"),
FabricBlockEntityTypeBuilder.create(GrowGlobeBlockEntity::new, GrowGlobeBlocks.GROW_GLOBE_BLOCK).build()
);
EnergyStorage.SIDED.registerForBlockEntity((be, dir) -> be.energyStorage, GROW_GLOBE);
}
}

View file

@ -0,0 +1,235 @@
package ovh.sad.growglobe;
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory;
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.Inventories;
import net.minecraft.inventory.SidedInventory;
import net.minecraft.inventory.SimpleInventory;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.screen.ArrayPropertyDelegate;
import net.minecraft.screen.PropertyDelegate;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.storage.ReadView;
import net.minecraft.storage.WriteView;
import net.minecraft.text.Text;
import net.minecraft.util.ItemScatterer;
import net.minecraft.util.collection.DefaultedList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
import team.reborn.energy.api.EnergyStorage;
import team.reborn.energy.api.base.SimpleEnergyStorage;
import java.util.List;
import static ovh.sad.growglobe.HarvestUtil.harvestBlock;
public class GrowGlobeBlockEntity extends BlockEntity implements ExtendedScreenHandlerFactory, SidedInventory {
public static final long CAPACITY = 10000;
public static final long MAX_INSERT = 200;
public static final long MAX_EXTRACT = 0;
public final SimpleEnergyStorage energyStorage = new SimpleEnergyStorage(CAPACITY, MAX_INSERT, MAX_EXTRACT) {
@Override
protected void onFinalCommit() {
markDirty();
}
};
// Inventory: slot 9 = input (sapling), slots 08 = outputs
private final SimpleInventory inventory = new SimpleInventory(10);
// [0]=currentEnergy, [1]=maxEnergy, [2]=progress, [3]=maxProgress
private final PropertyDelegate propertyDelegate;
private int progress = 0;
private static final int MAX_PROGRESS = 80;
private static final int ENERGY_PER_TICK = 65;
public GrowGlobeBlockEntity(BlockPos pos, BlockState state) {
super(GrowGlobeBlockEntities.GROW_GLOBE, pos, state);
this.propertyDelegate = new ArrayPropertyDelegate(4);
}
@Override
public void onBlockReplaced(BlockPos pos, BlockState oldState) {
if (this.world != null) {
ItemScatterer.spawn(this.world, pos, this.inventory);
}
}
private static final int[] INPUT_SLOTS = {9};
private static final int[] OUTPUT_SLOTS = {0,1,2,3,4,5,6,7,8};
@Override
public int[] getAvailableSlots(Direction side) {
if (side == Direction.UP) return INPUT_SLOTS; // hopper can insert sapling
if (side == Direction.DOWN) return OUTPUT_SLOTS; // hopper can extract outputs
return new int[0]; // sides cannot access
}
public static void tick(World world, BlockPos pos, BlockState state, GrowGlobeBlockEntity blockEntity) {
if (world.isClient) return;
for (Direction dir : Direction.values()) {
@Nullable EnergyStorage adjacent = EnergyStorage.SIDED.find(world, pos.offset(dir), dir.getOpposite());
if (adjacent == null) continue;
try (Transaction tx = Transaction.openOuter()) {
long moved = adjacent.extract(50, tx);
long inserted = blockEntity.energyStorage.insert(moved, tx);
if (inserted > 0) tx.commit();
}
}
blockEntity.propertyDelegate.set(0, (int) Math.min(Integer.MAX_VALUE, blockEntity.energyStorage.amount));
blockEntity.propertyDelegate.set(1, (int) Math.min(Integer.MAX_VALUE, blockEntity.energyStorage.capacity));
blockEntity.propertyDelegate.set(2, blockEntity.progress);
blockEntity.propertyDelegate.set(3, MAX_PROGRESS);
ItemStack input = blockEntity.inventory.getStack(9);
if (input.isEmpty()) {
blockEntity.progress = 0;
return;
}
if (blockEntity.energyStorage.amount >= ENERGY_PER_TICK) {
blockEntity.energyStorage.amount -= ENERGY_PER_TICK;
blockEntity.progress++;
if (blockEntity.progress >= MAX_PROGRESS) {
blockEntity.progress = 0;
Item toolItem = input.getItem();
List<ItemStack> drops = harvestBlock((ServerWorld) world, toolItem, new BlockPos(0, 200, 0));
outer:
for (ItemStack drop : drops) {
for (int i = 0; i <= 8; i++) {
ItemStack slot = blockEntity.inventory.getStack(i);
if (slot.isEmpty()) {
blockEntity.inventory.setStack(i, drop);
continue outer;
} else if (canCombine(slot, drop)) {
int combined = Math.min(slot.getMaxCount(), slot.getCount() + drop.getCount());
drop.setCount(drop.getCount() - (combined - slot.getCount()));
slot.setCount(combined);
blockEntity.inventory.setStack(i, slot);
if (drop.isEmpty()) continue outer;
}
}
}
blockEntity.markDirty();
}
}
}
private static boolean canCombine(ItemStack a, ItemStack b) {
if (a.getItem() != b.getItem()) return false;
if (a.hasEnchantments() != b.hasEnchantments()) return false;
if (a.hasEnchantments() && !a.getEnchantments().equals(b.getEnchantments())) return false;
return a.getCount() < a.getMaxCount();
}
public PropertyDelegate getPropertyDelegate() {
return propertyDelegate;
}
@Override
public Text getDisplayName() {
return Text.literal("Grow Globe");
}
@Override
public boolean canInsert(int slot, ItemStack stack, @Nullable Direction dir) {
return dir == Direction.UP && slot == 9; // only top can insert into slot 9
}
@Override
public boolean canExtract(int slot, ItemStack stack, Direction dir) {
return dir == Direction.DOWN && slot >= 0 && slot <= 8; // only bottom can extract outputs
}
@Override
public ScreenHandler createMenu(int syncId, PlayerInventory inv, PlayerEntity player) {
return new GrowGlobeScreenHandler(syncId, inv, inventory, propertyDelegate);
}
public SimpleInventory getInventory() {
return inventory;
}
@Override
protected void readData(ReadView view) {
energyStorage.amount = view.getLong("Energy", 0L);
progress = view.getInt("Progress", 0);
DefaultedList<ItemStack> list = this.inventory.getHeldStacks();
Inventories.readData(view, list);
for (int i = 0; i < list.size(); i++) {
inventory.setStack(i, list.get(i));
}
}
@Override
protected void writeData(WriteView view) {
view.putLong("Energy", energyStorage.amount);
view.putInt("Progress", progress);
Inventories.writeData(view, this.inventory.getHeldStacks());
}
@Override
public Object getScreenOpeningData(ServerPlayerEntity player) {
return this.getPos();
}
// --- Inventory delegation ---
@Override
public int size() {
return inventory.size();
}
@Override
public boolean isEmpty() {
return inventory.isEmpty();
}
@Override
public ItemStack getStack(int slot) {
return inventory.getStack(slot);
}
@Override
public ItemStack removeStack(int slot, int amount) {
return inventory.removeStack(slot, amount);
}
@Override
public ItemStack removeStack(int slot) {
return inventory.removeStack(slot);
}
@Override
public void setStack(int slot, ItemStack stack) {
inventory.setStack(slot, stack);
}
@Override
public boolean canPlayerUse(PlayerEntity player) {
return true;
}
@Override
public void clear() {
inventory.clear();
}
}

View file

@ -0,0 +1,58 @@
package ovh.sad.growglobe;
import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroups;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.sound.BlockSoundGroup;
import net.minecraft.util.Identifier;
import java.util.function.Function;
public class GrowGlobeBlocks {
private static Block register(String name, Function<AbstractBlock.Settings, Block> blockFactory, AbstractBlock.Settings settings, boolean shouldRegisterItem) {
// Create a registry key for the block
RegistryKey<Block> blockKey = keyOfBlock(name);
// Create the block instance
Block block = blockFactory.apply(settings.registryKey(blockKey));
// Sometimes, you may not want to register an item for the block.
// Eg: if it's a technical block like `minecraft:moving_piston` or `minecraft:end_gateway`
if (shouldRegisterItem) {
// Items need to be registered with a different type of registry key, but the ID
// can be the same.
RegistryKey<Item> itemKey = keyOfItem(name);
BlockItem blockItem = new BlockItem(block, new Item.Settings().registryKey(itemKey).useBlockPrefixedTranslationKey());
Registry.register(Registries.ITEM, itemKey, blockItem);
}
return Registry.register(Registries.BLOCK, blockKey, block);
}
private static RegistryKey<Block> keyOfBlock(String name) {
return RegistryKey.of(RegistryKeys.BLOCK, Identifier.of(Growglobe.MOD_ID, name));
}
private static RegistryKey<Item> keyOfItem(String name) {
return RegistryKey.of(RegistryKeys.ITEM, Identifier.of(Growglobe.MOD_ID, name));
}
public static final Block GROW_GLOBE_BLOCK = register(
"growglobe",
GrowGlobeBlock::new,
AbstractBlock.Settings.create().sounds(BlockSoundGroup.GLASS).nonOpaque(),
true
);
public static void initialize() {
ItemGroupEvents.modifyEntriesEvent(ItemGroups.FUNCTIONAL).register((itemGroup) -> {
itemGroup.add(GrowGlobeBlocks.GROW_GLOBE_BLOCK.asItem());
});
}
}

View file

@ -0,0 +1,119 @@
package ovh.sad.growglobe;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.ItemStack;
import net.minecraft.screen.PropertyDelegate;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.slot.Slot;
public class GrowGlobeScreenHandler extends ScreenHandler {
private final Inventory inventory;
private final PropertyDelegate energyProperty;
public GrowGlobeScreenHandler(int syncId, PlayerInventory playerInv, Inventory inventory, PropertyDelegate propertyDelegate) {
super(GrowGlobeScreenHandlers.GROW_GLOBE, syncId);
this.inventory = inventory;
this.energyProperty = propertyDelegate;
addProperties(propertyDelegate);
// Custom 3x3 machine grid slots (indices 0-8)
int startX = 81;
int startY = 15;
int[][] machinePositions = {
{startX, startY}, {startX + 18, startY}, {startX + 36, startY}, // row 1
{startX, startY + 18}, {startX + 18, startY + 18}, {startX + 36, startY + 18}, // row 2
{startX, startY + 36}, {startX + 18, startY + 36}, {startX + 36, startY + 36} // row 3
};
for (int i = 0; i < 9; i++) {
this.addSlot(new Slot(inventory, i, machinePositions[i][0], machinePositions[i][1]));
}
// Extra slot to the right (index 9)
this.addSlot(new Slot(inventory, 9, 38, 33));
// Player inventory slots
int playerStartY = startY + 65; // space below machine slots
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 9; col++) {
this.addSlot(new Slot(playerInv, col + row * 9 + 9, 8 + col * 18, playerStartY + row * 18));
}
}
// Player hotbar
for (int col = 0; col < 9; col++) {
this.addSlot(new Slot(playerInv, col, 8 + col * 18, playerStartY + 58));
}
}
@Override
public ItemStack quickMove(PlayerEntity player, int index) {
ItemStack newStack = ItemStack.EMPTY;
Slot slot = this.slots.get(index);
if (slot != null && slot.hasStack()) {
ItemStack original = slot.getStack();
newStack = original.copy();
// Number of machine slots (must match block entity SimpleInventory size)
int machineSlotCount = 10; // slots 0..9 (0=input, 1..9 outputs)
// Slot indexes in this.slots:
int playerInvStart = machineSlotCount; // start of player inventory in the slots list
int playerMainEnd = playerInvStart + 27; // end (exclusive) of main inventory
int hotbarStart = playerMainEnd; // start of hotbar
int hotbarEnd = hotbarStart + 9; // end (exclusive) of hotbar
if (index < machineSlotCount) {
// from machine -> try to move into player (first main then hotbar)
if (!this.insertItem(original, playerInvStart, hotbarEnd, true)) {
return ItemStack.EMPTY;
}
} else if (index >= playerInvStart && index < playerMainEnd) {
// from player main inventory -> try machine input first, then hotbar
if (!this.insertItem(original, 0, 1, false)) {
if (!this.insertItem(original, playerMainEnd, hotbarEnd, false)) {
return ItemStack.EMPTY;
}
}
} else if (index >= hotbarStart && index < hotbarEnd) {
// from hotbar -> try machine input first, then main inventory
if (!this.insertItem(original, 0, 1, false)) {
if (!this.insertItem(original, playerInvStart, playerMainEnd, false)) {
return ItemStack.EMPTY;
}
}
} else {
return ItemStack.EMPTY;
}
if (original.isEmpty()) {
slot.setStack(ItemStack.EMPTY);
} else {
slot.markDirty();
}
}
return newStack;
}
@Override
public boolean canUse(PlayerEntity player) {
return inventory.canPlayerUse(player);
}
public int getEnergy() {
return energyProperty.get(0);
}
public int getMaxEnergy() {
return energyProperty.get(1);
}
public int getProgress() {
return energyProperty.get(2);
}
public int getMaxProgress() {
return energyProperty.get(3);
}
}

View file

@ -0,0 +1,36 @@
package ovh.sad.growglobe;
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerType;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.screen.ScreenHandlerType;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
// ModScreenHandlers.java
public class GrowGlobeScreenHandlers {
public static ScreenHandlerType<GrowGlobeScreenHandler> GROW_GLOBE;
public static void register() {
GROW_GLOBE = Registry.register(
Registries.SCREEN_HANDLER,
Identifier.of(Growglobe.MOD_ID, "growglobe"),
new ExtendedScreenHandlerType<>(
// Decoder: called on client
(syncId, playerInv, buf) -> {
BlockPos pos = buf;
var be = playerInv.player.getWorld().getBlockEntity(pos);
if (be instanceof GrowGlobeBlockEntity blockEntity) {
return new GrowGlobeScreenHandler(syncId, playerInv, blockEntity.getInventory(), blockEntity.getPropertyDelegate());
}
// Fallback if BE missing
return new GrowGlobeScreenHandler(syncId, playerInv, new net.minecraft.inventory.SimpleInventory(7), new net.minecraft.screen.ArrayPropertyDelegate(2));
},
// PacketCodec to sync BlockPos
BlockPos.PACKET_CODEC
)
);
}
}

View file

@ -0,0 +1,14 @@
package ovh.sad.growglobe;
import net.fabricmc.api.ModInitializer;
import net.minecraft.world.World;
public class Growglobe implements ModInitializer {
public static String MOD_ID = "growglobe";
@Override
public void onInitialize() {
GrowGlobeBlocks.initialize();
GrowGlobeBlockEntities.register();
}
}

View file

@ -0,0 +1,92 @@
package ovh.sad.growglobe;
import net.minecraft.block.*;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.registry.Registries;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.registry.tag.BlockTags;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.gen.feature.ConfiguredFeature;
import net.minecraft.world.gen.feature.TreeFeatureConfig;
import net.minecraft.world.gen.foliage.SpruceFoliagePlacer;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class HarvestUtil {
/**
* Dynamically harvests any block or item and returns the resulting drops.
* Works with vanilla and modded blocks.
*
* @param world the world
* @param item the Item (usually BlockItem) to harvest
* @param pos the position (optional for context; can be BlockPos.ORIGIN)
* @return List of ItemStacks that would drop
*/
public static List<ItemStack> harvestBlock(ServerWorld world, Item item, BlockPos pos) {
List<ItemStack> drops = new ArrayList<>();
if (!(item instanceof BlockItem blockItem)) return drops;
Block block = blockItem.getBlock();
BlockState state = block.getDefaultState();
BlockEntityProvider blockEntityProvider = block instanceof BlockEntityProvider bep ? bep : null;
// CropBlock: drop crop + seeds
if (block instanceof CropBlock crop) {
state = crop.withAge(crop.getMaxAge());
}
// Stackable plants like sugar cane or bamboo
else if (block instanceof SugarCaneBlock) {
int height = 3; // or dynamically detect in world
for (int i = 0; i < height; i++) drops.add(new ItemStack(block));
return drops;
}
else if (state.isIn(BlockTags.SAPLINGS)) {
SaplingBlock sapling = (SaplingBlock) block;
SaplingGenerator generator = sapling.generator;
RegistryKey<ConfiguredFeature<?, ?>> featureKey =
generator.getSmallTreeFeature(world.random, true);
if (featureKey == null)
featureKey = generator.getMegaTreeFeature(world.random);
if (featureKey != null) {
var featureRegistry = world.getRegistryManager().getOrThrow(RegistryKeys.CONFIGURED_FEATURE);
var configured = featureRegistry.getOrThrow(featureKey);
if (configured.value().config() instanceof TreeFeatureConfig treeConfig) {
BlockState logState = treeConfig.trunkProvider.get(world.random, pos);
BlockState leafState = treeConfig.foliageProvider.get(world.random, pos);
int logCount = 5 + world.random.nextInt(3);
for (int i = 0; i < logCount; i++)
drops.addAll(Block.getDroppedStacks(logState, world, pos, null));
int leafCount = 10 + world.random.nextInt(10);
for (int i = 0; i < leafCount; i++)
drops.addAll(Block.getDroppedStacks(leafState, world, pos, null));
drops.add(new ItemStack(sapling));
return drops;
}
}
drops.add(new ItemStack(sapling));
return drops;
}
drops.addAll(Block.getDroppedStacks(state, world, pos, blockEntityProvider != null ? blockEntityProvider.createBlockEntity(pos, state) : null));
return drops;
}
}