use of soc.message.SOCGameElements 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");
}
}
use of soc.message.SOCGameElements in project JSettlers2 by jdmonin.
the class SOCGameHandler method sendGameState.
/**
* Send all game members the current state of the game with a message.
* May also send other messages to the current player.
* Note that the current (or new) player number is not sent here.
* If game is now OVER, sends appropriate messages.
*<P>
* State {@link SOCGame#WAITING_FOR_DISCARDS}:
* If a 7 is rolled, will also say who must discard (in a GAMETEXTMSG).
*<P>
* State {@link SOCGame#WAITING_FOR_ROB_CHOOSE_PLAYER}:
* If current player must choose which player to rob,
* will also prompt their client to choose (in a CHOOSEPLAYERREQUEST).
*<P>
* State {@link SOCGame#STARTS_WAITING_FOR_PICK_GOLD_RESOURCE}:
* To announce the player must pick a resource to gain from the gold hex initial placement,
* please call {@link #sendGameState_sendGoldPickAnnounceText(SOCGame, String, Connection, SOCGame.RollResult)}.
*<P>
* State {@link SOCGame#WAITING_FOR_PICK_GOLD_RESOURCE}:
* If a gold hex is rolled, does not say who
* must pick resources to gain (because of timing). Please call
* {@link #sendGameState_sendGoldPickAnnounceText(SOCGame, String, Connection, SOCGame.RollResult)}
* after sending the resource gain text ("x gets 1 sheep").
*<P>
* <b>Note:</b> If game is now {@link SOCGame#OVER OVER} and the {@link SOCGame#isBotsOnly} flag is set,
* {@link #sendGameStateOVER(SOCGame)} will call {@link SOCServer#destroyGameAndBroadcast(String, String)}.
* Be sure that callers to {@code sendGameState} don't assume the game will still exist after calling this method.
* Also, {@code destroyGame} might create more {@link SOCGame#isBotsOnly} games, depending on server settings.
*<P>
* <b>Locks:</b> Does not hold {@link SOCGameList#takeMonitor()} or
* {@link SOCGameList#takeMonitorForGame}<tt>(gaName)</tt> when called.
* Some callers call {@link SOCGame#takeMonitor()} before calling; not important here.
*
* @see #sendTurn(SOCGame, boolean)
* @see #sendGameState(SOCGame)
* @see #sendGameStateOVER(SOCGame)
*
* @param ga the game
* @param omitGameStateMessage if true, don't send the {@link SOCGameState} message itself
* but do send any other messages as described above. For use just after sending a message which
* includes a Game State field.
* @param sendRollPrompt If true, and if we send a text message to prompt
* the player to roll, send a RollDicePrompt data message.
* If the client is too old (1.0.6), it will ignore the prompt.
*
* @return did we send a text message to prompt the player to roll?
* If so, sendTurn can also send a RollDicePrompt data message.
* @since 1.1.00
*/
boolean sendGameState(SOCGame ga, final boolean omitGameStateMessage, final boolean sendRollPrompt) {
if (ga == null)
return false;
final int gaState = ga.getGameState();
final int cpn = ga.getCurrentPlayerNumber();
final String gname = ga.getName();
boolean promptedRoll = false;
if (gaState == SOCGame.OVER) {
/**
* Before sending state "OVER", enforce current player number.
* This helps the client's copy of game recognize winning condition.
*/
srv.messageToGame(gname, (ga.clientVersionLowest >= SOCGameElements.MIN_VERSION) ? new SOCGameElements(gname, SOCGameElements.CURRENT_PLAYER, cpn) : new SOCSetTurn(gname, cpn));
}
if (!omitGameStateMessage)
srv.messageToGame(gname, new SOCGameState(gname, gaState));
SOCPlayer player = null;
if (cpn != -1)
player = ga.getPlayer(cpn);
switch(gaState) {
case SOCGame.START1A:
case SOCGame.START2A:
case SOCGame.START3A:
// "It's Joe's turn to build a settlement."
srv.messageToGameKeyed(ga, true, "prompt.turn.to.build.stlmt", player.getName());
if ((gaState >= SOCGame.START2A) && ga.isGameOptionSet(SOCGameOption.K_SC_3IP)) {
// reminder to player before their 2nd, 3rd settlements
Connection con = srv.getConnection(player.getName());
if (con != null) {
srv.messageToPlayerKeyed(con, gname, "prompt.gameopt._SC_3IP.part1");
// "This game gives you 3 initial settlements and roads."
srv.messageToPlayerKeyed(con, gname, "prompt.gameopt._SC_3IP.part2");
// "Your free resources will be from the third settlement."
}
}
break;
case SOCGame.START1B:
case SOCGame.START2B:
case SOCGame.START3B:
srv.messageToGameKeyed(ga, true, (// "It's Joe's turn to build a road or ship."
(ga.hasSeaBoard) ? // "It's Joe's turn to build a road or ship."
"prompt.turn.to.build.road.or.ship" : "prompt.turn.to.build.road"), player.getName());
break;
case SOCGame.ROLL_OR_CARD:
// "It's Joe's turn to roll the dice."
srv.messageToGameKeyed(ga, true, "prompt.turn.to.roll.dice", player.getName());
promptedRoll = true;
if (sendRollPrompt)
srv.messageToGame(gname, new SOCRollDicePrompt(gname, player.getPlayerNumber()));
break;
case SOCGame.WAITING_FOR_DISCARDS:
{
ArrayList<String> names = new ArrayList<String>();
for (int i = 0; i < ga.maxPlayers; i++) if (ga.getPlayer(i).getNeedToDiscard())
names.add(ga.getPlayer(i).getName());
if (names.size() == 1)
// "Joe needs to discard"
srv.messageToGameKeyed(ga, true, "prompt.discard.1", names.get(0));
else
// "Joe and Ed need to discard"
srv.messageToGameKeyedSpecial(ga, true, "prompt.discard.n", names);
}
break;
case SOCGame.WAITING_FOR_ROBBER_OR_PIRATE:
// "{0} must choose to move the robber or the pirate."
srv.messageToGameKeyed(ga, true, "robber.willmove.choose", player.getName());
break;
case SOCGame.PLACING_ROBBER:
// "{0} will move the robber."
srv.messageToGameKeyed(ga, true, "robber.willmove", player.getName());
break;
case SOCGame.PLACING_PIRATE:
// "{0} will move the pirate ship."
srv.messageToGameKeyed(ga, true, "robber.willmove.pirate", player.getName());
break;
case SOCGame.WAITING_FOR_ROB_CHOOSE_PLAYER:
/**
* get the choices from the game
*/
final boolean canStealNone = ga.isGameOptionSet(SOCGameOption.K_SC_PIRI);
boolean[] choices = new boolean[ga.maxPlayers + (canStealNone ? 1 : 0)];
Arrays.fill(choices, false);
if (canStealNone)
choices[ga.maxPlayers] = true;
for (SOCPlayer pl : ga.getPossibleVictims()) choices[pl.getPlayerNumber()] = true;
/**
* ask the current player to choose a player to steal from
*/
Connection con = srv.getConnection(ga.getPlayer(cpn).getName());
if (con != null) {
con.put(SOCChoosePlayerRequest.toCmd(gname, choices));
}
break;
case SOCGame.OVER:
sendGameStateOVER(ga);
break;
}
return promptedRoll;
}
use of soc.message.SOCGameElements in project JSettlers2 by jdmonin.
the class SOCGameHandler method startGame.
// javadoc inherited from GameHandler
/**
* {@inheritDoc}
*<P>
* If {@link SOCGame#hasSeaBoard}: Once the board is made, send the updated
* {@link SOCPotentialSettlements potential settlements}.
*<P>
* If this code changes, must also update {@link soctest.TestBoardLayouts#testSingleLayout(SOCScenario, int)}.
*/
public void startGame(SOCGame ga) {
if (ga == null)
return;
final String gaName = ga.getName();
// TODO once multiple handler threads, encapsulate this
srv.numberOfGamesStarted++;
/**
* start the game, place any initial pieces.
* If anything is added to this game object setup code,
* update soctest.TestBoardLayouts.testSingleLayout(..).
*/
// for playerEvent, gameEvent callbacks (since 2.0.00)
ga.setScenarioEventListener(this);
ga.startGame();
// used on sea board; if null, all are legal
final int[][] legalSeaEdges;
if (ga.hasSeaBoard) {
legalSeaEdges = SOCBoardAtServer.getLegalSeaEdges(ga, -1);
if (legalSeaEdges != null)
for (int pn = 0; pn < ga.maxPlayers; ++pn) ga.getPlayer(pn).setRestrictedLegalShips(legalSeaEdges[pn]);
if (ga.isGameOptionSet(SOCGameOption.K_SC_FTRI) || ga.isGameOptionSet(SOCGameOption.K_SC_PIRI)) {
// scenario has initial pieces
((SOCBoardAtServer) (ga.getBoard())).startGame_putInitPieces(ga);
}
} else {
legalSeaEdges = null;
}
srv.gameList.takeMonitorForGame(gaName);
try {
/**
* send the board layout
*/
try {
srv.messageToGameWithMon(gaName, getBoardLayoutMessage(ga));
// For scenario option _SC_CLVI, the board layout message
// includes villages and the general supply cloth count.
// For _SC_PIRI, it includes the Pirate Path (additional layout part "PP").
} catch (IllegalArgumentException e) {
System.err.println("startGame: Cannot send board for " + gaName + ": " + e.getMessage());
// the enclosing try-finally will releaseMonitorForGame(gaName) before returning
return;
}
if (ga.hasSeaBoard) {
// See also joinGame which has very similar code.
// Send the updated Potential/Legal Settlement node list
// Note: Assumes all players have same potential settlements
// (sends with playerNumber -1 == all)
final HashSet<Integer> psList = ga.getPlayer(0).getPotentialSettlements();
// Some boards may have multiple land areas.
final HashSet<Integer>[] lan;
final int pan;
boolean addedPsList = false;
final SOCBoardLarge bl = (SOCBoardLarge) ga.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;
}
if (lan == null)
srv.messageToGameWithMon(gaName, new SOCPotentialSettlements(gaName, -1, new ArrayList<Integer>(psList)));
else
srv.messageToGameWithMon(gaName, new SOCPotentialSettlements(gaName, -1, pan, lan, legalSeaEdges));
if (addedPsList)
// Undo change to game's copy of landAreasLegalNodes
lan[0] = null;
}
/**
* send the player info
*/
boolean sentInitPiecesState = false;
for (int i = 0; i < ga.maxPlayers; i++) {
if (ga.isSeatVacant(i))
continue;
final SOCPlayer pl = ga.getPlayer(i);
final int[] counts = new int[(ga.hasSeaBoard) ? 4 : 3];
counts[0] = pl.getNumPieces(SOCPlayingPiece.ROAD);
counts[1] = pl.getNumPieces(SOCPlayingPiece.SETTLEMENT);
counts[2] = pl.getNumPieces(SOCPlayingPiece.CITY);
if (ga.hasSeaBoard) {
// Some scenarios like SC_PIRI may place initial pieces at fixed locations.
// Usually, pieces will be empty.
final Vector<SOCPlayingPiece> pieces = pl.getPieces();
if (!pieces.isEmpty()) {
if (!sentInitPiecesState) {
// Temporary state change, to avoid initial-piece placement actions.
// The actual game state will be sent soon.
srv.messageToGameWithMon(gaName, new SOCGameState(gaName, SOCGame.READY));
sentInitPiecesState = true;
}
for (SOCPlayingPiece pp : pieces) srv.messageToGameWithMon(gaName, new SOCPutPiece(gaName, i, pp.getType(), pp.getCoordinates()));
SOCPlayingPiece pp = pl.getFortress();
if (pp != null)
srv.messageToGameWithMon(gaName, new SOCPutPiece(gaName, i, pp.getType(), pp.getCoordinates()));
}
counts[3] = pl.getNumPieces(SOCPlayingPiece.SHIP);
}
if (ga.clientVersionLowest >= SOCPlayerElements.MIN_VERSION)
srv.messageToGameWithMon(gaName, new SOCPlayerElements(gaName, i, SOCPlayerElement.SET, (ga.hasSeaBoard) ? ELEM_PIECETYPES_SEA : ELEM_PIECETYPES_CLASSIC, counts));
else
for (int j = 0; j < counts.length; ++j) srv.messageToGameWithMon(gaName, new SOCPlayerElement(gaName, i, SOCPlayerElement.SET, ELEM_PIECETYPES_SEA[j], counts[j]));
if (ga.clientVersionLowest < SOCPlayerElement.VERSION_FOR_CARD_ELEMENTS)
srv.messageToGameWithMon(gaName, new SOCSetPlayedDevCard(gaName, i, false));
}
if (ga.clientVersionLowest >= SOCPlayerElement.VERSION_FOR_CARD_ELEMENTS)
srv.messageToGameWithMon(gaName, new SOCPlayerElement(gaName, -1, SOCPlayerElement.SET, SOCPlayerElement.PLAYED_DEV_CARD_FLAG, 0));
/**
* send the number of dev cards
*/
srv.messageToGameWithMon(gaName, (ga.clientVersionLowest >= SOCGameElements.MIN_VERSION) ? new SOCGameElements(gaName, SOCGameElements.DEV_CARD_COUNT, ga.getNumDevCards()) : new SOCDevCardCount(gaName, ga.getNumDevCards()));
/**
* ga.startGame() picks who goes first, but feedback is nice
*/
srv.messageToGameKeyed(ga, false, // "Randomly picking a starting player..."
"start.picking.random.starting.player");
} finally {
srv.gameList.releaseMonitorForGame(gaName);
}
/**
* send the game state and start the game.
* send game state and whose turn it is.
*/
if (ga.clientVersionLowest >= SOCGameState.VERSION_FOR_GAME_STATE_AS_FIELD) {
srv.messageToGame(gaName, new SOCStartGame(gaName, ga.getGameState()));
sendTurn(ga, false);
} else {
final int cpn = ga.getCurrentPlayerNumber();
final boolean sendRoll = sendGameState(ga, false, false);
srv.messageToGame(gaName, new SOCStartGame(gaName, 0));
srv.messageToGame(gaName, new SOCTurn(gaName, cpn, 0));
if (sendRoll)
srv.messageToGame(gaName, new SOCRollDicePrompt(gaName, cpn));
}
}
use of soc.message.SOCGameElements in project JSettlers2 by jdmonin.
the class SOCGameHandler method forceEndGameTurn.
/**
* Try to force-end the current player's turn in this game.
* Alter game state and send messages to players.
* Will call {@link #endGameTurn(SOCGame, SOCPlayer, boolean)} if appropriate.
* Will send gameState and current player (turn) to clients.
*<P>
* If the current player has lost connection, send the {@link SOCLeaveGame LEAVEGAME}
* message out <b>before</b> calling this method.
*<P>
* Assumes, as {@link #endGameTurn(SOCGame, SOCPlayer, boolean)} does:
* <UL>
* <LI> ga.canEndTurn already called, returned false
* <LI> ga.takeMonitor already called (not the same as {@link SOCGameList#takeMonitorForGame(String)})
* <LI> gamelist.takeMonitorForGame is NOT called, we do NOT have that monitor
* </UL>
* @param ga Game to force end turn
* @param plName Current player's name. Needed because if they have been disconnected by
* {@link #leaveGame(SOCGame, Connection)},
* their name within game object is already null.
* @return true if the turn was ended and game is still active;
* false if we find that all players have left and
* the gamestate has been changed here to {@link SOCGame#OVER}.
*
* @see #endGameTurnOrForce(SOCGame, int, String, Connection, boolean)
* @see SOCGame#forceEndTurn()
*/
private final boolean forceEndGameTurn(SOCGame ga, final String plName) {
final String gaName = ga.getName();
final int cpn = ga.getCurrentPlayerNumber();
final int endFromGameState = ga.getGameState();
SOCPlayer cp = ga.getPlayer(cpn);
if (cp.hasAskedSpecialBuild()) {
cp.setAskedSpecialBuild(false);
srv.messageToGame(gaName, new SOCPlayerElement(gaName, cpn, SOCPlayerElement.SET, SOCPlayerElement.ASK_SPECIAL_BUILD, 0));
}
final SOCForceEndTurnResult res = ga.forceEndTurn();
// also could be initial placement (START1A or START2A or START3A).
if (SOCGame.OVER == ga.getGameState())
// <--- Early return: All players have left ---
return false;
/**
* Report any resources lost or gained.
* See also forceGamePlayerDiscardOrGain for same reporting code.
*/
SOCResourceSet resGainLoss = res.getResourcesGainedLost();
if (resGainLoss != null) {
/**
* If gold hex or returning resources to player (not discarding), report actual types/amounts.
* For discard, tell the discarding player's client that they discarded the resources,
* tell everyone else that the player discarded unknown resources.
*/
if (!res.isLoss()) {
if ((endFromGameState == SOCGame.WAITING_FOR_PICK_GOLD_RESOURCE) || (endFromGameState == SOCGame.STARTS_WAITING_FOR_PICK_GOLD_RESOURCE)) {
// Send SOCPlayerElement messages, "gains" text
reportRsrcGainGold(ga, cp, cpn, resGainLoss, true, false);
} else {
// Send SOCPlayerElement messages
reportRsrcGainLoss(gaName, resGainLoss, false, false, cpn, -1, null, null);
}
} else {
Connection c = srv.getConnection(plName);
if ((c != null) && c.isConnected())
reportRsrcGainLoss(gaName, resGainLoss, true, true, cpn, -1, null, c);
int totalRes = resGainLoss.getTotal();
srv.messageToGameExcept(gaName, c, new SOCPlayerElement(gaName, cpn, SOCPlayerElement.LOSE, SOCPlayerElement.UNKNOWN, totalRes, true), true);
// "{0} discarded {1} resources."
srv.messageToGameKeyed(ga, true, "action.discarded", plName, totalRes);
}
}
/**
* report any dev-card or item returned to player's hand
*/
final SOCInventoryItem itemCard = res.getReturnedInvItem();
SOCInventoryItemAction retItemActionMsg = null;
if (itemCard != null) {
Connection c = srv.getConnection(plName);
if ((c != null) && c.isConnected()) {
if (itemCard instanceof SOCDevCard) {
int card = itemCard.itype;
if ((card == SOCDevCardConstants.KNIGHT) && (c.getVersion() < SOCDevCardConstants.VERSION_FOR_NEW_TYPES))
card = SOCDevCardConstants.KNIGHT_FOR_VERS_1_X;
srv.messageToPlayer(c, new SOCDevCardAction(gaName, cpn, SOCDevCardAction.ADD_OLD, card));
} else {
retItemActionMsg = new SOCInventoryItemAction(gaName, cpn, (itemCard.isPlayable() ? SOCInventoryItemAction.ADD_PLAYABLE : SOCInventoryItemAction.ADD_OTHER), itemCard.itype, itemCard.isKept(), itemCard.isVPItem(), itemCard.canCancelPlay);
srv.messageToPlayer(c, retItemActionMsg);
}
}
// Announce item to game with same retItemActionMsg sent to player?
boolean announceAsInvItemAction = false;
// Announce this item to game as an unknown dev card type?
boolean announceAsUnknown = true;
if (!(itemCard instanceof SOCDevCard)) {
// SOCInventoryItem: Add any new kinds here, to announce to all players.
// If it needs a special message, do so and set announceAsUnknown = false
// If it's private and doesn't need a special message, set handled = true and let it announce as unknown
boolean handled = false;
if (ga.isGameOptionSet(SOCGameOption.K_SC_FTRI)) {
// endFromGameState is PLACING_INV_ITEM.
// "Gift port" item details are public, send return message to whole game:
handled = true;
announceAsInvItemAction = true;
announceAsUnknown = false;
}
// Fallback:
if (!handled)
System.err.println("forceEndGameTurn: Unhandled inventory item type " + itemCard.itype + " class " + itemCard.getClass());
}
if (announceAsInvItemAction) {
srv.messageToGameExcept(gaName, c, retItemActionMsg, true);
} else if (announceAsUnknown) {
if (ga.clientVersionLowest >= SOCDevCardConstants.VERSION_FOR_NEW_TYPES) {
srv.messageToGameExcept(gaName, c, new SOCDevCardAction(gaName, cpn, SOCDevCardAction.ADD_OLD, SOCDevCardConstants.UNKNOWN), true);
} else {
srv.messageToGameForVersionsExcept(ga, -1, SOCDevCardConstants.VERSION_FOR_NEW_TYPES - 1, c, new SOCDevCardAction(gaName, cpn, SOCDevCardAction.ADD_OLD, SOCDevCardConstants.UNKNOWN_FOR_VERS_1_X), true);
srv.messageToGameForVersionsExcept(ga, SOCDevCardConstants.VERSION_FOR_NEW_TYPES, Integer.MAX_VALUE, c, new SOCDevCardAction(gaName, cpn, SOCDevCardAction.ADD_OLD, SOCDevCardConstants.UNKNOWN), true);
}
srv.messageToGameKeyed(ga, true, "forceend.devcard.returned", plName);
// "{0}''s just-played development card was returned."
}
}
/**
* For initial placements, we don't end turns as normal.
* (Player number may go forward or backwards, new state isn't ROLL_OR_CARD, etc.)
* Update clients' gamestate, but don't call endGameTurn.
*/
final int forceRes = res.getResult();
if ((forceRes == SOCForceEndTurnResult.FORCE_ENDTURN_SKIP_START_ADV) || (forceRes == SOCForceEndTurnResult.FORCE_ENDTURN_SKIP_START_ADVBACK)) {
if (res.didUpdateFP() || res.didUpdateLP()) {
final int fpn = ga.getFirstPlayer();
final SOCMessage msg = (ga.clientVersionLowest >= SOCGameElements.MIN_VERSION) ? new SOCGameElements(gaName, SOCGameElements.FIRST_PLAYER, fpn) : new SOCFirstPlayer(gaName, fpn);
// will cause clients to recalculate lastPlayer too
srv.messageToGame(gaName, msg);
}
sendTurn(ga, false);
// <--- Early return ---
return true;
}
/**
* If the turn can now end, proceed as if player requested it.
* Otherwise, send current gamestate. We'll all wait for other
* players to send discard messages, and afterwards this turn can end.
*/
if (ga.canEndTurn(cpn))
// could force gamestate to OVER, if a client leaves
endGameTurn(ga, null, true);
else
sendGameState(ga, false, false);
return (ga.getGameState() != SOCGame.OVER);
}
Aggregations