chess
@@ -52,6 +52,7 @@ dependencies {
|
|||||||
modImplementation("com.terraformersmc:modmenu:${project.modmenu_version}")
|
modImplementation("com.terraformersmc:modmenu:${project.modmenu_version}")
|
||||||
|
|
||||||
implementation 'com.github.bhlangonijr:chesslib:1.3.6'
|
implementation 'com.github.bhlangonijr:chesslib:1.3.6'
|
||||||
|
include 'com.github.bhlangonijr:chesslib:1.3.3'
|
||||||
}
|
}
|
||||||
|
|
||||||
processResources {
|
processResources {
|
||||||
|
|||||||
294
src/client/java/dev/tggamesyt/szar/client/ChessScreen.java
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
package dev.tggamesyt.szar.client;
|
||||||
|
|
||||||
|
import com.github.bhlangonijr.chesslib.Board;
|
||||||
|
import com.github.bhlangonijr.chesslib.Piece;
|
||||||
|
import com.github.bhlangonijr.chesslib.Side;
|
||||||
|
import com.github.bhlangonijr.chesslib.Square;
|
||||||
|
import com.github.bhlangonijr.chesslib.move.Move;
|
||||||
|
import dev.tggamesyt.szar.ChessBlockEntity;
|
||||||
|
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.*;
|
||||||
|
|
||||||
|
public class ChessScreen extends Screen {
|
||||||
|
|
||||||
|
// Board background texture
|
||||||
|
private static final Identifier BOARD_TEX =
|
||||||
|
new Identifier("szar", "textures/gui/chess_board.png");
|
||||||
|
|
||||||
|
// Piece textures — one per piece type
|
||||||
|
// Naming: chess_wp.png (white pawn), chess_bn.png (black knight) etc.
|
||||||
|
private static final Map<Piece, Identifier> PIECE_TEXTURES = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
String[] colors = {"w", "b"};
|
||||||
|
String[] types = {"p", "n", "b", "r", "q", "k"};
|
||||||
|
Piece[] whitePieces = {Piece.WHITE_PAWN, Piece.WHITE_KNIGHT, Piece.WHITE_BISHOP,
|
||||||
|
Piece.WHITE_ROOK, Piece.WHITE_QUEEN, Piece.WHITE_KING};
|
||||||
|
Piece[] blackPieces = {Piece.BLACK_PAWN, Piece.BLACK_KNIGHT, Piece.BLACK_BISHOP,
|
||||||
|
Piece.BLACK_ROOK, Piece.BLACK_QUEEN, Piece.BLACK_KING};
|
||||||
|
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
PIECE_TEXTURES.put(whitePieces[i],
|
||||||
|
new Identifier("szar", "textures/gui/chess_w" + types[i] + ".png"));
|
||||||
|
PIECE_TEXTURES.put(blackPieces[i],
|
||||||
|
new Identifier("szar", "textures/gui/chess_b" + types[i] + ".png"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int CELL = 24; // pixels per square
|
||||||
|
private static final int BOARD_PIXELS = CELL * 8; // 192
|
||||||
|
private static final int GUI_WIDTH = BOARD_PIXELS + 16;
|
||||||
|
private static final int GUI_HEIGHT = BOARD_PIXELS + 32;
|
||||||
|
private static final int BOARD_OFFSET_X = 8;
|
||||||
|
private static final int BOARD_OFFSET_Y = 8;
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
private static final int LIGHT_SQUARE = 0xFFF0D9B5;
|
||||||
|
private static final int DARK_SQUARE = 0xFFB58863;
|
||||||
|
private static final int SELECTED = 0xAA7FC97F;
|
||||||
|
private static final int VALID_MOVE = 0xAA7FC97F;
|
||||||
|
private static final int LAST_MOVE = 0xAA99CC66;
|
||||||
|
|
||||||
|
private ChessBlockEntity.State state;
|
||||||
|
private final BlockPos blockPos;
|
||||||
|
private final UUID localPlayer;
|
||||||
|
private boolean isSpectator;
|
||||||
|
|
||||||
|
private Square selectedSquare = null;
|
||||||
|
private Set<Square> validMoveTargets = new HashSet<>();
|
||||||
|
|
||||||
|
public ChessScreen(BlockPos pos, ChessBlockEntity.State state) {
|
||||||
|
super(Text.literal("Chess"));
|
||||||
|
this.blockPos = pos;
|
||||||
|
this.state = state;
|
||||||
|
this.localPlayer = MinecraftClient.getInstance().player.getUuid();
|
||||||
|
this.isSpectator = state.isSpectator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateState(ChessBlockEntity.State newState) {
|
||||||
|
this.state = newState;
|
||||||
|
this.isSpectator = newState.isSpectator;
|
||||||
|
// Clear selection on state update
|
||||||
|
selectedSquare = null;
|
||||||
|
validMoveTargets.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
|
||||||
|
renderBackground(context);
|
||||||
|
|
||||||
|
int bx = (this.width - GUI_WIDTH) / 2 + BOARD_OFFSET_X;
|
||||||
|
int by = (this.height - GUI_HEIGHT) / 2 + BOARD_OFFSET_Y;
|
||||||
|
|
||||||
|
Board board = new Board();
|
||||||
|
board.loadFromFen(state.fen);
|
||||||
|
|
||||||
|
// Draw squares
|
||||||
|
for (int rank = 0; rank < 8; rank++) {
|
||||||
|
for (int file = 0; file < 8; file++) {
|
||||||
|
int drawFile = state.isWhite ? file : (7 - file);
|
||||||
|
int drawRank = state.isWhite ? (7 - rank) : rank;
|
||||||
|
|
||||||
|
int sx = bx + drawFile * CELL;
|
||||||
|
int sy = by + drawRank * CELL;
|
||||||
|
|
||||||
|
boolean light = (file + rank) % 2 == 0;
|
||||||
|
int squareColor = light ? LIGHT_SQUARE : DARK_SQUARE;
|
||||||
|
|
||||||
|
// Check if selected or valid move target
|
||||||
|
Square sq = getSquare(file, rank);
|
||||||
|
if (sq != null && sq.equals(selectedSquare)) {
|
||||||
|
squareColor = SELECTED;
|
||||||
|
} else if (sq != null && validMoveTargets.contains(sq)) {
|
||||||
|
squareColor = VALID_MOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.fill(sx, sy, sx + CELL, sy + CELL, squareColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw pieces
|
||||||
|
for (int rank = 0; rank < 8; rank++) {
|
||||||
|
for (int file = 0; file < 8; file++) {
|
||||||
|
Square sq = getSquare(file, rank);
|
||||||
|
if (sq == null) continue;
|
||||||
|
|
||||||
|
Piece piece = board.getPiece(sq);
|
||||||
|
if (piece == null || piece == Piece.NONE) continue;
|
||||||
|
|
||||||
|
Identifier tex = PIECE_TEXTURES.get(piece);
|
||||||
|
if (tex == null) continue;
|
||||||
|
|
||||||
|
int drawFile = state.isWhite ? file : (7 - file);
|
||||||
|
int drawRank = state.isWhite ? (7 - rank) : rank;
|
||||||
|
|
||||||
|
int sx = bx + drawFile * CELL + 1;
|
||||||
|
int sy = by + drawRank * CELL + 1;
|
||||||
|
|
||||||
|
context.drawTexture(tex, sx, sy, 0, 0,
|
||||||
|
CELL - 2, CELL - 2, CELL - 2, CELL - 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw rank/file labels
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
String fileLabel = String.valueOf((char)('a' + (state.isWhite ? i : 7 - i)));
|
||||||
|
String rankLabel = String.valueOf(state.isWhite ? 8 - i : i + 1);
|
||||||
|
context.drawText(this.textRenderer, fileLabel,
|
||||||
|
bx + i * CELL + CELL / 2 - 3,
|
||||||
|
by + BOARD_PIXELS + 2, 0xFFFFFF, true);
|
||||||
|
context.drawText(this.textRenderer, rankLabel,
|
||||||
|
bx - 7, by + i * CELL + CELL / 2 - 4,
|
||||||
|
0xFFFFFF, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status text
|
||||||
|
String status;
|
||||||
|
if (state.winner == 1) status = "§fWhite wins!";
|
||||||
|
else if (state.winner == 2) status = "§8Black wins!";
|
||||||
|
else if (state.winner == 3) status = "§7Draw! " + state.statusMessage;
|
||||||
|
else if (!state.statusMessage.isEmpty()) status = "§e" + state.statusMessage;
|
||||||
|
else if (isSpectator) status = "§7Spectating...";
|
||||||
|
else {
|
||||||
|
Side sideToMove = board.getSideToMove();
|
||||||
|
boolean myTurn = (sideToMove == Side.WHITE && localPlayer.equals(state.player1))
|
||||||
|
|| (sideToMove == Side.BLACK && localPlayer.equals(state.player2));
|
||||||
|
status = myTurn ? "§aYour turn!" : "§7Opponent's turn...";
|
||||||
|
}
|
||||||
|
|
||||||
|
int statusX = (this.width - GUI_WIDTH) / 2 + GUI_WIDTH / 2
|
||||||
|
- this.textRenderer.getWidth(status) / 2;
|
||||||
|
int statusY = (this.height - GUI_HEIGHT) / 2 + BOARD_OFFSET_Y
|
||||||
|
+ BOARD_PIXELS + 16;
|
||||||
|
context.drawTextWithShadow(this.textRenderer, Text.literal(status),
|
||||||
|
statusX, statusY, 0xFFFFFF);
|
||||||
|
|
||||||
|
super.render(context, mouseX, mouseY, delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mouseClicked(double mouseX, double mouseY, int button) {
|
||||||
|
if (isSpectator || button != 0 || state.winner != 0)
|
||||||
|
return super.mouseClicked(mouseX, mouseY, button);
|
||||||
|
|
||||||
|
Board board = new Board();
|
||||||
|
board.loadFromFen(state.fen);
|
||||||
|
|
||||||
|
Side sideToMove = board.getSideToMove();
|
||||||
|
boolean myTurn = (sideToMove == Side.WHITE && localPlayer.equals(state.player1))
|
||||||
|
|| (sideToMove == Side.BLACK && localPlayer.equals(state.player2));
|
||||||
|
if (!myTurn) return super.mouseClicked(mouseX, mouseY, button);
|
||||||
|
|
||||||
|
int bx = (this.width - GUI_WIDTH) / 2 + BOARD_OFFSET_X;
|
||||||
|
int by = (this.height - GUI_HEIGHT) / 2 + BOARD_OFFSET_Y;
|
||||||
|
|
||||||
|
// Check if click is on the board
|
||||||
|
if (mouseX < bx || mouseX >= bx + BOARD_PIXELS
|
||||||
|
|| mouseY < by || mouseY >= by + BOARD_PIXELS)
|
||||||
|
return super.mouseClicked(mouseX, mouseY, button);
|
||||||
|
|
||||||
|
int drawFile = (int)((mouseX - bx) / CELL);
|
||||||
|
int drawRank = (int)((mouseY - by) / CELL);
|
||||||
|
|
||||||
|
// Convert draw coords back to board coords
|
||||||
|
int file = state.isWhite ? drawFile : (7 - drawFile);
|
||||||
|
int rank = state.isWhite ? (7 - drawRank) : drawRank;
|
||||||
|
|
||||||
|
Square clicked = getSquare(file, rank);
|
||||||
|
if (clicked == null) return super.mouseClicked(mouseX, mouseY, button);
|
||||||
|
|
||||||
|
if (selectedSquare == null) {
|
||||||
|
// Select a piece
|
||||||
|
Piece piece = board.getPiece(clicked);
|
||||||
|
if (piece != Piece.NONE) {
|
||||||
|
boolean ownPiece = (sideToMove == Side.WHITE && piece.getPieceSide() == Side.WHITE)
|
||||||
|
|| (sideToMove == Side.BLACK && piece.getPieceSide() == Side.BLACK);
|
||||||
|
if (ownPiece) {
|
||||||
|
selectedSquare = clicked;
|
||||||
|
// Calculate valid moves for this piece
|
||||||
|
validMoveTargets.clear();
|
||||||
|
for (Move move : board.legalMoves()) {
|
||||||
|
if (move.getFrom().equals(clicked)) {
|
||||||
|
validMoveTargets.add(move.getTo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (clicked.equals(selectedSquare)) {
|
||||||
|
// Deselect
|
||||||
|
selectedSquare = null;
|
||||||
|
validMoveTargets.clear();
|
||||||
|
} else if (validMoveTargets.contains(clicked)) {
|
||||||
|
// Make the move
|
||||||
|
String uci = selectedSquare.value().toLowerCase()
|
||||||
|
+ clicked.value().toLowerCase();
|
||||||
|
|
||||||
|
// Auto-promote to queen if pawn reaches last rank
|
||||||
|
Piece moving = board.getPiece(selectedSquare);
|
||||||
|
if (moving == Piece.WHITE_PAWN && clicked.getRank().ordinal() == 7) {
|
||||||
|
uci += "q";
|
||||||
|
} else if (moving == Piece.BLACK_PAWN && clicked.getRank().ordinal() == 0) {
|
||||||
|
uci += "q";
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMove(uci);
|
||||||
|
selectedSquare = null;
|
||||||
|
validMoveTargets.clear();
|
||||||
|
} else {
|
||||||
|
// Try selecting a different piece
|
||||||
|
Piece piece = board.getPiece(clicked);
|
||||||
|
if (piece != Piece.NONE) {
|
||||||
|
boolean ownPiece = (sideToMove == Side.WHITE && piece.getPieceSide() == Side.WHITE)
|
||||||
|
|| (sideToMove == Side.BLACK && piece.getPieceSide() == Side.BLACK);
|
||||||
|
if (ownPiece) {
|
||||||
|
selectedSquare = clicked;
|
||||||
|
validMoveTargets.clear();
|
||||||
|
for (Move move : board.legalMoves()) {
|
||||||
|
if (move.getFrom().equals(clicked)) {
|
||||||
|
validMoveTargets.add(move.getTo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectedSquare = null;
|
||||||
|
validMoveTargets.clear();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectedSquare = null;
|
||||||
|
validMoveTargets.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Square getSquare(int file, int rank) {
|
||||||
|
try {
|
||||||
|
String name = String.valueOf((char)('A' + file)) + (rank + 1);
|
||||||
|
return Square.valueOf(name);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMove(String uci) {
|
||||||
|
PacketByteBuf buf = PacketByteBufs.create();
|
||||||
|
buf.writeBlockPos(blockPos);
|
||||||
|
buf.writeString(uci);
|
||||||
|
ClientPlayNetworking.send(Szar.CHESS_MAKE_MOVE, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldPause() { return false; }
|
||||||
|
}
|
||||||
@@ -90,6 +90,35 @@ public class SzarClient implements ClientModInitializer {
|
|||||||
);
|
);
|
||||||
@Override
|
@Override
|
||||||
public void onInitializeClient() {
|
public void onInitializeClient() {
|
||||||
|
ClientPlayNetworking.registerGlobalReceiver(Szar.CHESS_OPEN_SCREEN, (client, handler, buf, sender) -> {
|
||||||
|
BlockPos pos = buf.readBlockPos();
|
||||||
|
ChessBlockEntity.State state = ChessBlockEntity.readStateFromBuf(buf);
|
||||||
|
client.execute(() -> {
|
||||||
|
if (client.currentScreen instanceof ChessScreen existing) {
|
||||||
|
existing.updateState(state);
|
||||||
|
} else {
|
||||||
|
client.setScreen(new ChessScreen(pos, state));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ClientPlayNetworking.registerGlobalReceiver(Szar.CHESS_STATE_SYNC, (client, handler, buf, sender) -> {
|
||||||
|
BlockPos pos = buf.readBlockPos();
|
||||||
|
ChessBlockEntity.State state = ChessBlockEntity.readStateFromBuf(buf);
|
||||||
|
client.execute(() -> {
|
||||||
|
if (client.currentScreen instanceof ChessScreen screen) {
|
||||||
|
screen.updateState(state);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ClientPlayNetworking.registerGlobalReceiver(Szar.CHESS_CLOSE_SCREEN, (client, handler, buf, sender) -> {
|
||||||
|
client.execute(() -> {
|
||||||
|
if (client.currentScreen instanceof ChessScreen) {
|
||||||
|
client.setScreen(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
// TTT
|
// TTT
|
||||||
ClientPlayNetworking.registerGlobalReceiver(Szar.TTT_OPEN_SCREEN, (client, handler, buf, sender) -> {
|
ClientPlayNetworking.registerGlobalReceiver(Szar.TTT_OPEN_SCREEN, (client, handler, buf, sender) -> {
|
||||||
BlockPos pos = buf.readBlockPos();
|
BlockPos pos = buf.readBlockPos();
|
||||||
|
|||||||
@@ -1,4 +1,74 @@
|
|||||||
package dev.tggamesyt.szar;
|
package dev.tggamesyt.szar;
|
||||||
|
|
||||||
public class ChessBlock {
|
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 ChessBlock extends BlockWithEntity {
|
||||||
|
|
||||||
|
private static final VoxelShape SHAPE = VoxelShapes.union(
|
||||||
|
VoxelShapes.cuboid(0f, 0f, 0f, 1f, 0.75f, 1f)
|
||||||
|
);
|
||||||
|
|
||||||
|
public ChessBlock(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 ChessBlockEntity(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 ChessBlockEntity be)) return ActionResult.PASS;
|
||||||
|
be.handlePlayerJoin(sp, pos);
|
||||||
|
return ActionResult.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(
|
||||||
|
World world, BlockState state, BlockEntityType<T> type) {
|
||||||
|
if (world.isClient) return null;
|
||||||
|
return type == Szar.CHESS_ENTITY
|
||||||
|
? (w, pos, s, be) -> ChessBlockEntity.tick(w, pos, s, (ChessBlockEntity) be)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBreak(World world, BlockPos pos, BlockState state, PlayerEntity player) {
|
||||||
|
if (!world.isClient && world.getBlockEntity(pos) instanceof ChessBlockEntity be) {
|
||||||
|
be.closeScreenForAll(world.getServer());
|
||||||
|
if (be.player1 != null) Szar.chessActivePlayers.remove(be.player1);
|
||||||
|
if (be.player2 != null) Szar.chessActivePlayers.remove(be.player2);
|
||||||
|
}
|
||||||
|
super.onBreak(world, pos, state, player);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,308 @@
|
|||||||
package dev.tggamesyt.szar;
|
package dev.tggamesyt.szar;
|
||||||
|
|
||||||
public class ChessBlockEntity {
|
import com.github.bhlangonijr.chesslib.Board;
|
||||||
|
import com.github.bhlangonijr.chesslib.Side;
|
||||||
|
import com.github.bhlangonijr.chesslib.move.Move;
|
||||||
|
import com.github.bhlangonijr.chesslib.Square;
|
||||||
|
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 ChessBlockEntity extends BlockEntity {
|
||||||
|
public String DEFAULT_POSITION = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
||||||
|
// FEN string stores full board state — chesslib handles all logic
|
||||||
|
public String fen = DEFAULT_POSITION;
|
||||||
|
public UUID player1 = null; // white
|
||||||
|
public UUID player2 = null; // black
|
||||||
|
public int winner = 0; // 0=ongoing, 1=white, 2=black, 3=draw
|
||||||
|
public String statusMessage = "";
|
||||||
|
public Set<UUID> spectators = new HashSet<>();
|
||||||
|
public int resetTimer = -1;
|
||||||
|
|
||||||
|
public ChessBlockEntity(BlockPos pos, BlockState state) {
|
||||||
|
super(Szar.CHESS_ENTITY, pos, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void tick(World world, BlockPos pos, BlockState state,
|
||||||
|
ChessBlockEntity 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.chessActivePlayers.get(uuid);
|
||||||
|
if (activePos != null && !activePos.equals(pos)) {
|
||||||
|
player.sendMessage(Text.literal("§cYou are already in a chess game elsewhere!"), true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uuid.equals(player1) || uuid.equals(player2)) {
|
||||||
|
if (player1 != null && player2 != null) {
|
||||||
|
openScreen(player);
|
||||||
|
} else {
|
||||||
|
// Leave if waiting
|
||||||
|
if (uuid.equals(player1)) {
|
||||||
|
Szar.chessActivePlayers.remove(player1);
|
||||||
|
player1 = null;
|
||||||
|
} else {
|
||||||
|
Szar.chessActivePlayers.remove(player2);
|
||||||
|
player2 = null;
|
||||||
|
}
|
||||||
|
player.sendMessage(Text.literal("§7Left the game."), true);
|
||||||
|
markDirty();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player1 == null) {
|
||||||
|
player1 = uuid;
|
||||||
|
Szar.chessActivePlayers.put(uuid, pos);
|
||||||
|
player.sendMessage(Text.literal("§aYou are §fWhite§a! Waiting for second player..."), true);
|
||||||
|
markDirty();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player2 == null && !uuid.equals(player1)) {
|
||||||
|
player2 = uuid;
|
||||||
|
Szar.chessActivePlayers.put(uuid, pos);
|
||||||
|
player.sendMessage(Text.literal("§aYou are §8Black§a! Game starting!"), true);
|
||||||
|
ServerPlayerEntity p1 = player.getServer().getPlayerManager().getPlayer(player1);
|
||||||
|
if (p1 != null) p1.sendMessage(Text.literal("§aSecond player joined! Your turn (White)!"), 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, String uciMove) {
|
||||||
|
if (winner != 0) return;
|
||||||
|
|
||||||
|
UUID uuid = player.getUuid();
|
||||||
|
Board board = new Board();
|
||||||
|
board.loadFromFen(fen);
|
||||||
|
|
||||||
|
// Check it's the right player's turn
|
||||||
|
Side sideToMove = board.getSideToMove();
|
||||||
|
boolean isWhite = uuid.equals(player1);
|
||||||
|
boolean isBlack = uuid.equals(player2);
|
||||||
|
if (!isWhite && !isBlack) return;
|
||||||
|
if (sideToMove == Side.WHITE && !isWhite) {
|
||||||
|
player.sendMessage(Text.literal("§cNot your turn!"), true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sideToMove == Side.BLACK && !isBlack) {
|
||||||
|
player.sendMessage(Text.literal("§cNot your turn!"), true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate and apply move
|
||||||
|
try {
|
||||||
|
Square from = Square.valueOf(uciMove.substring(0, 2).toUpperCase());
|
||||||
|
Square to = Square.valueOf(uciMove.substring(2, 4).toUpperCase());
|
||||||
|
|
||||||
|
// Handle promotion — default to queen
|
||||||
|
String promotion = uciMove.length() > 4 ? uciMove.substring(4) : "";
|
||||||
|
Move move;
|
||||||
|
if (!promotion.isEmpty()) {
|
||||||
|
com.github.bhlangonijr.chesslib.Piece promoPiece =
|
||||||
|
sideToMove == Side.WHITE
|
||||||
|
? com.github.bhlangonijr.chesslib.Piece.WHITE_QUEEN
|
||||||
|
: com.github.bhlangonijr.chesslib.Piece.BLACK_QUEEN;
|
||||||
|
move = new Move(from, to, promoPiece);
|
||||||
|
} else {
|
||||||
|
move = new Move(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check move is legal
|
||||||
|
if (!board.legalMoves().contains(move)) {
|
||||||
|
player.sendMessage(Text.literal("§cIllegal move!"), true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
board.doMove(move);
|
||||||
|
fen = board.getFen();
|
||||||
|
|
||||||
|
// Check game end conditions
|
||||||
|
if (board.isMated()) {
|
||||||
|
winner = sideToMove == Side.WHITE ? 1 : 2;
|
||||||
|
statusMessage = (sideToMove == Side.WHITE ? "White" : "Black") + " wins by checkmate!";
|
||||||
|
resetTimer = 100;
|
||||||
|
} else if (board.isStaleMate()) {
|
||||||
|
winner = 3;
|
||||||
|
statusMessage = "Draw by stalemate!";
|
||||||
|
resetTimer = 100;
|
||||||
|
} else if (board.isInsufficientMaterial()) {
|
||||||
|
winner = 3;
|
||||||
|
statusMessage = "Draw by insufficient material!";
|
||||||
|
resetTimer = 100;
|
||||||
|
} else if (board.isRepetition()) {
|
||||||
|
winner = 3;
|
||||||
|
statusMessage = "Draw by repetition!";
|
||||||
|
resetTimer = 100;
|
||||||
|
} else if (board.isKingAttacked()) {
|
||||||
|
statusMessage = "Check!";
|
||||||
|
} else {
|
||||||
|
statusMessage = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
markDirty();
|
||||||
|
syncToPlayers(player.getServer());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
player.sendMessage(Text.literal("§cInvalid move format!"), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.CHESS_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.CHESS_STATE_SYNC, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeStateToBuf(PacketByteBuf buf, UUID viewerUuid) {
|
||||||
|
buf.writeString(fen);
|
||||||
|
buf.writeBoolean(player1 != null);
|
||||||
|
if (player1 != null) buf.writeUuid(player1);
|
||||||
|
buf.writeBoolean(player2 != null);
|
||||||
|
if (player2 != null) buf.writeUuid(player2);
|
||||||
|
buf.writeInt(winner);
|
||||||
|
buf.writeString(statusMessage);
|
||||||
|
boolean isSpectator = viewerUuid != null
|
||||||
|
&& !viewerUuid.equals(player1)
|
||||||
|
&& !viewerUuid.equals(player2);
|
||||||
|
buf.writeBoolean(isSpectator);
|
||||||
|
// Is this viewer playing white or black
|
||||||
|
boolean isWhite = viewerUuid != null && viewerUuid.equals(player1);
|
||||||
|
buf.writeBoolean(isWhite);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static State readStateFromBuf(PacketByteBuf buf) {
|
||||||
|
State s = new State();
|
||||||
|
s.fen = buf.readString();
|
||||||
|
if (buf.readBoolean()) s.player1 = buf.readUuid();
|
||||||
|
if (buf.readBoolean()) s.player2 = buf.readUuid();
|
||||||
|
s.winner = buf.readInt();
|
||||||
|
s.statusMessage = buf.readString();
|
||||||
|
s.isSpectator = buf.readBoolean();
|
||||||
|
s.isWhite = buf.readBoolean();
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class State {
|
||||||
|
public String fen;
|
||||||
|
public UUID player1, player2;
|
||||||
|
public int winner;
|
||||||
|
public String statusMessage;
|
||||||
|
public boolean isSpectator;
|
||||||
|
public boolean isWhite; // true = viewing from white's perspective
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetGame(net.minecraft.server.MinecraftServer server) {
|
||||||
|
closeScreenForAll(server);
|
||||||
|
if (player1 != null) Szar.chessActivePlayers.remove(player1);
|
||||||
|
if (player2 != null) Szar.chessActivePlayers.remove(player2);
|
||||||
|
spectators.clear();
|
||||||
|
fen = DEFAULT_POSITION;
|
||||||
|
player1 = null;
|
||||||
|
player2 = null;
|
||||||
|
winner = 0;
|
||||||
|
statusMessage = "";
|
||||||
|
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.CHESS_CLOSE_SCREEN, PacketByteBufs.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeNbt(NbtCompound nbt) {
|
||||||
|
super.writeNbt(nbt);
|
||||||
|
nbt.putString("Fen", fen);
|
||||||
|
if (player1 != null) nbt.putUuid("Player1", player1);
|
||||||
|
if (player2 != null) nbt.putUuid("Player2", player2);
|
||||||
|
nbt.putInt("Winner", winner);
|
||||||
|
nbt.putString("Status", statusMessage);
|
||||||
|
nbt.putInt("ResetTimer", resetTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readNbt(NbtCompound nbt) {
|
||||||
|
super.readNbt(nbt);
|
||||||
|
fen = nbt.getString("Fen");
|
||||||
|
if (fen.isEmpty()) fen = DEFAULT_POSITION;
|
||||||
|
if (nbt.containsUuid("Player1")) player1 = nbt.getUuid("Player1");
|
||||||
|
if (nbt.containsUuid("Player2")) player2 = nbt.getUuid("Player2");
|
||||||
|
winner = nbt.getInt("Winner");
|
||||||
|
statusMessage = nbt.getString("Status");
|
||||||
|
resetTimer = nbt.getInt("ResetTimer");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NbtCompound toInitialChunkDataNbt() { return createNbt(); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockEntityUpdateS2CPacket toUpdatePacket() {
|
||||||
|
return BlockEntityUpdateS2CPacket.create(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -386,6 +386,7 @@ public class Szar implements ModInitializer {
|
|||||||
entries.add(Szar.KEBAB);
|
entries.add(Szar.KEBAB);
|
||||||
entries.add(Szar.TIC_TAC_TOE_ITEM);
|
entries.add(Szar.TIC_TAC_TOE_ITEM);
|
||||||
entries.add(Szar.CONNECT_FOUR_ITEM);
|
entries.add(Szar.CONNECT_FOUR_ITEM);
|
||||||
|
entries.add(Szar.CHESS_ITEM);
|
||||||
// crazy weponary
|
// crazy weponary
|
||||||
entries.add(Szar.BULLET_ITEM);
|
entries.add(Szar.BULLET_ITEM);
|
||||||
entries.add(Szar.AK47);
|
entries.add(Szar.AK47);
|
||||||
@@ -1347,6 +1348,15 @@ public class Szar implements ModInitializer {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
ServerPlayNetworking.registerGlobalReceiver(CHESS_MAKE_MOVE, (server, player, handler, buf, sender) -> {
|
||||||
|
BlockPos pos = buf.readBlockPos();
|
||||||
|
String move = buf.readString(); // UCI format e.g. "e2e4"
|
||||||
|
server.execute(() -> {
|
||||||
|
if (player.getWorld().getBlockEntity(pos) instanceof ChessBlockEntity be) {
|
||||||
|
be.handleMove(player, move);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Block TIC_TAC_TOE_BLOCK = Registry.register(
|
public static final Block TIC_TAC_TOE_BLOCK = Registry.register(
|
||||||
@@ -1390,6 +1400,28 @@ public class Szar implements ModInitializer {
|
|||||||
public static final Identifier C4_CLOSE_SCREEN = new Identifier(MOD_ID, "c4_close");
|
public static final Identifier C4_CLOSE_SCREEN = new Identifier(MOD_ID, "c4_close");
|
||||||
|
|
||||||
public static final Map<UUID, BlockPos> c4ActivePlayers = new java.util.HashMap<>();
|
public static final Map<UUID, BlockPos> c4ActivePlayers = new java.util.HashMap<>();
|
||||||
|
|
||||||
|
public static final Block CHESS_BLOCK = Registry.register(
|
||||||
|
Registries.BLOCK, new Identifier(MOD_ID, "chess"),
|
||||||
|
new ChessBlock(AbstractBlock.Settings.create().strength(2f))
|
||||||
|
);
|
||||||
|
public static final BlockItem CHESS_ITEM = Registry.register(
|
||||||
|
Registries.ITEM, new Identifier(MOD_ID, "chess"),
|
||||||
|
new BlockItem(CHESS_BLOCK, new Item.Settings())
|
||||||
|
);
|
||||||
|
public static final BlockEntityType<ChessBlockEntity> CHESS_ENTITY =
|
||||||
|
Registry.register(
|
||||||
|
Registries.BLOCK_ENTITY_TYPE, new Identifier(MOD_ID, "chess"),
|
||||||
|
FabricBlockEntityTypeBuilder.create(ChessBlockEntity::new, CHESS_BLOCK).build()
|
||||||
|
);
|
||||||
|
|
||||||
|
public static final Identifier CHESS_OPEN_SCREEN = new Identifier(MOD_ID, "chess_open");
|
||||||
|
public static final Identifier CHESS_MAKE_MOVE = new Identifier(MOD_ID, "chess_move");
|
||||||
|
public static final Identifier CHESS_STATE_SYNC = new Identifier(MOD_ID, "chess_sync");
|
||||||
|
public static final Identifier CHESS_CLOSE_SCREEN = new Identifier(MOD_ID, "chess_close");
|
||||||
|
|
||||||
|
public static final Map<UUID, BlockPos> chessActivePlayers = new java.util.HashMap<>();
|
||||||
|
|
||||||
// Blocks
|
// Blocks
|
||||||
public static final TrackerBlock TRACKER_BLOCK = Registry.register(
|
public static final TrackerBlock TRACKER_BLOCK = Registry.register(
|
||||||
Registries.BLOCK, new Identifier(MOD_ID, "tracker"),
|
Registries.BLOCK, new Identifier(MOD_ID, "tracker"),
|
||||||
|
|||||||
5
src/main/resources/assets/szar/blockstates/chess.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"variants": {
|
||||||
|
"": { "model": "szar:block/chess" }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -188,5 +188,6 @@
|
|||||||
"advancement.szar.backrooms.description": "Step into the Backrooms",
|
"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"
|
"block.szar.connectfour": "Connect Four",
|
||||||
|
"block.szar.chess": "Chess"
|
||||||
}
|
}
|
||||||
|
|||||||
24
src/main/resources/assets/szar/models/block/chess.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"parent": "block/block",
|
||||||
|
"format_version": "1.9.0",
|
||||||
|
"credit": "Made with Blockbench",
|
||||||
|
"textures": {
|
||||||
|
"0": "szar:block/chess",
|
||||||
|
"1": "szar:block/plank",
|
||||||
|
"particle": "szar:block/chess"
|
||||||
|
},
|
||||||
|
"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"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
3
src/main/resources/assets/szar/models/item/chess.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"parent": "szar:block/chess"
|
||||||
|
}
|
||||||
BIN
src/main/resources/assets/szar/textures/block/chess.png
Normal file
|
After Width: | Height: | Size: 562 B |
BIN
src/main/resources/assets/szar/textures/gui/chess_bb.png
Normal file
|
After Width: | Height: | Size: 390 B |
BIN
src/main/resources/assets/szar/textures/gui/chess_bk.png
Normal file
|
After Width: | Height: | Size: 438 B |
BIN
src/main/resources/assets/szar/textures/gui/chess_bn.png
Normal file
|
After Width: | Height: | Size: 398 B |
BIN
src/main/resources/assets/szar/textures/gui/chess_bp.png
Normal file
|
After Width: | Height: | Size: 337 B |
BIN
src/main/resources/assets/szar/textures/gui/chess_bq.png
Normal file
|
After Width: | Height: | Size: 390 B |
BIN
src/main/resources/assets/szar/textures/gui/chess_br.png
Normal file
|
After Width: | Height: | Size: 379 B |
BIN
src/main/resources/assets/szar/textures/gui/chess_wb.png
Normal file
|
After Width: | Height: | Size: 376 B |
BIN
src/main/resources/assets/szar/textures/gui/chess_wk.png
Normal file
|
After Width: | Height: | Size: 403 B |
BIN
src/main/resources/assets/szar/textures/gui/chess_wn.png
Normal file
|
After Width: | Height: | Size: 381 B |
BIN
src/main/resources/assets/szar/textures/gui/chess_wp.png
Normal file
|
After Width: | Height: | Size: 313 B |
BIN
src/main/resources/assets/szar/textures/gui/chess_wq.png
Normal file
|
After Width: | Height: | Size: 378 B |
BIN
src/main/resources/assets/szar/textures/gui/chess_wr.png
Normal file
|
After Width: | Height: | Size: 378 B |
@@ -3,6 +3,7 @@
|
|||||||
"szar:roulette",
|
"szar:roulette",
|
||||||
"szar:slot_machine",
|
"szar:slot_machine",
|
||||||
"szar:tictactoe",
|
"szar:tictactoe",
|
||||||
"szar:connectfour"
|
"szar:connectfour",
|
||||||
|
"szar:chess"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
14
src/main/resources/data/szar/loot_tables/blocks/chess.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:block",
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"rolls": 1,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "szar:chess"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
23
src/main/resources/data/szar/recipes/chess.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:crafting_shaped",
|
||||||
|
"pattern": [
|
||||||
|
"RBR",
|
||||||
|
"BPB",
|
||||||
|
"RBR"
|
||||||
|
],
|
||||||
|
"key": {
|
||||||
|
"R": {
|
||||||
|
"item": "minecraft:white_dye"
|
||||||
|
},
|
||||||
|
"B": {
|
||||||
|
"item": "minecraft:black_dye"
|
||||||
|
},
|
||||||
|
"P": {
|
||||||
|
"tag": "minecraft:planks"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"item": "szar:chess",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
}
|
||||||