From 277db8b1e76d2097103ebd824df82b21966dac6d Mon Sep 17 00:00:00 2001 From: Matisse <84678307+MatisseAD@users.noreply.github.com> Date: Mon, 25 Aug 2025 22:20:39 +0200 Subject: [PATCH] Add exchange block, menu, and price manager --- .../fr/jachou/cryptoworld/CryptoWorld.java | 7 ++ .../cryptoworld/block/ExchangeBlock.java | 45 +++++++++ .../jachou/cryptoworld/block/ModBlocks.java | 3 + .../blockentity/ExchangeBlockEntity.java | 91 +++++++++++++++++++ .../blockentity/ModBlockEntities.java | 22 +++++ .../datagen/ModBlockStateProvider.java | 1 + .../datagen/loot/ModBlockLootTables.java | 1 + .../cryptoworld/item/ModCreativeModTabs.java | 1 + .../jachou/cryptoworld/menu/ExchangeMenu.java | 67 ++++++++++++++ .../fr/jachou/cryptoworld/menu/ModMenus.java | 21 +++++ .../jachou/cryptoworld/util/PriceManager.java | 31 +++++++ 11 files changed, 290 insertions(+) create mode 100644 src/main/java/fr/jachou/cryptoworld/block/ExchangeBlock.java create mode 100644 src/main/java/fr/jachou/cryptoworld/blockentity/ExchangeBlockEntity.java create mode 100644 src/main/java/fr/jachou/cryptoworld/blockentity/ModBlockEntities.java create mode 100644 src/main/java/fr/jachou/cryptoworld/menu/ExchangeMenu.java create mode 100644 src/main/java/fr/jachou/cryptoworld/menu/ModMenus.java create mode 100644 src/main/java/fr/jachou/cryptoworld/util/PriceManager.java diff --git a/src/main/java/fr/jachou/cryptoworld/CryptoWorld.java b/src/main/java/fr/jachou/cryptoworld/CryptoWorld.java index eb900d5..6df51b3 100644 --- a/src/main/java/fr/jachou/cryptoworld/CryptoWorld.java +++ b/src/main/java/fr/jachou/cryptoworld/CryptoWorld.java @@ -1,9 +1,12 @@ package fr.jachou.cryptoworld; import fr.jachou.cryptoworld.block.ModBlocks; +import fr.jachou.cryptoworld.blockentity.ModBlockEntities; import fr.jachou.cryptoworld.datagen.DataGenerators; import fr.jachou.cryptoworld.item.ModCreativeModTabs; import fr.jachou.cryptoworld.item.ModItems; +import fr.jachou.cryptoworld.menu.ModMenus; +import fr.jachou.cryptoworld.util.PriceManager; import net.minecraft.world.item.CreativeModeTabs; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.data.event.GatherDataEvent; @@ -24,11 +27,15 @@ public CryptoWorld() { ModCreativeModTabs.register(bus); ModItems.register(bus); ModBlocks.register(bus); + ModBlockEntities.register(bus); + ModMenus.register(bus); bus.addListener(this::setup); MinecraftForge.EVENT_BUS.register(this); bus.addListener(this::doClientStuff); bus.addListener(this::addCreative); + + PriceManager.init(); } private void addCreative(BuildCreativeModeTabContentsEvent event) { diff --git a/src/main/java/fr/jachou/cryptoworld/block/ExchangeBlock.java b/src/main/java/fr/jachou/cryptoworld/block/ExchangeBlock.java new file mode 100644 index 0000000..8a5001c --- /dev/null +++ b/src/main/java/fr/jachou/cryptoworld/block/ExchangeBlock.java @@ -0,0 +1,45 @@ +package fr.jachou.cryptoworld.block; + +import fr.jachou.cryptoworld.blockentity.ExchangeBlockEntity; +import net.minecraft.core.BlockPos; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.state.BlockState; +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.phys.BlockHitResult; +import org.jetbrains.annotations.Nullable; + +public class ExchangeBlock extends Block implements EntityBlock { + public ExchangeBlock(Properties properties) { + super(properties); + } + + @Override + public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { + if (!level.isClientSide) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof ExchangeBlockEntity exchange) { + player.openMenu(exchange); + } + } + return InteractionResult.SUCCESS; + } + + @Nullable + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + return new ExchangeBlockEntity(pos, state); + } + + @Nullable + @Override + public BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType type) { + return null; // No ticking required for this block entity + } +} diff --git a/src/main/java/fr/jachou/cryptoworld/block/ModBlocks.java b/src/main/java/fr/jachou/cryptoworld/block/ModBlocks.java index beea685..10058c1 100644 --- a/src/main/java/fr/jachou/cryptoworld/block/ModBlocks.java +++ b/src/main/java/fr/jachou/cryptoworld/block/ModBlocks.java @@ -36,6 +36,9 @@ public class ModBlocks { public static final RegistryObject CRYPTONIUM_BLOCK = registerBlock("cryptonium_block", () -> new Block((BlockBehaviour.Properties.ofFullCopy(Blocks.DIAMOND_BLOCK)))); + public static final RegistryObject EXCHANGE_BLOCK = registerBlock("exchange_block", + () -> new ExchangeBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.IRON_BLOCK))); + private static RegistryObject registerBlock(String name, Supplier block) { diff --git a/src/main/java/fr/jachou/cryptoworld/blockentity/ExchangeBlockEntity.java b/src/main/java/fr/jachou/cryptoworld/blockentity/ExchangeBlockEntity.java new file mode 100644 index 0000000..43065f4 --- /dev/null +++ b/src/main/java/fr/jachou/cryptoworld/blockentity/ExchangeBlockEntity.java @@ -0,0 +1,91 @@ +package fr.jachou.cryptoworld.blockentity; + +import fr.jachou.cryptoworld.item.ModItems; +import fr.jachou.cryptoworld.menu.ExchangeMenu; +import fr.jachou.cryptoworld.util.PriceManager; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.items.ItemStackHandler; +import org.jetbrains.annotations.NotNull; + +public class ExchangeBlockEntity extends BlockEntity implements MenuProvider { + private final ItemStackHandler itemHandler = new ItemStackHandler(2) { + @Override + protected void onContentsChanged(int slot) { + super.onContentsChanged(slot); + setChanged(); + if (slot == 0) { + updateDemand(); + } + } + + @Override + public boolean isItemValid(int slot, @NotNull ItemStack stack) { + return slot == 0; + } + }; + + public ExchangeBlockEntity(BlockPos pos, BlockState state) { + super(ModBlockEntities.EXCHANGE_BLOCK_ENTITY.get(), pos, state); + } + + public void updateDemand() { + ItemStack offer = itemHandler.getStackInSlot(0); + if (offer.isEmpty()) { + itemHandler.setStackInSlot(1, ItemStack.EMPTY); + return; + } + if (offer.getItem() == ModItems.BITCOIN.get()) { + int rate = PriceManager.getBitcoinToEthereumRate(); + itemHandler.setStackInSlot(1, new ItemStack(ModItems.ETHEREUM.get(), offer.getCount() * rate)); + } else if (offer.getItem() == ModItems.ETHEREUM.get()) { + int rate = PriceManager.getEthereumToBitcoinRate(); + int amount = offer.getCount() / rate; + if (amount > 0) { + itemHandler.setStackInSlot(1, new ItemStack(ModItems.BITCOIN.get(), amount)); + } else { + itemHandler.setStackInSlot(1, ItemStack.EMPTY); + } + } else { + itemHandler.setStackInSlot(1, ItemStack.EMPTY); + } + } + + public ItemStackHandler getItemHandler() { + return itemHandler; + } + + public void removeOffer() { + itemHandler.setStackInSlot(0, ItemStack.EMPTY); + } + + @Override + public Component getDisplayName() { + return Component.literal("Exchange"); + } + + @Override + public AbstractContainerMenu createMenu(int id, Inventory inventory, Player player) { + return new ExchangeMenu(id, inventory, this); + } + + @Override + protected void saveAdditional(CompoundTag tag) { + tag.put("inventory", itemHandler.serializeNBT()); + super.saveAdditional(tag); + } + + @Override + public void load(CompoundTag tag) { + super.load(tag); + itemHandler.deserializeNBT(tag.getCompound("inventory")); + } +} diff --git a/src/main/java/fr/jachou/cryptoworld/blockentity/ModBlockEntities.java b/src/main/java/fr/jachou/cryptoworld/blockentity/ModBlockEntities.java new file mode 100644 index 0000000..0c482d8 --- /dev/null +++ b/src/main/java/fr/jachou/cryptoworld/blockentity/ModBlockEntities.java @@ -0,0 +1,22 @@ +package fr.jachou.cryptoworld.blockentity; + +import fr.jachou.cryptoworld.CryptoWorld; +import fr.jachou.cryptoworld.block.ModBlocks; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; + +public class ModBlockEntities { + public static final DeferredRegister> BLOCK_ENTITIES = + DeferredRegister.create(ForgeRegistries.BLOCK_ENTITY_TYPES, CryptoWorld.MODID); + + public static final RegistryObject> EXCHANGE_BLOCK_ENTITY = + BLOCK_ENTITIES.register("exchange_block_entity", + () -> BlockEntityType.Builder.of(ExchangeBlockEntity::new, ModBlocks.EXCHANGE_BLOCK.get()).build(null)); + + public static void register(IEventBus eventBus) { + BLOCK_ENTITIES.register(eventBus); + } +} diff --git a/src/main/java/fr/jachou/cryptoworld/datagen/ModBlockStateProvider.java b/src/main/java/fr/jachou/cryptoworld/datagen/ModBlockStateProvider.java index 5beb974..26b561e 100644 --- a/src/main/java/fr/jachou/cryptoworld/datagen/ModBlockStateProvider.java +++ b/src/main/java/fr/jachou/cryptoworld/datagen/ModBlockStateProvider.java @@ -24,6 +24,7 @@ protected void registerStatesAndModels() { blockWithItem(ModBlocks.SILICIUM_ORE); blockWithItem(ModBlocks.SILICIUM_BLOCK); blockWithItem(ModBlocks.SERVER_BLOCK); + blockWithItem(ModBlocks.EXCHANGE_BLOCK); } private void blockWithItem(RegistryObject blockRegistryObject) { diff --git a/src/main/java/fr/jachou/cryptoworld/datagen/loot/ModBlockLootTables.java b/src/main/java/fr/jachou/cryptoworld/datagen/loot/ModBlockLootTables.java index 2dead1c..c767284 100644 --- a/src/main/java/fr/jachou/cryptoworld/datagen/loot/ModBlockLootTables.java +++ b/src/main/java/fr/jachou/cryptoworld/datagen/loot/ModBlockLootTables.java @@ -23,6 +23,7 @@ protected void generate() { this.dropSelf(ModBlocks.CRYPTONIUM_ORE.get()); this.dropSelf(ModBlocks.SERVER_BLOCK.get()); this.dropSelf(ModBlocks.SILICIUM_ORE.get()); + this.dropSelf(ModBlocks.EXCHANGE_BLOCK.get()); } @Override diff --git a/src/main/java/fr/jachou/cryptoworld/item/ModCreativeModTabs.java b/src/main/java/fr/jachou/cryptoworld/item/ModCreativeModTabs.java index aeb5993..ea7b394 100644 --- a/src/main/java/fr/jachou/cryptoworld/item/ModCreativeModTabs.java +++ b/src/main/java/fr/jachou/cryptoworld/item/ModCreativeModTabs.java @@ -36,6 +36,7 @@ public class ModCreativeModTabs { pOutput.accept(ModBlocks.SILICIUM_ORE.get()); pOutput.accept(ModBlocks.CRYPTONIUM_BLOCK.get()); pOutput.accept(ModBlocks.SILICIUM_BLOCK.get()); + pOutput.accept(ModBlocks.EXCHANGE_BLOCK.get()); }) .build()); diff --git a/src/main/java/fr/jachou/cryptoworld/menu/ExchangeMenu.java b/src/main/java/fr/jachou/cryptoworld/menu/ExchangeMenu.java new file mode 100644 index 0000000..02f75f6 --- /dev/null +++ b/src/main/java/fr/jachou/cryptoworld/menu/ExchangeMenu.java @@ -0,0 +1,67 @@ +package fr.jachou.cryptoworld.menu; + +import fr.jachou.cryptoworld.block.ModBlocks; +import fr.jachou.cryptoworld.blockentity.ExchangeBlockEntity; +import net.minecraft.network.FriendlyByteBuf; +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.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.items.SlotItemHandler; + +public class ExchangeMenu extends AbstractContainerMenu { + private final ExchangeBlockEntity blockEntity; + private final ContainerLevelAccess access; + + public ExchangeMenu(int id, Inventory playerInv, FriendlyByteBuf buf) { + this(id, playerInv, (ExchangeBlockEntity) playerInv.player.level().getBlockEntity(buf.readBlockPos())); + } + + public ExchangeMenu(int id, Inventory playerInv, ExchangeBlockEntity blockEntity) { + super(ModMenus.EXCHANGE_MENU.get(), id); + this.blockEntity = blockEntity; + this.access = ContainerLevelAccess.create(blockEntity.getLevel(), blockEntity.getBlockPos()); + + var handler = blockEntity.getItemHandler(); + // Offer slot + this.addSlot(new SlotItemHandler(handler, 0, 44, 35)); + // Demand slot (output) + this.addSlot(new SlotItemHandler(handler, 1, 116, 35) { + @Override + public boolean mayPlace(ItemStack stack) { + return false; + } + + @Override + public void onTake(Player player, ItemStack stack) { + super.onTake(player, stack); + blockEntity.removeOffer(); + } + }); + + addPlayerInventory(playerInv); + } + + private void addPlayerInventory(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, 84 + row * 18)); + } + } + for (int col = 0; col < 9; ++col) { + this.addSlot(new Slot(inventory, col, 8 + col * 18, 142)); + } + } + + @Override + public boolean stillValid(Player player) { + return stillValid(access, player, ModBlocks.EXCHANGE_BLOCK.get()); + } + + @Override + public ItemStack quickMoveStack(Player player, int index) { + return ItemStack.EMPTY; + } +} diff --git a/src/main/java/fr/jachou/cryptoworld/menu/ModMenus.java b/src/main/java/fr/jachou/cryptoworld/menu/ModMenus.java new file mode 100644 index 0000000..c96593c --- /dev/null +++ b/src/main/java/fr/jachou/cryptoworld/menu/ModMenus.java @@ -0,0 +1,21 @@ +package fr.jachou.cryptoworld.menu; + +import fr.jachou.cryptoworld.CryptoWorld; +import net.minecraft.world.inventory.MenuType; +import net.minecraftforge.common.extensions.IForgeMenuType; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; + +public class ModMenus { + public static final DeferredRegister> MENUS = + DeferredRegister.create(ForgeRegistries.MENU_TYPES, CryptoWorld.MODID); + + public static final RegistryObject> EXCHANGE_MENU = + MENUS.register("exchange_menu", () -> IForgeMenuType.create(ExchangeMenu::new)); + + public static void register(IEventBus eventBus) { + MENUS.register(eventBus); + } +} diff --git a/src/main/java/fr/jachou/cryptoworld/util/PriceManager.java b/src/main/java/fr/jachou/cryptoworld/util/PriceManager.java new file mode 100644 index 0000000..997d3ee --- /dev/null +++ b/src/main/java/fr/jachou/cryptoworld/util/PriceManager.java @@ -0,0 +1,31 @@ +package fr.jachou.cryptoworld.util; + +import fr.jachou.cryptoworld.item.ModItems; +import net.minecraft.world.item.Item; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +public class PriceManager { + private static final Map PRICES = new HashMap<>(); + private static final Random RANDOM = new Random(); + + public static void init() { + PRICES.put(ModItems.BITCOIN.get(), 2); // 1 BTC -> 2 ETH + PRICES.put(ModItems.ETHEREUM.get(), 2); // 2 ETH -> 1 BTC + } + + public static void updateRandom() { + // Randomly vary BTC to ETH rate between 1 and 4 + PRICES.put(ModItems.BITCOIN.get(), 1 + RANDOM.nextInt(4)); + } + + public static int getBitcoinToEthereumRate() { + return PRICES.getOrDefault(ModItems.BITCOIN.get(), 1); + } + + public static int getEthereumToBitcoinRate() { + return PRICES.getOrDefault(ModItems.ETHEREUM.get(), 2); + } +}