-
Notifications
You must be signed in to change notification settings - Fork 0
Add exchange block and trading system with price manager #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> type) { | ||
| return null; // No ticking required for this block entity | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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")); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<BlockEntityType<?>> BLOCK_ENTITIES = | ||
| DeferredRegister.create(ForgeRegistries.BLOCK_ENTITY_TYPES, CryptoWorld.MODID); | ||
|
|
||
| public static final RegistryObject<BlockEntityType<ExchangeBlockEntity>> 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); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<MenuType<?>> MENUS = | ||
| DeferredRegister.create(ForgeRegistries.MENU_TYPES, CryptoWorld.MODID); | ||
|
|
||
| public static final RegistryObject<MenuType<ExchangeMenu>> EXCHANGE_MENU = | ||
| MENUS.register("exchange_menu", () -> IForgeMenuType.create(ExchangeMenu::new)); | ||
|
|
||
| public static void register(IEventBus eventBus) { | ||
| MENUS.register(eventBus); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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<Item, Integer> 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)); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
| PRICES.put(ModItems.BITCOIN.get(), 1 + RANDOM.nextInt(4)); | |
| PRICES.put(ModItems.ETHEREUM.get(), 1); // 2 ETH -> 1 BTC | |
| } | |
| public static void updateRandom() { | |
| // Randomly vary BTC to ETH rate between 1 and 4 | |
| int btcRate = 1 + RANDOM.nextInt(4); | |
| PRICES.put(ModItems.BITCOIN.get(), btcRate); | |
| PRICES.put(ModItems.ETHEREUM.get(), 1); // reciprocal: 2 ETH -> 1 BTC | |
| PRICES.put(ModItems.BITCOIN.get(), btcRate); | |
| PRICES.put(ModItems.ETHEREUM.get(), 1); // reciprocal: 2 ETH -> 1 BTC |
Copilot
AI
Aug 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The updateRandom method only updates the Bitcoin rate but doesn't update the corresponding Ethereum rate. This creates inconsistent exchange rates where the inverse relationship is not maintained.
| PRICES.put(ModItems.BITCOIN.get(), 1 + RANDOM.nextInt(4)); | |
| int btcToEth = 1 + RANDOM.nextInt(4); | |
| PRICES.put(ModItems.BITCOIN.get(), btcToEth); | |
| PRICES.put(ModItems.ETHEREUM.get(), btcToEth); // Maintain inverse relationship |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The quickMoveStack method returns EMPTY without implementing shift-click functionality. This prevents players from using shift-click to move items, which is a standard Minecraft GUI expectation.