Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/main/java/fr/jachou/cryptoworld/CryptoWorld.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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) {
Expand Down
45 changes: 45 additions & 0 deletions src/main/java/fr/jachou/cryptoworld/block/ExchangeBlock.java
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
}
}
3 changes: 3 additions & 0 deletions src/main/java/fr/jachou/cryptoworld/block/ModBlocks.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ public class ModBlocks {
public static final RegistryObject<Block> CRYPTONIUM_BLOCK = registerBlock("cryptonium_block",
() -> new Block((BlockBehaviour.Properties.ofFullCopy(Blocks.DIAMOND_BLOCK))));

public static final RegistryObject<Block> EXCHANGE_BLOCK = registerBlock("exchange_block",
() -> new ExchangeBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.IRON_BLOCK)));



private static <T extends Block>RegistryObject<T> registerBlock(String name, Supplier<T> block) {
Expand Down
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
Expand Up @@ -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<Block> blockRegistryObject) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand Down
67 changes: 67 additions & 0 deletions src/main/java/fr/jachou/cryptoworld/menu/ExchangeMenu.java
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;
Copy link

Copilot AI Aug 25, 2025

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.

Suggested change
return ItemStack.EMPTY;
ItemStack itemstack = ItemStack.EMPTY;
Slot slot = this.slots.get(index);
if (slot != null && slot.hasItem()) {
ItemStack itemstack1 = slot.getItem();
itemstack = itemstack1.copy();
// Container slots: 0 (offer), 1 (demand/output)
int containerSlots = 2;
int playerInventoryStart = containerSlots;
int playerInventoryEnd = playerInventoryStart + 27; // 3 rows * 9 columns
int hotbarStart = playerInventoryEnd;
int hotbarEnd = hotbarStart + 9;
if (index < containerSlots) {
// Move from container to player inventory
if (!this.moveItemStackTo(itemstack1, playerInventoryStart, hotbarEnd, true)) {
return ItemStack.EMPTY;
}
slot.onQuickCraft(itemstack1, itemstack);
} else {
// Move from player inventory/hotbar to container offer slot (slot 0)
if (!this.slots.get(0).mayPlace(itemstack1)) {
return ItemStack.EMPTY;
}
if (!this.moveItemStackTo(itemstack1, 0, 1, false)) {
return ItemStack.EMPTY;
}
}
if (itemstack1.isEmpty()) {
slot.set(ItemStack.EMPTY);
} else {
slot.setChanged();
}
if (itemstack1.getCount() == itemstack.getCount()) {
return ItemStack.EMPTY;
}
slot.onTake(player, itemstack1);
}
return itemstack;

Copilot uses AI. Check for mistakes.
}
}
21 changes: 21 additions & 0 deletions src/main/java/fr/jachou/cryptoworld/menu/ModMenus.java
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);
}
}
31 changes: 31 additions & 0 deletions src/main/java/fr/jachou/cryptoworld/util/PriceManager.java
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));
Copy link

Copilot AI Aug 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exchange rates are inconsistent. If 1 BTC equals 2 ETH, then 2 ETH should equal 1 BTC, meaning the Ethereum rate should be 1, not 2. This creates an arbitrage opportunity where players can infinitely multiply their currency.

Suggested change
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 uses AI. Check for mistakes.
Copy link

Copilot AI Aug 25, 2025

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
}

public static int getBitcoinToEthereumRate() {
return PRICES.getOrDefault(ModItems.BITCOIN.get(), 1);
}

public static int getEthereumToBitcoinRate() {
return PRICES.getOrDefault(ModItems.ETHEREUM.get(), 2);
}
}
Loading