From ff8e52f95d4a6878bca147dc91a0f09df3f221c5 Mon Sep 17 00:00:00 2001 From: TGGamesYT Date: Sun, 22 Mar 2026 17:31:09 +0100 Subject: [PATCH] connect four, and some minor fixes --- .../szar/client/ConnectFourScreen.java | 153 +++++++++ .../dev/tggamesyt/szar/client/SzarClient.java | 41 ++- .../dev/tggamesyt/szar/ConnectFourBlock.java | 81 +++++ .../szar/ConnectFourBlockEntity.java | 308 ++++++++++++++++++ src/main/java/dev/tggamesyt/szar/Szar.java | 34 +- .../tggamesyt/szar/TicTacToeBlockEntity.java | 16 +- .../assets/szar/blockstates/connectfour.json | 5 + .../resources/assets/szar/lang/en_us.json | 3 +- .../assets/szar/models/block/connectfour.json | 64 ++++ .../assets/szar/models/item/connectfour.json | 3 + .../assets/szar/textures/block/connect4.png | Bin 0 -> 257 bytes .../assets/szar/textures/gui/c4_hover.png | Bin 0 -> 761 bytes .../assets/szar/textures/gui/c4_red.png | Bin 0 -> 694 bytes .../assets/szar/textures/gui/c4_yellow.png | Bin 0 -> 688 bytes .../assets/szar/textures/gui/connectfour.png | Bin 0 -> 1775 bytes .../minecraft/tags/blocks/mineable/axe.json | 3 +- .../szar/loot_tables/blocks/connectfour.json | 14 + .../data/szar/recipes/connectfour.json | 23 ++ 18 files changed, 741 insertions(+), 7 deletions(-) create mode 100644 src/client/java/dev/tggamesyt/szar/client/ConnectFourScreen.java create mode 100644 src/main/java/dev/tggamesyt/szar/ConnectFourBlock.java create mode 100644 src/main/java/dev/tggamesyt/szar/ConnectFourBlockEntity.java create mode 100644 src/main/resources/assets/szar/blockstates/connectfour.json create mode 100644 src/main/resources/assets/szar/models/block/connectfour.json create mode 100644 src/main/resources/assets/szar/models/item/connectfour.json create mode 100644 src/main/resources/assets/szar/textures/block/connect4.png create mode 100644 src/main/resources/assets/szar/textures/gui/c4_hover.png create mode 100644 src/main/resources/assets/szar/textures/gui/c4_red.png create mode 100644 src/main/resources/assets/szar/textures/gui/c4_yellow.png create mode 100644 src/main/resources/assets/szar/textures/gui/connectfour.png create mode 100644 src/main/resources/data/szar/loot_tables/blocks/connectfour.json create mode 100644 src/main/resources/data/szar/recipes/connectfour.json diff --git a/src/client/java/dev/tggamesyt/szar/client/ConnectFourScreen.java b/src/client/java/dev/tggamesyt/szar/client/ConnectFourScreen.java new file mode 100644 index 0000000..3e844c0 --- /dev/null +++ b/src/client/java/dev/tggamesyt/szar/client/ConnectFourScreen.java @@ -0,0 +1,153 @@ +package dev.tggamesyt.szar.client; + +import dev.tggamesyt.szar.ConnectFourBlockEntity; +import dev.tggamesyt.szar.Szar; +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 ConnectFourScreen extends Screen { + + private static final Identifier BG = + new Identifier("szar", "textures/gui/connectfour.png"); + private static final Identifier RED_TEX = + new Identifier("szar", "textures/gui/c4_red.png"); + private static final Identifier YELLOW_TEX = + new Identifier("szar", "textures/gui/c4_yellow.png"); + private static final Identifier HOVER_TEX = + new Identifier("szar", "textures/gui/c4_hover.png"); + + // GUI dimensions — adjust to match your texture + private static final int GUI_WIDTH = 204; // 7 cells * ~28px + padding + private static final int GUI_HEIGHT = 196; // 6 cells * ~28px + padding + status + private static final int BOARD_X = 18; + private static final int BOARD_Y = 18; + private static final int CELL_SIZE = 24; + + private ConnectFourBlockEntity.State state; + private final BlockPos blockPos; + private final UUID localPlayer; + private boolean isSpectator; + private int hoveredCol = -1; + + public ConnectFourScreen(BlockPos pos, ConnectFourBlockEntity.State state) { + super(Text.literal("Connect Four")); + this.blockPos = pos; + this.state = state; + this.localPlayer = MinecraftClient.getInstance().player.getUuid(); + this.isSpectator = state.isSpectator; + } + + public void updateState(ConnectFourBlockEntity.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); + + // Update hovered column + hoveredCol = -1; + for (int col = 0; col < ConnectFourBlockEntity.COLS; col++) { + int cx = x + BOARD_X + col * CELL_SIZE; + if (mouseX >= cx && mouseX < cx + CELL_SIZE) { + hoveredCol = col; + break; + } + } + + // Draw hover indicator on top row if it's our turn + boolean myTurn = !isSpectator && state.winner == 0 + && ((state.currentTurn == 1 && localPlayer.equals(state.player1)) + || (state.currentTurn == 2 && localPlayer.equals(state.player2))); + + if (myTurn && hoveredCol >= 0) { + int cx = x + BOARD_X + hoveredCol * CELL_SIZE; + context.drawTexture(HOVER_TEX, cx + 2, y + BOARD_Y - CELL_SIZE + 2, 0, 0, + CELL_SIZE - 4, CELL_SIZE - 4, CELL_SIZE - 4, CELL_SIZE - 4); + } + + // Draw board — row 0 is bottom, so render rows in reverse + for (int row = 0; row < ConnectFourBlockEntity.ROWS; row++) { + for (int col = 0; col < ConnectFourBlockEntity.COLS; col++) { + int cx = x + BOARD_X + col * CELL_SIZE; + // Flip row so row 0 (bottom) renders at the bottom of the GUI + int displayRow = ConnectFourBlockEntity.ROWS - 1 - row; + int cy = y + BOARD_Y + displayRow * CELL_SIZE; + + int val = state.board[row][col]; + if (val == 1) { + context.drawTexture(RED_TEX, cx + 2, cy + 2, 0, 0, + CELL_SIZE - 4, CELL_SIZE - 4, + CELL_SIZE - 4, CELL_SIZE - 4); + } else if (val == 2) { + context.drawTexture(YELLOW_TEX, cx + 2, cy + 2, 0, 0, + CELL_SIZE - 4, CELL_SIZE - 4, + CELL_SIZE - 4, CELL_SIZE - 4); + } + } + } + + // Status text + String status; + if (state.winner == 1) status = "§cRed wins!"; + else if (state.winner == 2) status = "§eYellow wins!"; + else if (state.winner == 3) status = "§7Draw!"; + else if (isSpectator) status = "§7Spectating..."; + else 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 + ConnectFourBlockEntity.ROWS * CELL_SIZE + 6, + 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); + + 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; + + for (int col = 0; col < ConnectFourBlockEntity.COLS; col++) { + int cx = x + BOARD_X + col * CELL_SIZE; + if (mouseX >= cx && mouseX < cx + CELL_SIZE) { + sendMove(col); + return true; + } + } + return super.mouseClicked(mouseX, mouseY, button); + } + + private void sendMove(int col) { + PacketByteBuf buf = PacketByteBufs.create(); + buf.writeBlockPos(blockPos); + buf.writeInt(col); + ClientPlayNetworking.send(Szar.C4_MAKE_MOVE, buf); + } + + @Override + public boolean shouldPause() { return false; } +} \ No newline at end of file diff --git a/src/client/java/dev/tggamesyt/szar/client/SzarClient.java b/src/client/java/dev/tggamesyt/szar/client/SzarClient.java index efb7902..b753bd5 100644 --- a/src/client/java/dev/tggamesyt/szar/client/SzarClient.java +++ b/src/client/java/dev/tggamesyt/szar/client/SzarClient.java @@ -90,11 +90,17 @@ public class SzarClient implements ClientModInitializer { ); @Override public void onInitializeClient() { - // Open screen + // TTT 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))); + client.execute(() -> { + if (client.currentScreen instanceof TicTacToeScreen existing) { + existing.updateState(state); // just update, don't replace + } else { + client.setScreen(new TicTacToeScreen(pos, state)); + } + }); }); ClientPlayNetworking.registerGlobalReceiver(Szar.TTT_CLOSE_SCREEN, (client, handler, buf, sender) -> { client.execute(() -> { @@ -112,6 +118,37 @@ public class SzarClient implements ClientModInitializer { } }); }); + +// C4 + ClientPlayNetworking.registerGlobalReceiver(Szar.C4_OPEN_SCREEN, (client, handler, buf, sender) -> { + BlockPos pos = buf.readBlockPos(); + ConnectFourBlockEntity.State state = ConnectFourBlockEntity.readStateFromBuf(buf); + client.execute(() -> { + if (client.currentScreen instanceof ConnectFourScreen existing) { + existing.updateState(state); + } else { + client.setScreen(new ConnectFourScreen(pos, state)); + } + }); + }); + + ClientPlayNetworking.registerGlobalReceiver(Szar.C4_STATE_SYNC, (client, handler, buf, sender) -> { + BlockPos pos = buf.readBlockPos(); + ConnectFourBlockEntity.State state = ConnectFourBlockEntity.readStateFromBuf(buf); + client.execute(() -> { + if (client.currentScreen instanceof ConnectFourScreen screen) { + screen.updateState(state); + } + }); + }); + + ClientPlayNetworking.registerGlobalReceiver(Szar.C4_CLOSE_SCREEN, (client, handler, buf, sender) -> { + client.execute(() -> { + if (client.currentScreen instanceof ConnectFourScreen) { + client.setScreen(null); + } + }); + }); ClientPlayNetworking.registerGlobalReceiver(Szar.DRUNK_TYPE_PACKET, (client, handler, buf, responseSender) -> { String typeName = buf.readString(); client.execute(() -> DrunkEffect.setDisplayType(typeName)); diff --git a/src/main/java/dev/tggamesyt/szar/ConnectFourBlock.java b/src/main/java/dev/tggamesyt/szar/ConnectFourBlock.java new file mode 100644 index 0000000..e5b36c2 --- /dev/null +++ b/src/main/java/dev/tggamesyt/szar/ConnectFourBlock.java @@ -0,0 +1,81 @@ +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 ConnectFourBlock extends BlockWithEntity { + + private static final VoxelShape SHAPE = VoxelShapes.union( + VoxelShapes.cuboid(0f, 0f, 0f, 1f, 0.75f, 1f) + ); + + public ConnectFourBlock(Settings settings) { + super(settings); + } + + @Override + public BlockRenderType getRenderType(BlockState state) { + return BlockRenderType.MODEL; + } + + @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 ConnectFourBlockEntity(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 sp)) return ActionResult.PASS; + if (!(world.getBlockEntity(pos) instanceof ConnectFourBlockEntity be)) return ActionResult.PASS; + be.handlePlayerJoin(sp, pos); + return ActionResult.SUCCESS; + } + + @Override + public BlockEntityTicker getTicker( + World world, BlockState state, BlockEntityType type) { + if (world.isClient) return null; + return type == Szar.CONNECT_FOUR_ENTITY + ? (w, pos, s, be) -> ConnectFourBlockEntity.tick(w, pos, s, + (ConnectFourBlockEntity) be) + : null; + } + + @Override + public void onBreak(World world, BlockPos pos, BlockState state, PlayerEntity player) { + if (!world.isClient && world.getBlockEntity(pos) instanceof ConnectFourBlockEntity be) { + be.closeScreenForAll(world.getServer()); + if (be.player1 != null) Szar.c4ActivePlayers.remove(be.player1); + if (be.player2 != null) Szar.c4ActivePlayers.remove(be.player2); + } + super.onBreak(world, pos, state, player); + } +} \ No newline at end of file diff --git a/src/main/java/dev/tggamesyt/szar/ConnectFourBlockEntity.java b/src/main/java/dev/tggamesyt/szar/ConnectFourBlockEntity.java new file mode 100644 index 0000000..b8de060 --- /dev/null +++ b/src/main/java/dev/tggamesyt/szar/ConnectFourBlockEntity.java @@ -0,0 +1,308 @@ +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.HashSet; +import java.util.Set; +import java.util.UUID; + +public class ConnectFourBlockEntity extends BlockEntity { + + public static final int COLS = 7; + public static final int ROWS = 6; + + // board[row][col], 0=empty, 1=player1(red), 2=player2(yellow) + // row 0 = bottom + public int[][] board = new int[ROWS][COLS]; + public UUID player1 = null; // red + public UUID player2 = null; // yellow + public int currentTurn = 1; + public int winner = 0; // 0=ongoing, 1=p1, 2=p2, 3=draw + public Set spectators = new HashSet<>(); + public int resetTimer = -1; + + public ConnectFourBlockEntity(BlockPos pos, BlockState state) { + super(Szar.CONNECT_FOUR_ENTITY, pos, state); + } + + public static void tick(World world, BlockPos pos, BlockState state, + ConnectFourBlockEntity entity) { + if (!world.isClient && entity.resetTimer > 0) { + entity.resetTimer--; + if (entity.resetTimer == 0) { + entity.resetTimer = -1; + var server = ((net.minecraft.server.world.ServerWorld) world).getServer(); + entity.resetGame(server); + entity.syncToPlayers(server); + } + } + } + + public void handlePlayerJoin(ServerPlayerEntity player, BlockPos pos) { + UUID uuid = player.getUuid(); + + BlockPos activePos = Szar.c4ActivePlayers.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)) { + if (player1 != null && player2 != null) { + // Game in progress — reopen screen + openScreen(player); + } else { + // Still waiting for second player — leave + if (uuid.equals(player1)) { + Szar.c4ActivePlayers.remove(player1); + player1 = null; + } else { + Szar.c4ActivePlayers.remove(player2); + player2 = null; + } + player.sendMessage(Text.literal("§7Left the game."), true); + markDirty(); + } + return; + } + + if (player1 == null) { + player1 = uuid; + Szar.c4ActivePlayers.put(uuid, pos); + player.sendMessage(Text.literal("§aYou are §cRed§a! Waiting for second player..."), true); + markDirty(); + return; + } + + if (player2 == null && !uuid.equals(player1)) { + player2 = uuid; + Szar.c4ActivePlayers.put(uuid, pos); + player.sendMessage(Text.literal("§aYou are §eYellow§a! Game starting!"), true); + ServerPlayerEntity p1 = player.getServer().getPlayerManager().getPlayer(player1); + if (p1 != null) p1.sendMessage(Text.literal("§aSecond player joined! Your turn!"), true); + openScreenForBoth(player); + markDirty(); + return; + } + + if (player1 != null && player2 != null) { + spectators.add(uuid); + player.sendMessage(Text.literal("§7Spectating the match..."), true); + openScreen(player); + markDirty(); + } + } + + public void handleMove(ServerPlayerEntity player, int col) { + if (winner != 0) return; + if (col < 0 || col >= COLS) 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; + } + + // Find lowest empty row in this column (gravity) + int targetRow = -1; + for (int row = 0; row < ROWS; row++) { + if (board[row][col] == 0) { + targetRow = row; + break; + } + } + if (targetRow == -1) { + player.sendMessage(Text.literal("§cColumn is full!"), true); + return; + } + + board[targetRow][col] = playerNum; + currentTurn = (currentTurn == 1) ? 2 : 1; + checkWinner(); + markDirty(); + syncToPlayers(player.getServer()); + } + + private void checkWinner() { + // Check all 4-in-a-row directions + int[][] directions = {{0,1},{1,0},{1,1},{1,-1}}; + for (int row = 0; row < ROWS; row++) { + for (int col = 0; col < COLS; col++) { + int val = board[row][col]; + if (val == 0) continue; + for (int[] dir : directions) { + int count = 1; + for (int i = 1; i < 4; i++) { + int r = row + dir[0] * i; + int c = col + dir[1] * i; + if (r < 0 || r >= ROWS || c < 0 || c >= COLS) break; + if (board[r][c] != val) break; + count++; + } + if (count == 4) { + winner = val; + resetTimer = 100; // 5 seconds + markDirty(); + return; + } + } + } + } + + // Check draw — top row full + boolean full = true; + for (int col = 0; col < COLS; col++) { + if (board[ROWS - 1][col] == 0) { full = false; break; } + } + if (full) { + winner = 3; + resetTimer = 100; + markDirty(); + } + } + + 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.C4_OPEN_SCREEN, buf); + } + + 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.C4_STATE_SYNC, buf); + } + + public void writeStateToBuf(PacketByteBuf buf, UUID viewerUuid) { + for (int row = 0; row < ROWS; row++) + for (int col = 0; col < COLS; col++) + buf.writeInt(board[row][col]); + 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); + 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[ROWS][COLS]; + for (int row = 0; row < ROWS; row++) + for (int col = 0; col < COLS; col++) + s.board[row][col] = 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; + } + + public void resetGame(net.minecraft.server.MinecraftServer server) { + closeScreenForAll(server); + if (player1 != null) Szar.c4ActivePlayers.remove(player1); + if (player2 != null) Szar.c4ActivePlayers.remove(player2); + spectators.clear(); + board = new int[ROWS][COLS]; + 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.C4_CLOSE_SCREEN, PacketByteBufs.empty()); + } + + @Override + public void writeNbt(NbtCompound nbt) { + super.writeNbt(nbt); + int[] flat = new int[ROWS * COLS]; + for (int r = 0; r < ROWS; r++) + for (int c = 0; c < COLS; c++) + flat[r * COLS + c] = board[r][c]; + nbt.putIntArray("Board", flat); + 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[] flat = nbt.getIntArray("Board"); + if (flat.length == ROWS * COLS) { + for (int r = 0; r < ROWS; r++) + for (int c = 0; c < COLS; c++) + board[r][c] = flat[r * COLS + c]; + } + 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); + } +} \ 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 766d7f7..63da258 100644 --- a/src/main/java/dev/tggamesyt/szar/Szar.java +++ b/src/main/java/dev/tggamesyt/szar/Szar.java @@ -385,6 +385,7 @@ public class Szar implements ModInitializer { entries.add(Szar.ALMOND_WATER); entries.add(Szar.KEBAB); entries.add(Szar.TIC_TAC_TOE_ITEM); + entries.add(Szar.CONNECT_FOUR_ITEM); // crazy weponary entries.add(Szar.BULLET_ITEM); entries.add(Szar.AK47); @@ -398,11 +399,11 @@ public class Szar implements ModInitializer { entries.add(Szar.WHEEL); entries.add(Szar.PLANE); // drugs + entries.add(Szar.BEER); + entries.add(Szar.CHEMICAL_WORKBENCH_ITEM); entries.add(Szar.CANNABIS_ITEM); entries.add(Szar.WEED_ITEM); entries.add(Szar.WEED_JOINT_ITEM); - entries.add(Szar.CHEMICAL_WORKBENCH_ITEM); - entries.add(Szar.BEER); // war guys entries.add(Szar.HITTER_SPAWNEGG); entries.add(Szar.NAZI_SPAWNEGG); @@ -1337,6 +1338,15 @@ public class Szar implements ModInitializer { } }); }); + ServerPlayNetworking.registerGlobalReceiver(C4_MAKE_MOVE, (server, player, handler, buf, sender) -> { + BlockPos pos = buf.readBlockPos(); + int col = buf.readInt(); + server.execute(() -> { + if (player.getWorld().getBlockEntity(pos) instanceof ConnectFourBlockEntity be) { + be.handleMove(player, col); + } + }); + }); } public static final Block TIC_TAC_TOE_BLOCK = Registry.register( @@ -1359,7 +1369,27 @@ public class Szar implements ModInitializer { 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"); + public static final Block CONNECT_FOUR_BLOCK = Registry.register( + Registries.BLOCK, new Identifier(MOD_ID, "connectfour"), + new ConnectFourBlock(AbstractBlock.Settings.create().strength(2f)) + ); + public static final BlockItem CONNECT_FOUR_ITEM = Registry.register( + Registries.ITEM, new Identifier(MOD_ID, "connectfour"), + new BlockItem(CONNECT_FOUR_BLOCK, new Item.Settings()) + ); + public static final BlockEntityType CONNECT_FOUR_ENTITY = + Registry.register( + Registries.BLOCK_ENTITY_TYPE, new Identifier(MOD_ID, "connectfour"), + FabricBlockEntityTypeBuilder.create(ConnectFourBlockEntity::new, + CONNECT_FOUR_BLOCK).build() + ); + public static final Identifier C4_OPEN_SCREEN = new Identifier(MOD_ID, "c4_open"); + public static final Identifier C4_MAKE_MOVE = new Identifier(MOD_ID, "c4_move"); + public static final Identifier C4_STATE_SYNC = new Identifier(MOD_ID, "c4_sync"); + public static final Identifier C4_CLOSE_SCREEN = new Identifier(MOD_ID, "c4_close"); + + public static final Map c4ActivePlayers = new java.util.HashMap<>(); // 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/TicTacToeBlockEntity.java b/src/main/java/dev/tggamesyt/szar/TicTacToeBlockEntity.java index 66252a4..35cf7a6 100644 --- a/src/main/java/dev/tggamesyt/szar/TicTacToeBlockEntity.java +++ b/src/main/java/dev/tggamesyt/szar/TicTacToeBlockEntity.java @@ -52,7 +52,21 @@ public class TicTacToeBlockEntity extends BlockEntity { // Rejoin existing game if (uuid.equals(player1) || uuid.equals(player2)) { - openScreen(player); + if (player1 != null && player2 != null) { + // Game in progress — reopen screen + openScreen(player); + } else { + // Still waiting for second player — leave + if (uuid.equals(player1)) { + Szar.tttActivePlayers.remove(player1); // use c4ActivePlayers for ConnectFour + player1 = null; + } else { + Szar.tttActivePlayers.remove(player2); + player2 = null; + } + player.sendMessage(Text.literal("§7Left the game."), true); + markDirty(); + } return; } diff --git a/src/main/resources/assets/szar/blockstates/connectfour.json b/src/main/resources/assets/szar/blockstates/connectfour.json new file mode 100644 index 0000000..856252b --- /dev/null +++ b/src/main/resources/assets/szar/blockstates/connectfour.json @@ -0,0 +1,5 @@ +{ + "variants": { + "": { "model": "szar:block/connectfour" } + } +} \ 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 5b39264..d9635c1 100644 --- a/src/main/resources/assets/szar/lang/en_us.json +++ b/src/main/resources/assets/szar/lang/en_us.json @@ -187,5 +187,6 @@ "advancement.szar.backrooms.title": "Where did the world go?", "advancement.szar.backrooms.description": "Step into the Backrooms", - "block.szar.tictactoe": "Tic Tac Toe" + "block.szar.tictactoe": "Tic Tac Toe", + "block.szar.connectfour": "Connect Four" } diff --git a/src/main/resources/assets/szar/models/block/connectfour.json b/src/main/resources/assets/szar/models/block/connectfour.json new file mode 100644 index 0000000..9cb3a7b --- /dev/null +++ b/src/main/resources/assets/szar/models/block/connectfour.json @@ -0,0 +1,64 @@ +{ + "format_version": "1.9.0", + "credit": "Made with Blockbench", + "parent": "block/block", + "texture_size": [32, 32], + "textures": { + "0": "szar:block/plank", + "1": "szar:block/connect4", + "particle": "szar:block/plank" + }, + "elements": [ + { + "from": [0, 0, 0], + "to": [16, 12, 16], + "faces": { + "north": {"uv": [0, 0, 16, 12], "texture": "#0"}, + "east": {"uv": [0, 0, 16, 12], "texture": "#0"}, + "south": {"uv": [0, 0, 16, 12], "texture": "#0"}, + "west": {"uv": [0, 0, 16, 12], "texture": "#0"}, + "up": {"uv": [0, 0, 16, 16], "texture": "#0"}, + "down": {"uv": [0, 0, 16, 16], "texture": "#0"} + } + }, + { + "from": [7, 14, 0], + "to": [9, 24, 16], + "rotation": {"angle": 0, "axis": "y", "origin": [1, 12, 0]}, + "faces": { + "north": {"uv": [9, 0, 10, 5], "texture": "#1"}, + "east": {"uv": [0, 0, 8, 5], "texture": "#1"}, + "south": {"uv": [9, 5, 10, 10], "texture": "#1"}, + "west": {"uv": [0, 5, 8, 10], "texture": "#1"}, + "up": {"uv": [9, 8, 8, 0], "texture": "#1"}, + "down": {"uv": [9, 8, 8, 16], "texture": "#1"} + } + }, + { + "from": [7, 12, 0], + "to": [9, 14, 1], + "rotation": {"angle": 0, "axis": "y", "origin": [1, 10, 0]}, + "faces": { + "north": {"uv": [0, 10, 1, 11], "texture": "#1"}, + "east": {"uv": [2, 10, 2.5, 11], "texture": "#1"}, + "south": {"uv": [10, 0, 11, 1], "texture": "#1"}, + "west": {"uv": [10, 2, 10.5, 3], "texture": "#1"}, + "up": {"uv": [3.5, 10.5, 2.5, 10], "texture": "#1"}, + "down": {"uv": [11, 3, 10, 3.5], "texture": "#1"} + } + }, + { + "from": [7, 12, 15], + "to": [9, 14, 16], + "rotation": {"angle": 0, "axis": "y", "origin": [1, 10, 15]}, + "faces": { + "north": {"uv": [1, 10, 2, 11], "texture": "#1"}, + "east": {"uv": [3.5, 10, 4, 11], "texture": "#1"}, + "south": {"uv": [10, 1, 11, 2], "texture": "#1"}, + "west": {"uv": [10, 3.5, 10.5, 4.5], "texture": "#1"}, + "up": {"uv": [5, 10.5, 4, 10], "texture": "#1"}, + "down": {"uv": [11, 4.5, 10, 5], "texture": "#1"} + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/szar/models/item/connectfour.json b/src/main/resources/assets/szar/models/item/connectfour.json new file mode 100644 index 0000000..e55f00b --- /dev/null +++ b/src/main/resources/assets/szar/models/item/connectfour.json @@ -0,0 +1,3 @@ +{ + "parent": "szar:block/connectfour" +} \ No newline at end of file diff --git a/src/main/resources/assets/szar/textures/block/connect4.png b/src/main/resources/assets/szar/textures/block/connect4.png new file mode 100644 index 0000000000000000000000000000000000000000..e52c92f59b08ab803ec5aedde675773cf56f5063 GIT binary patch literal 257 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJZJsWUAr*7pgavXnDDZ6k;1YM1 zxkdbqSKCadOC_s2g{tK}{qEc@w!4ybd+#g8bg?r=YuS={<@0_2c(3WcXsvLrA@z%( z3+p&kTYx-o_AnZ zd{^{5y9e(X9|@+g-nc8!*(vg%%vnY4UE?(Af3A#s1RbT9GsW$jRAL%=W6sB)Uwsx# zTTuMTBf+4hK|p}X=>gE;|NpO6HoE|1uz`HQ%&_@zb?LDe?Y%$|22WQ%mvv4FO#nb} BWA*?5 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/szar/textures/gui/c4_hover.png b/src/main/resources/assets/szar/textures/gui/c4_hover.png new file mode 100644 index 0000000000000000000000000000000000000000..2fb4dbe9b9843be0ec5e8cbd355b396f86b3131d GIT binary patch literal 761 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{1_{@c^VF(ktM?KE$`W&6uz#> z^Ud=F9kH3BPagC1YXqD)xoPe0oXbD<*}HDbJkY#XNhsuuOi8FqQs>IK7ma$2w)O}EwT3+ncArw66Wq^OjPVY6U xhf1txWN7-f2`D~WHAD4~Lvz-iq!k>Wnf1eK)`q`4a0Hb8JYD@<);T3K0RR%f7FqxR literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/szar/textures/gui/c4_red.png b/src/main/resources/assets/szar/textures/gui/c4_red.png new file mode 100644 index 0000000000000000000000000000000000000000..7d798e80816586f0bf8d9a3caa843b5f5e6d70c0 GIT binary patch literal 694 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{1_zQfbSF(ktM?R7)G76Trai!It0g5Lf0ck$ev-jEgW zfvsOzp+faP*CY}DWei1GZ>(#>b}lp3E1s4e_3ZFkftjLi{IRVkBz*4fk#05#DSfQj zv4-t;Dq3qy-y;t&i+$i{+0T_(-+1CPrB@xuJA=<4V&X~=@)rZ+C0Ssc5x*Oq*#Sq d|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_zRAJq^1Z)ON#yB&7(&8+Eqi^|Sw*Hnlkbsu`MaJ7JR^Um4j$rIXN<=L=q{F-3T zz1iTzBD=S~ZG5|KUUqytG_d2S;qHAqSUimUOGD;tTF9 WB}=s_$C}=Ql8vXUpUXO@geCxezVZVA literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/szar/textures/gui/connectfour.png b/src/main/resources/assets/szar/textures/gui/connectfour.png new file mode 100644 index 0000000000000000000000000000000000000000..27ae74f5025ab00696a0e8356faddc69390b4a22 GIT binary patch literal 1775 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G!U;i$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBK4*bPWHAE+-(e7DJf6QI1*oAtGbExU!q>+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?#kqFe)HWQ{NEXW{QdoX_kXS$Q+<7XW8>nTG0$%QX6LitQp3S;L5V>?jTH3l zq4GhwpPdf&_5b$#zZhg+_h-h;nXkWA{m;-W{vJ5D+G_4Q`Py6m>r^BzZ2H5tVQS9S z`2BTf&z$*KF~_i6viSSl-zSWB7BVxW{Sg$n#lmoiDrnkTw%vF0jNgaJynR|@cYen1 zIXiz>JeheXf|u>gnmvpW{QIT>1-`|+KG>K#?LNb|xaEJ=n4WvKE)?#XJ!~yzj130V zLmN_0JLYe{eP*|6d$r~K^TyBg(%;$1RjQd!Yi2Um&S$t?^Vs?8@3#Y^=h_|jc_(|XCLB0(O1k0egY20=hn?RlClitO zlQ-dg>8bNkk8|EdU8O}(K*BS6o6MK8-Dmei7wwq-`KL|tyP{|B9#2d!O19K6*!+oY z!&>(Hr?+kUS+)0`eblKtwIR>v`7QtLKK~gT!C>l&zqjxIUt3*U`}e%_ zgV(!a^#0#r;Jd%Set$JjP3ZK~scFwP++{G!FTc+me>pMjnSSWSrcZAJ)?5PBI-ah6 JF6*2UngH<0WOD!j 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 6b4a702..d5cd026 100644 --- a/src/main/resources/data/minecraft/tags/blocks/mineable/axe.json +++ b/src/main/resources/data/minecraft/tags/blocks/mineable/axe.json @@ -2,6 +2,7 @@ "values": [ "szar:roulette", "szar:slot_machine", - "szar:tictactoe" + "szar:tictactoe", + "szar:connectfour" ] } \ No newline at end of file diff --git a/src/main/resources/data/szar/loot_tables/blocks/connectfour.json b/src/main/resources/data/szar/loot_tables/blocks/connectfour.json new file mode 100644 index 0000000..0cdecca --- /dev/null +++ b/src/main/resources/data/szar/loot_tables/blocks/connectfour.json @@ -0,0 +1,14 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "rolls": 1, + "entries": [ + { + "type": "minecraft:item", + "name": "szar:connectfour" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/data/szar/recipes/connectfour.json b/src/main/resources/data/szar/recipes/connectfour.json new file mode 100644 index 0000000..c8d0fad --- /dev/null +++ b/src/main/resources/data/szar/recipes/connectfour.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "RBR", + "BPB", + "RBR" + ], + "key": { + "R": { + "item": "minecraft:red_dye" + }, + "B": { + "item": "minecraft:yellow_dye" + }, + "P": { + "tag": "minecraft:planks" + } + }, + "result": { + "item": "szar:connectfour", + "count": 1 + } +} \ No newline at end of file