feat: add custom magical crafting screen handler

This commit is contained in:
alphafetus committed 2025-09-03 21:42:17 -04:00
1 parent ce56bbe9da
commit 6e224e1fdf
9 files changed
+217 -13

No files matched your search

+2 -1
View File
@@ -7,6 +7,7 @@ org.gradle.parallel=true
minecraft_version=1.21.5
yarn_mappings=1.21.5+build.1
loader_version=0.16.12
loom_version=1.11-SNAPSHOT
# Mod Properties
mod_version=1.0.0
@@ -14,4 +15,4 @@ maven_group=com.uncreativebunch.magicmod
archives_base_name=magic_mod
# Dependencies
fabric_version=0.119.9+1.21.5
fabric_version=0.128.2+1.21.5
@@ -1,10 +1,14 @@
package com.uncreativebunch.magicmod;
import com.uncreativebunch.magicmod.screen.MagicalCraftingScreen;
import com.uncreativebunch.magicmod.screen.ModScreenHandlers;
import net.fabricmc.api.ClientModInitializer;
import net.minecraft.client.gui.screen.ingame.HandledScreens;
public class MagicModClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
// This entrypoint is suitable for setting up client-specific logic, such as rendering.
HandledScreens.register(ModScreenHandlers.MAGICAL_CRAFTING_SCREEN_HANDLER, MagicalCraftingScreen::new);
}
}
@@ -0,0 +1,39 @@
package com.uncreativebunch.magicmod.screen;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
public class MagicalCraftingScreen extends HandledScreen<MagicalCraftingScreenHandler> {
private static final Identifier TEXTURE = Identifier.of("textures/gui/container/anvil.png");
public MagicalCraftingScreen(MagicalCraftingScreenHandler handler, PlayerInventory inventory, Text title) {
super(handler, inventory, title);
}
@Override
protected void drawBackground(DrawContext context, float delta, int mouseX, int mouseY) {
context.drawTexture(RenderLayer::getGuiTextured, TEXTURE,
this.x, (this.height - this.backgroundHeight) / 2,
0.0F, 0.0F,
this.backgroundWidth, this.backgroundHeight,
256, 256);
}
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
renderBackground(context, mouseX, mouseY, delta);
super.render(context, mouseX, mouseY, delta);
drawMouseoverTooltip(context, mouseX, mouseY);
}
@Override
protected void init() {
super.init();
// Centers the title
titleX = (backgroundWidth - textRenderer.getWidth(title)) / 2;
}
}
@@ -3,6 +3,7 @@ package com.uncreativebunch.magicmod;
import com.uncreativebunch.magicmod.block.ModBlocks;
import com.uncreativebunch.magicmod.item.ModItemGroups;
import com.uncreativebunch.magicmod.item.ModItems;
import com.uncreativebunch.magicmod.screen.ModScreenHandlers;
import net.fabricmc.api.ModInitializer;
import org.slf4j.Logger;
@@ -29,6 +30,7 @@ public class MagicMod implements ModInitializer {
ModBlocks.init();
ModItems.init();
ModItemGroups.init();
ModScreenHandlers.init();
LOGGER.info("Finished Magic Mod initialization!");
}
}
@@ -4,7 +4,6 @@ import com.uncreativebunch.magicmod.MagicMod;
import com.uncreativebunch.magicmod.block.crafting.MagicalCrafterBlock;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.SlabBlock;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.registry.Registries;
@@ -19,6 +18,7 @@ import java.util.function.Function;
public class ModBlocks {
//region Block Definitions
/** The magical crafting table block. */
public static final Block MAGICAL_CRAFTING_TABLE = register(
"magical_crafting_table",
MagicalCrafterBlock::new,
@@ -1,18 +1,24 @@
package com.uncreativebunch.magicmod.block.crafting;
import com.uncreativebunch.magicmod.MagicMod;
import com.uncreativebunch.magicmod.screen.MagicalCraftingScreenHandler;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.BlockState;
import net.minecraft.block.SlabBlock;
import net.minecraft.block.CraftingTableBlock;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LightningEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.screen.NamedScreenHandlerFactory;
import net.minecraft.screen.ScreenHandlerContext;
import net.minecraft.screen.SimpleNamedScreenHandlerFactory;
import net.minecraft.stat.Stats;
import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
public class MagicalCrafterBlock extends SlabBlock {
public class MagicalCrafterBlock extends CraftingTableBlock {
/**
* Creates a new MagicalCrafterBlock.
@@ -21,7 +27,8 @@ public class MagicalCrafterBlock extends SlabBlock {
public MagicalCrafterBlock(AbstractBlock.Settings settings) { super(settings); }
/**
* Occurs whenever this block is used. This is intended to open a crafting window in the future.
* Occurs whenever this block is used.
* It opens a crafting window and summons lightning on the interacting player.
* @param state The current {@link BlockState} of this block.
* @param world The {@link World} that this block exists in.
* @param pos The {@link BlockPos} that this block is at.
@@ -31,17 +38,37 @@ public class MagicalCrafterBlock extends SlabBlock {
*/
@Override
protected ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit) {
if (world.isClient) return ActionResult.PASS;
// The server is the game's sole source of truth. Even though some things are displayed
// on the client, these actions usually, with very few exceptions, are routed and triggered
// through the server first.
if (!world.isClient) {
MagicMod.LOGGER.info("Block was used at location {}", pos.toShortString());
MagicMod.LOGGER.info("Block was used at location {}", pos.toShortString());
MagicMod.LOGGER.info("Electrocuting player...");
BlockPos playerPos = player.getBlockPos();
LightningEntity lightningEntity = new LightningEntity(EntityType.LIGHTNING_BOLT, world);
lightningEntity.setPosition(playerPos.toCenterPos());
world.spawnEntity(lightningEntity);
MagicMod.LOGGER.info("Player electrocuted.");
MagicMod.LOGGER.info("Electrocuting player...");
BlockPos playerPos = player.getBlockPos();
LightningEntity lightningEntity = new LightningEntity(EntityType.LIGHTNING_BOLT, world);
lightningEntity.setPosition(playerPos.toCenterPos());
world.spawnEntity(lightningEntity);
MagicMod.LOGGER.info("Player electrocuted.");
NamedScreenHandlerFactory screenFactory = state.createScreenHandlerFactory(world, pos);
if (screenFactory != null) {
player.openHandledScreen(screenFactory);
player.incrementStat(Stats.INTERACT_WITH_CRAFTING_TABLE);
} else {
MagicMod.LOGGER.info("NamedScreenHandlerFactory returned from the BlockState is null.");
}
}
return ActionResult.SUCCESS;
}
@Override
public NamedScreenHandlerFactory createScreenHandlerFactory(BlockState state, World world, BlockPos pos) {
return new SimpleNamedScreenHandlerFactory(
((syncId, playerInventory, player) ->
new MagicalCraftingScreenHandler(syncId, playerInventory, ScreenHandlerContext.create(world, pos))),
Text.translatable("container.magical_crafting_table")
);
}
}
@@ -0,0 +1,109 @@
package com.uncreativebunch.magicmod.screen;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.CraftingInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.ScreenHandlerContext;
import net.minecraft.screen.slot.Slot;
public class MagicalCraftingScreenHandler extends ScreenHandler {
private final ScreenHandlerContext context;
private final CraftingInventory inventory;
/**
* Client-side constructor.
* @param syncId The sync ID that's sent from the server.
* @param playerInventory The player's inventory.
*/
public MagicalCraftingScreenHandler(int syncId, PlayerInventory playerInventory) {
this(syncId, playerInventory, ScreenHandlerContext.EMPTY);
}
/**
* Server-side constructor. The server knows the player's inventory and so can provide it to the client.
* It handles creating the inventory slots and syncs all information from the server to the client.
* @param syncId The sync ID that's sent from the server.
* @param playerInventory The player's inventory.
* @param screenHandlerContext The context of the screen handler. Not sure if this is needed anymore.
*/
public MagicalCraftingScreenHandler(int syncId, PlayerInventory playerInventory, ScreenHandlerContext screenHandlerContext) {
super(ModScreenHandlers.MAGICAL_CRAFTING_SCREEN_HANDLER, syncId);
this.context = screenHandlerContext;
this.inventory = new CraftingInventory(this, 3, 3);
// Ensure that the crafting inventory is the correct size.
checkSize(inventory, 9);
// Ensure that the inventory's custom logic runs (if any)
inventory.onOpen(playerInventory.player);
// Place all inventory slots within the screen. This piece doesn't actually render the background of the slots,
// as this is handled by the client-side application (the Screen), but it does tell the screen where to render.
int m, l;
for (m = 0; m < 3; m++) {
for (l = 0; l < 3; l++) {
// Create the crafting slots. This is good for a 3x3 grid of crafting slots. It registers them to a
// specific inventory and location on the client's screen, relative to the screen's size and origin.
this.addSlot(new Slot(inventory, l + m * 3, 62 + l * 18, 17 + m * 18));
}
}
// Add all player inventory slots. Start with the main inventory, then the hotbar.
for (m = 0; m < 3; m++) {
for (l = 0; l < 9; l++) {
this.addSlot(new Slot(playerInventory, l + m * 9 + 9, 8 + l * 18, 84 + m * 18));
}
}
for (m = 0; m < 9; m++) {
this.addSlot(new Slot(playerInventory, m, 8 + m * 18, 142));
}
}
/**
* Checks if the specified slot index is within the crafting inventory.
* @param slotIndex The slot index to check.
* @return True if the slot index is within the crafting inventory, false otherwise.
*/
protected boolean isCraftingInventorySlot(int slotIndex) {
return slotIndex < this.inventory.size();
}
@Override
public ItemStack quickMove(PlayerEntity player, int slotIndex) {
// If the slot is in the crafting inventory, move it to the player's inventory.
// Otherwise, don't move it at all.
ItemStack newStack = ItemStack.EMPTY;
Slot slot = this.slots.get(slotIndex);
if (slot.hasStack()) {
ItemStack stack = slot.getStack();
newStack = stack.copy();
if (isCraftingInventorySlot(slotIndex)) {
// Attempt to move the stack into the player's inventory.
if (!this.insertItem(stack, this.inventory.size(), this.slots.size(), true)) {
return ItemStack.EMPTY;
}
} else {
// Don't move the stack.
return ItemStack.EMPTY;
}
// Check if the stack was moved. If moved entirely, empty the slot.
if (stack.isEmpty()) {
slot.setStack(ItemStack.EMPTY);
} else {
slot.markDirty();
}
}
return newStack;
}
@Override
public boolean canUse(PlayerEntity player) {
return inventory.canPlayerUse(player);
}
}
@@ -0,0 +1,21 @@
package com.uncreativebunch.magicmod.screen;
import com.uncreativebunch.magicmod.MagicMod;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.resource.featuretoggle.FeatureFlags;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.ScreenHandlerType;
public class ModScreenHandlers {
public static final ScreenHandlerType<MagicalCraftingScreenHandler> MAGICAL_CRAFTING_SCREEN_HANDLER =
register("magical_crafting", MagicalCraftingScreenHandler::new);
private static <T extends ScreenHandler> ScreenHandlerType<T> register(String id, ScreenHandlerType.Factory<T> factory) {
return Registry.register(Registries.SCREEN_HANDLER, id, new ScreenHandlerType<>(factory, FeatureFlags.VANILLA_FEATURES));
}
public static void init() {
MagicMod.LOGGER.info("Screen handlers initialized.");
}
}
@@ -13,6 +13,7 @@
"item.magic_mod.pale_oak_stick": "Pale Oak Stick",
"item.magic_mod.spruce_stick": "Spruce Stick",
"item.magic_mod.warped_stick": "Warped Stick",
"container.magical_crafting_table": "Magical Crafting",
"magic_mod.magic_group": "Testing Mod Items",
"tag.item.magic_mod.sticks": "Sticks"
}