Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement multiplayer chess #488

Draft
wants to merge 5 commits into
base: fabric
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,6 @@ public static void registerCommands(CommandDispatcher<FabricClientCommandSource>
PosCommand.register(dispatcher);
CrackRNGCommand.register(dispatcher);
WeatherCommand.register(dispatcher);
ChessCommand.register(dispatcher);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,24 @@
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import com.mojang.logging.LogUtils;
import io.netty.buffer.Unpooled;
import net.earthcomputer.clientcommands.c2c.packets.MessageC2CPacket;
import net.earthcomputer.clientcommands.c2c.chess.ChessBoard;
import net.earthcomputer.clientcommands.c2c.chess.ChessGame;
import net.earthcomputer.clientcommands.c2c.chess.ChessPiece;
import net.earthcomputer.clientcommands.c2c.chess.ChessTeam;
import net.earthcomputer.clientcommands.c2c.packets.*;
import net.earthcomputer.clientcommands.command.ChessCommand;
import net.earthcomputer.clientcommands.features.RunnableClickEventActionHelper;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.PlayerListEntry;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.encryption.PlayerPublicKey;
import net.minecraft.network.encryption.PublicPlayerSession;
import net.minecraft.text.ClickEvent;
import net.minecraft.text.HoverEvent;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import org.joml.Vector2i;
import org.slf4j.Logger;

import java.security.PublicKey;
Expand All @@ -28,6 +37,8 @@ public class CCNetworkHandler implements CCPacketListener {

private static final Logger LOGGER = LogUtils.getLogger();

private static final MinecraftClient client = MinecraftClient.getInstance();

private CCNetworkHandler() {
}

Expand Down Expand Up @@ -81,7 +92,7 @@ public void sendPacket(C2CPacket packet, PlayerListEntry recipient) throws Comma
if (commandString.length() >= 256) {
throw MESSAGE_TOO_LONG_EXCEPTION.create(commandString.length());
}
MinecraftClient.getInstance().getNetworkHandler().sendChatCommand(commandString);
client.getNetworkHandler().sendChatCommand(commandString);
OutgoingPacketFilter.addPacket(packetString);
}

Expand All @@ -95,6 +106,118 @@ public void onMessageC2CPacket(MessageC2CPacket packet) {
prefix.append(Text.literal("]").formatted(Formatting.DARK_GRAY));
prefix.append(Text.literal(" "));
Text text = prefix.append(Text.translatable("ccpacket.messageC2CPacket.incoming", sender, message).formatted(Formatting.GRAY));
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(text);
client.inGameHud.getChatHud().addMessage(text);
}

@Override
public void onChessInviteC2CPacket(ChessInviteC2CPacket packet) {
String sender = packet.getSender();
PlayerListEntry player = client.getNetworkHandler().getPlayerList().stream()
.filter(p -> p.getProfile().getName().equalsIgnoreCase(sender))
.findFirst()
.orElse(null);
if (player == null) {
return;
}
ChessTeam chessTeam = packet.getChessTeam();
if (ChessCommand.currentGame != null) {
try {
ChessAcceptInviteC2CPacket acceptInvitePacket = new ChessAcceptInviteC2CPacket(client.getNetworkHandler().getProfile().getName(), false, chessTeam);
CCNetworkHandler.getInstance().sendPacket(acceptInvitePacket, player);
} catch (CommandSyntaxException e) {
e.printStackTrace();
return;
}
client.inGameHud.getChatHud().addMessage(Text.translatable("ccpacket.chessInviteC2CPacket.incoming.alreadyInGame", player.getProfile().getName()));
return;
}
MutableText body = Text.translatable("ccpacket.chessInviteC2CPacket.incoming", sender, chessTeam.asString());
Text accept = Text.translatable("ccpacket.chessInviteC2CPacket.incoming.accept").styled(style -> style
.withFormatting(Formatting.GREEN)
.withClickEvent(new ClickEvent(ClickEvent.Action.CHANGE_PAGE, RunnableClickEventActionHelper.registerCode(() -> {
try {
ChessAcceptInviteC2CPacket acceptInvitePacket = new ChessAcceptInviteC2CPacket(client.getNetworkHandler().getProfile().getName(), true, chessTeam);
CCNetworkHandler.getInstance().sendPacket(acceptInvitePacket, player);
client.inGameHud.getChatHud().addMessage(Text.translatable("ccpacket.chessAcceptInviteC2CPacket.outgoing.accept"));

ChessCommand.currentGame = new ChessGame(new ChessBoard(), player, chessTeam.other());
} catch (CommandSyntaxException e) {
e.printStackTrace();
}
})))
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.translatable("ccpacket.chessInviteC2CPacket.incoming.accept.hover"))));
Text deny = Text.translatable("ccpacket.chessInviteC2CPacket.incoming.deny").styled(style -> style
.withFormatting(Formatting.RED)
.withClickEvent(new ClickEvent(ClickEvent.Action.CHANGE_PAGE, RunnableClickEventActionHelper.registerCode(() -> {
try {
ChessAcceptInviteC2CPacket acceptInvitePacket = new ChessAcceptInviteC2CPacket(client.getNetworkHandler().getProfile().getName(), false, chessTeam);
CCNetworkHandler.getInstance().sendPacket(acceptInvitePacket, player);
client.inGameHud.getChatHud().addMessage(Text.translatable("ccpacket.chessAcceptInviteC2CPacket.outgoing.deny"));
} catch (CommandSyntaxException e) {
e.printStackTrace();
}
})))
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.translatable("ccpacket.chessInviteC2CPacket.incoming.deny.hover"))));
Text message = body.append(" ").append(accept).append("/").append(deny);
client.inGameHud.getChatHud().addMessage(message);
}

@Override
public void onChessAcceptInviteC2CPacket(ChessAcceptInviteC2CPacket packet) {
if (ChessCommand.lastInvitedPlayer == null) {
return;
}
String sender = packet.getSender();
if (!sender.equals(ChessCommand.lastInvitedPlayer)) {
return;
}
PlayerListEntry opponent = client.getNetworkHandler().getPlayerList().stream()
.filter(p -> p.getProfile().getName().equalsIgnoreCase(sender))
.findFirst()
.orElse(null);
if (opponent == null) {
return;
}
boolean accept = packet.isAccept();
Text text;
if (accept) {
text = Text.translatable("ccpacket.chessAcceptInviteC2CPacket.incoming.accept", sender);
ChessCommand.currentGame = new ChessGame(new ChessBoard(), opponent, packet.getChessTeam());
} else {
text = Text.translatable("ccpacket.chessAcceptInviteC2CPacket.incoming.deny", sender);
}
client.inGameHud.getChatHud().addMessage(text);
}

@Override
public void onChessBoardUpdateC2CPacket(ChessBoardUpdateC2CPacket packet) {
if (ChessCommand.currentGame == null) {
return;
}
int fromX = packet.getFromX();
int fromY = packet.getFromY();
int toX = packet.getToX();
int toY = packet.getToY();
ChessPiece piece = ChessCommand.currentGame.getBoard().getPieceAt(fromX, fromY);
String sender = ChessCommand.currentGame.getOpponent().getProfile().getName();
Text text;
if (ChessCommand.currentGame.move(piece, new Vector2i(toX, toY))) {
//noinspection ConstantConditions
text = Text.translatable("ccpacket.chessBoardUpdateC2CPacket.incoming", sender, piece.getName(), ChessBoard.indexToFile(toX), toY + 1);
} else {
text = Text.translatable("ccpacket.chessBoardUpdateC2CPacket.incoming.invalid", sender);
}
client.inGameHud.getChatHud().addMessage(text);
}

@Override
public void onChessResignC2CPacket(ChessResignC2CPacket packet) {
if (ChessCommand.currentGame == null) {
return;
}
String sender = ChessCommand.currentGame.getOpponent().getProfile().getName();
Text text = Text.translatable("ccpacket.chessResignC2CPacket.incoming", sender);
ChessCommand.currentGame = null;
client.inGameHud.getChatHud().addMessage(text);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import net.earthcomputer.clientcommands.c2c.packets.MessageC2CPacket;
import net.earthcomputer.clientcommands.c2c.packets.*;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.Util;
import org.jetbrains.annotations.Nullable;
Expand All @@ -18,6 +18,10 @@ public class CCPacketHandler {

static {
CCPacketHandler.register(MessageC2CPacket.class, MessageC2CPacket::new);
CCPacketHandler.register(ChessInviteC2CPacket.class, ChessInviteC2CPacket::new);
CCPacketHandler.register(ChessAcceptInviteC2CPacket.class, ChessAcceptInviteC2CPacket::new);
CCPacketHandler.register(ChessBoardUpdateC2CPacket.class, ChessBoardUpdateC2CPacket::new);
CCPacketHandler.register(ChessResignC2CPacket.class, ChessResignC2CPacket::new);
}

public static <P extends C2CPacket> void register(Class<P> packet, Function<PacketByteBuf, P> packetFactory) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
package net.earthcomputer.clientcommands.c2c;

import net.earthcomputer.clientcommands.c2c.packets.MessageC2CPacket;
import net.earthcomputer.clientcommands.c2c.packets.*;

public interface CCPacketListener {
void onMessageC2CPacket(MessageC2CPacket packet);

void onChessInviteC2CPacket(ChessInviteC2CPacket packet);

void onChessAcceptInviteC2CPacket(ChessAcceptInviteC2CPacket packet);

void onChessBoardUpdateC2CPacket(ChessBoardUpdateC2CPacket packet);

void onChessResignC2CPacket(ChessResignC2CPacket packet);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package net.earthcomputer.clientcommands.c2c.chess;

import net.earthcomputer.clientcommands.c2c.chess.pieces.*;
import org.joml.Vector2i;

import org.jetbrains.annotations.Nullable;

public class ChessBoard {

private final ChessPiece[][] pieces = new ChessPiece[8][8];

private static final char[] indexToFile = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'};

public ChessBoard() {
final ChessTeam white = ChessTeam.WHITE;
this.addPiece(0, 0, white, RookChessPiece::new);
this.addPiece(1, 0, white, KnightChessPiece::new);
this.addPiece(2, 0, white, BishopChessPiece::new);
this.addPiece(3, 0, white, QueenChessPiece::new);
this.addPiece(4, 0, white, KingChessPiece::new);
this.addPiece(5, 0, white, BishopChessPiece::new);
this.addPiece(6, 0, white, KnightChessPiece::new);
this.addPiece(7, 0, white, RookChessPiece::new);
for (int x = 0; x < 8; x++) {
this.addPiece(x, 1, white, PawnChessPiece::new);
}

final ChessTeam black = ChessTeam.BLACK;
this.addPiece(0, 7, black, RookChessPiece::new);
this.addPiece(1, 7, black, KnightChessPiece::new);
this.addPiece(2, 7, black, BishopChessPiece::new);
this.addPiece(3, 7, black, QueenChessPiece::new);
this.addPiece(4, 7, black, KingChessPiece::new);
this.addPiece(5, 7, black, BishopChessPiece::new);
this.addPiece(6, 7, black, KnightChessPiece::new);
this.addPiece(7, 7, black, RookChessPiece::new);
for (int x = 0; x < 8; x++) {
this.addPiece(x, 6, black, PawnChessPiece::new);
}
}

public <P extends ChessPiece> void addPiece(int x, int y, ChessTeam team, ChessPieceFactory<P> factory) {
this.pieces[x][y] = factory.create(this, team, new Vector2i(x, y));
}

public void addPiece(ChessPiece piece) {
Vector2i position = piece.getPosition();
this.pieces[position.x][position.y] = piece;
}

ChessPiece[][] getPieces() {
return this.pieces;
}

public KingChessPiece getKing(ChessTeam team) {
for (ChessPiece[] file : this.pieces) {
for (ChessPiece piece : file) {
if (piece instanceof KingChessPiece king) {
if (king.team == team) {
return king;
}
}
}
}
return null;
}

@Nullable
public ChessPiece getPieceAt(Vector2i square) {
return this.getPieceAt(square.x, square.y);
}

@Nullable
public ChessPiece getPieceAt(int x, int y) {
return this.pieces[x][y];
}

public boolean isOccupied(Vector2i square) {
return this.getPieceAt(square) != null;
}

/**
* Try to move a piece.
* @param piece the piece to move
* @param target the square to move to
* @return {@code true} if the move was successful, {@code false} otherwise
*/
public boolean tryMove(ChessPiece piece, Vector2i target) {
if (piece.canMoveTo(target)) {
piece.moveTo(target);
return true;
}
return false;
}

public static char indexToFile(int x) {
return indexToFile[x];
}
}

@FunctionalInterface
interface ChessPieceFactory<T extends ChessPiece> {
T create(ChessBoard board, ChessTeam team, Vector2i position);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package net.earthcomputer.clientcommands.c2c.chess;

import net.minecraft.client.network.PlayerListEntry;
import org.joml.Vector2i;

public class ChessGame {

private final ChessBoard board;
private final PlayerListEntry opponent;
private final ChessTeam team;
private ChessTeam teamToPlay = ChessTeam.WHITE;

public ChessGame(ChessBoard board, PlayerListEntry opponent, ChessTeam team) {
this.board = board;
this.opponent = opponent;
this.team = team;
}

public ChessBoard getBoard() {
return this.board;
}

public PlayerListEntry getOpponent() {
return this.opponent;
}

public ChessTeam getChessTeam() {
return this.team;
}

public boolean move(ChessPiece piece, Vector2i target) {
if (piece == null) {
return false;
}
if (piece.team == this.teamToPlay) {
if (this.board.tryMove(piece, target)) {
this.teamToPlay = this.teamToPlay.other();
return true;
}
}
return false;
}
}
Loading