/projects/megamek-0.35.18/src/megamek/server/Server.java
https://gitlab.com/essere.lab.public/qualitas.class-corpus · Java · 1375 lines · 911 code · 169 blank · 295 comment · 151 complexity · d722323833971bedda75cb7b6fb7ddec MD5 · raw file
- /*
- * MegaMek -
- * Copyright (C) 2000,2001,2002,2003,2004,2005 Ben Mazur (bmazur@sev.org)
- *
- * This program 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.
- *
- * This program 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.
- */
-
- package megamek.server;
-
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InterruptedIOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.net.InetAddress;
- import java.net.ServerSocket;
- import java.net.Socket;
- import java.net.UnknownHostException;
- import java.text.SimpleDateFormat;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.Comparator;
- import java.util.Date;
- import java.util.Enumeration;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.Hashtable;
- import java.util.Iterator;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- import java.util.Timer;
- import java.util.TimerTask;
- import java.util.TreeMap;
- import java.util.Vector;
-
- import megamek.MegaMek;
- import megamek.client.ui.AWT.util.PlayerColors;
- import megamek.common.Aero;
- import megamek.common.AmmoType;
- import megamek.common.BattleArmor;
- import megamek.common.Bay;
- import megamek.common.BipedMech;
- import megamek.common.Board;
- import megamek.common.BombType;
- import megamek.common.Building;
- import megamek.common.CalledShot;
- import megamek.common.CommonConstants;
- import megamek.common.Compute;
- import megamek.common.Coords;
- import megamek.common.CriticalSlot;
- import megamek.common.Dropship;
- import megamek.common.Engine;
- import megamek.common.Entity;
- import megamek.common.EntityMovementMode;
- import megamek.common.EntityMovementType;
- import megamek.common.EntitySelector;
- import megamek.common.EntityWeightClass;
- import megamek.common.EquipmentMode;
- import megamek.common.EquipmentType;
- import megamek.common.FighterSquadron;
- import megamek.common.Flare;
- import megamek.common.FuelTank;
- import megamek.common.Game;
- import megamek.common.GameTurn;
- import megamek.common.GunEmplacement;
- import megamek.common.HexTarget;
- import megamek.common.HitData;
- import megamek.common.IArmorState;
- import megamek.common.IBoard;
- import megamek.common.IEntityRemovalConditions;
- import megamek.common.IGame;
- import megamek.common.IHex;
- import megamek.common.ILocationExposureStatus;
- import megamek.common.INarcPod;
- import megamek.common.ITerrain;
- import megamek.common.Infantry;
- import megamek.common.InfernoTracker;
- import megamek.common.Jumpship;
- import megamek.common.LargeSupportTank;
- import megamek.common.LocationFullException;
- import megamek.common.LosEffects;
- import megamek.common.MapSettings;
- import megamek.common.Mech;
- import megamek.common.MechWarrior;
- import megamek.common.Minefield;
- import megamek.common.MiscType;
- import megamek.common.Mounted;
- import megamek.common.MovePath;
- import megamek.common.MoveStep;
- import megamek.common.OffBoardDirection;
- import megamek.common.PhysicalResult;
- import megamek.common.Pilot;
- import megamek.common.PilotingRollData;
- import megamek.common.PlanetaryConditions;
- import megamek.common.Player;
- import megamek.common.Protomech;
- import megamek.common.QuadMech;
- import megamek.common.Report;
- import megamek.common.Roll;
- import megamek.common.SmallCraft;
- import megamek.common.SpaceStation;
- import megamek.common.SpecialHexDisplay;
- import megamek.common.SupportTank;
- import megamek.common.SupportVTOL;
- import megamek.common.Tank;
- import megamek.common.TargetRoll;
- import megamek.common.Targetable;
- import megamek.common.Team;
- import megamek.common.TeleMissile;
- import megamek.common.Terrain;
- import megamek.common.Terrains;
- import megamek.common.ToHitData;
- import megamek.common.TurnOrdered;
- import megamek.common.TurnVectors;
- import megamek.common.UnitLocation;
- import megamek.common.VTOL;
- import megamek.common.Warship;
- import megamek.common.WeaponComparator;
- import megamek.common.WeaponType;
- import megamek.common.IGame.Phase;
- import megamek.common.MovePath.MoveStepType;
- import megamek.common.actions.AbstractAttackAction;
- import megamek.common.actions.ArtilleryAttackAction;
- import megamek.common.actions.AttackAction;
- import megamek.common.actions.BAVibroClawAttackAction;
- import megamek.common.actions.BreakGrappleAttackAction;
- import megamek.common.actions.BrushOffAttackAction;
- import megamek.common.actions.ChargeAttackAction;
- import megamek.common.actions.ClearMinefieldAction;
- import megamek.common.actions.ClubAttackAction;
- import megamek.common.actions.DfaAttackAction;
- import megamek.common.actions.DodgeAction;
- import megamek.common.actions.EntityAction;
- import megamek.common.actions.FindClubAction;
- import megamek.common.actions.FlipArmsAction;
- import megamek.common.actions.GrappleAttackAction;
- import megamek.common.actions.JumpJetAttackAction;
- import megamek.common.actions.KickAttackAction;
- import megamek.common.actions.LayExplosivesAttackAction;
- import megamek.common.actions.ProtomechPhysicalAttackAction;
- import megamek.common.actions.PunchAttackAction;
- import megamek.common.actions.PushAttackAction;
- import megamek.common.actions.RamAttackAction;
- import megamek.common.actions.RepairWeaponMalfunctionAction;
- import megamek.common.actions.SearchlightAttackAction;
- import megamek.common.actions.SpotAction;
- import megamek.common.actions.TeleMissileAttackAction;
- import megamek.common.actions.ThrashAttackAction;
- import megamek.common.actions.TorsoTwistAction;
- import megamek.common.actions.TriggerAPPodAction;
- import megamek.common.actions.TriggerBPodAction;
- import megamek.common.actions.TripAttackAction;
- import megamek.common.actions.UnjamAction;
- import megamek.common.actions.UnjamTurretAction;
- import megamek.common.actions.UnloadStrandedAction;
- import megamek.common.actions.WeaponAttackAction;
- import megamek.common.containers.PlayerIDandList;
- import megamek.common.event.GameListener;
- import megamek.common.net.ConnectionFactory;
- import megamek.common.net.ConnectionListenerAdapter;
- import megamek.common.net.DisconnectedEvent;
- import megamek.common.net.IConnection;
- import megamek.common.net.Packet;
- import megamek.common.net.PacketReceivedEvent;
- import megamek.common.options.IBasicOption;
- import megamek.common.options.IOption;
- import megamek.common.preference.PreferenceManager;
- import megamek.common.util.BoardUtilities;
- import megamek.common.util.StringUtil;
- import megamek.common.verifier.EntityVerifier;
- import megamek.common.verifier.TestEntity;
- import megamek.common.verifier.TestMech;
- import megamek.common.verifier.TestTank;
- import megamek.common.weapons.AttackHandler;
- import megamek.common.weapons.TAGHandler;
- import megamek.common.weapons.Weapon;
- import megamek.common.weapons.WeaponHandler;
- import megamek.server.commands.AddBotCommand;
- import megamek.server.commands.CheckBVCommand;
- import megamek.server.commands.DefeatCommand;
- import megamek.server.commands.ExportListCommand;
- import megamek.server.commands.FixElevationCommand;
- import megamek.server.commands.HelpCommand;
- import megamek.server.commands.KickCommand;
- import megamek.server.commands.ListSavesCommand;
- import megamek.server.commands.LoadGameCommand;
- import megamek.server.commands.LocalLoadGameCommand;
- import megamek.server.commands.LocalSaveGameCommand;
- import megamek.server.commands.NukeCommand;
- import megamek.server.commands.ResetCommand;
- import megamek.server.commands.RollCommand;
- import megamek.server.commands.RulerCommand;
- import megamek.server.commands.SaveGameCommand;
- import megamek.server.commands.SeeAllCommand;
- import megamek.server.commands.ServerCommand;
- import megamek.server.commands.ShowEntityCommand;
- import megamek.server.commands.ShowTileCommand;
- import megamek.server.commands.ShowValidTargetsCommand;
- import megamek.server.commands.SkipCommand;
- import megamek.server.commands.TeamCommand;
- import megamek.server.commands.VictoryCommand;
- import megamek.server.commands.WhoCommand;
- import megamek.server.victory.Victory;
-
- /**
- * @author Ben Mazur
- */
- public class Server implements Runnable {
-
- /**
- * The DamageType enumeration is used for the damageEntity function.
- */
- public enum DamageType {
- NONE, FRAGMENTATION, FLECHETTE, ACID, INCENDIARY, IGNORE_PASSENGER, ANTI_TSM, ANTI_INFANTRY, NAIL_RIVET, NONPENETRATING
- }
-
- // public final static String LEGAL_CHARS =
- // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-";
- public final static String DEFAULT_BOARD = MapSettings.BOARD_SURPRISE;
-
- private final static String VERIFIER_CONFIG_FILENAME = "data/mechfiles/UnitVerifierOptions.xml";
-
- // server setup
- private String password;
-
- private ServerSocket serverSocket;
-
- private String motd;
-
- // game info
- Vector<IConnection> connections = new Vector<IConnection>(4);
-
- Vector<IConnection> connectionsPending = new Vector<IConnection>(4);
-
- Hashtable<Integer, IConnection> connectionIds = new Hashtable<Integer, IConnection>();
-
- private int connectionCounter;
-
- IGame game = new Game();
-
- private Vector<Report> vPhaseReport = new Vector<Report>();
-
- private MapSettings mapSettings = new MapSettings();
-
- // commands
- private Hashtable<String, ServerCommand> commandsHash = new Hashtable<String, ServerCommand>();
-
- // listens for and connects players
- private Thread connector;
-
- // Track buildings that are affected by an entity's movement.
- private Hashtable<Building, Boolean> affectedBldgs = new Hashtable<Building, Boolean>();
-
- // Track Physical Action results, HACK to deal with opposing pushes
- // canceling each other
- private Vector<PhysicalResult> physicalResults = new Vector<PhysicalResult>();
-
- private Vector<DynamicTerrainProcessor> terrainProcessors = new Vector<DynamicTerrainProcessor>();
-
- private Timer timer = new Timer();
-
- private static EntityVerifier entityVerifier;
-
- private ArrayList<int[]> scheduledNukes = new ArrayList<int[]>();
-
- private static Server serverInstance = null;
-
- private ConnectionListenerAdapter connectionListener = new ConnectionListenerAdapter() {
-
- /**
- * Called when it is sensed that a connection has terminated.
- */
- @Override
- public void disconnected(DisconnectedEvent e) {
- IConnection conn = e.getConnection();
-
- // write something in the log
- System.out.println("s: connection " + conn.getId() + " disconnected");
-
- connections.removeElement(conn);
- connectionsPending.removeElement(conn);
- connectionIds.remove(new Integer(conn.getId()));
-
- // if there's a player for this connection, remove it too
- Player player = getPlayer(conn.getId());
- if (null != player) {
- Server.this.disconnected(player);
- }
-
- }
-
- @Override
- public void packetReceived(PacketReceivedEvent e) {
- Server.this.handle(e.getConnection().getId(), e.getPacket());
- }
-
- };
-
- /**
- * Construct a new GameHost and begin listening for incoming clients.
- *
- * @param password
- * the <code>String</code> that is set as a password
- * @param port
- * the <code>int</code> value that specifies the port that is
- * used
- */
- public Server(String password, int port) throws IOException {
- serverInstance = this;
- this.password = password.length() > 0 ? password : null;
- // initialize server socket
- serverSocket = new ServerSocket(port);
- serverSocket.setSoTimeout(50);
-
- motd = createMotd();
-
- game.getOptions().initialize();
- game.getOptions().loadOptions();
-
- changePhase(IGame.Phase.PHASE_LOUNGE);
-
- // display server start text
- System.out.println("s: starting a new server...");
-
- try {
- String host = InetAddress.getLocalHost().getHostName();
- System.out.print("s: hostname = '");
- System.out.print(host);
- System.out.print("' port = ");
- System.out.println(serverSocket.getLocalPort());
- InetAddress[] addresses = InetAddress.getAllByName(host);
- for (InetAddress addresse : addresses) {
- System.out.println("s: hosting on address = " + addresse.getHostAddress());
- }
- } catch (UnknownHostException e) {
- // oh well.
- }
-
- System.out.println("s: password = " + this.password);
-
- // register commands
- registerCommand(new DefeatCommand(this));
- registerCommand(new ExportListCommand(this));
- registerCommand(new FixElevationCommand(this));
- registerCommand(new HelpCommand(this));
- registerCommand(new KickCommand(this));
- registerCommand(new ListSavesCommand(this));
- registerCommand(new LocalSaveGameCommand(this));
- registerCommand(new LocalLoadGameCommand(this));
- registerCommand(new ResetCommand(this));
- registerCommand(new RollCommand(this));
- registerCommand(new SaveGameCommand(this));
- registerCommand(new LoadGameCommand(this));
- registerCommand(new SeeAllCommand(this));
- registerCommand(new SkipCommand(this));
- registerCommand(new VictoryCommand(this));
- registerCommand(new WhoCommand(this));
- registerCommand(new TeamCommand(this));
- registerCommand(new ShowTileCommand(this));
- registerCommand(new ShowEntityCommand(this));
- registerCommand(new RulerCommand(this));
- registerCommand(new ShowValidTargetsCommand(this));
- registerCommand(new AddBotCommand(this));
- registerCommand(new CheckBVCommand(this));
- registerCommand(new NukeCommand(this));
-
- // register terrain processors
- terrainProcessors.add(new FireProcessor(this));
- terrainProcessors.add(new SmokeProcessor(this));
- terrainProcessors.add(new GeyserProcessor(this));
- terrainProcessors.add(new ElevatorProcessor(this));
- terrainProcessors.add(new ScreenProcessor(this));
- terrainProcessors.add(new WeatherProcessor(this));
- terrainProcessors.add(new QuicksandProcessor(this));
-
- // Fully initialised, now accept connections
- connector = new Thread(this, "Connection Listener");
- connector.start();
- }
-
- /**
- * Sets the game for this server. Restores any transient fields, and sets
- * all players as ghosts. This should only be called during server
- * initialization before any players have connected.
- */
- public void setGame(IGame g) {
- game = g;
-
- // reattach the transient fields and ghost the players
- for (Enumeration<Entity> e = game.getEntities(); e.hasMoreElements();) {
- Entity ent = e.nextElement();
- ent.setGame(game);
- }
- game.setOutOfGameEntitiesVector(game.getOutOfGameEntitiesVector());
- for (Enumeration<Player> e = game.getPlayers(); e.hasMoreElements();) {
- Player p = e.nextElement();
- p.setGame(game);
- p.setGhost(true);
- }
-
- }
-
- /**
- * Resets all the connections. The only use of this right now is when games
- * are loaded after clients have connected
- */
- public void resetConnections() {
- for (Enumeration<IConnection> connEnum = connections.elements(); connEnum.hasMoreElements();) {
- IConnection conn = connEnum.nextElement();
- send(conn.getId(), new Packet(Packet.COMMAND_RESET_CONNECTION));
- }
- }
-
- /** Returns the current game object */
- public IGame getGame() {
- return game;
- }
-
- /**
- * Make a default message o' the day containing the version string, and if
- * it was found, the build timestamp
- */
- private String createMotd() {
- StringBuffer buf = new StringBuffer();
- buf.append("Welcome to MegaMek. Server is running version ");
- buf.append(MegaMek.VERSION);
- buf.append(", build date ");
- if (MegaMek.TIMESTAMP > 0L) {
- buf.append(new Date(MegaMek.TIMESTAMP).toString());
- } else {
- buf.append("unknown");
- }
- buf.append('.');
-
- return buf.toString();
- }
-
- /**
- * @return true if the server has a password
- */
- public boolean isPassworded() {
- return password != null;
- }
-
- /**
- * @return true if the password matches
- */
- public boolean isPassword(Object guess) {
- return password.equals(guess);
- }
-
- /**
- * Registers a new command in the server command table
- */
- private void registerCommand(ServerCommand command) {
- commandsHash.put(command.getName(), command);
- }
-
- /**
- * Returns the command associated with the specified name
- */
- public ServerCommand getCommand(String name) {
- return commandsHash.get(name);
- }
-
- /**
- * Shuts down the server.
- */
- public void die() {
- timer.cancel();
-
- // kill thread accepting new connections
- connector = null;
-
- // close socket
- try {
- serverSocket.close();
- } catch (IOException ex) {
- }
-
- // kill pending connnections
- for (Enumeration<IConnection> connEnum = connectionsPending.elements(); connEnum.hasMoreElements();) {
- IConnection conn = connEnum.nextElement();
- conn.close();
- }
- connectionsPending.removeAllElements();
-
- // Send "kill" commands to all connections
- // N.B. I may be starting a race here.
- for (Enumeration<IConnection> connEnum = connections.elements(); connEnum.hasMoreElements();) {
- IConnection conn = connEnum.nextElement();
- send(conn.getId(), new Packet(Packet.COMMAND_CLOSE_CONNECTION));
- }
-
- // kill active connnections
- for (Enumeration<IConnection> connEnum = connections.elements(); connEnum.hasMoreElements();) {
- IConnection conn = connEnum.nextElement();
- conn.close();
- }
-
- connections.removeAllElements();
- connectionIds.clear();
- System.out.flush();
- }
-
- /**
- * Returns an enumeration of all the command names
- */
- public Enumeration<String> getAllCommandNames() {
- return commandsHash.keys();
- }
-
- /**
- * Sent when a client attempts to connect.
- */
- void greeting(int cn) {
- // send server greeting -- client should reply with client info.
- sendToPending(cn, new Packet(Packet.COMMAND_SERVER_GREETING));
- }
-
- /**
- * Returns a free connection id.
- */
- public int getFreeConnectionId() {
- while ((getPendingConnection(connectionCounter) != null) || (getConnection(connectionCounter) != null)
- || (getPlayer(connectionCounter) != null)) {
- connectionCounter++;
- }
- return connectionCounter;
- }
-
- /**
- * Returns a free entity id. Perhaps this should be in Game instead.
- */
- public int getFreeEntityId() {
- return game.getNextEntityId();
- }
-
- /**
- * Allow the player to set whatever parameters he is able to
- */
- private void receivePlayerInfo(Packet packet, int connId) {
- Player player = (Player) packet.getObject(0);
- Player connPlayer = game.getPlayer(connId);
- if (null != connPlayer) {
- connPlayer.setColorIndex(player.getColorIndex());
- connPlayer.setStartingPos(player.getStartingPos());
- connPlayer.setTeam(player.getTeam());
- connPlayer.setCamoCategory(player.getCamoCategory());
- connPlayer.setCamoFileName(player.getCamoFileName());
- connPlayer.setNbrMFConventional(player.getNbrMFConventional());
- connPlayer.setNbrMFCommand(player.getNbrMFCommand());
- connPlayer.setNbrMFVibra(player.getNbrMFVibra());
- connPlayer.setNbrMFActive(player.getNbrMFActive());
- connPlayer.setNbrMFInferno(player.getNbrMFInferno());
- connPlayer.setConstantInitBonus(player.getConstantInitBonus());
- }
- }
-
- /**
- * Correct a duplicate playername
- *
- * @param oldName
- * the <code>String</code> old playername, that is a duplicate
- * @return the <code>String</code> new playername
- */
- private String correctDupeName(String oldName) {
- for (Enumeration<Player> i = game.getPlayers(); i.hasMoreElements();) {
- Player player = i.nextElement();
- if (player.getName().equals(oldName)) {
- // We need to correct it.
- String newName = oldName;
- int dupNum = 2;
- try {
- dupNum = Integer.parseInt(oldName.substring(oldName.lastIndexOf(".") + 1));
- dupNum++;
- newName = oldName.substring(0, oldName.lastIndexOf("."));
- } catch (Exception e) {
- // If this fails, we don't care much.
- // Just assume it's the first time for this name.
- dupNum = 2;
- }
- newName = newName.concat(".").concat(Integer.toString(dupNum));
- return correctDupeName(newName);
- }
- }
- return oldName;
- }
-
- /**
- * Recieves a player name, sent from a pending connection, and connects that
- * connection.
- */
- private void receivePlayerName(Packet packet, int connId) {
- final IConnection conn = getPendingConnection(connId);
- String name = (String) packet.getObject(0);
- boolean returning = false;
-
- // this had better be from a pending connection
- if (conn == null) {
- System.out.println("server: got a client name from a non-pending" + " connection");
- return;
- }
-
- // check if they're connecting with the same name as a ghost player
- for (Enumeration<Player> i = game.getPlayers(); i.hasMoreElements();) {
- Player player = i.nextElement();
- if (player.getName().equals(name)) {
- if (player.isGhost()) {
- returning = true;
- player.setGhost(false);
- // switch id
- connId = player.getId();
- conn.setId(connId);
- }
- }
- }
-
- if (!returning) {
- // Check to avoid duplicate names...
- name = correctDupeName(name);
- send(connId, new Packet(Packet.COMMAND_SERVER_CORRECT_NAME, name));
- }
-
- // right, switch the connection into the "active" bin
- connectionsPending.removeElement(conn);
- connections.addElement(conn);
- connectionIds.put(new Integer(conn.getId()), conn);
-
- // add and validate the player info
- if (!returning) {
- int team = Player.TEAM_NONE;
- for (Player p : game.getPlayersVector()) {
- if (p.getTeam() > team) {
- team = p.getTeam();
- }
- }
- team++;
- Player newPlayer = new Player(connId, name);
- newPlayer.setTeam(Math.min(team, 5));
- game.addPlayer(connId, newPlayer);
- validatePlayerInfo(connId);
- }
-
- // if it is not the lounge phase, this player becomes an observer
- Player player = getPlayer(connId);
- if ((game.getPhase() != IGame.Phase.PHASE_LOUNGE) && (null != player) && (game.getEntitiesOwnedBy(player) < 1)) {
- player.setObserver(true);
- }
-
- // send the player the motd
- sendServerChat(connId, motd);
-
- // send info that the player has connected
- send(createPlayerConnectPacket(connId));
-
- // tell them their local playerId
- send(connId, new Packet(Packet.COMMAND_LOCAL_PN, new Integer(connId)));
-
- // send current game info
- sendCurrentInfo(connId);
-
- try {
- InetAddress[] addresses = InetAddress.getAllByName(InetAddress.getLocalHost().getHostName());
- for (InetAddress addresse : addresses) {
- sendServerChat(connId, "Machine IP is " + addresse.getHostAddress());
- }
- } catch (UnknownHostException e) {
- // oh well.
- }
-
- // Send the port we're listening on. Only useful for the player
- // on the server machine to check.
- sendServerChat(connId, "Listening on port " + serverSocket.getLocalPort());
-
- // Get the player *again*, because they may have disconnected.
- player = getPlayer(connId);
- if (null != player) {
- StringBuffer buff = new StringBuffer();
- buff.append(player.getName()).append(" connected from ").append(getClient(connId).getInetAddress());
- String who = buff.toString();
- System.out.print("s: player #");
- System.out.print(connId);
- System.out.print(", ");
- System.out.println(who);
-
- sendServerChat(who);
-
- } // Found the player
-
- }
-
- /**
- * Sends a player the info they need to look at the current phase. This is
- * triggered when a player first connects to the server.
- */
- private void sendCurrentInfo(int connId) {
- // why are these two outside the player != null check below?
- transmitAllPlayerConnects(connId);
- send(connId, createGameSettingsPacket());
- send(connId, createPlanetaryConditionsPacket());
-
- Player player = game.getPlayer(connId);
- if (null != player) {
- send(connId, new Packet(Packet.COMMAND_SENDING_MINEFIELDS, player.getMinefields()));
-
- switch (game.getPhase()) {
- case PHASE_LOUNGE:
- send(connId, createMapSettingsPacket());
- // Send Entities *after* the Lounge Phase Change
- send(connId, new Packet(Packet.COMMAND_PHASE_CHANGE, game.getPhase()));
- if (doBlind()) {
- send(connId, createFilteredFullEntitiesPacket(player));
- } else {
- send(connId, createFullEntitiesPacket());
- }
- break;
- default:
- send(connId, new Packet(Packet.COMMAND_ROUND_UPDATE, new Integer(game.getRoundCount())));
- send(connId, createBoardPacket());
- send(connId, createAllReportsPacket(player));
-
- // Send entities *before* other phase changes.
- if (doBlind()) {
- send(connId, createFilteredFullEntitiesPacket(player));
- } else {
- send(connId, createFullEntitiesPacket());
- }
- player.setDone(game.getEntitiesOwnedBy(player) <= 0);
- send(connId, new Packet(Packet.COMMAND_PHASE_CHANGE, game.getPhase()));
- break;
- }
- if ((game.getPhase() == IGame.Phase.PHASE_FIRING) || (game.getPhase() == IGame.Phase.PHASE_TARGETING)
- || (game.getPhase() == IGame.Phase.PHASE_OFFBOARD)
- || (game.getPhase() == IGame.Phase.PHASE_PHYSICAL)) {
- // can't go above, need board to have been sent
- send(connId, createAttackPacket(game.getActionsVector(), 0));
- send(connId, createAttackPacket(game.getChargesVector(), 1));
- send(connId, createAttackPacket(game.getRamsVector(), 1));
- send(connId, createAttackPacket(game.getTeleMissileAttacksVector(), 1));
- }
- if (game.phaseHasTurns(game.getPhase())) {
- send(connId, createTurnVectorPacket());
- send(connId, createTurnIndexPacket());
- }
-
- send(connId, createArtilleryPacket(player));
- send(connId, createFlarePacket());
- send(connId, createSpecialHexDisplayPacket(connId));
-
- } // Found the player.
-
- }
-
- /**
- * Resend entities to the player called by seeall command
- */
- public void sendEntities(int connId) {
- if (doBlind()) {
- send(connId, createFilteredEntitiesPacket(getPlayer(connId)));
- } else {
- send(connId, createEntitiesPacket());
- }
- }
-
- /**
- * Validates the player info.
- */
- public void validatePlayerInfo(int playerId) {
- final Player player = getPlayer(playerId);
-
- // maybe this isn't actually useful
- // // replace characters we don't like with "X"
- // StringBuffer nameBuff = new StringBuffer(player.getName());
- // for (int i = 0; i < nameBuff.length(); i++) {
- // int chr = nameBuff.charAt(i);
- // if (LEGAL_CHARS.indexOf(chr) == -1) {
- // nameBuff.setCharAt(i, 'X');
- // }
- // }
- // player.setName(nameBuff.toString());
-
- // TODO: check for duplicate or reserved names
-
- // make sure colorIndex is unique
- boolean[] colorUsed = new boolean[Player.colorNames.length];
- for (Enumeration<Player> i = game.getPlayers(); i.hasMoreElements();) {
- final Player otherPlayer = i.nextElement();
- if (otherPlayer.getId() != playerId) {
- colorUsed[otherPlayer.getColorIndex()] = true;
- }
- }
- if ((null != player) && colorUsed[player.getColorIndex()]) {
- // find a replacement color;
- for (int i = 0; i < colorUsed.length; i++) {
- if (!colorUsed[i]) {
- player.setColorIndex(i);
- break;
- }
- }
- }
-
- }
-
- /**
- * Called when it's been determined that an actual player disconnected.
- * Notifies the other players and does the appropriate housekeeping.
- */
- void disconnected(Player player) {
- IGame.Phase phase = game.getPhase();
-
- // in the lounge, just remove all entities for that player
- if (phase == IGame.Phase.PHASE_LOUNGE) {
- removeAllEntitesOwnedBy(player);
- }
-
- // if a player has active entities, he becomes a ghost
- // except the VICTORY_PHASE when the dosconnected
- // player is most likely the Bot disconnected after receiving
- // the COMMAND_END_OF_GAME command
- // see the Bug 1225949.
- // TODO Perhaps there is a better solution to handle the Bot disconnect
- if ((game.getEntitiesOwnedBy(player) > 0) && (phase != IGame.Phase.PHASE_VICTORY)) {
- player.setGhost(true);
- player.setDone(true);
- send(createPlayerUpdatePacket(player.getId()));
- } else {
- game.removePlayer(player.getId());
- send(new Packet(Packet.COMMAND_PLAYER_REMOVE, new Integer(player.getId())));
- }
-
- // make sure the game advances
- if (game.phaseHasTurns(game.getPhase()) && (null != game.getTurn())) {
- if (game.getTurn().isValid(player.getId(), game)) {
- sendGhostSkipMessage(player);
- }
- } else {
- checkReady();
- }
-
- // notify other players
- sendServerChat(player.getName() + " disconnected.");
-
- // log it
- System.out.println("s: removed player " + player.getName());
-
- // Reset the game after Elvis has left the building.
- if (0 == game.getNoOfPlayers()) {
- resetGame();
- }
- }
-
- /**
- * Checks each player to see if he has no entities, and if true, sets the
- * observer flag for that player. An exception is that there are no
- * observers during the lounge phase.
- */
- public void checkForObservers() {
- for (Enumeration<Player> e = game.getPlayers(); e.hasMoreElements();) {
- Player p = e.nextElement();
- p.setObserver((game.getEntitiesOwnedBy(p) < 1) && (game.getPhase() != IGame.Phase.PHASE_LOUNGE));
- }
- }
-
- /**
- * Reset the game back to the lounge. TODO: couldn't this be a hazard if
- * there are other things executing at the same time?
- */
- public void resetGame() {
- // remove all entities
- game.reset();
- send(createEntitiesPacket());
- send(new Packet(Packet.COMMAND_SENDING_MINEFIELDS, new Vector<Object>()));
-
- // remove ghosts
- ArrayList<Player> ghosts = new ArrayList<Player>();
- for (Enumeration<Player> players = game.getPlayers(); players.hasMoreElements();) {
- Player p = players.nextElement();
- if (p.isGhost()) {
- ghosts.add(p);
- } else {
- // non-ghosts set their starting positions to any
- p.setStartingPos(0);
- send(createPlayerUpdatePacket(p.getId()));
- }
- }
- for (Player p : ghosts) {
- game.removePlayer(p.getId());
- send(new Packet(Packet.COMMAND_PLAYER_REMOVE, new Integer(p.getId())));
- }
-
- // reset all players
- resetPlayersDone();
- transmitAllPlayerDones();
-
- // Write end of game to stdout so controlling scripts can rotate logs.
- SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
- System.out.print(format.format(new Date()));
- System.out.println(" END OF GAME");
-
- changePhase(IGame.Phase.PHASE_LOUNGE);
- }
-
- /**
- * automatically save the game
- */
- public void autoSave() {
- String fileName = "autosave";
- if (PreferenceManager.getClientPreferences().stampFilenames()) {
- fileName = StringUtil.addDateTimeStamp(fileName);
- }
- saveGame(fileName, game.getOptions().booleanOption("autosave_msg"));
- }
-
- /**
- * save the game and send it to the sepecified connection
- *
- * @param connId
- * The <code>int</code> connection id to send to
- * @param sFile
- * The <code>String</code> filename to use
- */
- public void sendSaveGame(int connId, String sFile) {
- saveGame(sFile, false);
- String sFinalFile = sFile;
- if (!sFinalFile.endsWith(".sav")) {
- sFinalFile = sFile + ".sav";
- }
- String localFile = "savegames" + File.separator + sFinalFile;
- File f = new File(localFile);
- try {
- ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f));
- send(connId, new Packet(Packet.COMMAND_SEND_SAVEGAME, new Object[] { sFinalFile, ois.readObject() }));
- sendChat(connId, "***Server", "Savegame has been sent to you.");
- ois.close();
- } catch (Exception e) {
- System.err.println("Unable to load file: " + f);
- e.printStackTrace();
- }
- }
-
- /**
- * save the game
- *
- * @param sFile
- * The <code>String</code> filename to use
- * @param sendChat
- * A <code>boolean</code> value wether or not to announce the
- * saving to the server chat.
- */
- public void saveGame(String sFile, boolean sendChat) {
- String sFinalFile = sFile;
- if (!sFinalFile.endsWith(".sav")) {
- sFinalFile = sFile + ".sav";
- }
- try {
- File sDir = new File("savegames");
- if (!sDir.exists()) {
- sDir.mkdir();
- }
-
- Vector<GameListener> gameListenersClone = new Vector<GameListener>();
- for (GameListener listener : getGame().getGameListeners()) {
- gameListenersClone.add(listener);
- }
- getGame().purgeGameListeners();
- // the passed in string might be an absolut name, including a path
- // when the user uses the save game dialog.
- // if so, save there
- // we also need to replace any | with " ", so we can support saving
- // in folders with spacse
- sFinalFile = sFinalFile.replace("|", " ");
- File dir = new File(sFinalFile);
- if (dir.getParent() == null) {
- sFinalFile = sDir + File.separator + sFinalFile;
- }
-
- ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(sFinalFile));
-
- oos.writeObject(game);
- oos.flush();
- oos.close();
-
- for (GameListener listener : gameListenersClone) {
- getGame().addGameListener(listener);
- }
- } catch (Exception e) {
- System.err.println("Unable to save file: " + sFinalFile);
- e.printStackTrace();
- }
-
- if (sendChat) {
- sendChat("MegaMek", "Game saved to " + sFinalFile);
- }
- }
-
- /**
- * save the game
- *
- * @param sFile
- * The <code>String</code> filename to use
- */
- public void saveGame(String sFile) {
- saveGame(sFile, true);
- }
-
- /**
- * send a packet to the connection tells it load a locally saved game
- *
- * @param connId
- * The <code>int</code> connection id to send to
- * @param sFile
- * The <code>String</code> filename to use
- */
- public void sendLoadGame(int connId, String sFile) {
- String sFinalFile = sFile;
- if (!sFinalFile.endsWith(".sav")) {
- sFinalFile = sFile + ".sav";
- }
- send(connId, new Packet(Packet.COMMAND_LOAD_SAVEGAME, new Object[] { sFinalFile }));
- }
-
- /**
- * load the game
- *
- * @param f
- * The <code>File</code> to load
- * @return A <code>boolean</code> value wether or not the loading was
- * successfull
- */
- public boolean loadGame(File f) {
- System.out.println("s: loading saved game file '" + f + '\'');
- try {
- ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f));
- game = (IGame) ois.readObject();
- ois.close();
- } catch (Exception e) {
- System.err.println("Unable to load file: " + f);
- e.printStackTrace();
- return false;
- }
-
- // a bit redundant, but there's some initialization code there
- setGame(game);
-
- return true;
- }
-
- /**
- * Shortcut to game.getPlayer(id)
- */
- public Player getPlayer(int id) {
- return game.getPlayer(id);
- }
-
- /**
- * Removes all entities owned by a player. Should only be called when it
- * won't cause trouble (the lounge, for instance, or between phases.)
- *
- * @param player
- * whose entites are to be removed
- */
- private void removeAllEntitesOwnedBy(Player player) {
- Vector<Entity> toRemove = new Vector<Entity>();
-
- for (Enumeration<Entity> e = game.getEntities(); e.hasMoreElements();) {
- final Entity entity = e.nextElement();
-
- if (entity.getOwner().equals(player)) {
- toRemove.addElement(entity);
- }
- }
-
- for (Entity entity : toRemove) {
- int id = entity.getId();
- game.removeEntity(id, IEntityRemovalConditions.REMOVE_NEVER_JOINED);
- send(createRemoveEntityPacket(id, IEntityRemovalConditions.REMOVE_NEVER_JOINED));
- }
- }
-
- /**
- * a shorter name for getConnection()
- */
- private IConnection getClient(int connId) {
- return getConnection(connId);
- }
-
- /**
- * Returns a connection, indexed by id
- */
- public Enumeration<IConnection> getConnections() {
- return connections.elements();
- }
-
- /**
- * Returns a connection, indexed by id
- */
- public IConnection getConnection(int connId) {
- return connectionIds.get(new Integer(connId));
- }
-
- /**
- * Returns a pending connection
- */
- IConnection getPendingConnection(int connId) {
- for (IConnection conn : connectionsPending) {
- if (conn.getId() == connId) {
- return conn;
- }
- }
- return null;
- }
-
- /**
- * Called at the beginning of each game round to reset values on this entity
- * that are reset every round
- */
- private void resetEntityRound() {
- for (Enumeration<Entity> e = game.getEntities(); e.hasMoreElements();) {
- Entity entity = e.nextElement();
-
- entity.newRound(game.getRoundCount());
- }
- }
-
- /**
- * Called at the beginning of each phase. Sets and resets any entity
- * parameters that need to be reset.
- */
- private void resetEntityPhase(IGame.Phase phase) {
- // first, mark doomed entities as destroyed and flag them
- Vector<Entity> toRemove = new Vector<Entity>(0, 10);
- for (Enumeration<Entity> e = game.getEntities(); e.hasMoreElements();) {
- final Entity entity = e.nextElement();
-
- if (entity.crew.isDoomed()) {
- entity.crew.setDoomed(false);
- entity.crew.setDead(true);
- if (entity instanceof Tank) {
- entity.setCarcass(true);
- ((Tank) entity).immobilize();
- } else {
- entity.setDestroyed(true);
- }
- }
-
- if (entity.isDoomed()) {
- entity.setDestroyed(true);
-
- // Is this unit swarming somebody? Better let go before
- // it's too late.
- final int swarmedId = entity.getSwarmTargetId();
- if (Entity.NONE != swarmedId) {
- final Entity swarmed = game.getEntity(swarmedId);
- swarmed.setSwarmAttackerId(Entity.NONE);
- entity.setSwarmTargetId(Entity.NONE);
- Report r = new Report(5165);
- r.subject = swarmedId;
- r.addDesc(swarmed);
- addReport(r);
- entityUpdate(swarmedId);
- }
- }
-
- if (entity.isDestroyed()) {
- toRemove.addElement(entity);
- }
- }
-
- // actually remove all flagged entities
- for (Entity entity : toRemove) {
- int condition = IEntityRemovalConditions.REMOVE_SALVAGEABLE;
- if (!entity.isSalvage()) {
- condition = IEntityRemovalConditions.REMOVE_DEVASTATED;
- }
-
- entityUpdate(entity.getId());
- game.removeEntity(entity.getId(), condition);
- send(createRemoveEntityPacket(entity.getId(), condition));
- }
-
- // do some housekeeping on all the remaining
- for (Enumeration<Entity> e = game.getEntities(); e.hasMoreElements();) {
- final Entity entity = e.nextElement();
-
- entity.applyDamage();
-
- entity.reloadEmptyWeapons();
-
- // reset damage this phase
- // tele-missiles need a record of damage last phase
- entity.damageThisRound += entity.damageThisPhase;
- entity.damageThisPhase = 0;
- entity.engineHitsThisRound = 0;
- entity.rolledForEngineExplosion = false;
- entity.dodging = false;
- entity.setShutDownThisPhase(false);
-
- // reset done to false
-
- if (phase == IGame.Phase.PHASE_DEPLOYMENT) {
- entity.setDone(!entity.shouldDeploy(game.getRoundCount()));
- } else {
- entity.setDone(false);
- }
-
- // reset spotlights
- entity.setIlluminated(false);
- entity.setUsedSearchlight(false);
- entity.setCarefulStand(false);
-
- if (entity instanceof MechWarrior) {
- ((MechWarrior) entity).setLanded(true);
- }
- }
- }
-
- /**
- * are we currently in a reporting phase
- *
- * @return <code>true</code> if we are or <code>false</code> if not.
- */
- private boolean isReportingPhase() {
-
- if ((game.getPhase() == IGame.Phase.PHASE_FIRING_REPORT)
- || (game.getPhase() == IGame.Phase.PHASE_INITIATIVE_REPORT)
- || (game.getPhase() == IGame.Phase.PHASE_MOVEMENT_REPORT)
- || (game.getPhase() == IGame.Phase.PHASE_OFFBOARD_REPORT)
- || (game.getPhase() == IGame.Phase.PHASE_PHYSICAL_REPORT)) {
- return true;
- }
-
- return false;
- }
-
- /**
- * Called at the beginning of certain phases to make every player not ready.
- */
- private void resetPlayersDone() {
- if (isReportingPhase()) {
- return;
- }
- for (Enumeration<Player> i = game.getPlayers(); i.hasMoreElements();) {
- final Player player = i.nextElement();
- player.setDone(false);
- }
- transmitAllPlayerDones();
- }
-
- /**
- * Called at the beginning of certain phases to make every active player not
- * ready.
- */
- private void resetActivePlayersDone() {
- if (isReportingPhase()) {
- return;
- }
- for (Enumeration<Player> i = game.getPlayers(); i.hasMoreElements();) {
- final Player player = i.nextElement();
-
- player.setDone(game.getEntitiesOwnedBy(player) <= 0);
-
- }
- transmitAllPlayerDones();
- }
-
- /**
- * Writes the victory report
- */
- private void prepareVictoryReport() {
- Report r;
-
- // remove carcasses to the graveyard
- Vector<Entity> toRemove = new Vector<Entity>();
- for (Entity e : game.getEntitiesVector()) {
- if (e.isCarcass() && !e.isDestroyed()) {
- toRemove.add(e);
- }
- }
- for (Entity e : toRemove) {
- destroyEntity(e, "crew death", false, true);
- game.removeEntity(e.getId(), IEntityRemovalConditions.REMOVE_SALVAGEABLE);
- e.setDestroyed(true);
- }
-
- addReport(new Report(7000, Report.PUBLIC));
-
- // Declare the victor
- r = new Report(1210);
- r.type = Report.PUBLIC;
- if (game.getVictoryTeam() == Player.TEAM_NONE) {
- Player player = getPlayer(game.getVictoryPlayerId());
- if (null == player) {
- r.messageId = 7005;
- } else {
- r.messageId = 7010;
- r.add(Server.getColorForPlayer(player));
- }
- } else {
- // Team victory
- r.messageId = 7015;
- r.add(game.getVictoryTeam());
- }
- addReport(r);
-
- // Show player BVs
- Enumeration<Player> players = game.getPlayers();
- while (players.hasMoreElements()) {
- Player player = players.nextElement();
- r = new Report();
- r.type = Report.PUBLIC;
- r.messageId = 7016;
- r.add(Server.getColorForPlayer(player));
- r.add(player.getBV());
- r.add(player.getInitialBV());
- r.add(player.getFledBV());
- addReport(r);
- }
-
- // List the survivors
- Enumeration<Entity> survivors = game.getEntities();
- if (survivors.hasMoreElements()) {
- addReport(new Report(7020, Report.PUBLIC));
- while (survivors.hasMoreElements()) {
- Entity entity = survivors.nextElement();
-
- if (!entity.isDeployed()) {
- continue;
- }
-
- addReport(entity.victoryReport());
- }
- }
- // List units that never deployed
- Enumeration<Entity> undeployed = game.getEntities();
- if (undeployed.hasMoreElements()) {
- boolean wroteHeader = false;
-
- while (undeployed.hasMoreElements()) {
- Entity entity = undeployed.nextElement();
-
- if (entity.isDeployed()) {
- continue;
- }
-
- if (!wroteHeader) {
- addReport(new Report(7075, Report.PUBLIC));
- wroteHeader = true;
- }
-
- addReport(entity.victoryReport());
- }
- }
- // List units that retreated
- Enumeration<Entity> retreat = game.getRetreatedEntities();
- if (retreat.hasMoreElements()) {
- addReport(new Report(7080, Report.PUBLIC));
- while (retreat.hasMoreElements()) {
- Entity entity = retreat.nextElement();
- addReport(entity.victoryReport());
- }
- }