Search in sources :

Example 1 with SOCJoinGame

use of soc.message.SOCJoinGame in project JSettlers2 by jdmonin.

the class SOCGameHandler method joinGame.

/**
 * Client has been approved to join game; send JOINGAMEAUTH and the entire state of the game to client.
 * Unless <tt>isTakingOver</tt>, announce {@link SOCJoinGame} client join event to other players.
 *<P>
 * Does not add the client to the game's or server's list of players,
 * that should be done before calling this method.
 *<P>
 * Assumes NEWGAME (or NEWGAMEWITHOPTIONS) has already been sent out.
 * The game's first message<B>*</B> sent to the connecting client is JOINGAMEAUTH, unless isReset.
 *<P>
 * Among other messages, player names are sent via SITDOWN, and pieces on board
 * sent by PUTPIECE.  See comments here for further details.
 * If <tt>isTakingOver</tt>, some details are sent by calling
 * {@link #sitDown_sendPrivateInfo(SOCGame, Connection, int)}.
 * The group of messages sent here ends with GAMEMEMBERS, SETTURN and GAMESTATE.
 * Then, the entire game is sent a JOINGAME for the new game member.
 *<P>
 * *<B>I18N:</B> If the game has a {@link SOCScenario} and the client needs scenario info or localized strings
 * for the scenario name and description, {@link SOCScenarioInfo} or {@link SOCLocalizedStrings} is
 * sent before JOINGAMEAUTH.  This handles i18n and scenarios added or changed between the client's
 * and server's versions.
 *
 * @param gameData Game to join
 * @param c        The connection of joining client
 * @param isReset  Game is a board-reset of an existing game.  This is always false when
 *                 called from SOCServer instead of from inside the SOCGameHandler.
 * @param isTakingOver  Client is re-joining; this connection replaces an earlier one which
 *                      is defunct because of a network problem.
 *                      If <tt>isTakingOver</tt>, don't send anything to other players.
 *
 * @see SOCServer#connectToGame(Connection, String, Map)
 * @see SOCServer#createOrJoinGameIfUserOK(Connection, String, String, String, Map)
 */
@SuppressWarnings("unchecked")
public void joinGame(SOCGame gameData, Connection c, final boolean isReset, final boolean isTakingOver) {
    // If game's already started, true if any bot is seated (can be taken over)
    boolean hasRobot = false;
    String gameName = gameData.getName();
    final String cliName = c.getData();
    if (!isReset) {
        // First, send updated scenario info or localized strings if needed
        // (SOCScenarioInfo or SOCLocalizedStrings); checks c.getVersion(), scd.scenariosInfoSent etc.
        final String gameScen = gameData.getGameOptionStringValue("SC");
        if (gameScen != null)
            srv.sendGameScenarioInfo(gameScen, null, c, false);
        // Now, join game
        c.put(SOCJoinGameAuth.toCmd(gameName));
        c.put(SOCStatusMessage.toCmd(SOCStatusMessage.SV_OK, // "Welcome to Java Settlers of Catan!"
        c.getLocalized("member.welcome")));
    }
    // c.put(SOCGameState.toCmd(gameName, gameData.getGameState()));
    for (int i = 0; i < gameData.maxPlayers; i++) {
        /**
         * send them the already-seated player information;
         * if isReset, don't send, because sitDown will
         * be sent from resetBoardAndNotify.
         */
        if (!isReset) {
            SOCPlayer pl = gameData.getPlayer(i);
            String plName = pl.getName();
            if ((plName != null) && !gameData.isSeatVacant(i)) {
                final boolean isRobot = pl.isRobot();
                if (isRobot)
                    hasRobot = true;
                c.put(SOCSitDown.toCmd(gameName, plName, i, isRobot));
            }
        }
        /**
         * send the seat lock information
         */
        final SOCGame.SeatLockState sl = gameData.getSeatLock(i);
        if ((sl != SOCGame.SeatLockState.CLEAR_ON_RESET) || (c.getVersion() >= 2000))
            srv.messageToPlayer(c, new SOCSetSeatLock(gameName, i, sl));
        else
            // old client
            srv.messageToPlayer(c, new SOCSetSeatLock(gameName, i, SOCGame.SeatLockState.LOCKED));
    }
    c.put(getBoardLayoutMessage(gameData).toCmd());
    /**
     * if game hasn't started yet, each player's potentialSettlements are
     * identical, so send that info once for all players.
     * Otherwise send each player's unique potential settlement list,
     * to populate legal sets before sending any of their PutPieces.
     */
    if ((gameData.getGameState() < SOCGame.START1A) && (c.getVersion() >= SOCPotentialSettlements.VERSION_FOR_PLAYERNUM_ALL)) {
        final HashSet<Integer> psList = gameData.getPlayer(0).getPotentialSettlements();
        // Some boards may have multiple land areas.
        // See also below, and startGame which has very similar code.
        final HashSet<Integer>[] lan;
        final int pan;
        boolean addedPsList = false;
        if (gameData.hasSeaBoard) {
            final SOCBoardLarge bl = (SOCBoardLarge) gameData.getBoard();
            lan = bl.getLandAreasLegalNodes();
            pan = bl.getStartingLandArea();
            if ((lan != null) && (pan != 0) && !lan[pan].equals(psList)) {
                // If potentials != legals[startingLandArea], send as legals[0]
                lan[0] = psList;
                addedPsList = true;
            }
        } else {
            lan = null;
            pan = 0;
        }
        if (lan == null) {
            c.put(SOCPotentialSettlements.toCmd(gameName, -1, new ArrayList<Integer>(psList)));
        } else {
            c.put(SOCPotentialSettlements.toCmd(gameName, -1, pan, lan, SOCBoardAtServer.getLegalSeaEdges(gameData, -1)));
        }
        if (addedPsList)
            // Undo change to game's copy of landAreasLegalNodes
            lan[0] = null;
        if (gameData.isGameOptionSet(SOCGameOption.K_SC_CLVI))
            c.put(SOCPlayerElement.toCmd(gameName, -1, SOCPlayerElement.SET, SOCPlayerElement.SCENARIO_CLOTH_COUNT, ((SOCBoardLarge) (gameData.getBoard())).getCloth()));
    // individual villages' cloth counts are sent soon below
    } else {
        for (int pn = 0; pn < gameData.maxPlayers; ++pn) {
            final SOCPlayer pl = gameData.getPlayer(pn);
            final HashSet<Integer> psList = pl.getPotentialSettlements();
            // Some boards may have multiple land areas.
            // See also above, and startGame which has very similar code.
            final HashSet<Integer>[] lan;
            if (gameData.hasSeaBoard && (pn == 0)) {
                // send this info once, not per-player:
                // Note: Assumes all players have same legal nodes.
                final SOCBoardLarge bl = (SOCBoardLarge) gameData.getBoard();
                lan = bl.getLandAreasLegalNodes();
                if (lan != null)
                    lan[0] = psList;
            } else {
                lan = null;
            }
            if (lan == null) {
                c.put(SOCPotentialSettlements.toCmd(gameName, pn, new ArrayList<Integer>(psList)));
            } else {
                c.put(SOCPotentialSettlements.toCmd(gameName, pn, 0, lan, SOCBoardAtServer.getLegalSeaEdges(gameData, pn)));
                // Undo change to game's copy of landAreasLegalNodes
                lan[0] = null;
            }
        }
    }
    /**
     * If normal game play has started:
     * _SC_CLVI: Send updated Cloth counts for any changed villages.
     * _SC_FTRI: Send any changed Special Edges.
     */
    if (gameData.hasSeaBoard && (gameData.getGameState() >= SOCGame.ROLL_OR_CARD)) {
        final SOCBoardLarge bl = (SOCBoardLarge) gameData.getBoard();
        // SC_CLVI:
        final HashMap<Integer, SOCVillage> villages = bl.getVillages();
        if (villages != null)
            for (final SOCVillage vi : villages.values()) {
                final int cl = vi.getCloth();
                if (cl != SOCVillage.STARTING_CLOTH)
                    srv.messageToGame(gameName, new SOCPieceValue(gameName, vi.getCoordinates(), cl, 0));
            }
        // SC_FTRI:
        boolean sendEdgeChanges = bl.hasSpecialEdges();
        if (!sendEdgeChanges) {
            // check the board for any Special Edge layout part
            for (String ap : SOCBoardLarge.SPECIAL_EDGE_LAYOUT_PARTS) {
                if (bl.getAddedLayoutPart(ap) != null) {
                    sendEdgeChanges = true;
                    break;
                }
            }
        }
        if (sendEdgeChanges)
            joinGame_sendBoardSpecialEdgeChanges(gameData, bl, c);
    }
    /**
     * Send the current player number.
     * Before v2.0.00, this wasn't sent so early; was sent
     * just before SOCGameState and the "joined the game" text.
     * This earlier send has been tested against 1.1.07 (released 2009-10-31).
     */
    if (c.getVersion() >= SOCGameElements.MIN_VERSION)
        c.put(new SOCGameElements(gameName, SOCGameElements.CURRENT_PLAYER, gameData.getCurrentPlayerNumber()).toCmd());
    else
        c.put(SOCSetTurn.toCmd(gameName, gameData.getCurrentPlayerNumber()));
    /**
     * Send the game's Special Item info, if any, if game has started:
     */
    final String[] gameSITypes;
    if (gameData.getGameState() >= SOCGame.START1A) {
        Set<String> ty = gameData.getSpecialItemTypes();
        gameSITypes = (ty != null) ? ty.toArray(new String[ty.size()]) : null;
    } else {
        gameSITypes = null;
    }
    /**
     * Holds any special items shared between game and player. Those must be sent just once, not twice,
     * when per-game and then per-player special item info is sent. Per-player loop should check
     * {@code gameSItoPlayer.get(typeKey)[playerNumber].get(itemIndex)}; unused per-player lists
     * and typeKeys are null, so check each dereference; also check itemIndex versus list length.
     */
    final HashMap<String, ArrayList<SOCSpecialItem>[]> gameSItoPlayer;
    if (gameSITypes == null) {
        gameSItoPlayer = null;
    } else {
        gameSItoPlayer = new HashMap<String, ArrayList<SOCSpecialItem>[]>();
        for (int i = 0; i < gameSITypes.length; ++i) {
            final String tkey = gameSITypes[i];
            ArrayList<SOCSpecialItem> gsi = gameData.getSpecialItems(tkey);
            if (gsi == null)
                // shouldn't happen
                continue;
            final int L = gsi.size();
            for (// use this loop type to avoid ConcurrentModificationException if locking bug
            int gi = 0; // use this loop type to avoid ConcurrentModificationException if locking bug
            gi < L; // use this loop type to avoid ConcurrentModificationException if locking bug
            ++gi) {
                final SOCSpecialItem si = gsi.get(gi);
                if (si == null) {
                    c.put(new SOCSetSpecialItem(gameName, SOCSetSpecialItem.OP_CLEAR, tkey, gi, -1, -1).toCmd());
                    continue;
                }
                // player index, or -1: if pl != null, must search pl's items for a match
                int pi = -1;
                final SOCPlayer pl = si.getPlayer();
                if (pl != null) {
                    ArrayList<SOCSpecialItem> iList = pl.getSpecialItems(tkey);
                    if (iList != null) {
                        for (int k = 0; k < iList.size(); ++k) {
                            if (si == iList.get(k)) {
                                pi = k;
                                break;
                            }
                        }
                    }
                }
                c.put(new SOCSetSpecialItem(gameData, SOCSetSpecialItem.OP_SET, tkey, gi, pi, si).toCmd());
                if (pi != -1) {
                    // remember for use when sending per-player info
                    ArrayList<SOCSpecialItem>[] toAllPl = gameSItoPlayer.get(tkey);
                    if (toAllPl == null) {
                        toAllPl = new ArrayList[gameData.maxPlayers];
                        gameSItoPlayer.put(tkey, toAllPl);
                    }
                    ArrayList<SOCSpecialItem> iList = toAllPl[pl.getPlayerNumber()];
                    if (iList == null) {
                        iList = new ArrayList<SOCSpecialItem>();
                        toAllPl[pl.getPlayerNumber()] = iList;
                    }
                    int iLL = iList.size();
                    while (iLL <= pi) {
                        iList.add(null);
                        ++iLL;
                    }
                    iList.set(pi, si);
                }
            }
        }
    }
    /**
     * send the per-player information
     */
    for (int i = 0; i < gameData.maxPlayers; i++) {
        SOCPlayer pl = gameData.getPlayer(i);
        /**
         * send scenario info before any putpiece, so they know their
         * starting land areas and scenario events
         */
        int itm = pl.getSpecialVP();
        if (itm != 0) {
            srv.messageToPlayer(c, new SOCPlayerElement(gameName, i, SOCPlayerElement.SET, SOCPlayerElement.SCENARIO_SVP, itm));
            ArrayList<SOCPlayer.SpecialVPInfo> svpis = pl.getSpecialVPInfo();
            if (svpis != null)
                for (SOCPlayer.SpecialVPInfo svpi : svpis) srv.messageToPlayer(c, new SOCSVPTextMessage(gameName, i, svpi.svp, c.getLocalized(svpi.desc)));
        }
        itm = pl.getScenarioPlayerEvents();
        if (itm != 0)
            srv.messageToPlayer(c, new SOCPlayerElement(gameName, i, SOCPlayerElement.SET, SOCPlayerElement.SCENARIO_PLAYEREVENTS_BITMASK, itm));
        itm = pl.getScenarioSVPLandAreas();
        if (itm != 0)
            srv.messageToPlayer(c, new SOCPlayerElement(gameName, i, SOCPlayerElement.SET, SOCPlayerElement.SCENARIO_SVP_LANDAREAS_BITMASK, itm));
        itm = pl.getStartingLandAreasEncoded();
        if (itm != 0)
            srv.messageToPlayer(c, new SOCPlayerElement(gameName, i, SOCPlayerElement.SET, SOCPlayerElement.STARTING_LANDAREAS, itm));
        itm = pl.getCloth();
        if (itm != 0)
            srv.messageToPlayer(c, new SOCPlayerElement(gameName, i, SOCPlayerElement.SET, SOCPlayerElement.SCENARIO_CLOTH_COUNT, itm));
        // Send piece info even if player has left the game (pl.getName() == null).
        // This lets them see "their" pieces before srv.sitDown(), if they rejoin at same position.
        Enumeration<SOCPlayingPiece> piecesEnum = pl.getPieces().elements();
        while (piecesEnum.hasMoreElements()) {
            SOCPlayingPiece piece = piecesEnum.nextElement();
            if (piece.getType() == SOCPlayingPiece.CITY)
                c.put(SOCPutPiece.toCmd(gameName, i, SOCPlayingPiece.SETTLEMENT, piece.getCoordinates()));
            c.put(SOCPutPiece.toCmd(gameName, i, piece.getType(), piece.getCoordinates()));
        }
        // _SC_PIRI: special-case piece not part of getPieces
        {
            final SOCFortress piece = pl.getFortress();
            if (piece != null) {
                final int coord = piece.getCoordinates(), str = piece.getStrength();
                c.put(SOCPutPiece.toCmd(gameName, i, piece.getType(), coord));
                if (str != SOCFortress.STARTING_STRENGTH)
                    c.put(SOCPieceValue.toCmd(gameName, coord, str, 0));
            }
        }
        // _SC_PIRI: for display, send count of warships only after SOCShip pieces are sent
        itm = pl.getNumWarships();
        if (itm != 0)
            srv.messageToPlayer(c, new SOCPlayerElement(gameName, i, SOCPlayerElement.SET, SOCPlayerElement.SCENARIO_WARSHIP_COUNT, itm));
        /**
         * send node coord of the last settlement, resources,
         * knight cards played, number of playing pieces in hand
         */
        final int[] counts = new int[(gameData.hasSeaBoard) ? 7 : 6];
        counts[0] = pl.getLastSettlementCoord();
        // will send with SOCPlayerElement.UNKNOWN
        counts[1] = pl.getResources().getTotal();
        counts[2] = pl.getNumKnights();
        counts[3] = pl.getNumPieces(SOCPlayingPiece.ROAD);
        counts[4] = pl.getNumPieces(SOCPlayingPiece.SETTLEMENT);
        counts[5] = pl.getNumPieces(SOCPlayingPiece.CITY);
        if (gameData.hasSeaBoard)
            counts[6] = pl.getNumPieces(SOCPlayingPiece.SHIP);
        if (c.getVersion() >= SOCPlayerElements.MIN_VERSION) {
            c.put(new SOCPlayerElements(gameName, i, SOCPlayerElement.SET, (gameData.hasSeaBoard) ? ELEM_JOINGAME_WITH_PIECETYPES_SEA : ELEM_JOINGAME_WITH_PIECETYPES_CLASSIC, counts).toCmd());
        } else {
            c.put(SOCLastSettlement.toCmd(gameName, i, counts[0]));
            // client too old for SOCPlayerElement.LAST_SETTLEMENT_NODE
            for (int j = 1; j < counts.length; ++j) c.put(SOCPlayerElement.toCmd(gameName, i, SOCPlayerElement.SET, ELEM_JOINGAME_WITH_PIECETYPES_CLASSIC[j], counts[j]));
        }
        final int numDevCards = pl.getInventory().getTotal();
        final int unknownType;
        if (c.getVersion() >= SOCDevCardConstants.VERSION_FOR_NEW_TYPES)
            unknownType = SOCDevCardConstants.UNKNOWN;
        else
            unknownType = SOCDevCardConstants.UNKNOWN_FOR_VERS_1_X;
        final String cardUnknownCmd = SOCDevCardAction.toCmd(gameName, i, SOCDevCardAction.ADD_OLD, unknownType);
        for (int j = 0; j < numDevCards; j++) {
            c.put(cardUnknownCmd);
        }
        if (gameSITypes != null) {
            for (int j = 0; j < gameSITypes.length; ++j) {
                final String tkey = gameSITypes[j];
                ArrayList<SOCSpecialItem> plsi = pl.getSpecialItems(tkey);
                if (plsi == null)
                    // shouldn't happen
                    continue;
                // pi loop body checks gameSItoPlayer to see if already sent (object shared with game)
                final ArrayList<SOCSpecialItem>[] toAllPl = gameSItoPlayer.get(tkey);
                final ArrayList<SOCSpecialItem> iList = (toAllPl != null) ? toAllPl[i] : null;
                final int L = plsi.size();
                for (// use this loop type to avoid ConcurrentModificationException
                int pi = 0; // use this loop type to avoid ConcurrentModificationException
                pi < L; // use this loop type to avoid ConcurrentModificationException
                ++pi) {
                    final SOCSpecialItem si = plsi.get(pi);
                    if (si == null) {
                        c.put(new SOCSetSpecialItem(gameName, SOCSetSpecialItem.OP_CLEAR, tkey, -1, pi, i).toCmd());
                        continue;
                    }
                    if ((iList != null) && (iList.size() > pi) && (iList.get(pi) == si))
                        // already sent (shared with game)
                        continue;
                    c.put(new SOCSetSpecialItem(gameData, SOCSetSpecialItem.OP_SET, tkey, -1, pi, si).toCmd());
                }
            }
        }
        if ((i == 0) && (c.getVersion() < SOCGameElements.MIN_VERSION)) {
            // per-game data, send once; send here only if client is
            // too old to send together with other game elements,
            // otherwise send soon with longest road / largest army
            c.put(SOCFirstPlayer.toCmd(gameName, gameData.getFirstPlayer()));
            c.put(SOCDevCardCount.toCmd(gameName, gameData.getNumDevCards()));
        }
        c.put(SOCChangeFace.toCmd(gameName, i, pl.getFaceId()));
        if (i == 0) {
            // per-game data, send once
            c.put(SOCDiceResult.toCmd(gameName, gameData.getCurrentDice()));
        }
    }
    // /
    // / send dev card count, rounds count, first player, who has longest road and largest army
    // /
    final SOCPlayer lrPlayer = gameData.getPlayerWithLongestRoad(), laPlayer = gameData.getPlayerWithLargestArmy();
    final int lrPlayerNum = (lrPlayer != null) ? lrPlayer.getPlayerNumber() : -1, laPlayerNum = (laPlayer != null) ? laPlayer.getPlayerNumber() : -1;
    if (c.getVersion() < SOCGameElements.MIN_VERSION) {
        c.put(SOCLongestRoad.toCmd(gameName, lrPlayerNum));
        c.put(SOCLargestArmy.toCmd(gameName, laPlayerNum));
    } else {
        c.put(new SOCGameElements(gameName, ELEM_JOINGAME_DEVCARDS_ROUNDS_PLNUMS_FIRST_LONGEST_LARGEST, new int[] { gameData.getNumDevCards(), gameData.getRoundCount(), gameData.getFirstPlayer(), lrPlayerNum, laPlayerNum }).toCmd());
    }
    /**
     * If we're rejoining and taking over a seat after a network problem,
     * send our resource and hand information.
     */
    if (isTakingOver) {
        SOCPlayer cliPl = gameData.getPlayer(cliName);
        if (cliPl != null) {
            int pn = cliPl.getPlayerNumber();
            if ((pn != -1) && !gameData.isSeatVacant(pn))
                sitDown_sendPrivateInfo(gameData, c, pn);
        }
    }
    String membersCommand = null;
    srv.gameList.takeMonitorForGame(gameName);
    /**
     * Almost done; send GAMEMEMBERS as a hint to client that we're almost ready for its input.
     * There's no new data in GAMEMEMBERS, because player names have already been sent by
     * the SITDOWN messages above.
     */
    try {
        Vector<Connection> gameMembers = srv.gameList.getMembers(gameName);
        membersCommand = SOCGameMembers.toCmd(gameName, gameMembers);
    } catch (Exception e) {
        D.ebugPrintln("Exception in SGH.joinGame (gameMembers) - " + e);
    }
    srv.gameList.releaseMonitorForGame(gameName);
    if (membersCommand != null)
        c.put(membersCommand);
    // before v2.0.00, current player number (SETTURN) was sent here,
    // between membersCommand and GAMESTATE.
    c.put(SOCGameState.toCmd(gameName, gameData.getGameState()));
    if (D.ebugOn)
        D.ebugPrintln("*** " + cliName + " joined the game " + gameName + " at " + DateFormat.getTimeInstance(DateFormat.SHORT).format(new Date()));
    /**
     * Let everyone else know about the change
     */
    if (isTakingOver) {
        return;
    }
    srv.messageToGame(gameName, new SOCJoinGame(cliName, "", "dummyhost", gameName));
    if ((!isReset) && gameData.getGameState() >= SOCGame.START2A) {
        srv.messageToPlayerKeyed(c, gameName, // "This game has started. To play, take over a robot."
        (hasRobot) ? // "This game has started. To play, take over a robot."
        "member.join.game.started.bots" : // "This game has started; no new players can sit down."
        "member.join.game.started");
    }
}
Also used : SOCSVPTextMessage(soc.message.SOCSVPTextMessage) SOCJoinGame(soc.message.SOCJoinGame) ArrayList(java.util.ArrayList) SOCPlayerElement(soc.message.SOCPlayerElement) HashSet(java.util.HashSet) SOCPieceValue(soc.message.SOCPieceValue) Connection(soc.server.genericServer.Connection) NoSuchElementException(java.util.NoSuchElementException) Date(java.util.Date) SOCSetSeatLock(soc.message.SOCSetSeatLock) SOCPlayerElements(soc.message.SOCPlayerElements) SOCSetSpecialItem(soc.message.SOCSetSpecialItem) SOCGameElements(soc.message.SOCGameElements)

Aggregations

ArrayList (java.util.ArrayList)1 Date (java.util.Date)1 HashSet (java.util.HashSet)1 NoSuchElementException (java.util.NoSuchElementException)1 SOCGameElements (soc.message.SOCGameElements)1 SOCJoinGame (soc.message.SOCJoinGame)1 SOCPieceValue (soc.message.SOCPieceValue)1 SOCPlayerElement (soc.message.SOCPlayerElement)1 SOCPlayerElements (soc.message.SOCPlayerElements)1 SOCSVPTextMessage (soc.message.SOCSVPTextMessage)1 SOCSetSeatLock (soc.message.SOCSetSeatLock)1 SOCSetSpecialItem (soc.message.SOCSetSpecialItem)1 Connection (soc.server.genericServer.Connection)1