From 4bd026f40aacb34522106268d22eaaa4b875f33f Mon Sep 17 00:00:00 2001 From: TGGamesYT Date: Sun, 22 Mar 2026 16:00:25 +0100 Subject: [PATCH] tictactoe and cosmetics system fix --- gradle.properties | 2 +- .../szar/client/ClientCosmetics.java | 8 +- .../dev/tggamesyt/szar/client/SzarClient.java | 57 +++- .../szar/client/TicTacToeScreen.java | 139 +++++++++ .../dev/tggamesyt/szar/ServerCosmetics.java | 135 +++++---- src/main/java/dev/tggamesyt/szar/Szar.java | 66 ++++- .../dev/tggamesyt/szar/TicTacToeBlock.java | 82 ++++++ .../tggamesyt/szar/TicTacToeBlockEntity.java | 270 ++++++++++++++++++ .../assets/szar/blockstates/tictactoe.json | 5 + .../resources/assets/szar/lang/en_us.json | 4 +- .../assets/szar/models/block/tictactoe.json | 24 ++ .../assets/szar/models/item/tictactoe.json | 3 + .../assets/szar/textures/block/plank.png | Bin 0 -> 405 bytes .../assets/szar/textures/block/tictactoe.png | Bin 0 -> 579 bytes .../resources/assets/szar/textures/gui/o.png | Bin 0 -> 754 bytes .../assets/szar/textures/gui/tictactoe.png | Bin 0 -> 1721 bytes .../resources/assets/szar/textures/gui/x.png | Bin 0 -> 794 bytes .../minecraft/tags/blocks/mineable/axe.json | 3 +- .../szar/loot_tables/blocks/tictactoe.json | 14 + .../data/szar/recipes/tictactoe.json | 23 ++ 20 files changed, 738 insertions(+), 97 deletions(-) create mode 100644 src/client/java/dev/tggamesyt/szar/client/TicTacToeScreen.java create mode 100644 src/main/java/dev/tggamesyt/szar/TicTacToeBlock.java create mode 100644 src/main/java/dev/tggamesyt/szar/TicTacToeBlockEntity.java create mode 100644 src/main/resources/assets/szar/blockstates/tictactoe.json create mode 100644 src/main/resources/assets/szar/models/block/tictactoe.json create mode 100644 src/main/resources/assets/szar/models/item/tictactoe.json create mode 100644 src/main/resources/assets/szar/textures/block/plank.png create mode 100644 src/main/resources/assets/szar/textures/block/tictactoe.png create mode 100644 src/main/resources/assets/szar/textures/gui/o.png create mode 100644 src/main/resources/assets/szar/textures/gui/tictactoe.png create mode 100644 src/main/resources/assets/szar/textures/gui/x.png create mode 100644 src/main/resources/data/szar/loot_tables/blocks/tictactoe.json create mode 100644 src/main/resources/data/szar/recipes/tictactoe.json diff --git a/gradle.properties b/gradle.properties index e830541..6609cd9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.20.1 yarn_mappings=1.20.1+build.10 loader_version=0.18.3 # Mod Properties -mod_version=26.3.21 +mod_version=26.3.22 maven_group=dev.tggamesyt archives_base_name=szar # Dependencies diff --git a/src/client/java/dev/tggamesyt/szar/client/ClientCosmetics.java b/src/client/java/dev/tggamesyt/szar/client/ClientCosmetics.java index 9d791e8..8f87d7c 100644 --- a/src/client/java/dev/tggamesyt/szar/client/ClientCosmetics.java +++ b/src/client/java/dev/tggamesyt/szar/client/ClientCosmetics.java @@ -66,11 +66,15 @@ public class ClientCosmetics { CosmeticProfile profile = PROFILES.get(uuid); if (profile == null) return null; - if (profile.nameType == NameType.STATIC && profile.staticColor != null) { + if (profile.nameType == NameType.STATIC) { + if (profile.staticColor == null) return null; return Text.literal(name) .styled(s -> s.withColor(profile.staticColor).withBold(true)); } + // GRADIENT + if (profile.gradientStart == null || profile.gradientEnd == null) return null; + long time = Util.getMeasuringTimeMs(); MutableText animated = Text.empty(); @@ -106,6 +110,7 @@ public class ClientCosmetics { public static void fetchMojangCapes(UUID uuid) { try { + System.out.println("SZAR: fetching Mojang capes for " + uuid); MinecraftClient client = MinecraftClient.getInstance(); String accessToken = client.getSession().getAccessToken(); if (accessToken == null) return; @@ -150,6 +155,7 @@ public class ClientCosmetics { buf.writeString(cape.name); buf.writeString(cape.url); } + System.out.println("SZAR: found " + list.size() + " Mojang capes, sending to server"); ClientPlayNetworking.send(MOJANG_CAPES_SYNC, buf); } diff --git a/src/client/java/dev/tggamesyt/szar/client/SzarClient.java b/src/client/java/dev/tggamesyt/szar/client/SzarClient.java index 809232d..efb7902 100644 --- a/src/client/java/dev/tggamesyt/szar/client/SzarClient.java +++ b/src/client/java/dev/tggamesyt/szar/client/SzarClient.java @@ -40,10 +40,7 @@ import net.minecraft.sound.SoundEvent; import net.minecraft.text.Text; import net.minecraft.util.Identifier; import net.minecraft.util.Util; -import net.minecraft.util.math.Box; -import net.minecraft.util.math.Direction; -import net.minecraft.util.math.MathHelper; -import net.minecraft.util.math.Vec3d; +import net.minecraft.util.math.*; import net.minecraft.util.math.random.Random; import org.lwjgl.glfw.GLFW; @@ -93,6 +90,28 @@ public class SzarClient implements ClientModInitializer { ); @Override public void onInitializeClient() { + // Open screen + ClientPlayNetworking.registerGlobalReceiver(Szar.TTT_OPEN_SCREEN, (client, handler, buf, sender) -> { + BlockPos pos = buf.readBlockPos(); + TicTacToeBlockEntity.State state = TicTacToeBlockEntity.readStateFromBuf(buf); + client.execute(() -> client.setScreen(new TicTacToeScreen(pos, state))); + }); + ClientPlayNetworking.registerGlobalReceiver(Szar.TTT_CLOSE_SCREEN, (client, handler, buf, sender) -> { + client.execute(() -> { + if (client.currentScreen instanceof TicTacToeScreen) { + client.setScreen(null); + } + }); + }); + ClientPlayNetworking.registerGlobalReceiver(Szar.TTT_STATE_SYNC, (client, handler, buf, sender) -> { + BlockPos pos = buf.readBlockPos(); + TicTacToeBlockEntity.State state = TicTacToeBlockEntity.readStateFromBuf(buf); + client.execute(() -> { + if (client.currentScreen instanceof TicTacToeScreen screen) { + screen.updateState(state); + } + }); + }); ClientPlayNetworking.registerGlobalReceiver(Szar.DRUNK_TYPE_PACKET, (client, handler, buf, responseSender) -> { String typeName = buf.readString(); client.execute(() -> DrunkEffect.setDisplayType(typeName)); @@ -242,24 +261,38 @@ public class SzarClient implements ClientModInitializer { ClientPlayNetworking.send(PlayerMovementManager.PACKET_ID, buf); }); ClientPlayNetworking.registerGlobalReceiver(SYNC_PACKET, (client, handler, buf, responseSender) -> { - // First read the player UUID UUID playerUuid = buf.readUuid(); - - // Read cosmetic data NameType nameType = buf.readEnumConstant(NameType.class); Integer staticColor = buf.readBoolean() ? buf.readInt() : null; Integer gradientStart = buf.readBoolean() ? buf.readInt() : null; Integer gradientEnd = gradientStart != null ? buf.readInt() : null; - String textureUrl = buf.readString(); - Identifier capeTexture = loadTextureFromURL(textureUrl, playerUuid.toString()); - // Apply the cosmetic profile on the main thread + // Load texture off main thread, apply on main thread + Identifier capeTexture = textureUrl.isEmpty() ? null + : loadTextureFromURL(textureUrl, playerUuid.toString()); + client.execute(() -> { - ClientCosmetics.fetchMojangCapes(playerUuid); - ClientCosmetics.apply(playerUuid, nameType, staticColor, gradientStart, gradientEnd, capeTexture); + // Check BEFORE applying if this is first sync for local player + boolean isFirstSync = client.player != null + && playerUuid.equals(client.player.getUuid()) + && ClientCosmetics.get(playerUuid) == null; + + ClientCosmetics.apply(playerUuid, nameType, staticColor, + gradientStart, gradientEnd, capeTexture); + + if (isFirstSync) { + java.util.concurrent.CompletableFuture.runAsync(() -> + ClientCosmetics.fetchMojangCapes(playerUuid)); + } }); }); + ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> { + if (client.player == null) return; + UUID uuid = client.player.getUuid(); + java.util.concurrent.CompletableFuture.runAsync(() -> + ClientCosmetics.fetchMojangCapes(uuid)); + }); ClientPlayNetworking.registerGlobalReceiver(Szar.OPEN_MERL_SCREEN, (client, handler, buf, responseSender) -> { int entityId = buf.readInt(); diff --git a/src/client/java/dev/tggamesyt/szar/client/TicTacToeScreen.java b/src/client/java/dev/tggamesyt/szar/client/TicTacToeScreen.java new file mode 100644 index 0000000..eb6927a --- /dev/null +++ b/src/client/java/dev/tggamesyt/szar/client/TicTacToeScreen.java @@ -0,0 +1,139 @@ +package dev.tggamesyt.szar.client; + +import dev.tggamesyt.szar.Szar; +import dev.tggamesyt.szar.TicTacToeBlockEntity; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; + +import java.util.UUID; + +public class TicTacToeScreen extends Screen { + + private static final Identifier BG = new Identifier("szar", "textures/gui/tictactoe.png"); + private static final Identifier X_TEX = new Identifier("szar", "textures/gui/x.png"); + private static final Identifier O_TEX = new Identifier("szar", "textures/gui/o.png"); + + private static final int GUI_WIDTH = 176; + private static final int GUI_HEIGHT = 166; + private static final int BOARD_X = 16; // offset inside GUI + private static final int BOARD_Y = 16; + private static final int CELL_SIZE = 44; + + private TicTacToeBlockEntity.State state; + private final BlockPos blockPos; + private final UUID localPlayer; + + // Add field + private boolean isSpectator; + + // Update constructor + public TicTacToeScreen(BlockPos pos, TicTacToeBlockEntity.State state) { + super(Text.literal("Tic Tac Toe")); + this.blockPos = pos; + this.state = state; + this.localPlayer = MinecraftClient.getInstance().player.getUuid(); + this.isSpectator = state.isSpectator; + } + + // Update updateState + public void updateState(TicTacToeBlockEntity.State newState) { + this.state = newState; + this.isSpectator = newState.isSpectator; + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta) { + renderBackground(context); + + int x = (this.width - GUI_WIDTH) / 2; + int y = (this.height - GUI_HEIGHT) / 2; + + // Draw background + context.drawTexture(BG, x, y, 0, 0, GUI_WIDTH, GUI_HEIGHT, GUI_WIDTH, GUI_HEIGHT); + + // Draw board cells + for (int i = 0; i < 9; i++) { + int col = i % 3; + int row = i / 3; + int cx = x + BOARD_X + col * CELL_SIZE; + int cy = y + BOARD_Y + row * CELL_SIZE; + + if (state.board[i] == 1) { + // O + context.drawTexture(O_TEX, cx + 4, cy + 4, 0, 0, + CELL_SIZE - 8, CELL_SIZE - 8, + CELL_SIZE - 8, CELL_SIZE - 8); + } else if (state.board[i] == 2) { + // X + context.drawTexture(X_TEX, cx + 4, cy + 4, 0, 0, + CELL_SIZE - 8, CELL_SIZE - 8, + CELL_SIZE - 8, CELL_SIZE - 8); + } + } + + // Status text + String status; + if (state.winner == 1) status = "§bO wins!"; + else if (state.winner == 2) status = "§cX wins!"; + else if (state.winner == 3) status = "§eDraw!"; + else if (isSpectator) status = "§7Spectating..."; + else { + boolean myTurn = (state.currentTurn == 1 && localPlayer.equals(state.player1)) + || (state.currentTurn == 2 && localPlayer.equals(state.player2)); + status = myTurn ? "§aYour turn!" : "§7Opponent's turn..."; + } + context.drawTextWithShadow(this.textRenderer, Text.literal(status), + x + GUI_WIDTH / 2 - this.textRenderer.getWidth(status) / 2, + y + BOARD_Y + 3 * CELL_SIZE + 8, 0xFFFFFF); + + super.render(context, mouseX, mouseY, delta); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (isSpectator) return super.mouseClicked(mouseX, mouseY, button); + if (button != 0) return super.mouseClicked(mouseX, mouseY, button); + if (state.winner != 0) return super.mouseClicked(mouseX, mouseY, button); + + // Check if it's our turn + boolean myTurn = (state.currentTurn == 1 && localPlayer.equals(state.player1)) + || (state.currentTurn == 2 && localPlayer.equals(state.player2)); + if (!myTurn) return super.mouseClicked(mouseX, mouseY, button); + + int x = (this.width - GUI_WIDTH) / 2; + int y = (this.height - GUI_HEIGHT) / 2; + + for (int i = 0; i < 9; i++) { + int col = i % 3; + int row = i / 3; + int cx = x + BOARD_X + col * CELL_SIZE; + int cy = y + BOARD_Y + row * CELL_SIZE; + + if (mouseX >= cx && mouseX < cx + CELL_SIZE + && mouseY >= cy && mouseY < cy + CELL_SIZE) { + if (state.board[i] == 0) { + sendMove(i); + return true; + } + } + } + return super.mouseClicked(mouseX, mouseY, button); + } + + private void sendMove(int cell) { + PacketByteBuf buf = PacketByteBufs.create(); + buf.writeBlockPos(blockPos); + buf.writeInt(cell); + ClientPlayNetworking.send(Szar.TTT_MAKE_MOVE, buf); + } + + @Override + public boolean shouldPause() { return false; } +} diff --git a/src/main/java/dev/tggamesyt/szar/ServerCosmetics.java b/src/main/java/dev/tggamesyt/szar/ServerCosmetics.java index ee5434c..2387a7a 100644 --- a/src/main/java/dev/tggamesyt/szar/ServerCosmetics.java +++ b/src/main/java/dev/tggamesyt/szar/ServerCosmetics.java @@ -57,6 +57,8 @@ public class ServerCosmetics { int size = buf.readInt(); List list = new ArrayList<>(); + System.out.println("SZAR: server received Mojang capes for " + uuid + ", count=" + size); + for (int i = 0; i < size; i++) { MojangCape c = new MojangCape(); c.id = buf.readString(); @@ -67,25 +69,6 @@ public class ServerCosmetics { PLAYER_MOJANG_CAPES.put(uuid, list); }); - ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { - ServerPlayerEntity player = handler.getPlayer(); - - // Send this player's own cosmetics to themselves - UserCosmetics user = USERS.get(player.getUuid()); - if (user != null) { - sync(player, user); - } - - // Optionally: send all other players' cosmetics to this new player - for (ServerPlayerEntity other : server.getPlayerManager().getPlayerList()) { - if (other.equals(player)) continue; - - UserCosmetics otherUser = USERS.get(other.getUuid()); - if (otherUser != null) { - sync(player, otherUser); // send other players’ cosmetics to the new player - } - } - }); } /* ---------------- LOAD JSON ---------------- */ @@ -152,20 +135,27 @@ public class ServerCosmetics { private static void registerCommand() { CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> dispatcher.register(CommandManager.literal("cape") - .then(CommandManager.argument("id", StringArgumentType.greedyString()) // <-- change here + .then(CommandManager.argument("id", StringArgumentType.greedyString()) .suggests((ctx, builder) -> { - ServerPlayerEntity player = ctx.getSource().getPlayer(); + if (player == null) return builder.buildFuture(); - // Custom capes - for (String id : CAPES.keySet()) - builder.suggest(id); + UserCosmetics user = USERS.get(player.getUuid()); - // Mojang capes + // Only suggest capes this player actually owns + if (user != null) { + for (String id : user.ownedCapes) { + builder.suggest(id); + } + } + + // Mojang capes from server-side map List mojang = PLAYER_MOJANG_CAPES.get(player.getUuid()); + System.out.println("SZAR: suggestions - mojang capes for " + player.getName().getString() + ": " + (mojang == null ? "null" : mojang.size())); if (mojang != null) { - for (MojangCape c : mojang) + for (MojangCape c : mojang) { builder.suggest(c.name); + } } builder.suggest("none"); @@ -173,10 +163,15 @@ public class ServerCosmetics { }) .executes(ctx -> { ServerPlayerEntity player = ctx.getSource().getPlayer(); - String id = StringArgumentType.getString(ctx, "id"); // this now includes spaces + String id = StringArgumentType.getString(ctx, "id"); - UserCosmetics user = USERS.get(player.getUuid()); - if (user == null) return 0; + // Create a default entry if player has no cosmetics profile + UserCosmetics user = USERS.computeIfAbsent(player.getUuid(), k -> { + UserCosmetics u = new UserCosmetics(); + u.nameType = NameType.STATIC; + u.ownedCapes = new ArrayList<>(); + return u; + }); // Deselect if (id.equalsIgnoreCase("none")) { @@ -186,12 +181,12 @@ public class ServerCosmetics { return 1; } - // Mojang cape selection (only from fetched list) + // Mojang cape selection List mojang = PLAYER_MOJANG_CAPES.get(player.getUuid()); if (mojang != null) { for (MojangCape c : mojang) { if (c.name.equalsIgnoreCase(id)) { - user.selectedCape = c.id; // vanilla cape + user.selectedCape = c.id; sync(player, user); player.sendMessage(Text.literal("Equipped Mojang cape: " + c.name), false); return 1; @@ -199,7 +194,7 @@ public class ServerCosmetics { } } - // Custom + // Custom cape check if (!user.ownedCapes.contains(id)) { player.sendMessage(Text.literal("You don't own this cape."), false); return 0; @@ -216,49 +211,47 @@ public class ServerCosmetics { /* ---------------- SYNC ---------------- */ - public static void sync(ServerPlayerEntity player, UserCosmetics user) { - List original = - player.getServer().getPlayerManager().getPlayerList(); - List list = new ArrayList<>(original); - if (!list.contains(player)) {list.add(player);} - for (ServerPlayerEntity p : list) { - PacketByteBuf buf = PacketByteBufs.create(); - - // Write player UUID first - buf.writeUuid(player.getUuid()); - - // Cosmetic data - buf.writeEnumConstant(user.nameType); - buf.writeBoolean(user.staticColor != null); - if (user.staticColor != null) buf.writeInt(user.staticColor); - - buf.writeBoolean(user.gradientStart != null); - if (user.gradientStart != null) { - buf.writeInt(user.gradientStart); - buf.writeInt(user.gradientEnd); - } - - String textureUrl = null; - if (user.selectedCape != null) { - textureUrl = CAPES.get(user.selectedCape); - if (textureUrl == null) { - List mojang = PLAYER_MOJANG_CAPES.get(player.getUuid()); - if (mojang != null) { - for (MojangCape c : mojang) { - if (c.id.equalsIgnoreCase(user.selectedCape)) { - textureUrl = c.url; - break; - } - } - } - } - } - buf.writeString(textureUrl == null ? "" : textureUrl); - ServerPlayNetworking.send(p, SYNC_PACKET, buf); + // Send ONE player's cosmetics to ALL online players (used for /cape changes) + public static void sync(ServerPlayerEntity owner, UserCosmetics user) { + for (ServerPlayerEntity p : owner.getServer().getPlayerManager().getPlayerList()) { + syncTo(p, owner, user); } } public enum NameType { STATIC, GRADIENT } + + // Send ONE player's cosmetics to ONE recipient + public static void syncTo(ServerPlayerEntity recipient, ServerPlayerEntity owner, + UserCosmetics user) { + PacketByteBuf buf = PacketByteBufs.create(); + buf.writeUuid(owner.getUuid()); // whose cosmetics these are + buf.writeEnumConstant(user.nameType); + buf.writeBoolean(user.staticColor != null); + if (user.staticColor != null) buf.writeInt(user.staticColor); + buf.writeBoolean(user.gradientStart != null); + if (user.gradientStart != null) { + buf.writeInt(user.gradientStart); + buf.writeInt(user.gradientEnd); + } + + String textureUrl = null; + if (user.selectedCape != null) { + textureUrl = CAPES.get(user.selectedCape); + if (textureUrl == null) { + List mojang = PLAYER_MOJANG_CAPES.get(owner.getUuid()); + if (mojang != null) { + for (MojangCape c : mojang) { + if (c.id.equalsIgnoreCase(user.selectedCape)) { + textureUrl = c.url; + break; + } + } + } + } + } + buf.writeString(textureUrl == null ? "" : textureUrl); + ServerPlayNetworking.send(recipient, SYNC_PACKET, buf); + } } \ No newline at end of file diff --git a/src/main/java/dev/tggamesyt/szar/Szar.java b/src/main/java/dev/tggamesyt/szar/Szar.java index cbf839f..766d7f7 100644 --- a/src/main/java/dev/tggamesyt/szar/Szar.java +++ b/src/main/java/dev/tggamesyt/szar/Szar.java @@ -186,6 +186,7 @@ public class Szar implements ModInitializer { RegistryKeys.DIMENSION_TYPE, new Identifier(MOD_ID, "backrooms") ); + public static final Map tttActivePlayers = new java.util.HashMap<>(); public static final Block SZAR_BLOCK = new SzarBlock(); public static final Block URANIUM_BLOCK = @@ -383,6 +384,7 @@ public class Szar implements ModInitializer { entries.add(Szar.CAN_OF_BEANS); entries.add(Szar.ALMOND_WATER); entries.add(Szar.KEBAB); + entries.add(Szar.TIC_TAC_TOE_ITEM); // crazy weponary entries.add(Szar.BULLET_ITEM); entries.add(Szar.AK47); @@ -611,19 +613,32 @@ public class Szar implements ModInitializer { PlayerMovementManager.init(); ServerCosmetics.init(); ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { - ServerPlayerEntity player = handler.getPlayer(); + ServerPlayerEntity joiner = handler.getPlayer(); - ServerCosmetics.UserCosmetics user = USERS.get(player.getUuid()); - if (user != null) { - - // AUTO SELECT FIRST CAPE IF NONE SELECTED - if (user.selectedCape == null && !user.ownedCapes.isEmpty()) { - user.selectedCape = user.ownedCapes.get(0); - } else { - user.selectedCape = null; + // Send joiner's own cosmetics to themselves + ServerCosmetics.UserCosmetics joinerUser = USERS.get(joiner.getUuid()); + if (joinerUser != null) { + if (joinerUser.selectedCape == null && !joinerUser.ownedCapes.isEmpty()) { + joinerUser.selectedCape = joinerUser.ownedCapes.get(0); } + ServerCosmetics.syncTo(joiner, joiner, joinerUser); + } - sync(player, user); + // Send all other online players' cosmetics to the joiner + for (ServerPlayerEntity other : server.getPlayerManager().getPlayerList()) { + if (other.equals(joiner)) continue; + ServerCosmetics.UserCosmetics otherUser = USERS.get(other.getUuid()); + if (otherUser != null) { + ServerCosmetics.syncTo(joiner, other, otherUser); + } + } + + // Send joiner's cosmetics to all other online players + if (joinerUser != null) { + for (ServerPlayerEntity other : server.getPlayerManager().getPlayerList()) { + if (other.equals(joiner)) continue; + ServerCosmetics.syncTo(other, joiner, joinerUser); + } } }); ServerLifecycleEvents.SERVER_STARTED.register(server -> { @@ -1313,7 +1328,38 @@ public class Szar implements ModInitializer { }); ServerTickEvents.END_SERVER_TICK.register(DrunkEffect::tick); FartManager.register(); + ServerPlayNetworking.registerGlobalReceiver(TTT_MAKE_MOVE, (server, player, handler, buf, sender) -> { + BlockPos pos = buf.readBlockPos(); + int cell = buf.readInt(); + server.execute(() -> { + if (player.getWorld().getBlockEntity(pos) instanceof TicTacToeBlockEntity be) { + be.handleMove(player, cell); + } + }); + }); } + + public static final Block TIC_TAC_TOE_BLOCK = Registry.register( + Registries.BLOCK, new Identifier(MOD_ID, "tictactoe"), + new TicTacToeBlock(AbstractBlock.Settings.create().strength(2f)) + ); + public static final BlockItem TIC_TAC_TOE_ITEM = Registry.register( + Registries.ITEM, new Identifier(MOD_ID, "tictactoe"), + new BlockItem(TIC_TAC_TOE_BLOCK, new Item.Settings()) + ); + public static final BlockEntityType TIC_TAC_TOE_ENTITY = + Registry.register( + Registries.BLOCK_ENTITY_TYPE, new Identifier(MOD_ID, "tictactoe"), + FabricBlockEntityTypeBuilder.create(TicTacToeBlockEntity::new, + TIC_TAC_TOE_BLOCK).build() + ); + + // Networking + public static final Identifier TTT_OPEN_SCREEN = new Identifier(MOD_ID, "ttt_open"); + public static final Identifier TTT_MAKE_MOVE = new Identifier(MOD_ID, "ttt_move"); + public static final Identifier TTT_STATE_SYNC = new Identifier(MOD_ID, "ttt_sync"); + public static final Identifier TTT_CLOSE_SCREEN = new Identifier(MOD_ID, "ttt_close"); + // Blocks public static final TrackerBlock TRACKER_BLOCK = Registry.register( Registries.BLOCK, new Identifier(MOD_ID, "tracker"), diff --git a/src/main/java/dev/tggamesyt/szar/TicTacToeBlock.java b/src/main/java/dev/tggamesyt/szar/TicTacToeBlock.java new file mode 100644 index 0000000..ae63782 --- /dev/null +++ b/src/main/java/dev/tggamesyt/szar/TicTacToeBlock.java @@ -0,0 +1,82 @@ +package dev.tggamesyt.szar; + +import net.minecraft.block.*; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityTicker; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.network.ServerPlayerEntity; +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.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; +import net.minecraft.world.BlockView; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +public class TicTacToeBlock extends BlockWithEntity { + + public TicTacToeBlock(Settings settings) { + super(settings); + } + + @Override + public BlockRenderType getRenderType(BlockState state) { + return BlockRenderType.MODEL; + } + + private static final VoxelShape SHAPE = VoxelShapes.union( + VoxelShapes.cuboid(0f, 0f, 0f, 1f, 0.75f, 1f) + ); + + @Override + public VoxelShape getOutlineShape(BlockState state, BlockView world, + BlockPos pos, ShapeContext ctx) { + return SHAPE; + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockView world, + BlockPos pos, ShapeContext ctx) { + return SHAPE; + } + + @Nullable + @Override + public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { + return new TicTacToeBlockEntity(pos, state); + } + + @Override + public ActionResult onUse(BlockState state, World world, BlockPos pos, + PlayerEntity player, Hand hand, BlockHitResult hit) { + if (world.isClient) return ActionResult.SUCCESS; + if (!(player instanceof ServerPlayerEntity serverPlayer)) return ActionResult.PASS; + if (!(world.getBlockEntity(pos) instanceof TicTacToeBlockEntity be)) return ActionResult.PASS; + + be.handlePlayerJoin(serverPlayer, pos); + return ActionResult.SUCCESS; + } + + @Override + public BlockEntityTicker getTicker( + World world, BlockState state, BlockEntityType type) { + if (world.isClient) return null; + return type == Szar.TIC_TAC_TOE_ENTITY + ? (w, pos, s, be) -> TicTacToeBlockEntity.tick(w, pos, s, + (TicTacToeBlockEntity) be) + : null; + } + + @Override + public void onBreak(World world, BlockPos pos, BlockState state, PlayerEntity player) { + if (!world.isClient && world.getBlockEntity(pos) instanceof TicTacToeBlockEntity be) { + be.closeScreenForAll(world.getServer()); + if (be.player1 != null) Szar.tttActivePlayers.remove(be.player1); + if (be.player2 != null) Szar.tttActivePlayers.remove(be.player2); + } + super.onBreak(world, pos, state, player); + } +} \ No newline at end of file diff --git a/src/main/java/dev/tggamesyt/szar/TicTacToeBlockEntity.java b/src/main/java/dev/tggamesyt/szar/TicTacToeBlockEntity.java new file mode 100644 index 0000000..66252a4 --- /dev/null +++ b/src/main/java/dev/tggamesyt/szar/TicTacToeBlockEntity.java @@ -0,0 +1,270 @@ +package dev.tggamesyt.szar; + +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import java.util.UUID; + +public class TicTacToeBlockEntity extends BlockEntity { + + // 0 = empty, 1 = O (player1), 2 = X (player2) + public int[] board = new int[9]; + public UUID player1 = null; // O + public UUID player2 = null; // X + public int currentTurn = 1; // 1 = O's turn, 2 = X's turn + public int winner = 0; // 0 = ongoing, 1 = O wins, 2 = X wins, 3 = draw + public final java.util.Set spectators = new java.util.HashSet<>(); + public int resetTimer = -1; // -1 = no reset pending + public TicTacToeBlockEntity(BlockPos pos, BlockState state) { + super(Szar.TIC_TAC_TOE_ENTITY, pos, state); + } + + public static void tick(World world, BlockPos pos, BlockState state, + TicTacToeBlockEntity entity) { + if (!world.isClient && entity.resetTimer > 0) { + entity.resetTimer--; + if (entity.resetTimer == 0) { + entity.resetTimer = -1; + entity.resetGame(((net.minecraft.server.world.ServerWorld) world).getServer()); + entity.syncToPlayers(((net.minecraft.server.world.ServerWorld) world).getServer()); + } + } + } + + public void handlePlayerJoin(ServerPlayerEntity player, BlockPos pos) { + UUID uuid = player.getUuid(); + + // Check if already in a different game + BlockPos activePos = Szar.tttActivePlayers.get(uuid); + if (activePos != null && !activePos.equals(pos)) { + player.sendMessage(Text.literal("§cYou are already in a game at another board!"), true); + return; + } + + // Rejoin existing game + if (uuid.equals(player1) || uuid.equals(player2)) { + openScreen(player); + return; + } + + if (player1 == null) { + player1 = uuid; + Szar.tttActivePlayers.put(uuid, pos); + player.sendMessage(Text.literal("§aYou are §bO§a! Waiting for second player..."), true); + markDirty(); + return; + } + + if (player2 == null && !uuid.equals(player1)) { + player2 = uuid; + Szar.tttActivePlayers.put(uuid, pos); + player.sendMessage(Text.literal("§aYou are §cX§a! Game starting!"), true); + + ServerPlayerEntity p1 = getServer(player).getPlayerManager().getPlayer(player1); + if (p1 != null) { + p1.sendMessage(Text.literal("§aSecond player joined! Your turn!"), true); + } + + openScreenForBoth(player); + markDirty(); + return; + } + + // At the bottom where it says "game is full", replace with: + if (player1 != null && player2 != null) { + spectators.add(uuid); + player.sendMessage(Text.literal("§7Spectating the match..."), true); + openScreen(player); + markDirty(); + } + } + + private net.minecraft.server.MinecraftServer getServer(ServerPlayerEntity player) { + return player.getServer(); + } + + public void openScreenForBoth(ServerPlayerEntity joiner) { + openScreen(joiner); + ServerPlayerEntity p1 = joiner.getServer().getPlayerManager().getPlayer(player1); + if (p1 != null) openScreen(p1); + } + + public void openScreen(ServerPlayerEntity player) { + PacketByteBuf buf = PacketByteBufs.create(); + buf.writeBlockPos(this.pos); + writeStateToBuf(buf, player.getUuid()); + ServerPlayNetworking.send(player, Szar.TTT_OPEN_SCREEN, buf); + } + + public void handleMove(ServerPlayerEntity player, int cell) { + if (winner != 0) return; + if (cell < 0 || cell > 8) return; + if (board[cell] != 0) return; + + UUID uuid = player.getUuid(); + int playerNum = uuid.equals(player1) ? 1 : uuid.equals(player2) ? 2 : 0; + if (playerNum == 0) return; + if (playerNum != currentTurn) { + player.sendMessage(Text.literal("§cNot your turn!"), true); + return; + } + + board[cell] = playerNum; + currentTurn = (currentTurn == 1) ? 2 : 1; + checkWinner(); + markDirty(); + syncToPlayers(player.getServer()); + } + + private void checkWinner() { + int[][] lines = { + {0,1,2},{3,4,5},{6,7,8}, + {0,3,6},{1,4,7},{2,5,8}, + {0,4,8},{2,4,6} + }; + + for (int[] line : lines) { + int a = board[line[0]], b = board[line[1]], c = board[line[2]]; + if (a != 0 && a == b && b == c) { + winner = a; + scheduleReset(); + return; + } + } + + boolean full = true; + for (int cell : board) { + if (cell == 0) { full = false; break; } + } + if (full) { + winner = 3; + scheduleReset(); + } + } + + private void scheduleReset() { + resetTimer = 60; + markDirty(); + } + + public void syncToPlayers(net.minecraft.server.MinecraftServer server) { + sendToPlayer(server, player1); + sendToPlayer(server, player2); + for (UUID uuid : spectators) { + sendToPlayer(server, uuid); + } + } + + private void sendToPlayer(net.minecraft.server.MinecraftServer server, UUID uuid) { + if (uuid == null) return; + ServerPlayerEntity p = server.getPlayerManager().getPlayer(uuid); + if (p == null) return; + PacketByteBuf buf = PacketByteBufs.create(); + buf.writeBlockPos(this.pos); + writeStateToBuf(buf, uuid); + ServerPlayNetworking.send(p, Szar.TTT_STATE_SYNC, buf); + } + + public void writeStateToBuf(PacketByteBuf buf, UUID viewerUuid) { + for (int cell : board) buf.writeInt(cell); + buf.writeBoolean(player1 != null); + if (player1 != null) buf.writeUuid(player1); + buf.writeBoolean(player2 != null); + if (player2 != null) buf.writeUuid(player2); + buf.writeInt(currentTurn); + buf.writeInt(winner); + // Is the viewer a spectator? + boolean isSpectator = viewerUuid != null + && !viewerUuid.equals(player1) + && !viewerUuid.equals(player2); + buf.writeBoolean(isSpectator); + } + + public static State readStateFromBuf(PacketByteBuf buf) { + State s = new State(); + s.board = new int[9]; + for (int i = 0; i < 9; i++) s.board[i] = buf.readInt(); + if (buf.readBoolean()) s.player1 = buf.readUuid(); + if (buf.readBoolean()) s.player2 = buf.readUuid(); + s.currentTurn = buf.readInt(); + s.winner = buf.readInt(); + s.isSpectator = buf.readBoolean(); + return s; + } + + public static class State { + public int[] board; + public UUID player1, player2; + public int currentTurn, winner; + public boolean isSpectator; + } + + @Override + public void writeNbt(NbtCompound nbt) { + super.writeNbt(nbt); + nbt.putIntArray("Board", board); + if (player1 != null) nbt.putUuid("Player1", player1); + if (player2 != null) nbt.putUuid("Player2", player2); + nbt.putInt("Turn", currentTurn); + nbt.putInt("Winner", winner); + nbt.putInt("ResetTimer", resetTimer); + } + + @Override + public void readNbt(NbtCompound nbt) { + super.readNbt(nbt); + int[] saved = nbt.getIntArray("Board"); + if (saved.length == 9) board = saved; + if (nbt.containsUuid("Player1")) player1 = nbt.getUuid("Player1"); + if (nbt.containsUuid("Player2")) player2 = nbt.getUuid("Player2"); + currentTurn = nbt.getInt("Turn"); + winner = nbt.getInt("Winner"); + resetTimer = nbt.getInt("ResetTimer"); + } + + @Override + public NbtCompound toInitialChunkDataNbt() { return createNbt(); } + + @Override + public BlockEntityUpdateS2CPacket toUpdatePacket() { + return BlockEntityUpdateS2CPacket.create(this); + } + + public void resetGame(net.minecraft.server.MinecraftServer server) { + closeScreenForAll(server); // kick everyone from screen first + if (player1 != null) Szar.tttActivePlayers.remove(player1); + if (player2 != null) Szar.tttActivePlayers.remove(player2); + spectators.clear(); + board = new int[9]; + player1 = null; + player2 = null; + currentTurn = 1; + winner = 0; + resetTimer = -1; + markDirty(); + } + + public void closeScreenForAll(net.minecraft.server.MinecraftServer server) { + closeScreenForPlayer(server, player1); + closeScreenForPlayer(server, player2); + for (UUID uuid : spectators) { + closeScreenForPlayer(server, uuid); + } + } + + private void closeScreenForPlayer(net.minecraft.server.MinecraftServer server, UUID uuid) { + if (uuid == null) return; + ServerPlayerEntity p = server.getPlayerManager().getPlayer(uuid); + if (p == null) return; + ServerPlayNetworking.send(p, Szar.TTT_CLOSE_SCREEN, PacketByteBufs.empty()); + } +} \ No newline at end of file diff --git a/src/main/resources/assets/szar/blockstates/tictactoe.json b/src/main/resources/assets/szar/blockstates/tictactoe.json new file mode 100644 index 0000000..a1e1599 --- /dev/null +++ b/src/main/resources/assets/szar/blockstates/tictactoe.json @@ -0,0 +1,5 @@ +{ + "variants": { + "": { "model": "szar:block/tictactoe" } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/szar/lang/en_us.json b/src/main/resources/assets/szar/lang/en_us.json index f635b60..5b39264 100644 --- a/src/main/resources/assets/szar/lang/en_us.json +++ b/src/main/resources/assets/szar/lang/en_us.json @@ -185,5 +185,7 @@ "advancement.szar.dontknow.description": "Ask a question from Merl", "advancement.szar.backrooms.title": "Where did the world go?", - "advancement.szar.backrooms.description": "Step into the Backrooms" + "advancement.szar.backrooms.description": "Step into the Backrooms", + + "block.szar.tictactoe": "Tic Tac Toe" } diff --git a/src/main/resources/assets/szar/models/block/tictactoe.json b/src/main/resources/assets/szar/models/block/tictactoe.json new file mode 100644 index 0000000..6748be1 --- /dev/null +++ b/src/main/resources/assets/szar/models/block/tictactoe.json @@ -0,0 +1,24 @@ +{ + "parent": "block/block", + "format_version": "1.9.0", + "credit": "Made with Blockbench", + "textures": { + "0": "szar:block/tictactoe", + "1": "szar:block/plank", + "particle": "szar:block/tictactoe" + }, + "elements": [ + { + "from": [0, 0, 0], + "to": [16, 12, 16], + "faces": { + "north": {"uv": [0, 0, 16, 12], "texture": "#1"}, + "east": {"uv": [0, 0, 16, 12], "texture": "#1"}, + "south": {"uv": [0, 0, 16, 12], "texture": "#1"}, + "west": {"uv": [0, 0, 16, 12], "texture": "#1"}, + "up": {"uv": [0, 0, 16, 16], "texture": "#0"}, + "down": {"uv": [0, 0, 16, 16], "texture": "#1"} + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/szar/models/item/tictactoe.json b/src/main/resources/assets/szar/models/item/tictactoe.json new file mode 100644 index 0000000..cb779a2 --- /dev/null +++ b/src/main/resources/assets/szar/models/item/tictactoe.json @@ -0,0 +1,3 @@ +{ + "parent": "szar:block/tictactoe" +} \ No newline at end of file diff --git a/src/main/resources/assets/szar/textures/block/plank.png b/src/main/resources/assets/szar/textures/block/plank.png new file mode 100644 index 0000000000000000000000000000000000000000..04309b75f3247e96ec1c316f7e4a71b0b88d6f29 GIT binary patch literal 405 zcmV;G0c!qNkl+OdMHfM&o`gMl@Zf(CynFEIAM(feXDTAdD(Vt++Z0N{H+k(4YI6va z`O16q#ymaV$NAK5m8BgP*_p=0f6w#FtAT}V)WmtwzI|D@iut)C0qe&Q6<);kf-guMgpEQdfQE zz`|s9ed$el$amjAziY@MJr2!~>}BwDRNhaN5XgSO{$@NnBGV$e_y zJoM+G0d(NJ73==qZn5bg0n-y5fPRTG-6W$kRbm1!^d_}0*MJr)##$pPIp5|S)^t!O zRZ^$9ySc*DQB5YJxZoD34mN3`t7U+gzmt=KVHZ9*4=kG$5IUtm1_Bfg8$b~lRc^vT z*Cr>Ud~}z1{r~^~|Nl#YogV-I00v1!K~w_(jWqg7@@eUP00000NkvXXu0mjf9eJ@< literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/szar/textures/block/tictactoe.png b/src/main/resources/assets/szar/textures/block/tictactoe.png new file mode 100644 index 0000000000000000000000000000000000000000..b61a85dc96abf30bab4b28852cb9cfab38412250 GIT binary patch literal 579 zcmV-J0=)f+P)CUDilaR#x#76U;LVIUF&f(wNjqR|Oj zJ;(_+27zG^6;X>^wDAuQWE6E6{4-A&M1S`l5y6d@$K$)_o^!wNJLkI7W8Fcc9u7q! z;WKh<)q}m0k%c9{36#ov)NE{S-Od$bxgsDIdYL%?iVU^{0eqePrEtqRm(|k1|%Oc%$NbB7>k44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`0h7I;J!GcfQS24TkI`72U@8rm~MB1$5BeXNr6bM+Ea@{>~aDsl^esu>t; z>?;Zqle1Gx6p~WYGxKcK-|yb9u8^5xs~&FZYv5bpoSKp8QB{;0T;&&%T$P<{nWAoQ z$IE3?VFffHH?<^Dp&~aYuh^=>Rtapd6_5=Q)>pE#DN0GR3UYCSssQqAl`=|73as?? z%gf94%8m8%i_-NCEiEne4UF`SjC6r2bc-wVN)jt{^NN+B2DqdaCl_TFlw{`TDS!-2 zOv*1Uu~jN9%}lXMOH4CON=Y%*O-eLQ(KR$oNz_eDF*ejqF*Z&yH#M{{N;6DSf?8ja znTD`GuNWE(zyQ$)$>>wgQzXDnC zkO2h~Jakj@fI(Ug3_G1EGq{1_{>9V9F(ktM?Q}<>W(6MCh09;Otg`TbuKQ-uWU(Zl zI)k(~Jo)JjA3C}sx~He!t?+pIb&buE#`14n1rCP0FGmXYDK0Z>J1x*P@6+tIX_n~F(p4KRj(qq0H~UQ z!N$I#ATc>RwL~E)H9a%WR{j0%{pt#tDYok2roINg1e6A&sHg;q@=(~ zU%$M(T(8_%FTW^V-_X+1Qs2Nx-^fT8s6w~6GOr}DLN~8i8ESw_YH@N=WpeUOa4p`HQA$so3se^F*C&=nvn?F?fT^vIy;@;jpn49D&;&9R3y>*s}2TP~H1P+!< z2U@1XDW1**K^;fR@_nB$^v-s0@r7GfAKR>!j=bgRCny}SB_x!ex zsmu8JB$G<xQ)!w-K*QOe;u>Wr}_h9(_)!~wi4U)rFqM zyZ8Qk`EOc$?T2$?ZJK|5K5u?!b+~N*@tHF%=lb1aSm48;@Fv>50Z)LXu`s0dFkJX{ z+O6St_@eUi|JUDrt*VU+YE;oDyNk>}NU`}+m!<4SGh($k)mSXDAN7%?_%$d_d-+kN-5JLm1U z-?rJ!KhIyy`t|wz<;FL1%=oMK&lY5uVaVigTVI;-U3}-gyMN<%Uw{2IH`Wpq@RucP zzV5A>3Q;nf!C}UbLN~I~rlm%vr9G?KYxjQtlcJq>uKoV|FLC{&0*gD>j)HPd&EdUO zS3g$FnK|?F%O|`Hr&t&c?JH$r_-EtBkZ_EHA(x3EkpiSRKc4en=GXJ*pFV#4*nakb z-@E?rXIMSozTPgE`TeUBtFvd;*xhHa`}FVdQ I&MBb@0G5|M_y7O^ literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/szar/textures/gui/x.png b/src/main/resources/assets/szar/textures/gui/x.png new file mode 100644 index 0000000000000000000000000000000000000000..00b8b6187c343288e24cbdda9c0d2b54ea0f8430 GIT binary patch literal 794 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB7>k44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`0h7I;J!GcfQS24TkI`72U@8rm~MB1$5BeXNr6bM+Ea@{>~aDsl^esu>t; z>?;Zqle1Gx6p~WYGxKcK-|yb9u8^5xs~&FZYv5bpoSKp8QB{;0T;&&%T$P<{nWAoQ z$IE3?VFffHH?<^Dp&~aYuh^=>Rtapd6_5=Q)>pE#DN0GR3UYCSssQqAl`=|73as?? z%gf94%8m8%i_-NCEiEne4UF`SjC6r2bc-wVN)jt{^NN+B2DqdaCl_TFlw{`TDS!-2 zOv*1Uu~jN9%}lXMOH4CON=Y%*O-eLQ(KR$oNz_eDF*ejqF*Z&yH#M{{N;6DSf?8ja znTD`GuNWE(zyQ$)$>>wgQzXDnC zkO2h~Jakj@fI(Ug3_G1EGq{0KBID`e7!u*WcZQ+RAp;&3{hG#Ulm74Tm^97Y;i-?o zv=VjAoTE1`R-J4)$s^6MC-1wt+Pd?{&DNJD&E2`yj4j=@GCl5DtMa{sX(wK?H411L ztTW(PqR~_u;3&|dJ>@3vqlRA#R=%$feeKsEfsl|&VHaKQQrr=q{&L3^vp z*-phS(m!*I=Z40sMOkMWI=S>;bV<4dzDp}VbD-PEe9DZ=@#{njQ^i&p=fS?83{1OV!*ATR&` literal 0 HcmV?d00001 diff --git a/src/main/resources/data/minecraft/tags/blocks/mineable/axe.json b/src/main/resources/data/minecraft/tags/blocks/mineable/axe.json index a2e547b..6b4a702 100644 --- a/src/main/resources/data/minecraft/tags/blocks/mineable/axe.json +++ b/src/main/resources/data/minecraft/tags/blocks/mineable/axe.json @@ -1,6 +1,7 @@ { "values": [ "szar:roulette", - "szar:slot_machine" + "szar:slot_machine", + "szar:tictactoe" ] } \ No newline at end of file diff --git a/src/main/resources/data/szar/loot_tables/blocks/tictactoe.json b/src/main/resources/data/szar/loot_tables/blocks/tictactoe.json new file mode 100644 index 0000000..607ecf7 --- /dev/null +++ b/src/main/resources/data/szar/loot_tables/blocks/tictactoe.json @@ -0,0 +1,14 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "rolls": 1, + "entries": [ + { + "type": "minecraft:item", + "name": "szar:tictactoe" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/data/szar/recipes/tictactoe.json b/src/main/resources/data/szar/recipes/tictactoe.json new file mode 100644 index 0000000..4235150 --- /dev/null +++ b/src/main/resources/data/szar/recipes/tictactoe.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "RBR", + "BPB", + "RBR" + ], + "key": { + "R": { + "item": "minecraft:red_dye" + }, + "B": { + "item": "minecraft:blue_dye" + }, + "P": { + "tag": "minecraft:planks" + } + }, + "result": { + "item": "szar:tictactoe", + "count": 1 + } +} \ No newline at end of file