mirror of https://github.com/FreeCol/freecol.git
1109 lines
40 KiB
Java
1109 lines
40 KiB
Java
/**
|
|
* Copyright (C) 2002-2015 The FreeCol Team
|
|
*
|
|
* This file is part of FreeCol.
|
|
*
|
|
* FreeCol is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* FreeCol is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with FreeCol. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package net.sf.freecol.client.control;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
|
|
import net.sf.freecol.client.FreeColClient;
|
|
import net.sf.freecol.common.debug.FreeColDebugger;
|
|
import net.sf.freecol.common.model.Ability;
|
|
import net.sf.freecol.common.model.Colony;
|
|
import net.sf.freecol.common.model.DiplomaticTrade;
|
|
import net.sf.freecol.common.model.FoundingFather;
|
|
import net.sf.freecol.common.model.FreeColGameObject;
|
|
import net.sf.freecol.common.model.FreeColObject;
|
|
import net.sf.freecol.common.model.Game;
|
|
import net.sf.freecol.common.model.Goods;
|
|
import net.sf.freecol.common.model.HistoryEvent;
|
|
import net.sf.freecol.common.model.LastSale;
|
|
import net.sf.freecol.common.model.ModelMessage;
|
|
import net.sf.freecol.common.model.Modifier;
|
|
import net.sf.freecol.common.model.Ownable;
|
|
import net.sf.freecol.common.model.Player;
|
|
import net.sf.freecol.common.model.Stance;
|
|
import net.sf.freecol.common.model.Region;
|
|
import net.sf.freecol.common.model.Settlement;
|
|
import net.sf.freecol.common.model.Specification;
|
|
import net.sf.freecol.common.model.Tile;
|
|
import net.sf.freecol.common.model.TradeRoute;
|
|
import net.sf.freecol.common.model.Unit;
|
|
import net.sf.freecol.common.networking.ChatMessage;
|
|
import net.sf.freecol.common.networking.ChooseFoundingFatherMessage;
|
|
import net.sf.freecol.common.networking.Connection;
|
|
import net.sf.freecol.common.networking.DOMMessage;
|
|
import net.sf.freecol.common.networking.DiplomacyMessage;
|
|
import net.sf.freecol.common.networking.FirstContactMessage;
|
|
import net.sf.freecol.common.networking.IndianDemandMessage;
|
|
import net.sf.freecol.common.networking.LootCargoMessage;
|
|
import net.sf.freecol.common.networking.MonarchActionMessage;
|
|
import net.sf.freecol.common.networking.NewLandNameMessage;
|
|
import net.sf.freecol.common.networking.NewRegionNameMessage;
|
|
|
|
import org.w3c.dom.Element;
|
|
import org.w3c.dom.NodeList;
|
|
|
|
|
|
/**
|
|
* Handle the network messages that arrives while in the game.
|
|
*
|
|
* Delegate to the real handlers in InGameController which are allowed
|
|
* to touch the GUI. Call IGC through invokeLater except for the messages
|
|
* that demand a response, which requires invokeAndWait.
|
|
*
|
|
* Note that the EDT often calls the controller, which queries the
|
|
* server, which results in handling the reply here, still within the
|
|
* EDT. invokeAndWait is illegal within the EDT, but none of messages
|
|
* that require a response are client-initiated so the problem does
|
|
* not arise...
|
|
*
|
|
* ...except for the special case of the animations. These have to be
|
|
* done in series but are sometimes in the EDT (our unit moves) and
|
|
* sometimes not (other nation unit moves). Hence the hack
|
|
* GUI.invokeNowOrWait.
|
|
*/
|
|
public final class InGameInputHandler extends InputHandler {
|
|
|
|
private static final Logger logger = Logger.getLogger(InGameInputHandler.class.getName());
|
|
|
|
// A bunch of predefined non-closure runnables.
|
|
private final Runnable closeMenusRunnable = () -> {
|
|
igc().closeMenus();
|
|
};
|
|
private final Runnable displayModelMessagesRunnable = () -> {
|
|
igc().displayModelMessages(false);
|
|
};
|
|
private final Runnable reconnectRunnable = () -> {
|
|
igc().reconnect();
|
|
};
|
|
|
|
|
|
/**
|
|
* The constructor to use.
|
|
*
|
|
* @param freeColClient The <code>FreeColClient</code> for the game.
|
|
*/
|
|
public InGameInputHandler(FreeColClient freeColClient) {
|
|
super(freeColClient);
|
|
}
|
|
|
|
|
|
/**
|
|
* Shorthand to get the controller.
|
|
*
|
|
* @return The in-game controller.
|
|
*/
|
|
private InGameController igc() {
|
|
return getFreeColClient().getInGameController();
|
|
}
|
|
|
|
/**
|
|
* Shorthand to run in the EDT and wait.
|
|
*
|
|
* @param runnable The <code>Runnable</code> to run.
|
|
*/
|
|
private void invokeAndWait(Runnable runnable) {
|
|
getFreeColClient().getGUI().invokeNowOrWait(runnable);
|
|
}
|
|
|
|
/**
|
|
* Shorthand to run in the EDT eventually.
|
|
*
|
|
* @param runnable The <code>Runnable</code> to run.
|
|
*/
|
|
private void invokeLater(Runnable runnable) {
|
|
getFreeColClient().getGUI().invokeNowOrLater(runnable);
|
|
}
|
|
|
|
/**
|
|
* Get the integer value of an element attribute.
|
|
*
|
|
* @param element The <code>Element</code> to query.
|
|
* @param attrib The attribute to use.
|
|
* @return The integer value of the attribute, or
|
|
* Integer.MIN_VALUE on failure.
|
|
*/
|
|
private static int getIntegerAttribute(Element element, String attrib) {
|
|
int n;
|
|
try {
|
|
n = Integer.parseInt(element.getAttribute(attrib));
|
|
} catch (NumberFormatException e) {
|
|
n = Integer.MIN_VALUE;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/**
|
|
* Select a child element with the given object identifier from a
|
|
* parent element.
|
|
*
|
|
* @param parent The parent <code>Element</code>.
|
|
* @param key The key to search for.
|
|
* @return An <code>Element</code> with matching key,
|
|
* or null if none found.
|
|
*/
|
|
private static Element selectElement(Element parent, String key) {
|
|
NodeList nodes = parent.getChildNodes();
|
|
for (int i = 0; i < nodes.getLength(); i++) {
|
|
Element e = (Element)nodes.item(i);
|
|
if (key.equals(FreeColObject.readId(e))) return e;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Sometimes units appear which the client does not know about,
|
|
* and are passed in as the children of the parent element.
|
|
* Worse, if their location is a Unit, that unit has to be passed in too.
|
|
* Pull a unit out of the children by id.
|
|
*
|
|
* @param game The <code>Game</code> to add the unit to.
|
|
* @param element The <code>Element</code> to find a unit in.
|
|
* @param id The object identifier of the unit to find.
|
|
* @return A unit or null if none found.
|
|
*/
|
|
private static Unit selectUnitFromElement(Game game, Element element,
|
|
String id) {
|
|
Element e = selectElement(element, id);
|
|
Unit u = null;
|
|
if (e != null) {
|
|
u = new Unit(game, e);
|
|
if (u.getLocation() == null) {
|
|
throw new RuntimeException("Null location: " + u);
|
|
}
|
|
}
|
|
return u;
|
|
}
|
|
|
|
|
|
// Override InputHandler
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public Element handle(Connection connection, Element element) {
|
|
if (element == null) {
|
|
throw new RuntimeException("Received empty (null) message!");
|
|
}
|
|
|
|
Element reply;
|
|
String type = element.getTagName();
|
|
logger.log(Level.FINEST, "Received: " + type);
|
|
switch (type) {
|
|
case "disconnect":
|
|
reply = disconnect(element); break; // Inherited
|
|
case "addObject":
|
|
reply = addObject(element); break;
|
|
case "addPlayer":
|
|
reply = addPlayer(element); break;
|
|
case "animateAttack":
|
|
reply = animateAttack(element); break;
|
|
case "animateMove":
|
|
reply = animateMove(element); break;
|
|
case "chat":
|
|
reply = chat(element); break;
|
|
case "chooseFoundingFather":
|
|
reply = chooseFoundingFather(element); break;
|
|
case "closeMenus":
|
|
reply = closeMenus(); break;
|
|
case "diplomacy":
|
|
reply = diplomacy(element); break;
|
|
case "error":
|
|
reply = error(element); break;
|
|
case "featureChange":
|
|
reply = featureChange(element); break;
|
|
case "firstContact":
|
|
reply = firstContact(element); break;
|
|
case "fountainOfYouth":
|
|
reply = fountainOfYouth(element); break;
|
|
case "gameEnded":
|
|
reply = gameEnded(element); break;
|
|
case "indianDemand":
|
|
reply = indianDemand(element); break;
|
|
case "lootCargo":
|
|
reply = lootCargo(element); break;
|
|
case "monarchAction":
|
|
reply = monarchAction(element); break;
|
|
case "multiple":
|
|
reply = multiple(connection, element); break;
|
|
case "newLandName":
|
|
reply = newLandName(element); break;
|
|
case "newRegionName":
|
|
reply = newRegionName(element); break;
|
|
case "newTurn":
|
|
reply = newTurn(element); break;
|
|
case "reconnect":
|
|
reply = reconnect(element); break;
|
|
case "remove":
|
|
reply = remove(element); break;
|
|
case "setAI":
|
|
reply = setAI(element); break;
|
|
case "setCurrentPlayer":
|
|
reply = setCurrentPlayer(element); break;
|
|
case "setDead":
|
|
reply = setDead(element); break;
|
|
case "setStance":
|
|
reply = setStance(element); break;
|
|
case "spyResult":
|
|
reply = spyResult(element); break;
|
|
case "update":
|
|
reply = update(element); break;
|
|
default:
|
|
logger.warning("Unsupported message type: " + type);
|
|
return null;
|
|
}
|
|
logger.log(Level.FINEST, "Handled message: " + type
|
|
+ " replying with: "
|
|
+ ((reply == null) ? "null" : reply.getTagName()));
|
|
|
|
// If there is a "flush" attribute present, encourage the client
|
|
// to display any new messages.
|
|
final FreeColClient fcc = getFreeColClient();
|
|
if (Boolean.TRUE.toString().equals(element.getAttribute("flush"))
|
|
&& fcc.currentPlayerIsMyPlayer()) {
|
|
invokeLater(displayModelMessagesRunnable);
|
|
}
|
|
return reply;
|
|
}
|
|
|
|
|
|
// Individual message handlers
|
|
|
|
/**
|
|
* Add the objects which are the children of this Element.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return Null.
|
|
*/
|
|
private Element addObject(Element element) {
|
|
final Game game = getGame();
|
|
final Specification spec = game.getSpecification();
|
|
NodeList nodes = element.getChildNodes();
|
|
for (int i = 0; i < nodes.getLength(); i++) {
|
|
Element e = (Element)nodes.item(i);
|
|
String owner = e.getAttribute("owner");
|
|
Player player = game.getFreeColGameObject(owner, Player.class);
|
|
if (player == null) {
|
|
logger.warning("addObject with broken owner: " + owner);
|
|
continue;
|
|
}
|
|
|
|
final String tag = e.getTagName();
|
|
if (FoundingFather.getXMLElementTagName().equals(tag)) {
|
|
FoundingFather father
|
|
= spec.getFoundingFather(FreeColObject.readId(e));
|
|
if (father != null) player.addFather(father);
|
|
player.invalidateCanSeeTiles();// Might be coronado?
|
|
|
|
} else if (HistoryEvent.getXMLElementTagName().equals(tag)) {
|
|
player.getHistory().add(new HistoryEvent(e));
|
|
|
|
} else if (LastSale.getXMLElementTagName().equals(tag)) {
|
|
player.addLastSale(new LastSale(e));
|
|
|
|
} else if (ModelMessage.getXMLElementTagName().equals(tag)) {
|
|
player.addModelMessage(new ModelMessage(e));
|
|
|
|
} else if (TradeRoute.getXMLElementTagName().equals(tag)) {
|
|
player.getTradeRoutes().add(new TradeRoute(game, e));
|
|
|
|
} else {
|
|
logger.warning("addObject unrecognized: " + tag);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handle an "addPlayer"-message.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return Null.
|
|
*/
|
|
private Element addPlayer(Element element) {
|
|
final Game game = getGame();
|
|
NodeList nodes = element.getChildNodes();
|
|
for (int i = 0; i < nodes.getLength(); i++) {
|
|
Element playerElement = (Element)nodes.item(i);
|
|
String id = FreeColObject.readId(playerElement);
|
|
Player p = game.getFreeColGameObject(id, Player.class);
|
|
if (p == null) {
|
|
game.addPlayer(new Player(game, playerElement));
|
|
} else {
|
|
p.readFromXMLElement(playerElement);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handle an "animateAttack"-message. This only performs animation, if
|
|
* required. It does not actually perform any attacks.
|
|
*
|
|
* @param element An element (root element in a DOM-parsed XML
|
|
* tree) that holds attributes for the old and new tiles and
|
|
* an element for the unit that is moving (which are used
|
|
* solely to operate the animation).
|
|
* @return Null.
|
|
*/
|
|
private Element animateAttack(Element element) {
|
|
final FreeColClient freeColClient = getFreeColClient();
|
|
final Game game = getGame();
|
|
final Player player = freeColClient.getMyPlayer();
|
|
String str;
|
|
Unit u;
|
|
|
|
if ((str = element.getAttribute("attacker")).isEmpty()) {
|
|
throw new IllegalStateException("Attack animation for: "
|
|
+ player.getId() + " missing attacker attribute.");
|
|
}
|
|
if ((u = game.getFreeColGameObject(str, Unit.class)) == null
|
|
&& (u = selectUnitFromElement(game, element, str)) == null) {
|
|
throw new IllegalStateException("Attack animation for: "
|
|
+ player.getId() + " omitted attacker: " + str);
|
|
}
|
|
final Unit attacker = u;
|
|
|
|
if ((str = element.getAttribute("defender")).isEmpty()) {
|
|
throw new IllegalStateException("Attack animation for: "
|
|
+ player.getId() + " missing defender attribute.");
|
|
}
|
|
if ((u = game.getFreeColGameObject(str, Unit.class)) == null
|
|
&& (u = selectUnitFromElement(game, element, str)) == null) {
|
|
throw new IllegalStateException("Attack animation for: "
|
|
+ player.getId() + " omitted defender: " + str);
|
|
}
|
|
final Unit defender = u;
|
|
|
|
if ((str = element.getAttribute("attackerTile")).isEmpty()) {
|
|
throw new IllegalStateException("Attack animation for: "
|
|
+ player.getId() + " missing attacker tile attribute.");
|
|
}
|
|
final Tile attackerTile = game.getFreeColGameObject(str, Tile.class);
|
|
if (attackerTile == null) {
|
|
throw new IllegalStateException("Attack animation for: "
|
|
+ player.getId() + " omitted attacker tile: " + str);
|
|
}
|
|
|
|
if ((str = element.getAttribute("defenderTile")).isEmpty()) {
|
|
throw new IllegalStateException("Attack animation for: "
|
|
+ player.getId() + " missing defender tile attribute.");
|
|
}
|
|
final Tile defenderTile = game.getFreeColGameObject(str, Tile.class);
|
|
if (defenderTile == null) {
|
|
throw new IllegalStateException("Attack animation for: "
|
|
+ player.getId() + " omitted defender tile: " + str);
|
|
}
|
|
|
|
final boolean success
|
|
= Boolean.parseBoolean(element.getAttribute("success"));
|
|
|
|
// All is well, do the animation.
|
|
invokeAndWait(() -> {
|
|
igc().animateAttack(attacker, defender,
|
|
attackerTile, defenderTile, success);
|
|
});
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handle an "animateMove"-message. This only performs
|
|
* animation, if required. It does not actually change unit
|
|
* positions, which happens in an "update".
|
|
*
|
|
* @param element An element (root element in a DOM-parsed XML
|
|
* tree) that holds attributes for the old and new tiles and
|
|
* an element for the unit that is moving (which are used
|
|
* solely to operate the animation).
|
|
* @return Null.
|
|
*/
|
|
private Element animateMove(Element element) {
|
|
final FreeColClient freeColClient = getFreeColClient();
|
|
final Game game = getGame();
|
|
final Player player = freeColClient.getMyPlayer();
|
|
|
|
String unitId = element.getAttribute("unit");
|
|
if (unitId.isEmpty()) {
|
|
logger.warning("Animation for: " + player.getId()
|
|
+ " missing unitId.");
|
|
return null;
|
|
}
|
|
Unit u = game.getFreeColGameObject(unitId, Unit.class);
|
|
if (u == null) {
|
|
u = selectUnitFromElement(game, element, unitId);
|
|
//if (u != null) logger.info("Added unit from element: " + unitId);
|
|
}
|
|
if (u == null) {
|
|
logger.warning("Animation for: " + player.getId()
|
|
+ " missing unit:" + unitId);
|
|
return null;
|
|
}
|
|
final Unit unit = u;
|
|
|
|
String oldTileId = element.getAttribute("oldTile");
|
|
if (oldTileId.isEmpty()) {
|
|
logger.warning("Animation for: " + player.getId()
|
|
+ " missing oldTileId");
|
|
return null;
|
|
}
|
|
final Tile oldTile = game.getFreeColGameObject(oldTileId, Tile.class);
|
|
if (oldTile == null) {
|
|
logger.warning("Animation for: " + player.getId()
|
|
+ " missing oldTile: " + oldTileId);
|
|
return null;
|
|
}
|
|
|
|
String newTileId = element.getAttribute("newTile");
|
|
if (newTileId.isEmpty()) {
|
|
logger.warning("Animation for: " + player.getId()
|
|
+ " missing newTileId");
|
|
return null;
|
|
}
|
|
final Tile newTile = game.getFreeColGameObject(newTileId, Tile.class);
|
|
if (newTile == null) {
|
|
logger.warning("Animation for: " + player.getId()
|
|
+ " missing newTile: " + newTileId);
|
|
return null;
|
|
}
|
|
|
|
invokeAndWait(() -> { igc().animateMove(unit, oldTile, newTile); });
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handle a "chat"-message.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return Null.
|
|
*/
|
|
private Element chat(Element element) {
|
|
final Game game = getGame();
|
|
final ChatMessage chatMessage = new ChatMessage(game, element);
|
|
|
|
invokeLater(() -> {
|
|
igc().chat(chatMessage.getPlayer(game),
|
|
chatMessage.getMessage(), chatMessage.isPrivate());
|
|
});
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handle an "chooseFoundingFather"-request.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return Null. The choice is returned asynchronously.
|
|
*/
|
|
private Element chooseFoundingFather(Element element) {
|
|
final ChooseFoundingFatherMessage message
|
|
= new ChooseFoundingFatherMessage(getGame(), element);
|
|
final List<FoundingFather> ffs = message.getFathers();
|
|
|
|
invokeLater(() -> { igc().chooseFoundingFather(ffs); });
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Trivial handler to allow the server to signal to the client
|
|
* that an offer that caused a popup (for example, a native demand
|
|
* or diplomacy proposal) has not been answered quickly enough and
|
|
* that the offering player has assumed this player has
|
|
* refused-by-inaction, and therefore, the popup needs to be
|
|
* closed.
|
|
*
|
|
* @return Null.
|
|
*/
|
|
private Element closeMenus() {
|
|
invokeAndWait(closeMenusRunnable);
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handle a "diplomacy"-request. If the message informs of an
|
|
* acceptance or rejection then display the result and return
|
|
* null. If the message is a proposal, then ask the user about
|
|
* it and return the response with appropriate response set.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) containing a "diplomacy"-message.
|
|
* @return A diplomacy response, or null if none required.
|
|
*/
|
|
private Element diplomacy(Element element) {
|
|
final Game game = getGame();
|
|
final DiplomacyMessage message
|
|
= new DiplomacyMessage(getGame(), element);
|
|
final DiplomaticTrade agreement = message.getAgreement();
|
|
|
|
final FreeColGameObject our = message.getOurFCGO(game);
|
|
if (our == null) {
|
|
logger.warning("Our FCGO omitted from diplomacy message.");
|
|
return null;
|
|
}
|
|
|
|
final FreeColGameObject other = message.getOtherFCGO(game);
|
|
if (other == null) {
|
|
logger.warning("Other FCGO omitted from diplomacy message.");
|
|
return null;
|
|
}
|
|
|
|
invokeAndWait(() -> {
|
|
message.setAgreement(igc().diplomacy(our, other, agreement));
|
|
});
|
|
return (message.getAgreement() == null) ? null
|
|
: message.toXMLElement();
|
|
}
|
|
|
|
/**
|
|
* Disposes of the <code>Unit</code>s which are the children of this
|
|
* Element.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return Null.
|
|
*/
|
|
private Element disposeUnits(Element element) {
|
|
Game game = getGame();
|
|
NodeList nodes = element.getChildNodes();
|
|
|
|
for (int i = 0; i < nodes.getLength(); i++) {
|
|
// Do not read the whole unit out of the element as we are
|
|
// only going to dispose of it, not forgetting that the
|
|
// server may have already done so and its view will only
|
|
// mislead us here in the client.
|
|
Element e = (Element) nodes.item(i);
|
|
String id = FreeColObject.readId(e);
|
|
Unit u = game.getFreeColGameObject(id, Unit.class);
|
|
if (u == null) {
|
|
logger.warning("Object is not a unit");
|
|
} else {
|
|
u.dispose();
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handle an "error"-message.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return Null.
|
|
*/
|
|
private Element error(Element element) {
|
|
final String messageId = element.getAttribute("messageID");
|
|
final String message = element.getAttribute("message");
|
|
|
|
invokeLater(() -> { igc().error(messageId, message); });
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Adds a feature to or removes a feature from a FreeColGameObject.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return Null.
|
|
*/
|
|
private Element featureChange(Element element) {
|
|
final Game game = getGame();
|
|
final Specification spec = game.getSpecification();
|
|
boolean add = "add".equalsIgnoreCase(element.getAttribute("add"));
|
|
String id = FreeColObject.readId(element);
|
|
FreeColGameObject object = game.getFreeColGameObject(id);
|
|
if (object == null) {
|
|
logger.warning("featureChange with null object");
|
|
return null;
|
|
}
|
|
|
|
NodeList nodes = element.getChildNodes();
|
|
for (int i = 0; i < nodes.getLength(); i++) {
|
|
Element e = (Element) nodes.item(i);
|
|
|
|
final String tag = e.getTagName();
|
|
if (Ability.getXMLElementTagName().equals(tag)) {
|
|
if (add) {
|
|
object.addAbility(new Ability(e, spec));
|
|
} else {
|
|
object.removeAbility(new Ability(e, spec));
|
|
}
|
|
|
|
} else if (Modifier.getXMLElementTagName().equals(tag)) {
|
|
if (add) {
|
|
object.addModifier(new Modifier(e, spec));
|
|
} else {
|
|
object.removeModifier(new Modifier(e, spec));
|
|
}
|
|
|
|
} else {
|
|
logger.warning("featureChange unrecognized: " + tag);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handle a first contact with a native nation.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return Null.
|
|
*/
|
|
private Element firstContact(Element element) {
|
|
final Game game = getGame();
|
|
final FirstContactMessage message
|
|
= new FirstContactMessage(game, element);
|
|
|
|
final Player player = message.getPlayer(game);
|
|
if (player == null || player != getFreeColClient().getMyPlayer()) {
|
|
logger.warning("firstContact with bad player: " + player);
|
|
return null;
|
|
}
|
|
final Player other = message.getOtherPlayer(game);
|
|
if (other == null || other == player || !other.isIndian()) {
|
|
logger.warning("firstContact with bad other player: " + other);
|
|
return null;
|
|
}
|
|
final Tile tile = message.getTile(game);
|
|
if (tile != null && tile.getOwner() != other) {
|
|
logger.warning("firstContact with bad tile: " + tile);
|
|
return null;
|
|
}
|
|
final int n = message.getSettlementCount();
|
|
|
|
invokeLater(() -> { igc().firstContact(player, other, tile, n); });
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Ask the player to choose migrants from a fountain of youth event.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return Null.
|
|
*/
|
|
private Element fountainOfYouth(Element element) {
|
|
final int n = getIntegerAttribute(element, "migrants");
|
|
if (n <= 0) {
|
|
logger.warning("Invalid migrants attribute: "
|
|
+ element.getAttribute("migrants"));
|
|
return null;
|
|
}
|
|
|
|
invokeLater(() -> { igc().fountainOfYouth(n); });
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handle a "gameEnded"-message.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return Null.
|
|
*/
|
|
private Element gameEnded(Element element) {
|
|
FreeColClient freeColClient = getFreeColClient();
|
|
FreeColDebugger.finishDebugRun(freeColClient, true);
|
|
final Player winner
|
|
= getGame().getFreeColGameObject(element.getAttribute("winner"),
|
|
Player.class);
|
|
if (winner == null) {
|
|
logger.warning("Invalid player for gameEnded");
|
|
return null;
|
|
}
|
|
final String highScore = element.getAttribute("highScore");
|
|
|
|
if (winner == freeColClient.getMyPlayer()) {
|
|
invokeLater(() -> { igc().victory(highScore); });
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handle an "indianDemand"-request.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return An <code>IndianDemand</code> message containing the response,
|
|
* or null on error.
|
|
*/
|
|
private Element indianDemand(Element element) {
|
|
final Game game = getGame();
|
|
final Player player = getFreeColClient().getMyPlayer();
|
|
final IndianDemandMessage message
|
|
= new IndianDemandMessage(game, element);
|
|
final Unit unit = message.getUnit(game);
|
|
if (unit == null) {
|
|
logger.warning("IndianDemand with null unit: "
|
|
+ element.getAttribute("unit"));
|
|
return null;
|
|
}
|
|
final Colony colony = message.getColony(game);
|
|
if (colony == null) {
|
|
logger.warning("IndianDemand with null colony: "
|
|
+ element.getAttribute("colony"));
|
|
return null;
|
|
} else if (!player.owns(colony)) {
|
|
throw new IllegalArgumentException("Demand to anothers colony");
|
|
}
|
|
|
|
invokeAndWait(() -> {
|
|
message.setResult(igc().indianDemand(unit, colony,
|
|
message.getType(game), message.getAmount()));
|
|
});
|
|
return message.toXMLElement();
|
|
}
|
|
|
|
/**
|
|
* Ask the player to choose something to loot.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return Null. The choice is returned asynchronously.
|
|
*/
|
|
private Element lootCargo(Element element) {
|
|
final Game game = getGame();
|
|
final LootCargoMessage message = new LootCargoMessage(game, element);
|
|
final Unit unit = message.getUnit(game);
|
|
final String defenderId = message.getDefenderId();
|
|
final List<Goods> goods = message.getGoods();
|
|
if (unit == null || goods == null) return null;
|
|
|
|
invokeLater(() -> { igc().loot(unit, goods, defenderId); });
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handle a "monarchAction"-request.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return Null. The response is returned asynchronously.
|
|
*/
|
|
private Element monarchAction(Element element) {
|
|
final Game game = getGame();
|
|
final MonarchActionMessage message
|
|
= new MonarchActionMessage(game, element);
|
|
|
|
invokeLater(() -> {
|
|
igc().monarch(message.getAction(), message.getTemplate(),
|
|
message.getMonarchKey());
|
|
});
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handle all the children of this element.
|
|
*
|
|
* @param connection The <code>Connection</code> the element arrived on.
|
|
* @param element The <code>Element</code> to process.
|
|
* @return An <code>Element</code> containing the response/s.
|
|
*/
|
|
private Element multiple(Connection connection, Element element) {
|
|
NodeList nodes = element.getChildNodes();
|
|
List<Element> results = new ArrayList<>();
|
|
|
|
for (int i = 0; i < nodes.getLength(); i++) {
|
|
try {
|
|
Element reply = handle(connection, (Element)nodes.item(i));
|
|
if (reply != null) results.add(reply);
|
|
} catch (Exception e) {
|
|
logger.log(Level.WARNING, "Caught crash in multiple item " + i
|
|
+ ", continuing.", e);
|
|
}
|
|
}
|
|
return DOMMessage.collapseElements(results);
|
|
}
|
|
|
|
/**
|
|
* Ask the player to name the new land.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return Null. The name is returned asynchronously.
|
|
*/
|
|
private Element newLandName(Element element) {
|
|
final Game game = getGame();
|
|
NewLandNameMessage message = new NewLandNameMessage(game, element);
|
|
final Unit unit = message.getUnit(getFreeColClient().getMyPlayer());
|
|
final String defaultName = message.getNewLandName();
|
|
if (unit == null || defaultName == null
|
|
|| !unit.hasTile()) return null;
|
|
|
|
invokeLater(() -> { igc().newLandName(defaultName, unit); });
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Ask the player to name a new region.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return Null. The name is returned asynchronously.
|
|
*/
|
|
private Element newRegionName(Element element) {
|
|
final Game game = getGame();
|
|
NewRegionNameMessage message = new NewRegionNameMessage(game, element);
|
|
final Tile tile = message.getTile(game);
|
|
final Unit unit = message.getUnit(getFreeColClient().getMyPlayer());
|
|
final Region region = message.getRegion(game);
|
|
final String defaultName = message.getNewRegionName();
|
|
if (defaultName == null || region == null) return null;
|
|
|
|
invokeLater(() -> {
|
|
igc().newRegionName(region, defaultName, tile, unit);
|
|
});
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handle a "newTurn"-message.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML tree)
|
|
* that holds all the information.
|
|
* @return Null.
|
|
*/
|
|
private Element newTurn(Element element) {
|
|
final int n = getIntegerAttribute(element, "turn");
|
|
if (n < 0) {
|
|
logger.warning("Invalid turn for newTurn");
|
|
return null;
|
|
}
|
|
|
|
invokeLater(() -> { igc().newTurn(n); });
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handle an "reconnect"-message.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return Null.
|
|
*/
|
|
private Element reconnect(@SuppressWarnings("unused") Element element) {
|
|
logger.finest("Entered reconnect.");
|
|
|
|
invokeLater(reconnectRunnable);
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handle a "remove"-message.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return Null.
|
|
*/
|
|
private Element remove(Element element) {
|
|
final Game game = getGame();
|
|
final FreeColGameObject divert
|
|
= game.getFreeColGameObject(element.getAttribute("divert"));
|
|
final List<FreeColGameObject> objects = new ArrayList<>();
|
|
NodeList nodeList = element.getChildNodes();
|
|
for (int i = 0; i < nodeList.getLength(); i++) {
|
|
Element e = (Element)nodeList.item(i);
|
|
String idString = FreeColObject.readId(e);
|
|
FreeColGameObject fcgo = game.getFreeColGameObject(idString);
|
|
if (fcgo == null) {
|
|
// This can happen legitimately when an update that
|
|
// removes pointers to a disappearing unit happens,
|
|
// then a gc which drops the weak reference in
|
|
// freeColGameObjects, before this remove is processed.
|
|
continue;
|
|
}
|
|
objects.add(fcgo);
|
|
}
|
|
|
|
if (!objects.isEmpty()) {
|
|
invokeLater(() -> { igc().remove(objects, divert); });
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handle a "setAI"-message.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return Null.
|
|
*/
|
|
private Element setAI(Element element) {
|
|
final Game game = getGame();
|
|
Player p = game.getFreeColGameObject(element.getAttribute("player"),
|
|
Player.class);
|
|
p.setAI(Boolean.parseBoolean(element.getAttribute("ai")));
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handle a "setCurrentPlayer"-message.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return Null.
|
|
*/
|
|
private Element setCurrentPlayer(Element element) {
|
|
final Player player
|
|
= getGame().getFreeColGameObject(element.getAttribute("player"),
|
|
Player.class);
|
|
if (player == null) {
|
|
logger.warning("Invalid player for setCurrentPlayer");
|
|
return null;
|
|
}
|
|
|
|
igc().setCurrentPlayer(player); // It is safe to call this one directly
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handle a "setDead"-message.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return Null.
|
|
*/
|
|
private Element setDead(Element element) {
|
|
final Player player = getGame()
|
|
.getFreeColGameObject(element.getAttribute("player"),Player.class);
|
|
if (player == null) {
|
|
logger.warning("Invalid player for setDead");
|
|
return null;
|
|
}
|
|
|
|
invokeLater(() -> { igc().setDead(player); });
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handle a "setStance"-request.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return Null.
|
|
*/
|
|
private Element setStance(Element element) {
|
|
final Game game = getGame();
|
|
final Stance stance = Enum.valueOf(Stance.class,
|
|
element.getAttribute("stance"));
|
|
if (stance == null) {
|
|
logger.warning("Invalid stance for setStance");
|
|
return null;
|
|
}
|
|
final Player p1 = game
|
|
.getFreeColGameObject(element.getAttribute("first"), Player.class);
|
|
if (p1 == null) {
|
|
logger.warning("Invalid player1 for setStance");
|
|
return null;
|
|
}
|
|
final Player p2 = game
|
|
.getFreeColGameObject(element.getAttribute("second"),Player.class);
|
|
if (p2 == null) {
|
|
logger.warning("Invalid player2 for setStance");
|
|
return null;
|
|
}
|
|
|
|
invokeLater(() -> { igc().setStance(stance, p1, p2); });
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handle a "spyResult" message.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return Null.
|
|
*/
|
|
private Element spyResult(Element element) {
|
|
// The element contains two children, being the full and
|
|
// normal versions of the settlement-being-spied-upon's tile.
|
|
// It has to be the tile, as otherwise we do not see the units
|
|
// defending the settlement. So, we have to unpack, update
|
|
// with the first, display, then update with the second. This
|
|
// is hacky as the client could retain the settlement
|
|
// information, but the potential abuses are limited.
|
|
NodeList nodeList = element.getChildNodes();
|
|
if (nodeList.getLength() != 2) {
|
|
logger.warning("spyResult length = " + nodeList.getLength());
|
|
return null;
|
|
}
|
|
|
|
final Game game = getGame();
|
|
final String tileId = element.getAttribute("tile");
|
|
final Tile tile = game.getFreeColGameObject(tileId, Tile.class);
|
|
if (tile == null) {
|
|
logger.warning("spyResult bad tile = " + tileId);
|
|
return null;
|
|
}
|
|
|
|
// Read the privileged tile information from fullElement, and
|
|
// pass a runnable to the display routine that restores the
|
|
// normal view of the tile, which happens when the colony panel
|
|
// is closed.
|
|
final Element fullElement = (Element)nodeList.item(0);
|
|
final Element normalElement = (Element)nodeList.item(1);
|
|
tile.readFromXMLElement(fullElement);
|
|
invokeLater(() -> {
|
|
igc().spyColony(tile, () -> {
|
|
tile.readFromXMLElement(normalElement);
|
|
});
|
|
});
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handle an "update"-message.
|
|
*
|
|
* @param element The element (root element in a DOM-parsed XML
|
|
* tree) that holds all the information.
|
|
* @return Null.
|
|
*/
|
|
private Element update(Element element) {
|
|
final Player player = getFreeColClient().getMyPlayer();
|
|
boolean visibilityChange = false;
|
|
|
|
NodeList nodeList = element.getChildNodes();
|
|
for (int i = 0; i < nodeList.getLength(); i++) {
|
|
Element e = (Element)nodeList.item(i);
|
|
String id = FreeColObject.readId(e);
|
|
FreeColGameObject fcgo = getGame().getFreeColGameObject(id);
|
|
if (fcgo == null) {
|
|
logger.warning("Update object not present in client: " + id);
|
|
} else {
|
|
fcgo.readFromXMLElement(e);
|
|
}
|
|
if ((fcgo instanceof Player && (fcgo == player))
|
|
|| ((fcgo instanceof Settlement || fcgo instanceof Unit)
|
|
&& player.owns((Ownable)fcgo))) {
|
|
visibilityChange = true;//-vis(player)
|
|
}
|
|
}
|
|
if (visibilityChange) player.invalidateCanSeeTiles();//+vis(player)
|
|
|
|
return null;
|
|
}
|
|
}
|