package me.desht.chesscraft.chess; import chesspresso.Chess; import chesspresso.move.Move; import chesspresso.position.ImmutablePosition; import chesspresso.position.PositionChangeListener; import chesspresso.position.PositionListener; import me.desht.chesscraft.*; import me.desht.chesscraft.chess.pieces.ChessSet; import me.desht.chesscraft.chess.pieces.PieceDesigner; import me.desht.chesscraft.chess.player.ChessPlayer; import me.desht.chesscraft.controlpanel.*; import me.desht.chesscraft.enums.BoardRotation; import me.desht.chesscraft.enums.GameState; import me.desht.chesscraft.exceptions.ChessException; import me.desht.chesscraft.util.EconomyUtil; import me.desht.chesscraft.util.TerrainBackup; import me.desht.dhutils.*; import me.desht.dhutils.block.CraftMassBlockUpdate; import me.desht.dhutils.block.MassBlockUpdate; import me.desht.dhutils.cuboid.Cuboid; import me.desht.dhutils.cuboid.Cuboid.CuboidDirection; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.configuration.Configuration; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.MemoryConfiguration; import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.entity.Player; import org.bukkit.material.MaterialData; import java.io.File; import java.util.*; import java.util.Map.Entry; public class BoardView implements PositionListener, PositionChangeListener, GameListener, ConfigurationSerializable, ChessPersistable, ConfigurationListener { private static final String DEFAULT_STAKE = "defaultstake"; private static final String LOCK_STAKE = "lockstake"; private static final String DEFAULT_TC = "defaulttc"; private static final String LOCK_TC = "locktc"; private static final String OVERRIDE_PIECE_STYLE = "overridepiecestyle"; private static final String BOARD_STYLE = "boardstyle"; // map attribute names as typed in-game to the corresponding key in the save file // backward compatibility is such fun! private static final Map<String, String> attr2save = new HashMap<String, String>(); private static final Map<String, String> save2attr = new HashMap<String, String>(); static { attr2save.put(DEFAULT_STAKE, "defaultStake"); attr2save.put(LOCK_STAKE, "lockStake"); attr2save.put(DEFAULT_TC, "defaultTcSpec"); attr2save.put(LOCK_TC, "lockTcSpec"); attr2save.put(OVERRIDE_PIECE_STYLE, "pieceStyle"); attr2save.put(BOARD_STYLE, "boardStyle"); for (String k : attr2save.keySet()) { save2attr.put(attr2save.get(k), k); } } private final String name; private final ControlPanel controlPanel; private final ChessBoard chessBoard; private final String worldName; private final String savedGameName; private ChessGame game = null; // null indicates board not currently used by any game private PersistableLocation teleportOutDest; private final AttributeCollection attributes; public BoardView(String boardName, Location origin, String bStyle, String pStyle) throws ChessException { this(boardName, origin, BoardRotation.getRotation(origin), bStyle, pStyle); } public BoardView(String boardName, Location origin, BoardRotation rotation, String bStyle, String pStyle) throws ChessException { this.name = boardName; if (BoardViewManager.getManager().boardViewExists(name)) { throw new ChessException(Messages.getString("BoardView.boardExists")); } attributes = new AttributeCollection(this); registerAttributes(); attributes.set(BOARD_STYLE, bStyle); attributes.set(OVERRIDE_PIECE_STYLE, pStyle); chessBoard = new ChessBoard(origin, rotation, bStyle, pStyle); controlPanel = new ControlPanel(this); worldName = chessBoard.getA1Center().getWorld().getName(); savedGameName = ""; teleportOutDest = null; } private void registerAttributes() { attributes.registerAttribute(DEFAULT_STAKE, -1.0, "Default stake for games on this board"); attributes.registerAttribute(LOCK_STAKE, false, "Disallow changing of stake by players"); attributes.registerAttribute(DEFAULT_TC, "", "Default time control for games on this board"); attributes.registerAttribute(LOCK_TC, false, "Disallow changing of time control by players"); attributes.registerAttribute(OVERRIDE_PIECE_STYLE, "", "Overridden piece style for this board"); attributes.registerAttribute(BOARD_STYLE, "", "Board style for this board"); } @SuppressWarnings("unchecked") public BoardView(ConfigurationSection conf) { this.name = conf.getString("name"); if (BoardViewManager.getManager().boardViewExists(name)) { throw new ChessException(Messages.getString("BoardView.boardExists")); } savedGameName = conf.getString("game", ""); attributes = new AttributeCollection(this); registerAttributes(); for (Entry<String, String> e : save2attr.entrySet()) { attributes.set(e.getValue(), conf.getString(e.getKey())); } List<?> origin = conf.getList("origin"); worldName = (String) origin.get(0); Location where = ChessPersistence.thawLocation(origin); if (where == null) { // world not available chessBoard = null; controlPanel = null; return; } BoardRotation dir = BoardRotation.getRotation(conf.getString("direction")); chessBoard = new ChessBoard(where, dir, (String)attributes.get(BOARD_STYLE), (String)attributes.get(OVERRIDE_PIECE_STYLE)); controlPanel = new ControlPanel(this); Map<String, String> m = (Map<String,String>)conf.get("designer"); if (m != null) { String designerName = m.get("setName"); String designerId = m.get("playerName"); if (designerName != null && !designerName.isEmpty()) { setDesigner(new PieceDesigner(this, designerName, UUID.fromString(designerId))); } } teleportOutDest = conf.contains("teleportOutDest") ? (PersistableLocation) conf.get("teleportOutDest") : null; } public boolean isWorldAvailable() { return chessBoard != null; } /** * Get the game name from the save file. This is set even if the game hasn't actually been loaded yet. * * @return the saved game name */ public String getSavedGameName() { return savedGameName; } /** * Get the world name from the save file. This is set even if the world is not loaded. * * @return the world name */ public String getWorldName() { return worldName; } @Override public Map<String, Object> serialize() { Map<String, Object> result = new HashMap<String, Object>(); result.put("name", name); result.put("game", game == null ? "" : game.getName()); result.put("origin", ChessPersistence.freezeLocation(chessBoard.getA1Center())); result.put("direction", chessBoard.getRotation().name()); Map<String, Object> d = new HashMap<String, Object>(); if (isDesigning()) { d.put("setName", chessBoard.getDesigner().getSetName()); d.put("playerName", chessBoard.getDesigner().getPlayerId().toString()); } else { d.put("setName", ""); d.put("playerName", ""); } result.put("designer", d); for (String k : attributes.listAttributeKeys(false)) { result.put(attr2save.get(k), attributes.get(k)); } if (teleportOutDest != null) { result.put("teleportOutDest", teleportOutDest); } return result; } public static BoardView deserialize(Map<String, Object> map) throws ChessException { Configuration conf = new MemoryConfiguration(); for (Entry<String, Object> e : map.entrySet()) { if (!conf.contains(e.getKey())) { conf.set(e.getKey(), e.getValue()); } } return new BoardView(conf); } public void tick() { if (game != null) { ChessPlayer player = game.getPlayerToMove(); if (player != null) { updateClock(player.getColour()); } game.tick(); } } private void updateClock(int colour) { if (game.getState() != GameState.RUNNING) { return; } TwoPlayerClock clock = game.getClock(); clock.tick(); getControlPanel().updateClock(colour, clock.getClockString(colour)); ChessPlayer cp = game.getPlayer(colour); if (clock.getRemainingTime(colour) <= 0) { try { ChessPlayer other = game.getPlayer(Chess.otherPlayer(colour)); game.winByDefault(other.getColour()); } catch (ChessException e) { LogUtils.severe("unexpected exception: " + e.getMessage(), e); } } else { cp.timeControlCheck(); } } public void save() { ChessCraft.getInstance().getPersistenceHandler().savePersistable("board", this); } public String getName() { return name; } public ChessBoard getChessBoard() { return chessBoard; } public File getSaveDirectory() { return DirectoryStructure.getBoardPersistDirectory(); } public String getBoardStyleName() { return chessBoard.getBoardStyleName(); } public String getPieceStyleName() { return chessBoard.getPieceStyleName(); } public ChessGame getGame() { return game; } public double getDefaultStake() { double defaultStake = (Double) attributes.get(DEFAULT_STAKE); return defaultStake >= 0.0 ? defaultStake : ChessCraft.getInstance().getConfig().getDouble("stake.default", 0.0); } public void setGame(ChessGame game) { MassBlockUpdate mbu = CraftMassBlockUpdate.createMassBlockUpdater(ChessCraft.getInstance(), chessBoard.getBoard().getWorld()); this.game = game; if (game != null) { game.getPosition().addPositionListener(this); game.getPosition().addPositionChangeListener(this); game.addGameListener(this); Move lastMove = game.getPosition().getLastMove(); if (lastMove != null) { chessBoard.highlightSquares(lastMove.getFromSqi(), lastMove.getToSqi()); } chessBoard.getChessSet().syncToPosition(game.getPosition(), chessBoard); getControlPanel().getTcDefs().addCustomSpec(game.getTimeControl().getSpec()); } else { chessBoard.highlightSquares(Chess.NO_ROW, Chess.NO_COL); chessBoard.getBoard() .shift(CuboidDirection.Up, 1) .expand(CuboidDirection.Up, chessBoard.getBoardStyle().getHeight() - 1) .fill(new MaterialData(Material.AIR), mbu); chessBoard.getChessSet().syncToPosition(null, chessBoard); attributes.set(DEFAULT_TC, getDefaultTcSpec()); chessBoard.getChessSet().syncToPosition(null, chessBoard); System.out.println("board cleared: " + this.getName()); } mbu.notifyClients(); controlPanel.repaintClocks(); controlPanel.repaintControls(); save(); } public void setTeleportDestination(Location loc) { teleportOutDest = loc == null ? null : new PersistableLocation(loc); save(); } public Location getTeleportDestination() { return teleportOutDest == null ? null : teleportOutDest.getLocation(); } public boolean hasTeleportDestination() { return teleportOutDest != null; } public Location getA1Square() { return chessBoard.getA1Corner(); } public ControlPanel getControlPanel() { return controlPanel; } public int getFrameWidth() { return chessBoard.getBoardStyle().getFrameWidth(); } public int getSquareSize() { return chessBoard.getBoardStyle().getSquareSize(); } public int getHeight() { return chessBoard.getBoardStyle().getHeight(); } public int getLightLevel() { return chessBoard.getBoardStyle().getLightLevel(); } public MaterialData getBlackSquareMaterial() { return chessBoard.getBoardStyle().getBlackSquareMaterial(); } public MaterialData getWhiteSquareMaterial() { return chessBoard.getBoardStyle().getWhiteSquareMaterial(); } public MaterialData getFrameMaterial() { return chessBoard.getBoardStyle().getFrameMaterial(); } public MaterialData getControlPanelMaterial() { return chessBoard.getBoardStyle().getControlPanelMaterial(); } public MaterialData getEnclosureMaterial() { return chessBoard.getBoardStyle().getEnclosureMaterial(); } public MaterialData getStrutsMaterial() { return chessBoard.getBoardStyle().getStrutsMaterial(); } public BoardRotation getRotation() { return chessBoard.getRotation(); } public boolean isDesigning() { return chessBoard.isDesigning(); } public void setDesigner(PieceDesigner designer) { chessBoard.setDesigner(designer); save(); } public String getDefaultTcSpec() { String defaultTcSpec = (String) attributes.get(DEFAULT_TC); return defaultTcSpec.isEmpty() ? ChessCraft.getInstance().getConfig().getString("time_control.default") : defaultTcSpec; } public boolean getLockTcSpec() { return (Boolean) attributes.get(LOCK_TC); } public boolean getLockStake() { return (Boolean) attributes.get(LOCK_STAKE); } public void paintAll() { MassBlockUpdate mbu = CraftMassBlockUpdate.createMassBlockUpdater(ChessCraft.getInstance(), getChessBoard().getBoard().getWorld()); chessBoard.paintAll(mbu); controlPanel.repaintAll(mbu); if (game != null) { chessBoard.paintChessPieces(game.getPosition()); } mbu.notifyClients(); } /** * Get the bounds of the chess board itself (not including the frame). * * @return the bounds of the chess board */ public Cuboid getBounds() { return chessBoard.getBoard(); } /** * Get the region that encloses the outer extremities of this board, which * includes the frame and enclosure. * * @return The outermost bounding box - outer edge of the frame */ public Cuboid getOuterBounds() { return chessBoard.getFullBoard(); } //--------------------- Chesspresso PositionListener impl. -------------------------------- @Override public void castlesChanged(int castles) { // Ignored } @Override public void halfMoveClockChanged(int halfMoveClock) { controlPanel.updateHalfMoveClock(halfMoveClock); } @Override public void plyNumberChanged(int plyNumber) { controlPanel.updatePlyCount(plyNumber); } @Override public void sqiEPChanged(int sqiEP) { // Ignored } @Override public void squareChanged(int sqi, int stone) { int col = Chess.sqiToCol(sqi); int row = Chess.sqiToRow(sqi); chessBoard.paintChessPiece(row, col, stone); } @Override public void toPlayChanged(int toPlay) { controlPanel.updateToMoveIndicator(toPlay); } //-----------------Chesspresso PositionChangeListener impl. ---------------------- @Override public void notifyPositionChanged(ImmutablePosition position) { // Ignored } @Override public void notifyMoveDone(ImmutablePosition position, short move) { int fromSqi = Move.getFromSqi(move); int toSqi = Move.getToSqi(move); Location loc = chessBoard.getSquare(Chess.sqiToRow(toSqi), Chess.sqiToCol(toSqi)).getCenter(); if (Move.isCapturing(move)) { // TODO: handle differently for entity chess sets? ChessCraft.getInstance().getFX().playEffect(loc, "piece_captured"); } else { getGame().getPlayer(Chess.WHITE).playEffect("piece_moved"); getGame().getPlayer(Chess.BLACK).playEffect("piece_moved"); } if (Move.isEPMove(move)) { // en passant capture - the piece to actually capture isn't in the square we're moving to int captureSqi = toSqi + (getGame().getPosition().getToPlay() == Chess.WHITE ? 8 : -8); chessBoard.moveChessPiece(fromSqi, toSqi, captureSqi, Chess.NO_STONE); } else { chessBoard.moveChessPiece(fromSqi, toSqi, toSqi, Move.isPromotion(move) ? position.getStone(toSqi) : Chess.NO_STONE); } if (Move.isCastle(move)) { // if the king has done a castling move, also move the rook to the right place int rookFrom, rookTo; switch (toSqi) { case 2: rookFrom = 0; rookTo = 3; break; // white, queen's side case 6: rookFrom = 7; rookTo = 5; break; // white, king's side case 58: rookFrom = 56; rookTo = 59; break; // black, queen's side case 62: rookFrom = 63; rookTo = 61; break; // black, king's side default: rookFrom = rookTo = Chess.NO_SQUARE; // should never happen } if (rookFrom != Chess.NO_SQUARE) { chessBoard.moveChessPiece(rookFrom, rookTo, rookTo, Chess.NO_STONE); } } pieceRidingCheck(fromSqi, toSqi); getChessBoard().setSelectedSquare(Chess.NO_SQUARE); getChessBoard().highlightSquares(fromSqi, toSqi); } @Override public void notifyMoveUndone(ImmutablePosition position) { // Repaint the selected & last-move indicators getChessBoard().setSelectedSquare(Chess.NO_SQUARE); Move m = getGame().getChesspressoGame().getLastMove(); if (m != null) { getChessBoard().highlightSquares(m.getFromSqi(), m.getToSqi()); } else { getChessBoard().highlightSquares(Chess.NO_SQUARE, Chess.NO_SQUARE); } getControlPanel().updatePlyCount(getGame().getChesspressoGame().getCurrentPly()); getControlPanel().repaintAll(null); // for entity sets, we need to ensure a redraw is done ChessSet cs = getChessBoard().getChessSet(); if (cs.hasMovablePieces()) { cs.syncToPosition(getGame().getPosition(), getChessBoard()); } } // ------------------------------------------------------------------------------- /** * Check for players standing on the piece that is being moved, and move them with the piece. */ private void pieceRidingCheck(int fromSqi, int toSqi) { if (!ChessCraft.getInstance().getConfig().getBoolean("effects.piece_riding") || !chessBoard.getChessSet().canRide()) { return; } Cuboid cFrom = chessBoard.getPieceRegion(Chess.sqiToRow(fromSqi), Chess.sqiToCol(fromSqi)); for (Player p : chessBoard.getA1Corner().getWorld().getPlayers()) { Location loc = p.getLocation(); if (cFrom.contains(loc) && loc.getY() > cFrom.getLowerY()) { Cuboid cTo = chessBoard.getPieceRegion(Chess.sqiToRow(toSqi), Chess.sqiToCol(toSqi)); int xOff = cTo.getLowerX() - cFrom.getLowerX(); int zOff = cTo.getLowerZ() - cFrom.getLowerZ(); loc.add(xOff, 0, zOff); p.teleport(loc); } } } public boolean isOnBoard(Location loc, int minHeight, int maxHeight) { Cuboid bounds = getBounds().shift(CuboidDirection.Up, minHeight).expand(CuboidDirection.Up, maxHeight - minHeight); return bounds.contains(loc); } /** * Check if the location is a part of the board itself. * * @param loc location to check * @return true if the location is part of the board itself */ public boolean isOnBoard(Location loc) { return isOnBoard(loc, 0, 0); } /** * Check if the location is above the board but below the enclosure roof. * * @param loc location to check * @return true if the location is above the board AND within the board's height range */ public boolean isAboveBoard(Location loc) { return isOnBoard(loc, 1, chessBoard.getBoardStyle().getHeight()); } /** * Check if this is somewhere within the board bounds. * * @param loc location to check * @param fudge fudge factor - check within a slightly larger area * @return true if the location is *anywhere* within the board <br> * including frame & enclosure */ public boolean isPartOfBoard(Location loc, int fudge) { Cuboid o = chessBoard.getFullBoard(); if (fudge != 0) o = o.outset(CuboidDirection.Both, fudge); return o.contains(loc); } public boolean isPartOfBoard(Location loc) { return isPartOfBoard(loc, 0); } public boolean isControlPanel(Location loc) { // outsetting the cuboid allows the signs on the panel to be targeted too return controlPanel.getPanelBlocks().outset(CuboidDirection.Horizontal, 1).contains(loc); } /** * Check if the given location is OK for designing on by the given player. * * @param player the player to check * @param location the location to check * @return true is designing is OK here */ public boolean canDesignHere(Player player, Location location) { if (!isDesigning() || !PermissionUtils.isAllowedTo(player, "chesscraft.designer")) { return false; } if (!isAboveBoard(location)) { return false; } int sqi = chessBoard.getSquareAt(location); return (sqi >= 0 && sqi <= 4) || (sqi >= 8 && sqi <= 12) || sqi == 48 || (sqi >= 56 && sqi <= 60); } /** * Get the Chesspresso square index (sqi) of the given Location. * * @param loc The Location to check * @return The sqi of the Location or Chess.NO_SQUARE if not on the board */ public int getSquareAt(Location loc) { return chessBoard.getSquareAt(loc); } /** * Called by the board view manager when the board is permanently deleted */ void restoreTerrain() { boolean restored = false; // signs can get dropped otherwise getControlPanel().removeSigns(); if (ChessCraft.getInstance().getWorldEdit() != null) { // WorldEdit will take care of changes being pushed to client restored = TerrainBackup.reload(this); } if (!restored) { // we couldn't restore the original terrain - just set the board to air chessBoard.clearAll(); } } /** * Find a safe location to teleport a player out of this board. * * @return The location */ public Location findSafeLocationOutside() { Location dest = chessBoard.getFullBoard().getLowerNE(); dest.add(-1, 0, 1); return dest.getWorld().getHighestBlockAt(dest).getLocation(); } public void reloadStyle() throws ChessException { chessBoard.reloadStyles(); } public List<String> getBoardDetail() { List<String> res = new ArrayList<String>(); String bullet = MessagePager.BULLET + ChatColor.YELLOW; Cuboid bounds = getOuterBounds(); String gameName = getGame() != null ? getGame().getName() : Messages.getString("ChessCommandExecutor.noGame"); res.add(Messages.getString("ChessCommandExecutor.boardDetail.board", getName())); res.add(bullet + Messages.getString("ChessCommandExecutor.boardDetail.boardExtents", MiscUtil.formatLocation(bounds.getLowerNE()), MiscUtil.formatLocation(bounds.getUpperSW()))); res.add(bullet + Messages.getString("ChessCommandExecutor.boardDetail.game", gameName)); // TODO: yeah, we lie about the direction of the board here. Blame Mojang/Bukkit for changing the meaning of the compass points res.add(bullet + Messages.getString("ChessCommandExecutor.boardDetail.boardOrientation", getRotation().getLeft().toString())); res.add(bullet + Messages.getString("ChessCommandExecutor.boardDetail.boardStyle", getBoardStyleName())); res.add(bullet + Messages.getString("ChessCommandExecutor.boardDetail.pieceStyle", getPieceStyleName())); res.add(bullet + Messages.getString("ChessCommandExecutor.boardDetail.squareSize", getSquareSize(), getWhiteSquareMaterial(), getBlackSquareMaterial())); res.add(bullet + Messages.getString("ChessCommandExecutor.boardDetail.frameWidth", getFrameWidth(), getFrameMaterial())); res.add(bullet + Messages.getString("ChessCommandExecutor.boardDetail.enclosure", getEnclosureMaterial())); res.add(bullet + Messages.getString("ChessCommandExecutor.boardDetail.struts", getStrutsMaterial())); res.add(bullet + Messages.getString("ChessCommandExecutor.boardDetail.height", getHeight())); res.add(bullet + Messages.getString("ChessCommandExecutor.boardDetail.lightLevel", getLightLevel())); String lockStakeStr = getLockStake() ? Messages.getString("ChessCommandExecutor.boardDetail.locked") : ""; res.add(bullet + Messages.getString("ChessCommandExecutor.boardDetail.defaultStake", EconomyUtil.formatStakeStr(getDefaultStake()), lockStakeStr)); String lockTcStr = getLockTcSpec() ? Messages.getString("ChessCommandExecutor.boardDetail.locked") : ""; res.add(bullet + Messages.getString("ChessCommandExecutor.boardDetail.defaultTimeControl", getDefaultTcSpec(), lockTcStr)); String dest = hasTeleportDestination() ? MiscUtil.formatLocation(getTeleportDestination()) : "-"; res.add(bullet + Messages.getString("ChessCommandExecutor.boardDetail.teleportDest", dest)); if (chessBoard.getDesigner() != null) { res.add(bullet + Messages.getString("ChessCommandExecutor.designMode", chessBoard.getDesigner().getSetName())); } String setComment = getChessBoard().getChessSet().getComment(); if (setComment != null && !setComment.isEmpty()) { for (String s : setComment.split("\n")) { res.add(ChatColor.YELLOW + s); } } return res; } public void summonPlayer(Player player) { if (isPartOfBoard(player.getLocation())) { return; // already there } Location loc = getControlPanel().getTeleportLocation(); ChessCraft.getInstance().getPlayerTracker().teleportPlayer(player, loc); if (game != null) { ChessGameManager.getManager().setCurrentGame(player, game); } } public AttributeCollection getAttributes() { return attributes; } @Override public Object onConfigurationValidate(ConfigurationManager configurationManager, String key, Object oldVal, Object newVal) { if (key.equals(DEFAULT_TC) && !newVal.toString().isEmpty()) { new TimeControl(newVal.toString()); // force validation of the spec } return newVal; } @Override public void onConfigurationChanged(ConfigurationManager configurationManager, String key, Object oldVal, Object newVal) { if (key.equals(DEFAULT_TC) && getControlPanel() != null) { getControlPanel().setTimeControl(newVal.toString()); } else if (key.equals(BOARD_STYLE) && chessBoard != null) { chessBoard.setBoardStyle(newVal.toString()); } else if (key.equals(OVERRIDE_PIECE_STYLE) && chessBoard != null) { chessBoard.setChessSet(newVal.toString()); chessBoard.getChessSet().syncToPosition(getGame() == null ? null : getGame().getPosition(), chessBoard); } } public void defaultTimeControlChanged() { String spec = (String) getAttributes().get(DEFAULT_TC); if (spec.isEmpty()) { getControlPanel().setTimeControl(""); } } @Override public void gameStateChanged(GameState state) { if (state == GameState.RUNNING) { if (ChessCraft.getInstance().getConfig().getBoolean("auto_teleport_on_start")) { getGame().getPlayer(Chess.WHITE).teleport(this); getGame().getPlayer(Chess.BLACK).teleport(this); } } getControlPanel().repaintControls(); } @Override public boolean tryTimeControlChange(String tcSpec) { return !getLockTcSpec(); } @Override public void timeControlChanged(String spec) { ControlPanel cp = getControlPanel(); cp.getTcDefs().addCustomSpec(spec); cp.getSignButton(TimeControlButton.class).repaint(); updateClock(Chess.WHITE); updateClock(Chess.BLACK); } @Override public boolean tryStakeChange(double newStake) { return !getLockStake(); } @Override public void stakeChanged(double newStake) { getControlPanel().getSignButton(StakeButton.class).repaint(); } @Override public void playerAdded(ChessPlayer cp) { if (cp != null) { getControlPanel().repaintControls(); if (ChessCraft.getInstance().getConfig().getBoolean("auto_teleport_on_join")) { cp.teleport(this); } else { cp.alert(Messages.getString("ChessCommandExecutor.canTeleport", game.getName())); } } } @Override public void gameDeleted() { setGame(null); } @Override public void promotionPieceChanged(ChessPlayer chessPlayer, int promotionPiece) { getControlPanel().getSignButton(chessPlayer.getColour() == Chess.WHITE ? PromoteWhiteButton.class : PromoteBlackButton.class).repaint(); } }