PageRenderTime 55ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/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
  1. /*
  2. * MegaMek -
  3. * Copyright (C) 2000,2001,2002,2003,2004,2005 Ben Mazur (bmazur@sev.org)
  4. *
  5. * This program is free software; you can redistribute it and/or modify it
  6. * under the terms of the GNU General Public License as published by the Free
  7. * Software Foundation; either version 2 of the License, or (at your option)
  8. * any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful, but
  11. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  12. * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  13. * for more details.
  14. */
  15. package megamek.server;
  16. import java.io.File;
  17. import java.io.FileInputStream;
  18. import java.io.FileOutputStream;
  19. import java.io.IOException;
  20. import java.io.InterruptedIOException;
  21. import java.io.ObjectInputStream;
  22. import java.io.ObjectOutputStream;
  23. import java.net.InetAddress;
  24. import java.net.ServerSocket;
  25. import java.net.Socket;
  26. import java.net.UnknownHostException;
  27. import java.text.SimpleDateFormat;
  28. import java.util.ArrayList;
  29. import java.util.Collection;
  30. import java.util.Collections;
  31. import java.util.Comparator;
  32. import java.util.Date;
  33. import java.util.Enumeration;
  34. import java.util.HashMap;
  35. import java.util.HashSet;
  36. import java.util.Hashtable;
  37. import java.util.Iterator;
  38. import java.util.LinkedList;
  39. import java.util.List;
  40. import java.util.Map;
  41. import java.util.Set;
  42. import java.util.Timer;
  43. import java.util.TimerTask;
  44. import java.util.TreeMap;
  45. import java.util.Vector;
  46. import megamek.MegaMek;
  47. import megamek.client.ui.AWT.util.PlayerColors;
  48. import megamek.common.Aero;
  49. import megamek.common.AmmoType;
  50. import megamek.common.BattleArmor;
  51. import megamek.common.Bay;
  52. import megamek.common.BipedMech;
  53. import megamek.common.Board;
  54. import megamek.common.BombType;
  55. import megamek.common.Building;
  56. import megamek.common.CalledShot;
  57. import megamek.common.CommonConstants;
  58. import megamek.common.Compute;
  59. import megamek.common.Coords;
  60. import megamek.common.CriticalSlot;
  61. import megamek.common.Dropship;
  62. import megamek.common.Engine;
  63. import megamek.common.Entity;
  64. import megamek.common.EntityMovementMode;
  65. import megamek.common.EntityMovementType;
  66. import megamek.common.EntitySelector;
  67. import megamek.common.EntityWeightClass;
  68. import megamek.common.EquipmentMode;
  69. import megamek.common.EquipmentType;
  70. import megamek.common.FighterSquadron;
  71. import megamek.common.Flare;
  72. import megamek.common.FuelTank;
  73. import megamek.common.Game;
  74. import megamek.common.GameTurn;
  75. import megamek.common.GunEmplacement;
  76. import megamek.common.HexTarget;
  77. import megamek.common.HitData;
  78. import megamek.common.IArmorState;
  79. import megamek.common.IBoard;
  80. import megamek.common.IEntityRemovalConditions;
  81. import megamek.common.IGame;
  82. import megamek.common.IHex;
  83. import megamek.common.ILocationExposureStatus;
  84. import megamek.common.INarcPod;
  85. import megamek.common.ITerrain;
  86. import megamek.common.Infantry;
  87. import megamek.common.InfernoTracker;
  88. import megamek.common.Jumpship;
  89. import megamek.common.LargeSupportTank;
  90. import megamek.common.LocationFullException;
  91. import megamek.common.LosEffects;
  92. import megamek.common.MapSettings;
  93. import megamek.common.Mech;
  94. import megamek.common.MechWarrior;
  95. import megamek.common.Minefield;
  96. import megamek.common.MiscType;
  97. import megamek.common.Mounted;
  98. import megamek.common.MovePath;
  99. import megamek.common.MoveStep;
  100. import megamek.common.OffBoardDirection;
  101. import megamek.common.PhysicalResult;
  102. import megamek.common.Pilot;
  103. import megamek.common.PilotingRollData;
  104. import megamek.common.PlanetaryConditions;
  105. import megamek.common.Player;
  106. import megamek.common.Protomech;
  107. import megamek.common.QuadMech;
  108. import megamek.common.Report;
  109. import megamek.common.Roll;
  110. import megamek.common.SmallCraft;
  111. import megamek.common.SpaceStation;
  112. import megamek.common.SpecialHexDisplay;
  113. import megamek.common.SupportTank;
  114. import megamek.common.SupportVTOL;
  115. import megamek.common.Tank;
  116. import megamek.common.TargetRoll;
  117. import megamek.common.Targetable;
  118. import megamek.common.Team;
  119. import megamek.common.TeleMissile;
  120. import megamek.common.Terrain;
  121. import megamek.common.Terrains;
  122. import megamek.common.ToHitData;
  123. import megamek.common.TurnOrdered;
  124. import megamek.common.TurnVectors;
  125. import megamek.common.UnitLocation;
  126. import megamek.common.VTOL;
  127. import megamek.common.Warship;
  128. import megamek.common.WeaponComparator;
  129. import megamek.common.WeaponType;
  130. import megamek.common.IGame.Phase;
  131. import megamek.common.MovePath.MoveStepType;
  132. import megamek.common.actions.AbstractAttackAction;
  133. import megamek.common.actions.ArtilleryAttackAction;
  134. import megamek.common.actions.AttackAction;
  135. import megamek.common.actions.BAVibroClawAttackAction;
  136. import megamek.common.actions.BreakGrappleAttackAction;
  137. import megamek.common.actions.BrushOffAttackAction;
  138. import megamek.common.actions.ChargeAttackAction;
  139. import megamek.common.actions.ClearMinefieldAction;
  140. import megamek.common.actions.ClubAttackAction;
  141. import megamek.common.actions.DfaAttackAction;
  142. import megamek.common.actions.DodgeAction;
  143. import megamek.common.actions.EntityAction;
  144. import megamek.common.actions.FindClubAction;
  145. import megamek.common.actions.FlipArmsAction;
  146. import megamek.common.actions.GrappleAttackAction;
  147. import megamek.common.actions.JumpJetAttackAction;
  148. import megamek.common.actions.KickAttackAction;
  149. import megamek.common.actions.LayExplosivesAttackAction;
  150. import megamek.common.actions.ProtomechPhysicalAttackAction;
  151. import megamek.common.actions.PunchAttackAction;
  152. import megamek.common.actions.PushAttackAction;
  153. import megamek.common.actions.RamAttackAction;
  154. import megamek.common.actions.RepairWeaponMalfunctionAction;
  155. import megamek.common.actions.SearchlightAttackAction;
  156. import megamek.common.actions.SpotAction;
  157. import megamek.common.actions.TeleMissileAttackAction;
  158. import megamek.common.actions.ThrashAttackAction;
  159. import megamek.common.actions.TorsoTwistAction;
  160. import megamek.common.actions.TriggerAPPodAction;
  161. import megamek.common.actions.TriggerBPodAction;
  162. import megamek.common.actions.TripAttackAction;
  163. import megamek.common.actions.UnjamAction;
  164. import megamek.common.actions.UnjamTurretAction;
  165. import megamek.common.actions.UnloadStrandedAction;
  166. import megamek.common.actions.WeaponAttackAction;
  167. import megamek.common.containers.PlayerIDandList;
  168. import megamek.common.event.GameListener;
  169. import megamek.common.net.ConnectionFactory;
  170. import megamek.common.net.ConnectionListenerAdapter;
  171. import megamek.common.net.DisconnectedEvent;
  172. import megamek.common.net.IConnection;
  173. import megamek.common.net.Packet;
  174. import megamek.common.net.PacketReceivedEvent;
  175. import megamek.common.options.IBasicOption;
  176. import megamek.common.options.IOption;
  177. import megamek.common.preference.PreferenceManager;
  178. import megamek.common.util.BoardUtilities;
  179. import megamek.common.util.StringUtil;
  180. import megamek.common.verifier.EntityVerifier;
  181. import megamek.common.verifier.TestEntity;
  182. import megamek.common.verifier.TestMech;
  183. import megamek.common.verifier.TestTank;
  184. import megamek.common.weapons.AttackHandler;
  185. import megamek.common.weapons.TAGHandler;
  186. import megamek.common.weapons.Weapon;
  187. import megamek.common.weapons.WeaponHandler;
  188. import megamek.server.commands.AddBotCommand;
  189. import megamek.server.commands.CheckBVCommand;
  190. import megamek.server.commands.DefeatCommand;
  191. import megamek.server.commands.ExportListCommand;
  192. import megamek.server.commands.FixElevationCommand;
  193. import megamek.server.commands.HelpCommand;
  194. import megamek.server.commands.KickCommand;
  195. import megamek.server.commands.ListSavesCommand;
  196. import megamek.server.commands.LoadGameCommand;
  197. import megamek.server.commands.LocalLoadGameCommand;
  198. import megamek.server.commands.LocalSaveGameCommand;
  199. import megamek.server.commands.NukeCommand;
  200. import megamek.server.commands.ResetCommand;
  201. import megamek.server.commands.RollCommand;
  202. import megamek.server.commands.RulerCommand;
  203. import megamek.server.commands.SaveGameCommand;
  204. import megamek.server.commands.SeeAllCommand;
  205. import megamek.server.commands.ServerCommand;
  206. import megamek.server.commands.ShowEntityCommand;
  207. import megamek.server.commands.ShowTileCommand;
  208. import megamek.server.commands.ShowValidTargetsCommand;
  209. import megamek.server.commands.SkipCommand;
  210. import megamek.server.commands.TeamCommand;
  211. import megamek.server.commands.VictoryCommand;
  212. import megamek.server.commands.WhoCommand;
  213. import megamek.server.victory.Victory;
  214. /**
  215. * @author Ben Mazur
  216. */
  217. public class Server implements Runnable {
  218. /**
  219. * The DamageType enumeration is used for the damageEntity function.
  220. */
  221. public enum DamageType {
  222. NONE, FRAGMENTATION, FLECHETTE, ACID, INCENDIARY, IGNORE_PASSENGER, ANTI_TSM, ANTI_INFANTRY, NAIL_RIVET, NONPENETRATING
  223. }
  224. // public final static String LEGAL_CHARS =
  225. // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-";
  226. public final static String DEFAULT_BOARD = MapSettings.BOARD_SURPRISE;
  227. private final static String VERIFIER_CONFIG_FILENAME = "data/mechfiles/UnitVerifierOptions.xml";
  228. // server setup
  229. private String password;
  230. private ServerSocket serverSocket;
  231. private String motd;
  232. // game info
  233. Vector<IConnection> connections = new Vector<IConnection>(4);
  234. Vector<IConnection> connectionsPending = new Vector<IConnection>(4);
  235. Hashtable<Integer, IConnection> connectionIds = new Hashtable<Integer, IConnection>();
  236. private int connectionCounter;
  237. IGame game = new Game();
  238. private Vector<Report> vPhaseReport = new Vector<Report>();
  239. private MapSettings mapSettings = new MapSettings();
  240. // commands
  241. private Hashtable<String, ServerCommand> commandsHash = new Hashtable<String, ServerCommand>();
  242. // listens for and connects players
  243. private Thread connector;
  244. // Track buildings that are affected by an entity's movement.
  245. private Hashtable<Building, Boolean> affectedBldgs = new Hashtable<Building, Boolean>();
  246. // Track Physical Action results, HACK to deal with opposing pushes
  247. // canceling each other
  248. private Vector<PhysicalResult> physicalResults = new Vector<PhysicalResult>();
  249. private Vector<DynamicTerrainProcessor> terrainProcessors = new Vector<DynamicTerrainProcessor>();
  250. private Timer timer = new Timer();
  251. private static EntityVerifier entityVerifier;
  252. private ArrayList<int[]> scheduledNukes = new ArrayList<int[]>();
  253. private static Server serverInstance = null;
  254. private ConnectionListenerAdapter connectionListener = new ConnectionListenerAdapter() {
  255. /**
  256. * Called when it is sensed that a connection has terminated.
  257. */
  258. @Override
  259. public void disconnected(DisconnectedEvent e) {
  260. IConnection conn = e.getConnection();
  261. // write something in the log
  262. System.out.println("s: connection " + conn.getId() + " disconnected");
  263. connections.removeElement(conn);
  264. connectionsPending.removeElement(conn);
  265. connectionIds.remove(new Integer(conn.getId()));
  266. // if there's a player for this connection, remove it too
  267. Player player = getPlayer(conn.getId());
  268. if (null != player) {
  269. Server.this.disconnected(player);
  270. }
  271. }
  272. @Override
  273. public void packetReceived(PacketReceivedEvent e) {
  274. Server.this.handle(e.getConnection().getId(), e.getPacket());
  275. }
  276. };
  277. /**
  278. * Construct a new GameHost and begin listening for incoming clients.
  279. *
  280. * @param password
  281. * the <code>String</code> that is set as a password
  282. * @param port
  283. * the <code>int</code> value that specifies the port that is
  284. * used
  285. */
  286. public Server(String password, int port) throws IOException {
  287. serverInstance = this;
  288. this.password = password.length() > 0 ? password : null;
  289. // initialize server socket
  290. serverSocket = new ServerSocket(port);
  291. serverSocket.setSoTimeout(50);
  292. motd = createMotd();
  293. game.getOptions().initialize();
  294. game.getOptions().loadOptions();
  295. changePhase(IGame.Phase.PHASE_LOUNGE);
  296. // display server start text
  297. System.out.println("s: starting a new server...");
  298. try {
  299. String host = InetAddress.getLocalHost().getHostName();
  300. System.out.print("s: hostname = '");
  301. System.out.print(host);
  302. System.out.print("' port = ");
  303. System.out.println(serverSocket.getLocalPort());
  304. InetAddress[] addresses = InetAddress.getAllByName(host);
  305. for (InetAddress addresse : addresses) {
  306. System.out.println("s: hosting on address = " + addresse.getHostAddress());
  307. }
  308. } catch (UnknownHostException e) {
  309. // oh well.
  310. }
  311. System.out.println("s: password = " + this.password);
  312. // register commands
  313. registerCommand(new DefeatCommand(this));
  314. registerCommand(new ExportListCommand(this));
  315. registerCommand(new FixElevationCommand(this));
  316. registerCommand(new HelpCommand(this));
  317. registerCommand(new KickCommand(this));
  318. registerCommand(new ListSavesCommand(this));
  319. registerCommand(new LocalSaveGameCommand(this));
  320. registerCommand(new LocalLoadGameCommand(this));
  321. registerCommand(new ResetCommand(this));
  322. registerCommand(new RollCommand(this));
  323. registerCommand(new SaveGameCommand(this));
  324. registerCommand(new LoadGameCommand(this));
  325. registerCommand(new SeeAllCommand(this));
  326. registerCommand(new SkipCommand(this));
  327. registerCommand(new VictoryCommand(this));
  328. registerCommand(new WhoCommand(this));
  329. registerCommand(new TeamCommand(this));
  330. registerCommand(new ShowTileCommand(this));
  331. registerCommand(new ShowEntityCommand(this));
  332. registerCommand(new RulerCommand(this));
  333. registerCommand(new ShowValidTargetsCommand(this));
  334. registerCommand(new AddBotCommand(this));
  335. registerCommand(new CheckBVCommand(this));
  336. registerCommand(new NukeCommand(this));
  337. // register terrain processors
  338. terrainProcessors.add(new FireProcessor(this));
  339. terrainProcessors.add(new SmokeProcessor(this));
  340. terrainProcessors.add(new GeyserProcessor(this));
  341. terrainProcessors.add(new ElevatorProcessor(this));
  342. terrainProcessors.add(new ScreenProcessor(this));
  343. terrainProcessors.add(new WeatherProcessor(this));
  344. terrainProcessors.add(new QuicksandProcessor(this));
  345. // Fully initialised, now accept connections
  346. connector = new Thread(this, "Connection Listener");
  347. connector.start();
  348. }
  349. /**
  350. * Sets the game for this server. Restores any transient fields, and sets
  351. * all players as ghosts. This should only be called during server
  352. * initialization before any players have connected.
  353. */
  354. public void setGame(IGame g) {
  355. game = g;
  356. // reattach the transient fields and ghost the players
  357. for (Enumeration<Entity> e = game.getEntities(); e.hasMoreElements();) {
  358. Entity ent = e.nextElement();
  359. ent.setGame(game);
  360. }
  361. game.setOutOfGameEntitiesVector(game.getOutOfGameEntitiesVector());
  362. for (Enumeration<Player> e = game.getPlayers(); e.hasMoreElements();) {
  363. Player p = e.nextElement();
  364. p.setGame(game);
  365. p.setGhost(true);
  366. }
  367. }
  368. /**
  369. * Resets all the connections. The only use of this right now is when games
  370. * are loaded after clients have connected
  371. */
  372. public void resetConnections() {
  373. for (Enumeration<IConnection> connEnum = connections.elements(); connEnum.hasMoreElements();) {
  374. IConnection conn = connEnum.nextElement();
  375. send(conn.getId(), new Packet(Packet.COMMAND_RESET_CONNECTION));
  376. }
  377. }
  378. /** Returns the current game object */
  379. public IGame getGame() {
  380. return game;
  381. }
  382. /**
  383. * Make a default message o' the day containing the version string, and if
  384. * it was found, the build timestamp
  385. */
  386. private String createMotd() {
  387. StringBuffer buf = new StringBuffer();
  388. buf.append("Welcome to MegaMek. Server is running version ");
  389. buf.append(MegaMek.VERSION);
  390. buf.append(", build date ");
  391. if (MegaMek.TIMESTAMP > 0L) {
  392. buf.append(new Date(MegaMek.TIMESTAMP).toString());
  393. } else {
  394. buf.append("unknown");
  395. }
  396. buf.append('.');
  397. return buf.toString();
  398. }
  399. /**
  400. * @return true if the server has a password
  401. */
  402. public boolean isPassworded() {
  403. return password != null;
  404. }
  405. /**
  406. * @return true if the password matches
  407. */
  408. public boolean isPassword(Object guess) {
  409. return password.equals(guess);
  410. }
  411. /**
  412. * Registers a new command in the server command table
  413. */
  414. private void registerCommand(ServerCommand command) {
  415. commandsHash.put(command.getName(), command);
  416. }
  417. /**
  418. * Returns the command associated with the specified name
  419. */
  420. public ServerCommand getCommand(String name) {
  421. return commandsHash.get(name);
  422. }
  423. /**
  424. * Shuts down the server.
  425. */
  426. public void die() {
  427. timer.cancel();
  428. // kill thread accepting new connections
  429. connector = null;
  430. // close socket
  431. try {
  432. serverSocket.close();
  433. } catch (IOException ex) {
  434. }
  435. // kill pending connnections
  436. for (Enumeration<IConnection> connEnum = connectionsPending.elements(); connEnum.hasMoreElements();) {
  437. IConnection conn = connEnum.nextElement();
  438. conn.close();
  439. }
  440. connectionsPending.removeAllElements();
  441. // Send "kill" commands to all connections
  442. // N.B. I may be starting a race here.
  443. for (Enumeration<IConnection> connEnum = connections.elements(); connEnum.hasMoreElements();) {
  444. IConnection conn = connEnum.nextElement();
  445. send(conn.getId(), new Packet(Packet.COMMAND_CLOSE_CONNECTION));
  446. }
  447. // kill active connnections
  448. for (Enumeration<IConnection> connEnum = connections.elements(); connEnum.hasMoreElements();) {
  449. IConnection conn = connEnum.nextElement();
  450. conn.close();
  451. }
  452. connections.removeAllElements();
  453. connectionIds.clear();
  454. System.out.flush();
  455. }
  456. /**
  457. * Returns an enumeration of all the command names
  458. */
  459. public Enumeration<String> getAllCommandNames() {
  460. return commandsHash.keys();
  461. }
  462. /**
  463. * Sent when a client attempts to connect.
  464. */
  465. void greeting(int cn) {
  466. // send server greeting -- client should reply with client info.
  467. sendToPending(cn, new Packet(Packet.COMMAND_SERVER_GREETING));
  468. }
  469. /**
  470. * Returns a free connection id.
  471. */
  472. public int getFreeConnectionId() {
  473. while ((getPendingConnection(connectionCounter) != null) || (getConnection(connectionCounter) != null)
  474. || (getPlayer(connectionCounter) != null)) {
  475. connectionCounter++;
  476. }
  477. return connectionCounter;
  478. }
  479. /**
  480. * Returns a free entity id. Perhaps this should be in Game instead.
  481. */
  482. public int getFreeEntityId() {
  483. return game.getNextEntityId();
  484. }
  485. /**
  486. * Allow the player to set whatever parameters he is able to
  487. */
  488. private void receivePlayerInfo(Packet packet, int connId) {
  489. Player player = (Player) packet.getObject(0);
  490. Player connPlayer = game.getPlayer(connId);
  491. if (null != connPlayer) {
  492. connPlayer.setColorIndex(player.getColorIndex());
  493. connPlayer.setStartingPos(player.getStartingPos());
  494. connPlayer.setTeam(player.getTeam());
  495. connPlayer.setCamoCategory(player.getCamoCategory());
  496. connPlayer.setCamoFileName(player.getCamoFileName());
  497. connPlayer.setNbrMFConventional(player.getNbrMFConventional());
  498. connPlayer.setNbrMFCommand(player.getNbrMFCommand());
  499. connPlayer.setNbrMFVibra(player.getNbrMFVibra());
  500. connPlayer.setNbrMFActive(player.getNbrMFActive());
  501. connPlayer.setNbrMFInferno(player.getNbrMFInferno());
  502. connPlayer.setConstantInitBonus(player.getConstantInitBonus());
  503. }
  504. }
  505. /**
  506. * Correct a duplicate playername
  507. *
  508. * @param oldName
  509. * the <code>String</code> old playername, that is a duplicate
  510. * @return the <code>String</code> new playername
  511. */
  512. private String correctDupeName(String oldName) {
  513. for (Enumeration<Player> i = game.getPlayers(); i.hasMoreElements();) {
  514. Player player = i.nextElement();
  515. if (player.getName().equals(oldName)) {
  516. // We need to correct it.
  517. String newName = oldName;
  518. int dupNum = 2;
  519. try {
  520. dupNum = Integer.parseInt(oldName.substring(oldName.lastIndexOf(".") + 1));
  521. dupNum++;
  522. newName = oldName.substring(0, oldName.lastIndexOf("."));
  523. } catch (Exception e) {
  524. // If this fails, we don't care much.
  525. // Just assume it's the first time for this name.
  526. dupNum = 2;
  527. }
  528. newName = newName.concat(".").concat(Integer.toString(dupNum));
  529. return correctDupeName(newName);
  530. }
  531. }
  532. return oldName;
  533. }
  534. /**
  535. * Recieves a player name, sent from a pending connection, and connects that
  536. * connection.
  537. */
  538. private void receivePlayerName(Packet packet, int connId) {
  539. final IConnection conn = getPendingConnection(connId);
  540. String name = (String) packet.getObject(0);
  541. boolean returning = false;
  542. // this had better be from a pending connection
  543. if (conn == null) {
  544. System.out.println("server: got a client name from a non-pending" + " connection");
  545. return;
  546. }
  547. // check if they're connecting with the same name as a ghost player
  548. for (Enumeration<Player> i = game.getPlayers(); i.hasMoreElements();) {
  549. Player player = i.nextElement();
  550. if (player.getName().equals(name)) {
  551. if (player.isGhost()) {
  552. returning = true;
  553. player.setGhost(false);
  554. // switch id
  555. connId = player.getId();
  556. conn.setId(connId);
  557. }
  558. }
  559. }
  560. if (!returning) {
  561. // Check to avoid duplicate names...
  562. name = correctDupeName(name);
  563. send(connId, new Packet(Packet.COMMAND_SERVER_CORRECT_NAME, name));
  564. }
  565. // right, switch the connection into the "active" bin
  566. connectionsPending.removeElement(conn);
  567. connections.addElement(conn);
  568. connectionIds.put(new Integer(conn.getId()), conn);
  569. // add and validate the player info
  570. if (!returning) {
  571. int team = Player.TEAM_NONE;
  572. for (Player p : game.getPlayersVector()) {
  573. if (p.getTeam() > team) {
  574. team = p.getTeam();
  575. }
  576. }
  577. team++;
  578. Player newPlayer = new Player(connId, name);
  579. newPlayer.setTeam(Math.min(team, 5));
  580. game.addPlayer(connId, newPlayer);
  581. validatePlayerInfo(connId);
  582. }
  583. // if it is not the lounge phase, this player becomes an observer
  584. Player player = getPlayer(connId);
  585. if ((game.getPhase() != IGame.Phase.PHASE_LOUNGE) && (null != player) && (game.getEntitiesOwnedBy(player) < 1)) {
  586. player.setObserver(true);
  587. }
  588. // send the player the motd
  589. sendServerChat(connId, motd);
  590. // send info that the player has connected
  591. send(createPlayerConnectPacket(connId));
  592. // tell them their local playerId
  593. send(connId, new Packet(Packet.COMMAND_LOCAL_PN, new Integer(connId)));
  594. // send current game info
  595. sendCurrentInfo(connId);
  596. try {
  597. InetAddress[] addresses = InetAddress.getAllByName(InetAddress.getLocalHost().getHostName());
  598. for (InetAddress addresse : addresses) {
  599. sendServerChat(connId, "Machine IP is " + addresse.getHostAddress());
  600. }
  601. } catch (UnknownHostException e) {
  602. // oh well.
  603. }
  604. // Send the port we're listening on. Only useful for the player
  605. // on the server machine to check.
  606. sendServerChat(connId, "Listening on port " + serverSocket.getLocalPort());
  607. // Get the player *again*, because they may have disconnected.
  608. player = getPlayer(connId);
  609. if (null != player) {
  610. StringBuffer buff = new StringBuffer();
  611. buff.append(player.getName()).append(" connected from ").append(getClient(connId).getInetAddress());
  612. String who = buff.toString();
  613. System.out.print("s: player #");
  614. System.out.print(connId);
  615. System.out.print(", ");
  616. System.out.println(who);
  617. sendServerChat(who);
  618. } // Found the player
  619. }
  620. /**
  621. * Sends a player the info they need to look at the current phase. This is
  622. * triggered when a player first connects to the server.
  623. */
  624. private void sendCurrentInfo(int connId) {
  625. // why are these two outside the player != null check below?
  626. transmitAllPlayerConnects(connId);
  627. send(connId, createGameSettingsPacket());
  628. send(connId, createPlanetaryConditionsPacket());
  629. Player player = game.getPlayer(connId);
  630. if (null != player) {
  631. send(connId, new Packet(Packet.COMMAND_SENDING_MINEFIELDS, player.getMinefields()));
  632. switch (game.getPhase()) {
  633. case PHASE_LOUNGE:
  634. send(connId, createMapSettingsPacket());
  635. // Send Entities *after* the Lounge Phase Change
  636. send(connId, new Packet(Packet.COMMAND_PHASE_CHANGE, game.getPhase()));
  637. if (doBlind()) {
  638. send(connId, createFilteredFullEntitiesPacket(player));
  639. } else {
  640. send(connId, createFullEntitiesPacket());
  641. }
  642. break;
  643. default:
  644. send(connId, new Packet(Packet.COMMAND_ROUND_UPDATE, new Integer(game.getRoundCount())));
  645. send(connId, createBoardPacket());
  646. send(connId, createAllReportsPacket(player));
  647. // Send entities *before* other phase changes.
  648. if (doBlind()) {
  649. send(connId, createFilteredFullEntitiesPacket(player));
  650. } else {
  651. send(connId, createFullEntitiesPacket());
  652. }
  653. player.setDone(game.getEntitiesOwnedBy(player) <= 0);
  654. send(connId, new Packet(Packet.COMMAND_PHASE_CHANGE, game.getPhase()));
  655. break;
  656. }
  657. if ((game.getPhase() == IGame.Phase.PHASE_FIRING) || (game.getPhase() == IGame.Phase.PHASE_TARGETING)
  658. || (game.getPhase() == IGame.Phase.PHASE_OFFBOARD)
  659. || (game.getPhase() == IGame.Phase.PHASE_PHYSICAL)) {
  660. // can't go above, need board to have been sent
  661. send(connId, createAttackPacket(game.getActionsVector(), 0));
  662. send(connId, createAttackPacket(game.getChargesVector(), 1));
  663. send(connId, createAttackPacket(game.getRamsVector(), 1));
  664. send(connId, createAttackPacket(game.getTeleMissileAttacksVector(), 1));
  665. }
  666. if (game.phaseHasTurns(game.getPhase())) {
  667. send(connId, createTurnVectorPacket());
  668. send(connId, createTurnIndexPacket());
  669. }
  670. send(connId, createArtilleryPacket(player));
  671. send(connId, createFlarePacket());
  672. send(connId, createSpecialHexDisplayPacket(connId));
  673. } // Found the player.
  674. }
  675. /**
  676. * Resend entities to the player called by seeall command
  677. */
  678. public void sendEntities(int connId) {
  679. if (doBlind()) {
  680. send(connId, createFilteredEntitiesPacket(getPlayer(connId)));
  681. } else {
  682. send(connId, createEntitiesPacket());
  683. }
  684. }
  685. /**
  686. * Validates the player info.
  687. */
  688. public void validatePlayerInfo(int playerId) {
  689. final Player player = getPlayer(playerId);
  690. // maybe this isn't actually useful
  691. // // replace characters we don't like with "X"
  692. // StringBuffer nameBuff = new StringBuffer(player.getName());
  693. // for (int i = 0; i < nameBuff.length(); i++) {
  694. // int chr = nameBuff.charAt(i);
  695. // if (LEGAL_CHARS.indexOf(chr) == -1) {
  696. // nameBuff.setCharAt(i, 'X');
  697. // }
  698. // }
  699. // player.setName(nameBuff.toString());
  700. // TODO: check for duplicate or reserved names
  701. // make sure colorIndex is unique
  702. boolean[] colorUsed = new boolean[Player.colorNames.length];
  703. for (Enumeration<Player> i = game.getPlayers(); i.hasMoreElements();) {
  704. final Player otherPlayer = i.nextElement();
  705. if (otherPlayer.getId() != playerId) {
  706. colorUsed[otherPlayer.getColorIndex()] = true;
  707. }
  708. }
  709. if ((null != player) && colorUsed[player.getColorIndex()]) {
  710. // find a replacement color;
  711. for (int i = 0; i < colorUsed.length; i++) {
  712. if (!colorUsed[i]) {
  713. player.setColorIndex(i);
  714. break;
  715. }
  716. }
  717. }
  718. }
  719. /**
  720. * Called when it's been determined that an actual player disconnected.
  721. * Notifies the other players and does the appropriate housekeeping.
  722. */
  723. void disconnected(Player player) {
  724. IGame.Phase phase = game.getPhase();
  725. // in the lounge, just remove all entities for that player
  726. if (phase == IGame.Phase.PHASE_LOUNGE) {
  727. removeAllEntitesOwnedBy(player);
  728. }
  729. // if a player has active entities, he becomes a ghost
  730. // except the VICTORY_PHASE when the dosconnected
  731. // player is most likely the Bot disconnected after receiving
  732. // the COMMAND_END_OF_GAME command
  733. // see the Bug 1225949.
  734. // TODO Perhaps there is a better solution to handle the Bot disconnect
  735. if ((game.getEntitiesOwnedBy(player) > 0) && (phase != IGame.Phase.PHASE_VICTORY)) {
  736. player.setGhost(true);
  737. player.setDone(true);
  738. send(createPlayerUpdatePacket(player.getId()));
  739. } else {
  740. game.removePlayer(player.getId());
  741. send(new Packet(Packet.COMMAND_PLAYER_REMOVE, new Integer(player.getId())));
  742. }
  743. // make sure the game advances
  744. if (game.phaseHasTurns(game.getPhase()) && (null != game.getTurn())) {
  745. if (game.getTurn().isValid(player.getId(), game)) {
  746. sendGhostSkipMessage(player);
  747. }
  748. } else {
  749. checkReady();
  750. }
  751. // notify other players
  752. sendServerChat(player.getName() + " disconnected.");
  753. // log it
  754. System.out.println("s: removed player " + player.getName());
  755. // Reset the game after Elvis has left the building.
  756. if (0 == game.getNoOfPlayers()) {
  757. resetGame();
  758. }
  759. }
  760. /**
  761. * Checks each player to see if he has no entities, and if true, sets the
  762. * observer flag for that player. An exception is that there are no
  763. * observers during the lounge phase.
  764. */
  765. public void checkForObservers() {
  766. for (Enumeration<Player> e = game.getPlayers(); e.hasMoreElements();) {
  767. Player p = e.nextElement();
  768. p.setObserver((game.getEntitiesOwnedBy(p) < 1) && (game.getPhase() != IGame.Phase.PHASE_LOUNGE));
  769. }
  770. }
  771. /**
  772. * Reset the game back to the lounge. TODO: couldn't this be a hazard if
  773. * there are other things executing at the same time?
  774. */
  775. public void resetGame() {
  776. // remove all entities
  777. game.reset();
  778. send(createEntitiesPacket());
  779. send(new Packet(Packet.COMMAND_SENDING_MINEFIELDS, new Vector<Object>()));
  780. // remove ghosts
  781. ArrayList<Player> ghosts = new ArrayList<Player>();
  782. for (Enumeration<Player> players = game.getPlayers(); players.hasMoreElements();) {
  783. Player p = players.nextElement();
  784. if (p.isGhost()) {
  785. ghosts.add(p);
  786. } else {
  787. // non-ghosts set their starting positions to any
  788. p.setStartingPos(0);
  789. send(createPlayerUpdatePacket(p.getId()));
  790. }
  791. }
  792. for (Player p : ghosts) {
  793. game.removePlayer(p.getId());
  794. send(new Packet(Packet.COMMAND_PLAYER_REMOVE, new Integer(p.getId())));
  795. }
  796. // reset all players
  797. resetPlayersDone();
  798. transmitAllPlayerDones();
  799. // Write end of game to stdout so controlling scripts can rotate logs.
  800. SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
  801. System.out.print(format.format(new Date()));
  802. System.out.println(" END OF GAME");
  803. changePhase(IGame.Phase.PHASE_LOUNGE);
  804. }
  805. /**
  806. * automatically save the game
  807. */
  808. public void autoSave() {
  809. String fileName = "autosave";
  810. if (PreferenceManager.getClientPreferences().stampFilenames()) {
  811. fileName = StringUtil.addDateTimeStamp(fileName);
  812. }
  813. saveGame(fileName, game.getOptions().booleanOption("autosave_msg"));
  814. }
  815. /**
  816. * save the game and send it to the sepecified connection
  817. *
  818. * @param connId
  819. * The <code>int</code> connection id to send to
  820. * @param sFile
  821. * The <code>String</code> filename to use
  822. */
  823. public void sendSaveGame(int connId, String sFile) {
  824. saveGame(sFile, false);
  825. String sFinalFile = sFile;
  826. if (!sFinalFile.endsWith(".sav")) {
  827. sFinalFile = sFile + ".sav";
  828. }
  829. String localFile = "savegames" + File.separator + sFinalFile;
  830. File f = new File(localFile);
  831. try {
  832. ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f));
  833. send(connId, new Packet(Packet.COMMAND_SEND_SAVEGAME, new Object[] { sFinalFile, ois.readObject() }));
  834. sendChat(connId, "***Server", "Savegame has been sent to you.");
  835. ois.close();
  836. } catch (Exception e) {
  837. System.err.println("Unable to load file: " + f);
  838. e.printStackTrace();
  839. }
  840. }
  841. /**
  842. * save the game
  843. *
  844. * @param sFile
  845. * The <code>String</code> filename to use
  846. * @param sendChat
  847. * A <code>boolean</code> value wether or not to announce the
  848. * saving to the server chat.
  849. */
  850. public void saveGame(String sFile, boolean sendChat) {
  851. String sFinalFile = sFile;
  852. if (!sFinalFile.endsWith(".sav")) {
  853. sFinalFile = sFile + ".sav";
  854. }
  855. try {
  856. File sDir = new File("savegames");
  857. if (!sDir.exists()) {
  858. sDir.mkdir();
  859. }
  860. Vector<GameListener> gameListenersClone = new Vector<GameListener>();
  861. for (GameListener listener : getGame().getGameListeners()) {
  862. gameListenersClone.add(listener);
  863. }
  864. getGame().purgeGameListeners();
  865. // the passed in string might be an absolut name, including a path
  866. // when the user uses the save game dialog.
  867. // if so, save there
  868. // we also need to replace any | with " ", so we can support saving
  869. // in folders with spacse
  870. sFinalFile = sFinalFile.replace("|", " ");
  871. File dir = new File(sFinalFile);
  872. if (dir.getParent() == null) {
  873. sFinalFile = sDir + File.separator + sFinalFile;
  874. }
  875. ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(sFinalFile));
  876. oos.writeObject(game);
  877. oos.flush();
  878. oos.close();
  879. for (GameListener listener : gameListenersClone) {
  880. getGame().addGameListener(listener);
  881. }
  882. } catch (Exception e) {
  883. System.err.println("Unable to save file: " + sFinalFile);
  884. e.printStackTrace();
  885. }
  886. if (sendChat) {
  887. sendChat("MegaMek", "Game saved to " + sFinalFile);
  888. }
  889. }
  890. /**
  891. * save the game
  892. *
  893. * @param sFile
  894. * The <code>String</code> filename to use
  895. */
  896. public void saveGame(String sFile) {
  897. saveGame(sFile, true);
  898. }
  899. /**
  900. * send a packet to the connection tells it load a locally saved game
  901. *
  902. * @param connId
  903. * The <code>int</code> connection id to send to
  904. * @param sFile
  905. * The <code>String</code> filename to use
  906. */
  907. public void sendLoadGame(int connId, String sFile) {
  908. String sFinalFile = sFile;
  909. if (!sFinalFile.endsWith(".sav")) {
  910. sFinalFile = sFile + ".sav";
  911. }
  912. send(connId, new Packet(Packet.COMMAND_LOAD_SAVEGAME, new Object[] { sFinalFile }));
  913. }
  914. /**
  915. * load the game
  916. *
  917. * @param f
  918. * The <code>File</code> to load
  919. * @return A <code>boolean</code> value wether or not the loading was
  920. * successfull
  921. */
  922. public boolean loadGame(File f) {
  923. System.out.println("s: loading saved game file '" + f + '\'');
  924. try {
  925. ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f));
  926. game = (IGame) ois.readObject();
  927. ois.close();
  928. } catch (Exception e) {
  929. System.err.println("Unable to load file: " + f);
  930. e.printStackTrace();
  931. return false;
  932. }
  933. // a bit redundant, but there's some initialization code there
  934. setGame(game);
  935. return true;
  936. }
  937. /**
  938. * Shortcut to game.getPlayer(id)
  939. */
  940. public Player getPlayer(int id) {
  941. return game.getPlayer(id);
  942. }
  943. /**
  944. * Removes all entities owned by a player. Should only be called when it
  945. * won't cause trouble (the lounge, for instance, or between phases.)
  946. *
  947. * @param player
  948. * whose entites are to be removed
  949. */
  950. private void removeAllEntitesOwnedBy(Player player) {
  951. Vector<Entity> toRemove = new Vector<Entity>();
  952. for (Enumeration<Entity> e = game.getEntities(); e.hasMoreElements();) {
  953. final Entity entity = e.nextElement();
  954. if (entity.getOwner().equals(player)) {
  955. toRemove.addElement(entity);
  956. }
  957. }
  958. for (Entity entity : toRemove) {
  959. int id = entity.getId();
  960. game.removeEntity(id, IEntityRemovalConditions.REMOVE_NEVER_JOINED);
  961. send(createRemoveEntityPacket(id, IEntityRemovalConditions.REMOVE_NEVER_JOINED));
  962. }
  963. }
  964. /**
  965. * a shorter name for getConnection()
  966. */
  967. private IConnection getClient(int connId) {
  968. return getConnection(connId);
  969. }
  970. /**
  971. * Returns a connection, indexed by id
  972. */
  973. public Enumeration<IConnection> getConnections() {
  974. return connections.elements();
  975. }
  976. /**
  977. * Returns a connection, indexed by id
  978. */
  979. public IConnection getConnection(int connId) {
  980. return connectionIds.get(new Integer(connId));
  981. }
  982. /**
  983. * Returns a pending connection
  984. */
  985. IConnection getPendingConnection(int connId) {
  986. for (IConnection conn : connectionsPending) {
  987. if (conn.getId() == connId) {
  988. return conn;
  989. }
  990. }
  991. return null;
  992. }
  993. /**
  994. * Called at the beginning of each game round to reset values on this entity
  995. * that are reset every round
  996. */
  997. private void resetEntityRound() {
  998. for (Enumeration<Entity> e = game.getEntities(); e.hasMoreElements();) {
  999. Entity entity = e.nextElement();
  1000. entity.newRound(game.getRoundCount());
  1001. }
  1002. }
  1003. /**
  1004. * Called at the beginning of each phase. Sets and resets any entity
  1005. * parameters that need to be reset.
  1006. */
  1007. private void resetEntityPhase(IGame.Phase phase) {
  1008. // first, mark doomed entities as destroyed and flag them
  1009. Vector<Entity> toRemove = new Vector<Entity>(0, 10);
  1010. for (Enumeration<Entity> e = game.getEntities(); e.hasMoreElements();) {
  1011. final Entity entity = e.nextElement();
  1012. if (entity.crew.isDoomed()) {
  1013. entity.crew.setDoomed(false);
  1014. entity.crew.setDead(true);
  1015. if (entity instanceof Tank) {
  1016. entity.setCarcass(true);
  1017. ((Tank) entity).immobilize();
  1018. } else {
  1019. entity.setDestroyed(true);
  1020. }
  1021. }
  1022. if (entity.isDoomed()) {
  1023. entity.setDestroyed(true);
  1024. // Is this unit swarming somebody? Better let go before
  1025. // it's too late.
  1026. final int swarmedId = entity.getSwarmTargetId();
  1027. if (Entity.NONE != swarmedId) {
  1028. final Entity swarmed = game.getEntity(swarmedId);
  1029. swarmed.setSwarmAttackerId(Entity.NONE);
  1030. entity.setSwarmTargetId(Entity.NONE);
  1031. Report r = new Report(5165);
  1032. r.subject = swarmedId;
  1033. r.addDesc(swarmed);
  1034. addReport(r);
  1035. entityUpdate(swarmedId);
  1036. }
  1037. }
  1038. if (entity.isDestroyed()) {
  1039. toRemove.addElement(entity);
  1040. }
  1041. }
  1042. // actually remove all flagged entities
  1043. for (Entity entity : toRemove) {
  1044. int condition = IEntityRemovalConditions.REMOVE_SALVAGEABLE;
  1045. if (!entity.isSalvage()) {
  1046. condition = IEntityRemovalConditions.REMOVE_DEVASTATED;
  1047. }
  1048. entityUpdate(entity.getId());
  1049. game.removeEntity(entity.getId(), condition);
  1050. send(createRemoveEntityPacket(entity.getId(), condition));
  1051. }
  1052. // do some housekeeping on all the remaining
  1053. for (Enumeration<Entity> e = game.getEntities(); e.hasMoreElements();) {
  1054. final Entity entity = e.nextElement();
  1055. entity.applyDamage();
  1056. entity.reloadEmptyWeapons();
  1057. // reset damage this phase
  1058. // tele-missiles need a record of damage last phase
  1059. entity.damageThisRound += entity.damageThisPhase;
  1060. entity.damageThisPhase = 0;
  1061. entity.engineHitsThisRound = 0;
  1062. entity.rolledForEngineExplosion = false;
  1063. entity.dodging = false;
  1064. entity.setShutDownThisPhase(false);
  1065. // reset done to false
  1066. if (phase == IGame.Phase.PHASE_DEPLOYMENT) {
  1067. entity.setDone(!entity.shouldDeploy(game.getRoundCount()));
  1068. } else {
  1069. entity.setDone(false);
  1070. }
  1071. // reset spotlights
  1072. entity.setIlluminated(false);
  1073. entity.setUsedSearchlight(false);
  1074. entity.setCarefulStand(false);
  1075. if (entity instanceof MechWarrior) {
  1076. ((MechWarrior) entity).setLanded(true);
  1077. }
  1078. }
  1079. }
  1080. /**
  1081. * are we currently in a reporting phase
  1082. *
  1083. * @return <code>true</code> if we are or <code>false</code> if not.
  1084. */
  1085. private boolean isReportingPhase() {
  1086. if ((game.getPhase() == IGame.Phase.PHASE_FIRING_REPORT)
  1087. || (game.getPhase() == IGame.Phase.PHASE_INITIATIVE_REPORT)
  1088. || (game.getPhase() == IGame.Phase.PHASE_MOVEMENT_REPORT)
  1089. || (game.getPhase() == IGame.Phase.PHASE_OFFBOARD_REPORT)
  1090. || (game.getPhase() == IGame.Phase.PHASE_PHYSICAL_REPORT)) {
  1091. return true;
  1092. }
  1093. return false;
  1094. }
  1095. /**
  1096. * Called at the beginning of certain phases to make every player not ready.
  1097. */
  1098. private void resetPlayersDone() {
  1099. if (isReportingPhase()) {
  1100. return;
  1101. }
  1102. for (Enumeration<Player> i = game.getPlayers(); i.hasMoreElements();) {
  1103. final Player player = i.nextElement();
  1104. player.setDone(false);
  1105. }
  1106. transmitAllPlayerDones();
  1107. }
  1108. /**
  1109. * Called at the beginning of certain phases to make every active player not
  1110. * ready.
  1111. */
  1112. private void resetActivePlayersDone() {
  1113. if (isReportingPhase()) {
  1114. return;
  1115. }
  1116. for (Enumeration<Player> i = game.getPlayers(); i.hasMoreElements();) {
  1117. final Player player = i.nextElement();
  1118. player.setDone(game.getEntitiesOwnedBy(player) <= 0);
  1119. }
  1120. transmitAllPlayerDones();
  1121. }
  1122. /**
  1123. * Writes the victory report
  1124. */
  1125. private void prepareVictoryReport() {
  1126. Report r;
  1127. // remove carcasses to the graveyard
  1128. Vector<Entity> toRemove = new Vector<Entity>();
  1129. for (Entity e : game.getEntitiesVector()) {
  1130. if (e.isCarcass() && !e.isDestroyed()) {
  1131. toRemove.add(e);
  1132. }
  1133. }
  1134. for (Entity e : toRemove) {
  1135. destroyEntity(e, "crew death", false, true);
  1136. game.removeEntity(e.getId(), IEntityRemovalConditions.REMOVE_SALVAGEABLE);
  1137. e.setDestroyed(true);
  1138. }
  1139. addReport(new Report(7000, Report.PUBLIC));
  1140. // Declare the victor
  1141. r = new Report(1210);
  1142. r.type = Report.PUBLIC;
  1143. if (game.getVictoryTeam() == Player.TEAM_NONE) {
  1144. Player player = getPlayer(game.getVictoryPlayerId());
  1145. if (null == player) {
  1146. r.messageId = 7005;
  1147. } else {
  1148. r.messageId = 7010;
  1149. r.add(Server.getColorForPlayer(player));
  1150. }
  1151. } else {
  1152. // Team victory
  1153. r.messageId = 7015;
  1154. r.add(game.getVictoryTeam());
  1155. }
  1156. addReport(r);
  1157. // Show player BVs
  1158. Enumeration<Player> players = game.getPlayers();
  1159. while (players.hasMoreElements()) {
  1160. Player player = players.nextElement();
  1161. r = new Report();
  1162. r.type = Report.PUBLIC;
  1163. r.messageId = 7016;
  1164. r.add(Server.getColorForPlayer(player));
  1165. r.add(player.getBV());
  1166. r.add(player.getInitialBV());
  1167. r.add(player.getFledBV());
  1168. addReport(r);
  1169. }
  1170. // List the survivors
  1171. Enumeration<Entity> survivors = game.getEntities();
  1172. if (survivors.hasMoreElements()) {
  1173. addReport(new Report(7020, Report.PUBLIC));
  1174. while (survivors.hasMoreElements()) {
  1175. Entity entity = survivors.nextElement();
  1176. if (!entity.isDeployed()) {
  1177. continue;
  1178. }
  1179. addReport(entity.victoryReport());
  1180. }
  1181. }
  1182. // List units that never deployed
  1183. Enumeration<Entity> undeployed = game.getEntities();
  1184. if (undeployed.hasMoreElements()) {
  1185. boolean wroteHeader = false;
  1186. while (undeployed.hasMoreElements()) {
  1187. Entity entity = undeployed.nextElement();
  1188. if (entity.isDeployed()) {
  1189. continue;
  1190. }
  1191. if (!wroteHeader) {
  1192. addReport(new Report(7075, Report.PUBLIC));
  1193. wroteHeader = true;
  1194. }
  1195. addReport(entity.victoryReport());
  1196. }
  1197. }
  1198. // List units that retreated
  1199. Enumeration<Entity> retreat = game.getRetreatedEntities();
  1200. if (retreat.hasMoreElements()) {
  1201. addReport(new Report(7080, Report.PUBLIC));
  1202. while (retreat.hasMoreElements()) {
  1203. Entity entity = retreat.nextElement();
  1204. addReport(entity.victoryReport());
  1205. }
  1206. }