/*
* 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.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.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.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.IEntityMovementMode;
import megamek.common.IEntityMovementType;
import megamek.common.IEntityRemovalConditions;
import megamek.common.IGame;
import megamek.common.IHex;
import megamek.common.ILocationExposureStatus;
import megamek.common.INarcPod;
import megamek.common.IOffBoardDirections;
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.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.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.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
}
// 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();
/*
* Tracks entities which have been destroyed recently. Allows refactoring of
* the damage and kill logic from Server, where it is now, to the Entity
* subclasses eventually. This has not been implemented yet -- I am just
* starting to build the groundwork into Server. It isn't in the execution
* path and shouldn't cause any bugs
*/
// Note from another coder - I have commented out your groundwork
// for now because it is using HashSet, which isn't available in
// Java 1.1 unless you import the collections classes. Since the
// Server class isn't using any other collecitons classes, there
// might be a reason we're avoiding them here...if not, feel free
// to add the import.
// private HashSet knownDeadEntities = new HashSet();
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, createReportPacket(player));
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, createBoardPacket());
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);
}
//if this is the deployment phase, then apply bombs and santa annas to
//the appropriate aero entities
//FIXME: there must be a better place to do this
if( phase == IGame.Phase.PHASE_DEPLOYMENT ) {
if(entity instanceof Aero) {
if(game.getOptions().booleanOption("at2_nukes") &&
((entity instanceof Dropship) || (entity instanceof Jumpship))) {
entity.applySantaAnna();
}
if(!((entity instanceof SmallCraft) || (entity instanceof Jumpship))) {
((Aero)entity).applyBombs();
}
}
}
// 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(player.getName());
}
} 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(player.getName());
r.add(player.getBV());
r.add(player.getInitialBV());
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());
}
}
// List destroyed units
Enumeration<Entity> graveyard = game.getGraveyardEntities();
if (graveyard.hasMoreElements()) {
addReport(new Report(7085, Report.PUBLIC));
while (graveyard.hasMoreElements()) {
Entity entity = graveyard.nextElement();
addReport(entity.victoryReport());
}
}
// List devastated units (not salvagable)
Enumeration<Entity> devastated = game.getDevastatedEntities();
if (devastated.hasMoreElements()) {
addReport(new Report(7090, Report.PUBLIC));
while (devastated.hasMoreElements()) {
Entity entity = devastated.nextElement();
addReport(entity.victoryReport());
}
}
// Let player know about entitystatus.txt file
addReport(new Report(7095, Report.PUBLIC));
}
/**
* Generates a detailed report for campaign use
*/
private String getDetailedVictoryReport() {
StringBuffer sb = new StringBuffer();
Vector<Entity> vAllUnits = new Vector<Entity>();
for (Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
vAllUnits.addElement(i.nextElement());
}
for (Enumeration<Entity> i = game.getRetreatedEntities(); i.hasMoreElements();) {
vAllUnits.addElement(i.nextElement());
}
for (Enumeration<Entity> i = game.getGraveyardEntities(); i.hasMoreElements();) {
vAllUnits.addElement(i.nextElement());
}
for (Enumeration<Player> i = game.getPlayers(); i.hasMoreElements();) {
// Record the player.
Player p = i.nextElement();
sb.append("++++++++++ ").append(p.getName()).append(" ++++++++++");
sb.append(CommonConstants.NL);
// Record the player's alive, retreated, or salvageable units.
for (int x = 0; x < vAllUnits.size(); x++) {
Entity e = vAllUnits.elementAt(x);
if (e.getOwner() == p) {
sb.append(UnitStatusFormatter.format(e));
}
}
// Record the player's devastated units.
Enumeration<Entity> devastated = game.getDevastatedEntities();
if (devastated.hasMoreElements()) {
sb.append("=============================================================");
sb.append(CommonConstants.NL);
sb.append("The following utterly destroyed units are not available for salvage:");
sb.append(CommonConstants.NL);
while (devastated.hasMoreElements()) {
Entity e = devastated.nextElement();
if (e.getOwner() == p) {
sb.append(e.getShortName()).append(", Pilot: ").append(e.getCrew().getName()).append(" (").append(e.getCrew().getGunnery()).append('/').append(e.getCrew().getPiloting()).append(')');
sb.append(CommonConstants.NL);
}
} // Handle the next unsalvageable unit for the player
sb.append("=============================================================");
sb.append(CommonConstants.NL);
}
} // Handle the next player
return sb.toString();
}
/**
* Forces victory for the specified player, or his/her team at the end of
* the round.
*/
public void forceVictory(Player victor) {
game.setForceVictory(true);
if (victor.getTeam() == Player.TEAM_NONE) {
game.setVictoryPlayerId(victor.getId());
game.setVictoryTeam(Player.TEAM_NONE);
} else {
game.setVictoryPlayerId(Player.PLAYER_NONE);
game.setVictoryTeam(victor.getTeam());
}
Vector<Player> players = game.getPlayersVector();
for (int i = 0; i < players.size(); i++) {
Player player = players.elementAt(i);
player.setAdmitsDefeat(false);
}
}
/** Cancels the force victory */
public void cancelVictory() {
game.setForceVictory(false);
game.setVictoryPlayerId(Player.PLAYER_NONE);
game.setVictoryTeam(Player.TEAM_NONE);
}
/**
* Called when a player declares that he is "done." Checks to see if all
* players are done, and if so, moves on to the next phase.
*/
private void checkReady() {
// check if all active players are done
for (Enumeration<Player> i = game.getPlayers(); i.hasMoreElements();) {
final Player player = i.nextElement();
if (!player.isGhost() && !player.isObserver() && !player.isDone()) {
return;
}
}
// Tactical Genius pilot special ability (lvl 3)
if (game.getNoOfInitiativeRerollRequests() > 0) {
resetActivePlayersDone();
game.rollInitAndResolveTies();
determineTurnOrder(IGame.Phase.PHASE_INITIATIVE);
clearReports();
writeInitiativeReport(true);
sendReport(true);
return; // don't end the phase yet, players need to see new report
}
// need at least one entity in the game for the lounge phase to end
if (!game.phaseHasTurns(game.getPhase()) && ((game.getPhase() != IGame.Phase.PHASE_LOUNGE) || (game.getNoOfEntities() > 0))) {
endCurrentPhase();
}
}
/**
* Called when the current player has done his current turn and the turn
* counter needs to be advanced. Also enforces the "protos_move_multi" and
* the "protos_move_multi" option. If the player has just moved
* infantry/protos with a "normal" turn, adds up to
* Game.INF_AND_PROTOS_MOVE_MULTI - 1 more infantry/proto-specific turns
* after the current turn.
*/
private void endCurrentTurn(Entity entityUsed) {
// Enforce "inf_move_multi" and "protos_move_multi" options.
// The "isNormalTurn" flag is checking to see if any non-Infantry
// or non-Protomech units can move during the current turn.
boolean turnsChanged = false;
boolean outOfOrder = false;
GameTurn turn = game.getTurn();
if(game.isPhaseSimultaneous() && !turn.isValid(entityUsed.getOwnerId(), game)) {
//turn played out of order
outOfOrder = true;
entityUsed.setDone(false);
GameTurn removed = game.removeFirstTurnFor(entityUsed);
entityUsed.setDone(true);
turnsChanged = true;
if(removed != null) {
turn = removed;
}
}
final int playerId = null == entityUsed ? Player.PLAYER_NONE : entityUsed.getOwnerId();
boolean infMoved = entityUsed instanceof Infantry;
boolean infMoveMulti = game.getOptions().booleanOption("inf_move_multi") && ((game.getPhase() == IGame.Phase.PHASE_MOVEMENT) || (game.getPhase() == IGame.Phase.PHASE_INITIATIVE));
boolean protosMoved = entityUsed instanceof Protomech;
boolean protosMoveMulti = game.getOptions().booleanOption("protos_move_multi");
boolean tanksMoved = entityUsed instanceof Tank;
boolean tanksMoveMulti = game.getOptions().booleanOption("vehicle_lance_movement") && ((game.getPhase() == IGame.Phase.PHASE_MOVEMENT) || (game.getPhase() == IGame.Phase.PHASE_INITIATIVE));
// If infantry or protos move multi see if any
// other unit types can move in the current turn.
int multiMask = 0;
if (infMoveMulti) {
multiMask += GameTurn.CLASS_INFANTRY;
}
if (protosMoveMulti) {
multiMask += GameTurn.CLASS_PROTOMECH;
}
// If a proto declared fire and protos don't move
// multi, ignore whether infantry move or not.
else if (protosMoved && (game.getPhase() == IGame.Phase.PHASE_FIRING)) {
multiMask = 0;
}
if (tanksMoveMulti) {
multiMask += GameTurn.CLASS_TANK;
}
// Is this a general move turn?
boolean isGeneralMoveTurn = !(turn instanceof GameTurn.SpecificEntityTurn) && !(turn instanceof GameTurn.UnitNumberTurn) && !(turn instanceof GameTurn.UnloadStrandedTurn) && (!(turn instanceof GameTurn.EntityClassTurn) || ((turn instanceof GameTurn.EntityClassTurn) && ((GameTurn.EntityClassTurn) turn).isValidClass(~multiMask)));
// Unless overridden by the "protos_move_multi" option, all Protomechs
// in a unit declare fire, and they don't mix with infantry.
if (protosMoved && !protosMoveMulti && isGeneralMoveTurn && (entityUsed != null)) {
// What's the unit number and ID of the entity used?
final char movingUnit = entityUsed.getUnitNumber();
final int movingId = entityUsed.getId();
// How many other Protomechs are in the unit that can fire?
int protoTurns = game.getSelectedEntityCount(new EntitySelector() {
private final int ownerId = playerId;
private final int entityId = movingId;
private final char unitNum = movingUnit;
public boolean accept(Entity entity) {
if ((entity instanceof Protomech) && entity.isSelectableThisTurn() && (ownerId == entity.getOwnerId()) && (entityId != entity.getId()) && (unitNum == entity.getUnitNumber())) {
return true;
}
return false;
}
});
// Add the correct number of turns for the Protomech unit number.
for (int i = 0; i < protoTurns; i++) {
GameTurn newTurn = new GameTurn.UnitNumberTurn(playerId, movingUnit);
game.insertNextTurn(newTurn);
turnsChanged = true;
}
}
// Otherwise, we may need to add turns for the "*_move_multi" options.
else if (((infMoved && infMoveMulti) || (protosMoved && protosMoveMulti)) && isGeneralMoveTurn) {
int remaining = 0;
// Calculate the number of EntityClassTurns need to be added.
if (infMoveMulti) {
remaining += game.getInfantryLeft(playerId);
}
if (protosMoveMulti) {
remaining += game.getProtomechsLeft(playerId);
}
int moreInfAndProtoTurns = Math.min(game.getOptions().intOption("inf_proto_move_multi") - 1, remaining);
// Add the correct number of turns for the right unit classes.
for (int i = 0; i < moreInfAndProtoTurns; i++) {
GameTurn newTurn = new GameTurn.EntityClassTurn(playerId, multiMask);
game.insertNextTurn(newTurn);
turnsChanged = true;
}
}
if (tanksMoved && tanksMoveMulti && isGeneralMoveTurn) {
int remaining = game.getVehiclesLeft(playerId);
int moreVeeTurns = Math.min(game.getOptions().intOption("vehicle_lance_movement_number") - 1, remaining);
// Add the correct number of turns for the right unit classes.
for (int i = 0; i < moreVeeTurns; i++) {
GameTurn newTurn = new GameTurn.EntityClassTurn(playerId, multiMask);
game.insertNextTurn(newTurn);
turnsChanged = true;
}
}
// brief everybody on the turn update, if they changed
if (turnsChanged) {
send(createTurnVectorPacket());
}
// move along
if(outOfOrder) {
send(createTurnIndexPacket());
} else {
changeToNextTurn();
}
}
/**
* Changes the current phase, does some bookkeeping and then tells the
* players.
*
* @param phase
* the <code>int</code> id of the phase to change to
*/
private void changePhase(IGame.Phase phase) {
game.setLastPhase(game.getPhase());
game.setPhase(phase);
// prepare for the phase
prepareForPhase(phase);
if (isPhasePlayable(phase)) {
// tell the players about the new phase
send(new Packet(Packet.COMMAND_PHASE_CHANGE, phase));
// post phase change stuff
executePhase(phase);
} else {
endCurrentPhase();
}
}
/**
* Prepares for, presumably, the next phase. This typically involves
* resetting the states of entities in the game and making sure the client
* has the information it needs for the new phase.
*
* @param phase
* the <code>int</code> id of the phase to prepare for
*/
private void prepareForPhase(IGame.Phase phase) {
switch (phase) {
case PHASE_LOUNGE:
clearReports();
mapSettings.setBoardsAvailableVector(scanForBoards(mapSettings.getBoardWidth(), mapSettings.getBoardHeight()));
mapSettings.setNullBoards(DEFAULT_BOARD);
send(createMapSettingsPacket());
break;
case PHASE_INITIATIVE:
// remove the last traces of last round
game.resetActions();
game.resetTagInfo();
clearReports();
resetEntityRound();
resetEntityPhase(phase);
checkForObservers();
// roll 'em
resetActivePlayersDone();
rollInitiative();
if (!game.shouldDeployThisRound()) {
incrementAndSendGameRound();
}
// setIneligible(phase);
determineTurnOrder(phase);
writeInitiativeReport(false);
//checks for environmental survival
checkForConditionDeath();
if (game.getBoard().inAtmosphere()) {
checkForAtmosphereDeath();
}
if (game.getBoard().inSpace()) {
checkForSpaceDeath();
}
System.out.println("Round " + game.getRoundCount() + " memory usage: " + MegaMek.getMemoryUsed());
break;
case PHASE_DEPLOY_MINEFIELDS:
checkForObservers();
resetActivePlayersDone();
setIneligible(phase);
Enumeration<Player> e = game.getPlayers();
Vector<GameTurn> turns = new Vector<GameTurn>();
while (e.hasMoreElements()) {
Player p = e.nextElement();
if (p.hasMinefields() && game.getBoard().onGround()) {
GameTurn gt = new GameTurn(p.getId());
turns.addElement(gt);
}
}
game.setTurnVector(turns);
game.resetTurnIndex();
// send turns to all players
send(createTurnVectorPacket());
break;
case PHASE_SET_ARTYAUTOHITHEXES:
// place off board entities actually off-board
Enumeration<Entity> entities = game.getEntities();
while (entities.hasMoreElements()) {
Entity en = entities.nextElement();
en.deployOffBoard();
}
checkForObservers();
resetActivePlayersDone();
setIneligible(phase);
Enumeration<Player> players = game.getPlayers();
Vector<GameTurn> turn = new Vector<GameTurn>();
// Walk through the players of the game, and add
// a turn for all players with artillery weapons.
while (players.hasMoreElements()) {
// Get the next player.
final Player p = players.nextElement();
// Does the player have any artillery-equipped units?
EntitySelector playerArtySelector = new EntitySelector() {
private Player owner = p;
public boolean accept(Entity entity) {
if (owner.equals(entity.getOwner()) && entity.isEligibleForTargetingPhase()) {
return true;
}
return false;
}
};
if (game.getSelectedEntities(playerArtySelector).hasMoreElements()) {
// Yes, the player has arty-equipped units.
GameTurn gt = new GameTurn(p.getId());
turn.addElement(gt);
}
}
game.setTurnVector(turn);
game.resetTurnIndex();
// send turns to all players
send(createTurnVectorPacket());
break;
case PHASE_MOVEMENT:
case PHASE_DEPLOYMENT:
//Update visibility indications if using double blind.
if (doBlind()) {
updateVisibilityIndicator();
}
case PHASE_FIRING:
case PHASE_PHYSICAL:
case PHASE_TARGETING:
case PHASE_OFFBOARD:
resetEntityPhase(phase);
checkForObservers();
setIneligible(phase);
determineTurnOrder(phase);
resetActivePlayersDone();
// send(createEntitiesPacket());
entityAllUpdate();
clearReports();
doTryUnstuck();
break;
case PHASE_END:
resetEntityPhase(phase);
clearReports();
resolveHeat();
if(game.getPlanetaryConditions().isSandBlowing()
&& (game.getPlanetaryConditions().getWindStrength() > PlanetaryConditions.WI_LIGHT_GALE)) {
addReport(resolveBlowingSandDamage());
}
addReport(resolveControlRolls());
// write End Phase header
addReport(new Report(5005, Report.PUBLIC));
checkForSuffocation();
checkForIndustrialEndOfTurn();
game.getPlanetaryConditions().determineWind();
send(createPlanetaryConditionsPacket());
for (DynamicTerrainProcessor tp : terrainProcessors) {
tp.doEndPhaseChanges(vPhaseReport);
}
applyBuildingDamage();
addReport(game.ageFlares());
send(createFlarePacket());
resolveAmmoDumps();
resolveCrewWakeUp();
resolveMechWarriorPickUp();
resolveVeeINarcPodRemoval();
resolveFortify();
checkForObservers();
entityAllUpdate();
break;
case PHASE_INITIATIVE_REPORT:
autoSave();
// Show player BVs
Enumeration<Player> players2 = game.getPlayers();
while (players2.hasMoreElements()) {
Player player = players2.nextElement();
Report r = new Report();
r.type = Report.PUBLIC;
if (doBlind()) {
r.type = Report.PLAYER;
r.player = player.getId();
}
r.messageId = 7016;
r.add(player.getName());
r.add(player.getBV());
r.add(player.getInitialBV());
addReport(r);
}
case PHASE_TARGETING_REPORT:
case PHASE_MOVEMENT_REPORT:
case PHASE_OFFBOARD_REPORT:
case PHASE_FIRING_REPORT:
case PHASE_PHYSICAL_REPORT:
case PHASE_END_REPORT:
resetActivePlayersDone();
sendReport();
if (game.getOptions().booleanOption("paranoid_autosave")) {
autoSave();
}
break;
case PHASE_VICTORY:
resetPlayersDone();
clearReports();
prepareVictoryReport();
game.addReports(vPhaseReport);
send(createFullEntitiesPacket());
send(createReportPacket(null));
send(createEndOfGamePacket());
break;
}
}
/**
* Should we play this phase or skip it?
*/
private boolean isPhasePlayable(IGame.Phase phase) {
switch (phase) {
case PHASE_INITIATIVE:
case PHASE_END:
return false;
case PHASE_SET_ARTYAUTOHITHEXES:
case PHASE_DEPLOY_MINEFIELDS:
case PHASE_DEPLOYMENT:
case PHASE_MOVEMENT:
case PHASE_FIRING:
case PHASE_PHYSICAL:
case PHASE_TARGETING:
return game.hasMoreTurns();
case PHASE_OFFBOARD:
return isOffboardPlayable();
default:
return true;
}
}
/**
* Skip offboard phase, if there is no homing / semiguided ammo in play
*/
private boolean isOffboardPlayable() {
if (!game.hasMoreTurns()) {
return false;
}
for (Enumeration<Entity> e = game.getEntities(); e.hasMoreElements();) {
Entity entity = e.nextElement();
for (Mounted m : entity.getAmmo()) {
AmmoType atype = (AmmoType) m.getType();
if (((atype.getAmmoType() == AmmoType.T_LRM) || (atype.getAmmoType() == AmmoType.T_MML)) && (atype.getMunitionType() == AmmoType.M_SEMIGUIDED)) {
return true;
}
if (((atype.getAmmoType() == AmmoType.T_ARROW_IV) || (atype.getAmmoType() == AmmoType.T_LONG_TOM) || (atype.getAmmoType() == AmmoType.T_SNIPER)) && (atype.getMunitionType() == AmmoType.M_HOMING)) {
return true;
}
}
}
// loop through all current attacks
// if there are any that use homing ammo, we are playable
// we need to do this because we might have a homing arty shot in flight
// when the unit that mounted that ammo is no longer on the field
for (Enumeration<AttackHandler> attacks = game.getAttacks(); attacks.hasMoreElements();) {
AttackHandler ah = attacks.nextElement();
Mounted ammo = ah.getWaa().getEntity(game).getEquipment(ah.getWaa().getAmmoId());
if (ammo != null) {
AmmoType atype = (AmmoType) ammo.getType();
if (atype.getMunitionType() == AmmoType.M_HOMING) {
return true;
}
}
}
return false;
}
/**
* Do anything we seed to start the new phase, such as give a turn to the
* first player to play.
*/
private void executePhase(IGame.Phase phase) {
switch (phase) {
case PHASE_EXCHANGE:
resetPlayersDone();
calculatePlayerBVs();
// Build teams vector
game.setupTeams();
applyBoardSettings();
game.getPlanetaryConditions().determineWind();
send(createPlanetaryConditionsPacket());
game.setupRoundDeployment();
game.setVictoryContext(new HashMap<String, Object>());
game.createVictoryConditions();
// If we add transporters for any Magnetic Clamp
// equiped squads, then update the clients' entities.
if (game.checkForMagneticClamp()) {
entityAllUpdate();
}
// transmit the board to everybody
send(createBoardPacket());
break;
case PHASE_MOVEMENT:
// write Movement Phase header to report
addReport(new Report(2000, Report.PUBLIC));
case PHASE_SET_ARTYAUTOHITHEXES:
case PHASE_DEPLOY_MINEFIELDS:
case PHASE_DEPLOYMENT:
case PHASE_FIRING:
case PHASE_PHYSICAL:
case PHASE_TARGETING:
case PHASE_OFFBOARD:
changeToNextTurn();
if (game.getOptions().booleanOption("paranoid_autosave")) {
autoSave();
}
break;
}
}
/**
* Calculates all players initial BV, should only be called at start of game
*/
public void calculatePlayerBVs() {
for (Enumeration<Player> players = game.getPlayers(); players.hasMoreElements();) {
players.nextElement().setInitialBV();
}
}
/**
* Ends this phase and moves on to the next.
*/
private void endCurrentPhase() {
switch (game.getPhase()) {
case PHASE_LOUNGE:
changePhase(IGame.Phase.PHASE_EXCHANGE);
break;
case PHASE_EXCHANGE:
changePhase(IGame.Phase.PHASE_SET_ARTYAUTOHITHEXES);
break;
case PHASE_STARTING_SCENARIO:
changePhase(IGame.Phase.PHASE_SET_ARTYAUTOHITHEXES);
break;
case PHASE_SET_ARTYAUTOHITHEXES:
sendSpecialHexDisplayPackets();
Enumeration<Player> e = game.getPlayers();
boolean mines = false;
while (e.hasMoreElements() && !mines) {
Player p = e.nextElement();
if (p.hasMinefields()) {
mines = true;
}
}
if (mines) {
changePhase(IGame.Phase.PHASE_DEPLOY_MINEFIELDS);
} else {
changePhase(IGame.Phase.PHASE_INITIATIVE);
}
break;
case PHASE_DEPLOY_MINEFIELDS:
changePhase(IGame.Phase.PHASE_INITIATIVE);
break;
case PHASE_DEPLOYMENT:
game.clearDeploymentThisRound();
game.checkForCompleteDeployment();
Enumeration<Player> pls = game.getPlayers();
while (pls.hasMoreElements()) {
Player p = pls.nextElement();
p.adjustStartingPosForReinforcements();
}
if (game.getRoundCount() < 1) {
changePhase(IGame.Phase.PHASE_INITIATIVE);
} else {
changePhase(IGame.Phase.PHASE_TARGETING);
}
break;
case PHASE_INITIATIVE:
game.addReports(vPhaseReport);
changePhase(IGame.Phase.PHASE_INITIATIVE_REPORT);
break;
case PHASE_INITIATIVE_REPORT:
// boolean doDeploy = game.shouldDeployThisRound() &&
// (game.getLastPhase() != IGame.Phase.PHASE_DEPLOYMENT);
if (game.shouldDeployThisRound()) {
changePhase(IGame.Phase.PHASE_DEPLOYMENT);
} else {
changePhase(IGame.Phase.PHASE_TARGETING);
}
break;
case PHASE_MOVEMENT:
doAllAssaultDrops();
addMovementHeat();
applyBuildingDamage();
checkForPSRFromDamage();
addReport(resolvePilotingRolls()); // Skids cause damage in
// movement phase
checkForFlamingDamage();
checkForTeleMissileAttacks();
// check phase report
if (vPhaseReport.size() > 1) {
game.addReports(vPhaseReport);
changePhase(IGame.Phase.PHASE_MOVEMENT_REPORT);
} else {
// just the header, so we'll add the <nothing> label
addReport(new Report(1205, Report.PUBLIC));
game.addReports(vPhaseReport);
sendReport();
changePhase(IGame.Phase.PHASE_OFFBOARD);
}
break;
case PHASE_MOVEMENT_REPORT:
changePhase(IGame.Phase.PHASE_OFFBOARD);
break;
case PHASE_FIRING:
resolveAllButWeaponAttacks();
reportGhostTargetRolls();
reportLargeCraftECCMRolls();
resolveOnlyWeaponAttacks();
assignAMS();
handleAttacks();
resolveScheduledNukes();
applyBuildingDamage();
checkForPSRFromDamage();
addReport(resolvePilotingRolls());
// check phase report
if (vPhaseReport.size() > 1) {
game.addReports(vPhaseReport);
changePhase(IGame.Phase.PHASE_FIRING_REPORT);
} else {
// just the header, so we'll add the <nothing> label
addReport(new Report(1205, Report.PUBLIC));
sendReport();
game.addReports(vPhaseReport);
changePhase(IGame.Phase.PHASE_PHYSICAL);
}
break;
case PHASE_FIRING_REPORT:
changePhase(IGame.Phase.PHASE_PHYSICAL);
break;
case PHASE_PHYSICAL:
resolvePhysicalAttacks();
applyBuildingDamage();
checkForPSRFromDamage();
addReport(resolvePilotingRolls());
resolveSinkVees();
// check phase report
if (vPhaseReport.size() > 1) {
game.addReports(vPhaseReport);
changePhase(IGame.Phase.PHASE_PHYSICAL_REPORT);
} else {
// just the header, so we'll add the <nothing> label
addReport(new Report(1205, Report.PUBLIC));
game.addReports(vPhaseReport);
sendReport();
changePhase(IGame.Phase.PHASE_END);
}
break;
case PHASE_PHYSICAL_REPORT:
changePhase(IGame.Phase.PHASE_END);
break;
case PHASE_TARGETING:
vPhaseReport.addElement(new Report(1035, Report.PUBLIC));
resolveAllButWeaponAttacks();
resolveOnlyWeaponAttacks();
handleAttacks();
// check reports
if (vPhaseReport.size() > 1) {
game.addReports(vPhaseReport);
changePhase(IGame.Phase.PHASE_TARGETING_REPORT);
} else {
// just the header, so we'll add the <nothing> label
vPhaseReport.addElement(new Report(1205, Report.PUBLIC));
game.addReports(vPhaseReport);
sendReport();
changePhase(IGame.Phase.PHASE_MOVEMENT);
}
sendSpecialHexDisplayPackets();
break;
case PHASE_OFFBOARD:
// write Offboard Attack Phase header
addReport(new Report(1100, Report.PUBLIC));
resolveAllButWeaponAttacks(); // torso twist or flip arms
// possible
resolveOnlyWeaponAttacks(); // should only be TAG at this point
handleAttacks();
for (Enumeration<Player> i = game.getPlayers(); i.hasMoreElements();) {
Player player = i.nextElement();
int connId = player.getId();
send(connId, createArtilleryPacket(player));
}
applyBuildingDamage();
checkForPSRFromDamage();
addReport(resolvePilotingRolls());
sendSpecialHexDisplayPackets();
// check reports
if (vPhaseReport.size() > 1) {
game.addReports(vPhaseReport);
changePhase(IGame.Phase.PHASE_OFFBOARD_REPORT);
} else {
// just the header, so we'll add the <nothing> label
addReport(new Report(1205, Report.PUBLIC));
game.addReports(vPhaseReport);
sendReport();
changePhase(IGame.Phase.PHASE_FIRING);
}
break;
case PHASE_OFFBOARD_REPORT:
sendSpecialHexDisplayPackets();
changePhase(IGame.Phase.PHASE_FIRING);
break;
case PHASE_TARGETING_REPORT:
changePhase(IGame.Phase.PHASE_MOVEMENT);
break;
case PHASE_END:
// remove any entities that died in the heat/end phase before
// check
// for victory
resetEntityPhase(IGame.Phase.PHASE_END);
boolean victory = victory(); // note this may add reports
// check phase report
// HACK: hardcoded message ID check
if ((vPhaseReport.size() > 3) || (vPhaseReport.elementAt(1).messageId != 1205)) {
game.addReports(vPhaseReport);
changePhase(IGame.Phase.PHASE_END_REPORT);
} else {
// just the heat and end headers, so we'll add
// the <nothing> label
addReport(new Report(1205, Report.PUBLIC));
game.addReports(vPhaseReport);
sendReport();
if (victory) {
changePhase(IGame.Phase.PHASE_VICTORY);
} else {
changePhase(IGame.Phase.PHASE_INITIATIVE);
}
}
break;
case PHASE_END_REPORT:
if (victory()) {
changePhase(IGame.Phase.PHASE_VICTORY);
} else {
changePhase(IGame.Phase.PHASE_INITIATIVE);
}
break;
case PHASE_VICTORY:
resetGame();
break;
}
}
/**
*
*/
private void sendSpecialHexDisplayPackets() {
if (connections == null) {
return;
}
for (int i = 0; i < connections.size(); i++) {
if (connections.get(i) != null) {
connections.get(i).send(createSpecialHexDisplayPacket(i));
}
}
}
/**
* Increment's the server's game round and send it to all the clients
*/
private void incrementAndSendGameRound() {
game.incrementRoundCount();
send(new Packet(Packet.COMMAND_ROUND_UPDATE, new Integer(game.getRoundCount())));
}
/**
* Tries to change to the next turn. If there are no more turns, ends the
* current phase. If the player whose turn it is next is not connected, we
* allow the other players to skip that player.
*/
private void changeToNextTurn() {
// if there aren't any more turns, end the phase
if (!game.hasMoreTurns()) {
endCurrentPhase();
return;
}
// okay, well next turn then!
GameTurn nextTurn = game.changeToNextTurn();
Player player = getPlayer(nextTurn.getPlayerNum());
if ((player != null) && (game.getEntitiesOwnedBy(player) == 0)) {
endCurrentTurn(null);
return;
}
send(createTurnIndexPacket());
if ((null != player) && player.isGhost()) {
sendGhostSkipMessage(player);
} else if ((null == game.getFirstEntity()) && (null != player) && ((game.getPhase() != IGame.Phase.PHASE_DEPLOY_MINEFIELDS) && (game.getPhase() != IGame.Phase.PHASE_SET_ARTYAUTOHITHEXES))) {
sendTurnErrorSkipMessage(player);
}
}
/**
* Sends out a notification message indicating that a ghost player may be
* skipped.
*
* @param ghost -
* the <code>Player</code> who is ghosted. This value must not
* be <code>null</code>.
*/
private void sendGhostSkipMessage(Player ghost) {
StringBuffer message = new StringBuffer();
message.append("Player '").append(ghost.getName()).append("' is disconnected. You may skip his/her current turn with the /skip command.");
sendServerChat(message.toString());
}
/**
* Sends out a notification message indicating that the current turn is an
* error and should be skipped.
*
* @param skip -
* the <code>Player</code> who is to be skipped. This value
* must not be <code>null</code>.
*/
private void sendTurnErrorSkipMessage(Player skip) {
StringBuffer message = new StringBuffer();
message.append("Player '").append(skip.getName()).append("' has no units to move. You should skip his/her/your current turn with the /skip command. You may want to report this error. See the MegaMek homepage (http://megamek.sf.net/) for details.");
sendServerChat(message.toString());
}
/**
* Skips the current turn. This only makes sense in phases that have turns.
* Operates by finding an entity to move and then doing nothing with it.
*/
public void skipCurrentTurn() {
// find an entity to skip...
Entity toSkip = game.getFirstEntity();
switch (game.getPhase()) {
case PHASE_DEPLOYMENT:
// allow skipping during deployment,
// we need that when someone removes a unit.
endCurrentTurn(null);
break;
case PHASE_MOVEMENT:
if (toSkip != null) {
processMovement(toSkip, new MovePath(game, toSkip));
}
endCurrentTurn(toSkip);
break;
case PHASE_FIRING:
case PHASE_PHYSICAL:
case PHASE_TARGETING:
case PHASE_OFFBOARD:
if (toSkip != null) {
processAttack(toSkip, new Vector<EntityAction>(0));
}
endCurrentTurn(toSkip);
break;
default:
}
}
/**
* Returns true if the current turn may be skipped. Ghost players' turns are
* skippable, and a turn should be skipped if there's nothing to move.
*/
public boolean isTurnSkippable() {
GameTurn turn = game.getTurn();
if (null == turn) {
return false;
}
Player player = getPlayer(turn.getPlayerNum());
return (null == player) || player.isGhost() || (game.getFirstEntity() == null);
}
/**
* Returns true if victory conditions have been met. Victory conditions are
* when there is only one player left with mechs or only one team. will also
* add some reports to reporting
*/
public boolean victory() {
Victory.Result vr = game.getVictory().victory(game, game.getVictoryContext());
for (Report r : vr.getReports()) {
addReport(r);
}
if (vr.victory()) {
boolean draw = vr.isDraw();
int wonPlayer = vr.getWinningPlayer();
int wonTeam = vr.getWinningTeam();
if (wonPlayer != Player.PLAYER_NONE) {
Report r = new Report(7200, Report.PUBLIC);
r.add(game.getPlayer(wonPlayer).getName());
addReport(r);
}
if (wonTeam != Player.TEAM_NONE) {
Report r = new Report(7200, Report.PUBLIC);
r.add("Team " + wonTeam);
addReport(r);
}
if (draw) {
// multiple-won draw
game.setVictoryPlayerId(Player.PLAYER_NONE);
game.setVictoryTeam(Player.TEAM_NONE);
} else {
// nobody-won draw or
// single player won or
// single team won
game.setVictoryPlayerId(wonPlayer);
game.setVictoryTeam(wonTeam);
}
} else {
game.setVictoryPlayerId(Player.PLAYER_NONE);
game.setVictoryTeam(Player.TEAM_NONE);
if (game.isForceVictory()) {
cancelVictory();
}
}
return vr.victory();
}// end victory
private boolean isPlayerForcedVictory(){
// check game options
if (!game.getOptions().booleanOption("skip_forced_victory")) {
return false;
}
if ( !game.isForceVictory() ){
return false;
}
for ( Player player : game.getPlayersVector() ){
if ( (player.getId() == game.getVictoryPlayerId())
|| ((player.getTeam() == game.getVictoryTeam()) && (game.getVictoryTeam() != Player.TEAM_NONE)) ){
continue;
}
if ( !player.admitsDefeat() ){
return false;
}
}
return true;
}
/**
* Applies board settings. This loads and combines all the boards that were
* specified into one mega-board and sets that board as current.
*/
public void applyBoardSettings() {
mapSettings.replaceBoardWithRandom(MapSettings.BOARD_RANDOM);
mapSettings.replaceBoardWithRandom(MapSettings.BOARD_SURPRISE);
IBoard[] sheetBoards = new IBoard[mapSettings.getMapWidth() * mapSettings.getMapHeight()];
for (int i = 0; i < mapSettings.getMapWidth() * mapSettings.getMapHeight(); i++) {
sheetBoards[i] = new Board();
String name = mapSettings.getBoardsSelectedVector().get(i);
boolean isRotated = false;
if (name.startsWith(Board.BOARD_REQUEST_ROTATION)) {
// only rotate boards with an even width
if (mapSettings.getBoardWidth() % 2 == 0) {
isRotated = true;
}
name = name.substring(Board.BOARD_REQUEST_ROTATION.length());
}
if (name.startsWith(MapSettings.BOARD_GENERATED) || (mapSettings.getMedium() == MapSettings.MEDIUM_SPACE)) {
sheetBoards[i] = BoardUtilities.generateRandom(mapSettings);
} else {
sheetBoards[i].load(name + ".board");
BoardUtilities.flip(sheetBoards[i], isRotated, isRotated);
}
}
IBoard newBoard = BoardUtilities.combine(mapSettings.getBoardWidth(), mapSettings.getBoardHeight(), mapSettings.getMapWidth(), mapSettings.getMapHeight(), sheetBoards, mapSettings.getMedium());
if (game.getOptions().getOption("bridgeCF").intValue() > 0) {
newBoard.setBridgeCF(game.getOptions().getOption("bridgeCF").intValue());
}
BoardUtilities.addWeatherConditions(newBoard, game.getPlanetaryConditions().getWeather(), game.getPlanetaryConditions().getWindStrength());
game.setBoard(newBoard);
}
/**
* Rolls initiative for all the players.
*/
private void rollInitiative() {
if (game.getOptions().booleanOption("individual_initiative")) {
TurnOrdered.rollInitiative(game.getEntitiesVector());
} else {
// Roll for initative on the teams.
TurnOrdered.rollInitiative(game.getTeamsVector());
}
transmitAllPlayerUpdates();
}
/**
* Determines the turn oder for a given phase (with individual init)
*
* @param phase
* the <code>int</code> id of the phase
*/
private void determineTurnOrderIUI(IGame.Phase phase) {
for (Enumeration<Entity> loop = game.getEntities(); loop.hasMoreElements();) {
final Entity entity = loop.nextElement();
entity.resetOtherTurns();
if (entity.isSelectableThisTurn()) {
entity.incrementOtherTurns();
}
}
// Now, generate the global order of all teams' turns.
TurnVectors team_order = TurnOrdered.generateTurnOrder(game.getEntitiesVector(), game);
// See if there are any loaded units stranded on immobile transports.
Enumeration<Entity> strandedUnits = game.getSelectedEntities(new EntitySelector() {
public boolean accept(Entity entity) {
if (game.isEntityStranded(entity)) {
return true;
}
return false;
}
});
// Now, we collect everything into a single vector.
Vector<GameTurn> turns;
if (strandedUnits.hasMoreElements() && (game.getPhase() == IGame.Phase.PHASE_MOVEMENT)) {
// Add a game turn to unload stranded units, if this
// is the movement phase.
turns = new Vector<GameTurn>(team_order.getTotalTurns() + team_order.getEvenTurns() + 1);
turns.addElement(new GameTurn.UnloadStrandedTurn(strandedUnits));
} else {
// No stranded units.
turns = new Vector<GameTurn>(team_order.getTotalTurns() + team_order.getEvenTurns());
}
// add the turns (this is easy)
while (team_order.hasMoreElements()) {
Entity e = (Entity) team_order.nextElement();
if (e.isSelectableThisTurn()) {
turns.addElement(new GameTurn.SpecificEntityTurn(e.getOwnerId(), e.getId()));
}
}
// set fields in game
game.setTurnVector(turns);
game.resetTurnIndex();
// send turns to all players
send(createTurnVectorPacket());
}
/**
* Determines the turn oder for a given phase
*
* @param phase
* the <code>int</code> id of the phase
*/
private void determineTurnOrder(IGame.Phase phase) {
if (game.getOptions().booleanOption("individual_initiative")) {
determineTurnOrderIUI(phase);
return;
}
// and/or deploy even according to game options.
boolean infMoveEven = (game.getOptions().booleanOption("inf_move_even") && ((game.getPhase() == IGame.Phase.PHASE_INITIATIVE) || (game.getPhase() == IGame.Phase.PHASE_MOVEMENT))) || (game.getOptions().booleanOption("inf_deploy_even") && (game.getPhase() == IGame.Phase.PHASE_DEPLOYMENT));
boolean infMoveMulti = game.getOptions().booleanOption("inf_move_multi") && ((game.getPhase() == IGame.Phase.PHASE_INITIATIVE) || (game.getPhase() == IGame.Phase.PHASE_MOVEMENT));
boolean protosMoveEven = (game.getOptions().booleanOption("protos_move_even") && ((game.getPhase() == IGame.Phase.PHASE_INITIATIVE) || (game.getPhase() == IGame.Phase.PHASE_MOVEMENT))) || (game.getOptions().booleanOption("protos_deploy_even") && (game.getPhase() == IGame.Phase.PHASE_DEPLOYMENT));
boolean protosMoveMulti = game.getOptions().booleanOption("protos_move_multi");
boolean protosMoveByPoint = !protosMoveMulti;
boolean tankMoveByLance = game.getOptions().booleanOption("vehicle_lance_movement") && ((game.getPhase() == IGame.Phase.PHASE_INITIATIVE) || (game.getPhase() == IGame.Phase.PHASE_MOVEMENT));
int evenMask = 0;
if (infMoveEven) {
evenMask += GameTurn.CLASS_INFANTRY;
}
if (protosMoveEven) {
evenMask += GameTurn.CLASS_PROTOMECH;
}
// Reset all of the Players' turn category counts
for (Enumeration<Player> loop = game.getPlayers(); loop.hasMoreElements();) {
final Player player = loop.nextElement();
player.resetEvenTurns();
player.resetMultiTurns();
player.resetOtherTurns();
player.resetSpaceStationTurns();
player.resetJumpshipTurns();
player.resetWarshipTurns();
player.resetDropshipTurns();
player.resetSmallCraftTurns();
// Add turns for protomechs weapons declaration.
if (protosMoveByPoint) {
// How many Protomechs does the player have?
Enumeration<Entity> playerProtos = game.getSelectedEntities(new EntitySelector() {
private final int ownerId = player.getId();
public boolean accept(Entity entity) {
if ((entity instanceof Protomech) && (ownerId == entity.getOwnerId()) && entity.isSelectableThisTurn()) {
return true;
}
return false;
}
});
HashSet<Integer> points = new HashSet<Integer>();
int numPlayerProtos = 0;
for (; playerProtos.hasMoreElements();) {
Entity proto = playerProtos.nextElement();
numPlayerProtos++;
points.add(new Integer(proto.getUnitNumber()));
}
int numProtoUnits = (int) Math.ceil(numPlayerProtos / 5.0);
if (!protosMoveEven) {
numProtoUnits = points.size();
}
for (int unit = 0; unit < numProtoUnits; unit++) {
if (protosMoveEven) {
player.incrementEvenTurns();
} else {
player.incrementOtherTurns();
}
}
} // End handle-proto-firing-turns
} // Handle the next player
// Go through all entities, and update the turn categories of the
// entity's player. The teams get their totals from their players.
// N.B. protomechs declare weapons fire based on their point.
for (Enumeration<Entity> loop = game.getEntities(); loop.hasMoreElements();) {
final Entity entity = loop.nextElement();
if (entity.isSelectableThisTurn()) {
final Player player = entity.getOwner();
if ((entity instanceof Infantry)) {
if (infMoveEven) {
player.incrementEvenTurns();
} else if (infMoveMulti) {
player.incrementMultiTurns();
} else {
player.incrementOtherTurns();
}
} else if (entity instanceof Protomech) {
if (!protosMoveByPoint) {
if (protosMoveEven) {
player.incrementEvenTurns();
} else if (protosMoveMulti) {
player.incrementMultiTurns();
} else {
player.incrementOtherTurns();
}
}
} else if ((entity instanceof Tank) && tankMoveByLance) {
player.incrementMultiTurns();
} else if ((entity instanceof SpaceStation) && (game.getPhase() == IGame.Phase.PHASE_MOVEMENT)) {
player.incrementSpaceStationTurns();
} else if ((entity instanceof Warship) && (game.getPhase() == IGame.Phase.PHASE_MOVEMENT)) {
player.incrementWarshipTurns();
} else if ((entity instanceof Jumpship) && (game.getPhase() == IGame.Phase.PHASE_MOVEMENT)) {
player.incrementJumpshipTurns();
} else if ((entity instanceof Dropship) && (game.getPhase() == IGame.Phase.PHASE_MOVEMENT)) {
player.incrementDropshipTurns();
} else if ((entity instanceof SmallCraft) && (game.getPhase() == IGame.Phase.PHASE_MOVEMENT)) {
player.incrementSmallCraftTurns();
} else {
player.incrementOtherTurns();
}
}
}
// Generate the turn order for the Players *within*
// each Team. Map the teams to their turn orders.
// Count the number of teams moving this turn.
int nTeams = game.getNoOfTeams();
Hashtable<Team, TurnVectors> allTeamTurns = new Hashtable<Team, TurnVectors>(nTeams);
Hashtable<Team, int[]> evenTrackers = new Hashtable<Team, int[]>(nTeams);
int numTeamsMoving = 0;
for (Enumeration<Team> loop = game.getTeams(); loop.hasMoreElements();) {
final Team team = loop.nextElement();
allTeamTurns.put(team, team.determineTeamOrder(game));
// Track both the number of times we've checked the team for
// "leftover" turns, and the number of "leftover" turns placed.
int[] evenTracker = new int[2];
evenTracker[0] = 0;
evenTracker[1] = 0;
evenTrackers.put(team, evenTracker);
// Count this team if it has any "normal" moves.
if (team.getNormalTurns(game) > 0) {
numTeamsMoving++;
}
}
// Now, generate the global order of all teams' turns.
TurnVectors team_order = TurnOrdered.generateTurnOrder(game.getTeamsVector(), game);
// See if there are any loaded units stranded on immobile transports.
Enumeration<Entity> strandedUnits = game.getSelectedEntities(new EntitySelector() {
public boolean accept(Entity entity) {
if (game.isEntityStranded(entity)) {
return true;
}
return false;
}
});
// Now, we collect everything into a single vector.
Vector<GameTurn> turns;
if (strandedUnits.hasMoreElements() && (game.getPhase() == IGame.Phase.PHASE_MOVEMENT)) {
// Add a game turn to unload stranded units, if this
// is the movement phase.
turns = new Vector<GameTurn>(team_order.getTotalTurns() + team_order.getEvenTurns() + 1);
turns.addElement(new GameTurn.UnloadStrandedTurn(strandedUnits));
} else {
// No stranded units.
turns = new Vector<GameTurn>(team_order.getTotalTurns() + team_order.getEvenTurns());
}
// Walk through the global order, assigning turns
// for individual players to the single vector.
// Keep track of how many turns we've added to the vector.
Team prevTeam = null;
int min = team_order.getMin();
for (int numTurn = 0; team_order.hasMoreElements(); numTurn++) {
Team team = (Team) team_order.nextElement();
TurnVectors withinTeamTurns = allTeamTurns.get(team);
int[] evenTracker = evenTrackers.get(team);
float teamEvenTurns = team.getEvenTurns();
// Calculate the number of "even" turns to add for this team.
int numEven = 0;
if (1 == numTeamsMoving) {
// The only team moving should move all "even" units.
numEven += teamEvenTurns;
} else if (prevTeam == null) {
// Increment the number of times we've checked for "leftovers".
evenTracker[0]++;
// The first team to move just adds the "baseline" turns.
numEven += teamEvenTurns / min;
} else if (!team.equals(prevTeam)) {
// Increment the number of times we've checked for "leftovers".
evenTracker[0]++;
// This wierd equation attempts to spread the "leftover"
// turns accross the turn's moves in a "fair" manner.
// It's based on the number of times we've checked for
// "leftovers" the number of "leftovers" we started with,
// the number of times we've added a turn for a "leftover",
// and the total number of times we're going to check.
numEven += Math.ceil(evenTracker[0] * (teamEvenTurns % min) / min - 0.5) - evenTracker[1];
// Update the number of turns actually added for "leftovers".
evenTracker[1] += numEven;
// Add the "baseline" number of turns.
numEven += teamEvenTurns / min;
}
// Record this team for the next move.
prevTeam = team;
if (withinTeamTurns.hasMoreSpaceStationElements()) {
Player player = (Player) withinTeamTurns.nextSpaceStationElement();
GameTurn turn = null;
turn = new GameTurn.EntityClassTurn(player.getId(), GameTurn.CLASS_SPACE_STATION);
turns.addElement(turn);
} else if (withinTeamTurns.hasMoreJumpshipElements()) {
Player player = (Player) withinTeamTurns.nextJumpshipElement();
GameTurn turn = null;
turn = new GameTurn.EntityClassTurn(player.getId(), GameTurn.CLASS_JUMPSHIP);
turns.addElement(turn);
} else if (withinTeamTurns.hasMoreWarshipElements()) {
Player player = (Player) withinTeamTurns.nextWarshipElement();
GameTurn turn = null;
turn = new GameTurn.EntityClassTurn(player.getId(), GameTurn.CLASS_WARSHIP);
turns.addElement(turn);
} else if (withinTeamTurns.hasMoreDropshipElements()) {
Player player = (Player) withinTeamTurns.nextDropshipElement();
GameTurn turn = null;
turn = new GameTurn.EntityClassTurn(player.getId(), GameTurn.CLASS_DROPSHIP);
turns.addElement(turn);
} else if (withinTeamTurns.hasMoreSmallCraftElements()) {
Player player = (Player) withinTeamTurns.nextSmallCraftElement();
GameTurn turn = null;
turn = new GameTurn.EntityClassTurn(player.getId(), GameTurn.CLASS_SMALL_CRAFT);
turns.addElement(turn);
}
// This may be a "placeholder" for a team without "normal" turns.
else if (withinTeamTurns.hasMoreElements()) {
// Not a placeholder... get the player who moves next.
Player player = (Player) withinTeamTurns.nextElement();
// If we've added all "normal" turns, allocate turns
// for the infantry and/or protomechs moving even.
GameTurn turn = null;
if (numTurn >= team_order.getTotalTurns()) {
turn = new GameTurn.EntityClassTurn(player.getId(), evenMask);
}
// If either Infantry or Protomechs move even, only allow
// the other classes to move during the "normal" turn.
else if (infMoveEven || protosMoveEven) {
turn = new GameTurn.EntityClassTurn(player.getId(), ~evenMask);
}
// Otherwise, let *anybody* move.
else {
turn = new GameTurn(player.getId());
}
turns.addElement(turn);
} // End team-has-"normal"-turns
// Add the calculated number of "even" turns.
// Allow the player at least one "normal" turn before the
// "even" turns to help with loading infantry in deployment.
while ((numEven > 0) && withinTeamTurns.hasMoreEvenElements()) {
Player evenPlayer = (Player) withinTeamTurns.nextEvenElement();
turns.addElement(new GameTurn.EntityClassTurn(evenPlayer.getId(), evenMask));
numEven--;
}
}
// set fields in game
game.setTurnVector(turns);
game.resetTurnIndex();
// send turns to all players
send(createTurnVectorPacket());
}
/**
* Write the initiative results to the report
*/
private void writeInitiativeReport(boolean abbreviatedReport) {
// write to report
Report r;
boolean deployment = false;
if (!abbreviatedReport) {
r = new Report(1210);
r.type = Report.PUBLIC;
if ((game.getLastPhase() == IGame.Phase.PHASE_DEPLOYMENT) || game.isDeploymentComplete() || !game.shouldDeployThisRound()) {
r.messageId = 1000;
r.add(game.getRoundCount());
} else {
deployment = true;
if (game.getRoundCount() == 0) {
r.messageId = 1005;
} else {
r.messageId = 1010;
r.add(game.getRoundCount());
}
}
addReport(r);
// write seperator
addReport(new Report(1200, Report.PUBLIC));
} else {
addReport(new Report(1210, Report.PUBLIC)); // newline
}
if (game.getOptions().booleanOption("individual_initiative")) {
r = new Report(1040, Report.PUBLIC);
addReport(r);
for (Enumeration<GameTurn> e = game.getTurns(); e.hasMoreElements();) {
GameTurn t = e.nextElement();
if (t instanceof GameTurn.SpecificEntityTurn) {
Entity entity = game.getEntity(((GameTurn.SpecificEntityTurn) t).getEntityNum());
r = new Report(1045);
r.subject = entity.getId();
r.addDesc(entity);
r.add(entity.getInitiative().toString());
addReport(r);
} else {
Player player = getPlayer(t.getPlayerNum());
if (null != player) {
r = new Report(1050, Report.PUBLIC);
r.add(player.getName());
addReport(r);
}
}
}
} else {
for (Enumeration<Team> i = game.getTeams(); i.hasMoreElements();) {
final Team team = i.nextElement();
// If there is only one player, list them as the 'team', and
// use the team iniative
if (team.getSize() == 1) {
final Player player = team.getPlayers().nextElement();
r = new Report(1015, Report.PUBLIC);
r.add(player.getName());
r.add(team.getInitiative().toString());
addReport(r);
} else {
// Multiple players. List the team, then break it down.
r = new Report(1015, Report.PUBLIC);
r.add(Player.teamNames[team.getId()]);
r.add(team.getInitiative().toString());
addReport(r);
for (Enumeration<Player> j = team.getPlayers(); j.hasMoreElements();) {
final Player player = j.nextElement();
r = new Report(1015, Report.PUBLIC);
r.indent();
r.add(player.getName());
r.add(player.getInitiative().toString());
addReport(r);
}
}
}
if (!doBlind()) {
// The turn order is different in movement phase
// if a player has any "even" moving units.
r = new Report(1020, Report.PUBLIC);
boolean hasEven = false;
for (Enumeration<GameTurn> i = game.getTurns(); i.hasMoreElements();) {
GameTurn turn = i.nextElement();
Player player = getPlayer(turn.getPlayerNum());
if (null != player) {
r.add(player.getName());
if (player.getEvenTurns() > 0) {
hasEven = true;
}
}
}
r.newlines = 2;
addReport(r);
if (hasEven) {
r = new Report(1021, Report.PUBLIC);
if ((game.getOptions().booleanOption("inf_deploy_even") || game.getOptions().booleanOption("protos_deploy_even")) && !(game.getLastPhase() == IGame.Phase.PHASE_END_REPORT)) {
r.choose(true);
} else {
r.choose(false);
}
r.indent();
r.newlines = 2;
addReport(r);
}
}
}
if (!abbreviatedReport) {
// Wind direction and strength
Report rWindDir = new Report(1025, Report.PUBLIC);
rWindDir.add(game.getPlanetaryConditions().getWindDirName());
rWindDir.newlines = 0;
Report rWindStr = new Report(1030, Report.PUBLIC);
rWindStr.add(game.getPlanetaryConditions().getWindCurrentName());
rWindStr.newlines = 0;
Report rWeather = new Report(1031, Report.PUBLIC);
rWeather.add(game.getPlanetaryConditions().getWeatherCurrentName());
rWeather.newlines = 0;
Report rLight = new Report(1032, Report.PUBLIC);
rLight.add(game.getPlanetaryConditions().getLightCurrentName());
addReport(rWindDir);
addReport(rWindStr);
addReport(rWeather);
addReport(rLight);
if (deployment) {
addNewLines();
}
}
}
/**
* Marks ineligible entities as not ready for this phase
*/
private void setIneligible(IGame.Phase phase) {
Vector<Entity> assistants = new Vector<Entity>();
boolean assistable = false;
if ( isPlayerForcedVictory() ){
assistants.addAll(game.getEntitiesVector());
}else {
for (Entity entity : game.getEntitiesVector()) {
if (entity.isEligibleFor(phase)) {
assistable = true;
} else {
assistants.addElement(entity);
}
}
}
for (Entity assistant : assistants) {
if (!assistable || !assistant.canAssist(phase)) {
assistant.setDone(true);
}
}
}
/**
* Have the loader load the indicated unit. The unit being loaded loses its
* turn.
*
* @param loader -
* the <code>Entity</code> that is loading the unit.
* @param unit -
* the <code>Entity</code> being loaded.
*/
private void loadUnit(Entity loader, Entity unit) {
if (!unit.isDone()) {
// Remove the *last* friendly turn (removing the *first* penalizes
// the opponent too much, and re-calculating moves is too hard).
game.removeTurnFor(unit);
send(createTurnVectorPacket());
}
// Load the unit.
loader.load(unit);
// The loaded unit is being carried by the loader.
unit.setTransportId(loader.getId());
// Remove the loaded unit from the screen.
unit.setPosition(null);
// Update the loaded unit.
entityUpdate(unit.getId());
}
/**
* Have the unloader unload the indicated unit. The unit being unloaded does
* *not* gain a turn.
*
* @param unloader -
* the <code>Entity</code> that is unloading the unit.
* @param unloaded -
* the <code>Targetable</code> unit being unloaded.
* @param pos -
* the <code>Coords</code> for the unloaded unit.
* @param facing -
* the <code>int</code> facing for the unloaded unit.
* @param elevation -
* the <code>int</code> elevation at which to unload, if both
* loader and loaded units use VTOL movement.
* @return <code>true</code> if the unit was successfully unloaded,
* <code>false</code> if the unit isn't carried in unloader.
*/
private boolean unloadUnit(Entity unloader, Targetable unloaded, Coords pos, int facing, int elevation) {
// We can only unload Entities.
Entity unit = null;
if (unloaded instanceof Entity) {
unit = (Entity) unloaded;
} else {
return false;
}
// Unload the unit.
if (!unloader.unload(unit)) {
return false;
}
// The unloaded unit is no longer being carried.
unit.setTransportId(Entity.NONE);
// Place the unloaded unit onto the screen.
unit.setPosition(pos);
// Units unloaded onto the screen are deployed.
if (pos != null) {
unit.setDeployed(true);
}
// Point the unloaded unit in the given direction.
unit.setFacing(facing);
unit.setSecondaryFacing(facing);
IHex hex = game.getBoard().getHex(pos);
boolean isBridge = hex.containsTerrain(Terrains.PAVEMENT);
if (unloader.getMovementMode() == IEntityMovementMode.VTOL) {
if (unit.getMovementMode() == IEntityMovementMode.VTOL) {
// Flying units onload to the same elevation as the flying
// transport
unit.setElevation(elevation);
} else if (game.getBoard().getBuildingAt(pos) != null) {
// non-flying unit onloaded from a flying onto a building
// -> sit on the roff
unit.setElevation(hex.terrainLevel(Terrains.BLDG_ELEV));
} else {
while (elevation >= -hex.depth()) {
if (unit.isElevationValid(elevation, hex)) {
unit.setElevation(elevation);
break;
}
elevation--;
unit.moved = IEntityMovementType.MOVE_JUMP;
}
if (!unit.isElevationValid(elevation, hex)) {
return false;
}
}
} else if (game.getBoard().getBuildingAt(pos) != null) {
// non flying unit unloading units into a building
// -> sit in the building at the same elevation
unit.setElevation(elevation);
} else if (hex.terrainLevel(Terrains.WATER) > 0) {
if ((unit.getMovementMode() == IEntityMovementMode.HOVER) || (unit.getMovementMode() == IEntityMovementMode.WIGE) || (unit.getMovementMode() == IEntityMovementMode.HYDROFOIL) || (unit.getMovementMode() == IEntityMovementMode.NAVAL) || (unit.getMovementMode() == IEntityMovementMode.SUBMARINE) || (unit.getMovementMode() == IEntityMovementMode.INF_UMU) || hex.containsTerrain(Terrains.ICE) || isBridge) {
// units that can float stay on the surface, or we go on the
// bridge
// this means elevation 0, because elevation is relative to the
// surface
unit.setElevation(0);
}
} else {
// default to the floor of the hex.
// unit elevation is relative to the surface
unit.setElevation(hex.floor() - hex.surface());
}
addReport(doSetLocationsExposure(unit, hex, false, unit.getElevation()));
// Update the unloaded unit.
entityUpdate(unit.getId());
// Unloaded successfully.
return true;
}
private boolean launchUnit(Entity unloader, Targetable unloaded, Coords pos, int facing, int velocity, int elevation, int[] moveVec, int bonus) {
Entity unit = null;
if (unloaded instanceof Entity) {
unit = (Entity) unloaded;
} else {
return false;
}
// must be an ASF or Small Craft
Aero a = new Aero();
if ((unit instanceof Aero) && !(unit instanceof Dropship) && !(unit instanceof Jumpship)) {
a = (Aero) unit;
} else {
return false;
}
Report r;
// Unload the unit.
if (!unloader.unload(unit)) {
return false;
}
// The unloaded unit is no longer being carried.
unit.setTransportId(Entity.NONE);
// Place the unloaded unit onto the screen.
unit.setPosition(pos);
// Units unloaded onto the screen are deployed.
if (pos != null) {
unit.setDeployed(true);
}
// Point the unloaded unit in the given direction.
unit.setFacing(facing);
unit.setSecondaryFacing(facing);
// the velocity of the unloadd unit is the same as the loader
a.setCurrentVelocity(velocity);
a.setNextVelocity(velocity);
// if using advanced movement then set vectors
a.setVectors(moveVec);
unit.setElevation(elevation);
// it seems that the done button is still being set and I can't figure
// out where
unit.setDone(false);
// if the bonus was greater than zero then too many fighters were
// launched and they
// must all make control rolls
if (bonus > 0) {
PilotingRollData psr = unit.getBasePilotingRoll();
psr.addModifier(bonus, "safe launch rate exceeded");
int ctrlroll = Compute.d6(2);
r = new Report(9375);
r.subject = unit.getId();
r.add(unit.getDisplayName());
r.add(psr.getValue());
r.add(ctrlroll);
r.newlines = 0;
r.indent(1);
if (ctrlroll < psr.getValue()) {
r.choose(false);
addReport(r);
// damage the unit
int damage = 10 * (psr.getValue() - ctrlroll);
HitData hit = a.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT);
addReport(damageEntity(unit, hit, damage));
// did we destroy the unit?
if (unit.isDoomed()) {
// Clean out the entity.
unit.setDestroyed(true);
game.moveToGraveyard(unit.getId());
send(createRemoveEntityPacket(unit.getId()));
}
} else {
// avoided damage
r.choose(true);
addReport(r);
}
}
// launching from an OOC vessel causes damage
// same thing if faster than 2 velocity in atmosphere
if ((((Aero) unloader).isOutControlTotal() && !unit.isDoomed()) || ((((Aero) unloader).getCurrentVelocity() > 2) && game.getBoard().inAtmosphere())) {
int damroll = Compute.d6(2);
int damage = damroll * 10;
r = new Report(9385);
r.subject = unit.getId();
r.add(unit.getDisplayName());
r.add(damage);
addReport(r);
HitData hit = a.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT);
addReport(damageEntity(unit, hit, damage));
// did we destroy the unit?
if (unit.isDoomed()) {
// Clean out the entity.
unit.setDestroyed(true);
game.moveToGraveyard(unit.getId());
send(createRemoveEntityPacket(unit.getId()));
}
}
// Update the unloaded unit.
entityUpdate(unit.getId());
return true;
}
/**
* Record that the given building has been affected by the current entity's
* movement. At the end of the entity's movement, notify the clients about
* the updates.
*
* @param bldg -
* the <code>Building</code> that has been affected.
* @param collapse -
* a <code>boolean</code> value that specifies that the
* building collapsed (when <code>true</code>).
*/
private void addAffectedBldg(Building bldg, boolean collapse) {
// If the building collapsed, then the clients have already
// been notified, so remove it from the notification list.
if (collapse) {
System.err.print("Removing building from a list of " + affectedBldgs.size() + '\n');// killme
affectedBldgs.remove(bldg);
System.err.print("... now list of " + affectedBldgs.size() + '\n');// killme
} else { // Otherwise, make sure that this building is tracked.
affectedBldgs.put(bldg, Boolean.FALSE);
}
}
/**
* Walk through the building hexes that were affected by the recent entity's
* movement. Notify the clients about the updates to all affected entities
* and uncollapsed buildings. The affected hexes is then cleared for the
* next entity's movement.
*/
private void applyAffectedBldgs() {
// Build a list of Building updates.
Vector<Building> bldgUpdates = new Vector<Building>();
// Only send a single turn update.
boolean bTurnsChanged = false;
// Walk the set of buildings.
Enumeration<Building> bldgs = affectedBldgs.keys();
while (bldgs.hasMoreElements()) {
final Building bldg = bldgs.nextElement();
// Walk through the building's coordinates.
Enumeration<Coords> bldgCoords = bldg.getCoords();
while (bldgCoords.hasMoreElements()) {
final Coords coords = bldgCoords.nextElement();
// Walk through the entities at these coordinates.
Enumeration<Entity> entities = game.getEntities(coords);
while (entities.hasMoreElements()) {
final Entity entity = entities.nextElement();
// Is the entity infantry?
if (entity instanceof Infantry) {
// Is the infantry dead?
if (entity.isDoomed() || entity.isDestroyed()) {
// Has the entity taken a turn?
if (!entity.isDone()) {
// Dead entities don't take turns.
game.removeTurnFor(entity);
bTurnsChanged = true;
} // End entity-still-to-move
// Clean out the dead entity.
entity.setDestroyed(true);
game.moveToGraveyard(entity.getId());
send(createRemoveEntityPacket(entity.getId()));
}
// Infantry that aren't dead are damaged.
else {
entityUpdate(entity.getId());
}
} // End entity-is-infantry
} // Check the next entity.
} // Handle the next hex in this building.
// Add this building to the report.
bldgUpdates.addElement(bldg);
} // Handle the next affected building.
// Did we update the turns?
if (bTurnsChanged) {
send(createTurnVectorPacket());
}
// Are there any building updates?
if (!bldgUpdates.isEmpty()) {
// Send the building updates to the clients.
sendChangedCFBuildings(bldgUpdates);
// Clear the list of affected buildings.
affectedBldgs.clear();
}
// And we're done.
} // End private void applyAffectedBldgs()
/**
* Receives an entity movement packet, and if valid, executes it and ends
* the current turn.
*/
private void receiveMovement(Packet packet, int connId) {
Entity entity = game.getEntity(packet.getIntValue(0));
MovePath md = (MovePath) packet.getObject(1);
// is this the right phase?
if (game.getPhase() != IGame.Phase.PHASE_MOVEMENT) {
System.err.println("error: server got movement packet in wrong phase");
return;
}
// can this player/entity act right now?
if (!game.getTurn().isValid(connId, entity, game)) {
System.err.println("error: server got invalid movement packet");
return;
}
// looks like mostly everything's okay
processMovement(entity, md);
// check the LOS of any telemissiles owned by this entity
for (int missileId : entity.getTMTracker().getMissiles()) {
Entity tm = game.getEntity(missileId);
if ((null != tm) && !tm.isDestroyed() && (tm instanceof TeleMissile)) {
if (LosEffects.calculateLos(game, entity.getId(), tm).canSee()) {
((TeleMissile) tm).setOutContact(false);
} else {
((TeleMissile) tm).setOutContact(true);
}
entityUpdate(tm.getId());
}
}
// Notify the clients about any building updates.
applyAffectedBldgs();
// Update visibility indications if using double blind.
if (doBlind()) {
updateVisibilityIndicator();
}
// This entity's turn is over.
// N.B. if the entity fell, a *new* turn has already been added.
endCurrentTurn(entity);
}
/**
* makes a unit skid or sideslip on the board
*
* @param entity
* the unit which should skid
* @param start
* the coordinates of the hex the unit was in prior to skidding
* @param elevation
* the elevation of the unit
* @param direction
* the direction of the skid
* @param distance
* the number of hexes skidded
* @param step
* the MoveStep which caused the skid
* @return true if the entity was removed from play
*/
private boolean processSkid(Entity entity, Coords start, int elevation, int direction, int distance, MoveStep step) {
Coords nextPos = start;
Coords curPos = nextPos;
IHex curHex = game.getBoard().getHex(start);
Report r;
int skidDistance = 0; // actual distance moved
ArrayList<Entity> avoidedChargeUnits = new ArrayList<Entity>();
while (!entity.isDoomed() && (distance > 0)) {
nextPos = curPos.translated(direction);
// Is the next hex off the board?
if (!game.getBoard().contains(nextPos)) {
// Can the entity skid off the map?
if (game.getOptions().booleanOption("push_off_board")) {
// Yup. One dead entity.
game.removeEntity(entity.getId(), IEntityRemovalConditions.REMOVE_PUSHED);
send(createRemoveEntityPacket(entity.getId(), IEntityRemovalConditions.REMOVE_PUSHED));
r = new Report(2030, Report.PUBLIC);
r.addDesc(entity);
addReport(r);
for (Entity e : entity.getLoadedUnits()) {
game.removeEntity(e.getId(), IEntityRemovalConditions.REMOVE_PUSHED);
send(createRemoveEntityPacket(e.getId(), IEntityRemovalConditions.REMOVE_PUSHED));
}
Entity swarmer = game.getEntity(entity.getSwarmAttackerId());
if (swarmer != null) {
if (!swarmer.isDone()) {
swarmer.setDone(true);
game.removeTurnFor(swarmer);
send(createTurnVectorPacket());
}
game.removeEntity(swarmer.getId(), IEntityRemovalConditions.REMOVE_PUSHED);
send(createRemoveEntityPacket(swarmer.getId(), IEntityRemovalConditions.REMOVE_PUSHED));
}
// The entity's movement is completed.
return true;
}
// Nope. Update the report.
r = new Report(2035);
r.subject = entity.getId();
r.indent();
addReport(r);
// Stay in the current hex and stop skidding.
break;
}
IHex nextHex = game.getBoard().getHex(nextPos);
distance -= nextHex.movementCost(entity.getMovementMode()) + 1;
// By default, the unit is going to fall to the floor of the next
// hex
int curAltitude = elevation + curHex.getElevation();
int nextAltitude = nextHex.floor();
// but VTOL keep altitude
if (entity.getMovementMode() == IEntityMovementMode.VTOL) {
nextAltitude = Math.max(nextAltitude, curAltitude);
} else {
// Is there a building to "catch" the unit?
if (nextHex.containsTerrain(Terrains.BLDG_ELEV)) {
// unit will land on the roof, if at a higher level,
// otherwise it will skid through the wall onto the same
// floor.
nextAltitude = Math.min(curAltitude, nextHex.getElevation() + nextHex.terrainLevel(Terrains.BLDG_ELEV));
}
// Is there a bridge to "catch" the unit?
if (nextHex.containsTerrain(Terrains.BRIDGE)) {
// unit will land on the bridge, if at a higher level,
// and the bridge exits towards the current hex,
// otherwise the bridge has no effect
int exitDir = (direction + 3) % 6;
exitDir = 1 << exitDir;
if ((nextHex.getTerrain(Terrains.BRIDGE).getExits() & exitDir) == exitDir) {
nextAltitude = Math.min(curAltitude, Math.max(nextAltitude, nextHex.getElevation() + nextHex.terrainLevel(Terrains.BRIDGE_ELEV)));
}
}
if ((nextAltitude <= nextHex.surface()) && (curAltitude >= curHex.surface())) {
// Hovercraft can "skid" over water.
// all units can skid over ice.
if ((entity instanceof Tank) && ((entity.getMovementMode() == IEntityMovementMode.HOVER) || (entity.getMovementMode() == IEntityMovementMode.WIGE))) {
if (nextHex.containsTerrain(Terrains.WATER)) {
nextAltitude = nextHex.surface();
}
} else {
if (nextHex.containsTerrain(Terrains.ICE)) {
nextAltitude = nextHex.surface();
}
}
}
}
// The elevation the skidding unit will occupy in next hex
int nextElevation = nextAltitude - nextHex.surface();
boolean crashedIntoTerrain = curAltitude < nextAltitude;
if (entity.getMovementMode() == IEntityMovementMode.VTOL) {
if ((nextElevation == 0) || ((nextElevation == 1) && (nextHex.containsTerrain(Terrains.WOODS) || nextHex.containsTerrain(Terrains.JUNGLE)))) {
crashedIntoTerrain = true;
}
}
if (nextHex.containsTerrain(Terrains.BLDG_ELEV)) {
Building bldg = game.getBoard().getBuildingAt(nextPos);
if (bldg.getType() == Building.WALL) {
crashedIntoTerrain = true;
}
}
// however WIGE can gain 1 level to avoid crashing into the terrain
if (entity.getMovementMode() == IEntityMovementMode.WIGE) {
if ((nextElevation == 0) && !(nextHex.containsTerrain(Terrains.WOODS) || nextHex.containsTerrain(Terrains.JUNGLE))) {
nextElevation = 1;
crashedIntoTerrain = false;
} else if ((nextElevation == 1) && (nextHex.containsTerrain(Terrains.WOODS) || nextHex.containsTerrain(Terrains.JUNGLE))) {
nextElevation = 2;
crashedIntoTerrain = false;
}
}
if (crashedIntoTerrain) {
if (nextHex.containsTerrain(Terrains.BLDG_ELEV)) {
Building bldg = game.getBoard().getBuildingAt(nextPos);
// If you crash into a wall you want to stop in the hex
// before the wall not in the wall
// Like a building.
if (bldg.getType() == Building.WALL) {
r = new Report(2047);
} else {
r = new Report(2045);
}
} else {
r = new Report(2045);
}
r.subject = entity.getId();
r.indent();
r.add(nextPos.getBoardNum(), true);
addReport(r);
if ((entity.getMovementMode() == IEntityMovementMode.WIGE) || (entity.getMovementMode() == IEntityMovementMode.VTOL)) {
int hitSide = step.getFacing() - direction + 6;
hitSide %= 6;
int table = 0;
switch (hitSide) {// quite hackish...I think it ought to
// work, though.
case 0:// can this happen?
table = ToHitData.SIDE_FRONT;
break;
case 1:
case 2:
table = ToHitData.SIDE_LEFT;
break;
case 3:
table = ToHitData.SIDE_REAR;
break;
case 4:
case 5:
table = ToHitData.SIDE_RIGHT;
break;
}
elevation = nextElevation;
addReport(crashVTOLorWiGE((VTOL) entity, true, distance, curPos, elevation, table));
if ((nextHex.containsTerrain(Terrains.WATER) && !nextHex.containsTerrain(Terrains.ICE)) || nextHex.containsTerrain(Terrains.WOODS) || nextHex.containsTerrain(Terrains.JUNGLE)) {
addReport(destroyEntity(entity, "could not land in crash site"));
} else if (elevation < nextHex.terrainLevel(Terrains.BLDG_ELEV)) {
Building bldg = game.getBoard().getBuildingAt(nextPos);
// If you crash into a wall you want to stop in the hex
// before the wall not in the wall
// Like a building.
if (bldg.getType() == Building.WALL) {
addReport(destroyEntity(entity, "crashed into a wall"));
break;
}
addReport(destroyEntity(entity, "crashed into building"));
} else {
entity.setPosition(nextPos);
entity.setElevation(0);
addReport(doEntityDisplacementMinefieldCheck(entity, curPos, nextPos, nextElevation));
}
curPos = nextPos;
break;
}
// skidding into higher terrain does weight/20
// damage in 5pt clusters to front.
int damage = ((int) entity.getWeight() + 19) / 20;
while (damage > 0) {
addReport(damageEntity(entity, entity.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT), Math.min(5, damage)));
damage -= 5;
}
// Stay in the current hex and stop skidding.
break;
}
// Have skidding units suffer falls (off a cliff).
else if (curAltitude > nextAltitude + entity.getMaxElevationChange()) {
// WIGE can avoid this too, if they have 2MP to spend
if ((entity.getMovementMode() == IEntityMovementMode.WIGE) && (entity.getRunMP() - 2 >= entity.mpUsed)) {
entity.mpUsed += 2;
nextAltitude = curAltitude;
} else {
addReport(doEntityFallsInto(entity, curPos, nextPos, entity.getBasePilotingRoll(step.getMovementType())));
addReport(doEntityDisplacementMinefieldCheck(entity, curPos, nextPos, nextElevation));
// Stay in the current hex and stop skidding.
break;
}
}
// Get any building in the hex.
Building bldg = null;
if (nextElevation < nextHex.terrainLevel(Terrains.BLDG_ELEV)) {
// We will only run into the building if its at a higher level,
// otherwise we skid over the roof
bldg = game.getBoard().getBuildingAt(nextPos);
}
boolean bldgSuffered = false;
boolean stopTheSkid = false;
// Does the next hex contain an entities?
// ASSUMPTION: hurt EVERYONE in the hex.
Enumeration<Entity> targets = game.getEntities(nextPos);
if (targets.hasMoreElements()) {
boolean skidChargeHit = false;
while (targets.hasMoreElements()) {
Entity target = targets.nextElement();
if ((target.getElevation() > nextElevation + entity.getHeight()) || (target.absHeight() < nextElevation)) {
// target is not in the way
continue;
}
// Can the target avoid the skid?
if (!target.isDone()) {
if (target instanceof Infantry) {
r = new Report(2420);
r.subject = target.getId();
r.addDesc(target);
addReport(r);
continue;
} else if (target instanceof Protomech) {
if (target != Compute.stackingViolation(game, entity, nextPos, null)) {
r = new Report(2420);
r.subject = target.getId();
r.addDesc(target);
addReport(r);
continue;
}
} else {
PilotingRollData psr = target.getBasePilotingRoll();
psr.addModifier(0, "avoiding collision");
int roll = Compute.d6(2);
r = new Report(2425);
r.subject = target.getId();
r.addDesc(target);
r.add(psr.getValue());
r.add(psr.getDesc());
r.add(roll);
addReport(r);
if (roll >= psr.getValue()) {
game.removeTurnFor(target);
avoidedChargeUnits.add(target);
continue;
// TODO: the charge should really be suspended
// and resumed after the target moved.
}
}
}
// Mechs and vehicles get charged,
// but need to make a to-hit roll
if ((target instanceof Mech) || (target instanceof Tank)) {
ChargeAttackAction caa = new ChargeAttackAction(entity.getId(), target.getTargetType(), target.getTargetId(), target.getPosition());
ToHitData toHit = caa.toHit(game, true);
// roll
int roll = Compute.d6(2);
// Update report.
r = new Report(2050);
r.subject = entity.getId();
r.indent();
r.add(target.getShortName(), true);
r.add(nextPos.getBoardNum(), true);
r.newlines = 0;
addReport(r);
if (toHit.getValue() == TargetRoll.IMPOSSIBLE) {
roll = -12;
r = new Report(2055);
r.subject = entity.getId();
r.add(toHit.getDesc());
r.newlines = 0;
addReport(r);
} else if (toHit.getValue() == TargetRoll.AUTOMATIC_SUCCESS) {
r = new Report(2060);
r.subject = entity.getId();
r.add(toHit.getDesc());
r.newlines = 0;
addReport(r);
roll = Integer.MAX_VALUE;
} else {
// report the roll
r = new Report(2065);
r.subject = entity.getId();
r.add(toHit.getValue());
r.add(roll);
r.newlines = 0;
addReport(r);
}
// Resolve a charge against the target.
// ASSUMPTION: buildings block damage for
// *EACH* entity charged.
if (roll < toHit.getValue()) {
r = new Report(2070);
r.subject = entity.getId();
addReport(r);
} else {
// Resolve the charge.
resolveChargeDamage(entity, target, toHit, direction);
// HACK: set the entity's location
// to the original hex again, for the other targets
if (targets.hasMoreElements()) {
entity.setPosition(curPos);
}
bldgSuffered = true;
skidChargeHit = true;
// The skid ends here if the target lives.
if (!target.isDoomed() && !target.isDestroyed() && !game.isOutOfGame(target)) {
stopTheSkid = true;
}
}
// if we don't do this here,
// we can have a mech without a leg
// standing on the field and moving
// as if it still had his leg after
// getting skid-charged.
if (!target.isDone()) {
addReport(resolvePilotingRolls(target));
game.resetPSRs(target);
target.applyDamage();
addNewLines();
}
}
// Resolve "move-through" damage on infantry.
// Infantry inside of a building don't get a
// move-through, but suffer "bleed through"
// from the building.
else if ((target instanceof Infantry) && (bldg != null)) {
// Update report.
r = new Report(2075);
r.subject = entity.getId();
r.indent();
r.add(target.getShortName(), true);
r.add(nextPos.getBoardNum(), true);
r.newlines = 0;
addReport(r);
// Infantry don't have different
// tables for punches and kicks
HitData hit = target.rollHitLocation(ToHitData.HIT_NORMAL, Compute.targetSideTable(entity, target));
hit.setGeneralDamageType(HitData.DAMAGE_PHYSICAL);
// Damage equals tonnage, divided by 5.
// ASSUMPTION: damage is applied in one hit.
addReport(damageEntity(target, hit, Math.round(entity.getWeight() / 5)));
addNewLines();
}
// Has the target been destroyed?
if (target.isDoomed()) {
// Has the target taken a turn?
if (!target.isDone()) {
// Dead entities don't take turns.
game.removeTurnFor(target);
send(createTurnVectorPacket());
} // End target-still-to-move
// Clean out the entity.
target.setDestroyed(true);
game.moveToGraveyard(target.getId());
send(createRemoveEntityPacket(target.getId()));
}
// Update the target's position,
// unless it is off the game map.
if (!game.isOutOfGame(target)) {
entityUpdate(target.getId());
}
} // Check the next entity in the hex.
if (skidChargeHit) {
// HACK: set the entities position to that
// hex's coords, because we had to move the entity
// back earlier for the other targets
entity.setPosition(nextPos);
}
for (Entity e : avoidedChargeUnits) {
GameTurn newTurn = new GameTurn.SpecificEntityTurn(e.getOwner().getId(), e.getId());
game.insertNextTurn(newTurn);
send(createTurnVectorPacket());
}
}
// Handle the building in the hex.
if (bldg != null) {
// Report that the entity has entered the bldg.
r = new Report(2080);
r.subject = entity.getId();
r.indent();
r.add(bldg.getName());
r.add(nextPos.getBoardNum(), true);
addReport(r);
// If the building hasn't already suffered
// damage, then apply charge damage to the
// building and displace the entity inside.
// ASSUMPTION: you don't charge the building
// if Tanks or Mechs were charged.
int chargeDamage = ChargeAttackAction.getDamageFor(entity, game.getOptions().booleanOption("tacops_charge_damage"), entity.delta_distance);
if (!bldgSuffered) {
Vector<Report> reports = damageBuilding(bldg, chargeDamage, nextPos);
for (Report report : reports) {
report.subject = entity.getId();
}
addReport(reports);
// Apply damage to the attacker.
int toAttacker = ChargeAttackAction.getDamageTakenBy(entity, bldg, nextPos);
HitData hit = entity.rollHitLocation(ToHitData.HIT_NORMAL, entity.sideTable(nextPos));
hit.setGeneralDamageType(HitData.DAMAGE_PHYSICAL);
addReport(damageEntity(entity, hit, toAttacker));
addNewLines();
entity.setPosition(nextPos);
entity.setElevation(nextElevation);
addReport(doEntityDisplacementMinefieldCheck(entity, curPos, nextPos, nextElevation));
curPos = nextPos;
} // End buildings-suffer-too
// Any infantry in the building take damage
// equal to the building being charged.
// ASSUMPTION: infantry take no damage from the
// building absorbing damage from
// Tanks and Mechs being charged.
damageInfantryIn(bldg, chargeDamage, nextPos);
// If a building still stands, then end the skid,
// and add it to the list of affected buildings.
if (bldg.getCurrentCF(nextPos) > 0) {
stopTheSkid = true;
addAffectedBldg(bldg, false);
} else {
// otherwise it collapses immediately on our head
checkForCollapse(bldg, game.getPositionMap(), nextPos, true);
}
} // End handle-building.
// Do we stay in the current hex and stop skidding?
if (stopTheSkid) {
break;
}
// Update entity position and elevation
entity.setPosition(nextPos);
entity.setElevation(nextElevation);
addReport(doEntityDisplacementMinefieldCheck(entity, curPos, nextPos, nextElevation));
skidDistance++;
// Check for collapse of any building the entity might be on
Building roof = game.getBoard().getBuildingAt(nextPos);
if (roof != null) {
if (checkForCollapse(roof, game.getPositionMap(), nextPos, true)) {
break; // stop skidding if the building collapsed
}
}
// Can the skiding entity enter the next hex from this?
// N.B. can skid along roads.
if ((entity.isHexProhibited(curHex) || entity.isHexProhibited(nextHex)) && !Compute.canMoveOnPavement(game, curPos, nextPos, step.getParentUpToThisStep())) {
// Update report.
r = new Report(2040);
r.subject = entity.getId();
r.indent();
r.add(nextPos.getBoardNum(), true);
addReport(r);
// If the prohibited terrain is water, entity is destroyed
if ((nextHex.terrainLevel(Terrains.WATER) > 0) && (entity instanceof Tank) && (entity.getMovementMode() != IEntityMovementMode.HOVER) && (entity.getMovementMode() != IEntityMovementMode.WIGE)) {
addReport(destroyEntity(entity, "skidded into a watery grave", false, true));
}
// otherwise, damage is weight/5 in 5pt clusters
int damage = ((int) entity.getWeight() + 4) / 5;
while (damage > 0) {
addReport(damageEntity(entity, entity.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT), Math.min(5, damage)));
damage -= 5;
}
// and unit is immobile
if (entity instanceof Tank) {
((Tank) entity).immobilize();
}
// Stay in the current hex and stop skidding.
break;
}
if ((nextHex.terrainLevel(Terrains.WATER) > 0) && (entity.getMovementMode() != IEntityMovementMode.HOVER) && (entity.getMovementMode() != IEntityMovementMode.WIGE)) {
// water ends the skid
break;
}
// check for breaking magma crust
if ((nextHex.terrainLevel(Terrains.MAGMA) == 1) && (nextElevation == 0)) {
int roll = Compute.d6(1);
r = new Report(2395);
r.addDesc(entity);
r.add(roll);
r.subject = entity.getId();
addReport(r);
if (roll == 6) {
nextHex.removeTerrain(Terrains.MAGMA);
nextHex.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.MAGMA, 2));
sendChangedHex(curPos);
for (Enumeration<Entity> e = game.getEntities(curPos); e.hasMoreElements();) {
Entity en = e.nextElement();
if (en != entity) {
doMagmaDamage(en, false);
}
}
}
}
// check for entering liquid magma
if ((nextHex.terrainLevel(Terrains.MAGMA) == 2) && (nextElevation == 0)) {
doMagmaDamage(entity, false);
}
// is the next hex a swamp?
PilotingRollData rollTarget = entity.checkBogDown(step, nextHex, curPos, nextPos, step.getElevation(), Compute.canMoveOnPavement(game, curPos, nextPos, step.getParentUpToThisStep()));
if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) {
//Taharqa: According to TacOps, you automatically stick if you are skidding, (pg. 63)
//if (0 < doSkillCheckWhileMoving(entity, curPos, nextPos, rollTarget, false)) {
entity.setStuck(true);
r = new Report(2081);
r.subject = entity.getId();
r.add(entity.getDisplayName(), true);
addReport(r);
//check for quicksand
addReport (checkQuickSand(nextPos));
// check for accidental stacking violation
Entity violation = Compute.stackingViolation(game, entity.getId(), curPos);
if (violation != null) {
// target gets displaced, because of low elevation
Coords targetDest = Compute.getValidDisplacement(game, entity.getId(), curPos, direction);
addReport(doEntityDisplacement(violation, curPos, targetDest, new PilotingRollData(violation.getId(), 0, "domino effect")));
// Update the violating entity's postion on the client.
entityUpdate(violation.getId());
}
// stay here and stop skidding, see bug 1115608
break;
// }
}
// Update the position and keep skidding.
curPos = nextPos;
curHex = nextHex;
r = new Report(2085);
r.subject = entity.getId();
r.indent();
r.add(curPos.getBoardNum(), true);
addReport(r);
} // Handle the next skid hex.
// If the skidding entity violates stacking,
// displace targets until it doesn't.
curPos = entity.getPosition();
Entity target = Compute.stackingViolation(game, entity.getId(), curPos);
while (target != null) {
nextPos = Compute.getValidDisplacement(game, target.getId(), target.getPosition(), direction);
// ASSUMPTION
// There should always be *somewhere* that
// the target can go... last skid hex if
// nothing else is available.
if (null == nextPos) {
// But I don't trust the assumption fully.
// Report the error and try to continue.
System.err.println("The skid of " + entity.getShortName() + " should displace " + target.getShortName() + " in hex " + curPos.getBoardNum() + " but there is nowhere to go.");
break;
}
// indent displacement
r = new Report(1210, Report.PUBLIC);
r.indent();
r.newlines = 0;
addReport(r);
addReport(doEntityDisplacement(target, curPos, nextPos, null));
addReport(doEntityDisplacementMinefieldCheck(entity, curPos, nextPos, entity.getElevation()));
target = Compute.stackingViolation(game, entity.getId(), curPos);
}
// Mechs suffer damage for every hex skidded.
if (entity instanceof Mech) {
// Calculate one half falling damage times skid length.
int damage = skidDistance * (int) Math.ceil(Math.round(entity.getWeight() / 10.0) / 2.0);
// report skid damage
r = new Report(2090);
r.subject = entity.getId();
r.indent();
r.addDesc(entity);
r.add(damage);
addReport(r);
// standard damage loop
// All skid damage is to the front.
while (damage > 0) {
int cluster = Math.min(5, damage);
HitData hit = entity.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT);
hit.setGeneralDamageType(HitData.DAMAGE_PHYSICAL);
addReport(damageEntity(entity, hit, cluster));
damage -= cluster;
}
addNewLines();
}
// Clean up the entity if it has been destroyed.
if (entity.isDoomed()) {
entity.setDestroyed(true);
game.moveToGraveyard(entity.getId());
send(createRemoveEntityPacket(entity.getId()));
// The entity's movement is completed.
return true;
}
// Let the player know the ordeal is over.
r = new Report(2095);
r.subject = entity.getId();
r.indent();
addReport(r);
return false;
}
/**
* processes a potential collision
*
* @param entity
* @param target
* @param src
* @return
*/
private boolean processCollision(Entity entity, Entity target, Coords src) {
Report r;
r = new Report(9035);
r.subject = entity.getId();
r.add(entity.getDisplayName());
r.add(target.getDisplayName());
addReport(r);
int partialroll = Compute.d6(1);
boolean partial = (partialroll == 6);
// has the target moved yet this round?
Aero ta = (Aero) target;
if ((target.mpUsed < target.getRunMPwithoutMASC()) && !ta.isOutControlTotal() && !target.isImmobile()) {
// give them a control roll to avoid the collision
// TODO: I should make this voluntary really
PilotingRollData psr = target.getBasePilotingRoll();
psr.addModifier(0, "avoiding collision");
int ctrlroll = Compute.d6(2);
r = new Report(9045);
r.subject = target.getId();
r.add(target.getDisplayName());
r.add(ctrlroll);
r.newlines = 0;
r.indent(2);
if (ctrlroll < psr.getValue()) {
r.choose(false);
addReport(r);
} else {
// avoided collision
r.choose(true);
addReport(r);
// two possibilities:
// 1) the target already moved, but had MP left - check for
// control roll conditions
// 2) the target had not yet moved, move them in straight line
if (!target.isDone()) {
int vel = ta.getCurrentVelocity();
MovePath md = new MovePath(game, target);
while (vel > 0) {
md.addStep(MovePath.STEP_FORWARDS);
vel--;
}
game.removeTurnFor(target);
send(createTurnVectorPacket());
processMovement(target, md);
// for some reason it is not clearing out turn
} else {
// what needs to get checked?
// this move puts them at over-thrust
target.moved = IEntityMovementType.MOVE_OVER_THRUST;
// they may have exceeded SI, only add if they hadn't
// exceeded it before
if (target.mpUsed <= ta.getSI()) {
PilotingRollData rollTarget = ta.checkThrustSITotal(target.getRunMPwithoutMASC(), ta.moved);
if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) {
game.addControlRoll(new PilotingRollData(ta.getId(), 0, "Thrust spent during turn exceeds SI"));
}
}
target.mpUsed = target.getRunMPwithoutMASC();
}
return false;
}
} else {
// can't avoid collision - write report
r = new Report(9040);
r.subject = entity.getId();
r.add(entity.getDisplayName());
r.indent(2);
addReport(r);
}
// if we are still here, then collide
ToHitData toHit = new ToHitData(TargetRoll.AUTOMATIC_SUCCESS, "Its a collision");
toHit.setSideTable(target.sideTable(src));
resolveRamDamage((Aero) entity, (Aero) target, toHit, partial, false);
// Has the target been destroyed?
if (target.isDoomed()) {
// Has the target taken a turn?
if (!target.isDone()) {
// Dead entities don't take turns.
game.removeTurnFor(target);
send(createTurnVectorPacket());
} // End target-still-to-move
// Clean out the entity.
target.setDestroyed(true);
game.moveToGraveyard(target.getId());
send(createRemoveEntityPacket(target.getId()));
}
// Update the target's position,
// unless it is off the game map.
if (!game.isOutOfGame(target)) {
entityUpdate(target.getId());
}
return true;
}
private Vector<Report> processCrash(Entity entity, int vel) {
Vector<Report> vReport = new Vector<Report>();
Report r;
if (vel < 1) {
vel = 1;
}
int crash_damage = Compute.d6(2) * 10 * vel;
r = new Report(9392, Report.PUBLIC);
r.indent();
r.addDesc(entity);
r.add(crash_damage);
r.newlines = 0;
vReport.add(r);
while (crash_damage > 0) {
HitData hit = entity.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT);
if (entity.getMovementMode() == IEntityMovementMode.SPHEROID) {
hit = entity.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_REAR);
}
if (crash_damage > 10) {
vReport.addAll(damageEntity(entity, hit, 10));
} else {
vReport.addAll(damageEntity(entity, hit, crash_damage));
}
crash_damage -= 10;
}
// if the entity survived they are useless anyway because we have no
// ground map yet so remove them
if (!entity.isDoomed() && !entity.isDestroyed()) {
r = new Report(9393, Report.PUBLIC);
r.indent();
r.addDesc(entity);
vReport.add(r);
entity.setDoomed(true);
}
return vReport;
}
/**
* Steps through an entity movement packet, executing it.
*/
private void processMovement(Entity entity, MovePath md) {
Report r;
boolean sideslipped = false; // for VTOL sideslipping
// check for fleeing
if (md.contains(MovePath.STEP_FLEE)) {
// Unit has fled the battlefield.
r = new Report(2005, Report.PUBLIC);
r.addDesc(entity);
addReport(r);
Coords pos = entity.getPosition();
int fleeDirection;
if (pos.x == 0) {
fleeDirection = IOffBoardDirections.WEST;
} else if (pos.y == 0) {
fleeDirection = IOffBoardDirections.SOUTH;
} else if (pos.x == game.getBoard().getWidth()) {
fleeDirection = IOffBoardDirections.EAST;
} else {
fleeDirection = IOffBoardDirections.NORTH;
}
// Is the unit carrying passengers?
final Vector<Entity> passengers = entity.getLoadedUnits();
if (!passengers.isEmpty()) {
for (Entity passenger : passengers) {
// Unit has fled the battlefield.
r = new Report(2010, Report.PUBLIC);
r.indent();
r.addDesc(passenger);
addReport(r);
passenger.setRetreatedDirection(fleeDirection);
game.removeEntity(passenger.getId(), IEntityRemovalConditions.REMOVE_IN_RETREAT);
send(createRemoveEntityPacket(passenger.getId(), IEntityRemovalConditions.REMOVE_IN_RETREAT));
}
}
// Handle any picked up MechWarriors
for (Integer mechWarriorId : entity.getPickedUpMechWarriors()) {
Entity mw = game.getEntity(mechWarriorId.intValue());
// Is the MechWarrior an enemy?
int condition = IEntityRemovalConditions.REMOVE_IN_RETREAT;
r = new Report(2010);
if (mw.isCaptured()) {
r = new Report(2015);
condition = IEntityRemovalConditions.REMOVE_CAPTURED;
} else {
mw.setRetreatedDirection(fleeDirection);
}
game.removeEntity(mw.getId(), condition);
send(createRemoveEntityPacket(mw.getId(), condition));
r.addDesc(mw);
r.indent();
addReport(r);
}
// Is the unit being swarmed?
final int swarmerId = entity.getSwarmAttackerId();
if (Entity.NONE != swarmerId) {
final Entity swarmer = game.getEntity(swarmerId);
// Has the swarmer taken a turn?
if (!swarmer.isDone()) {
// Dead entities don't take turns.
game.removeTurnFor(swarmer);
send(createTurnVectorPacket());
} // End swarmer-still-to-move
// Unit has fled the battlefield.
swarmer.setSwarmTargetId(Entity.NONE);
entity.setSwarmAttackerId(Entity.NONE);
r = new Report(2015, Report.PUBLIC);
r.indent();
r.addDesc(swarmer);
addReport(r);
game.removeEntity(swarmerId, IEntityRemovalConditions.REMOVE_CAPTURED);
send(createRemoveEntityPacket(swarmerId, IEntityRemovalConditions.REMOVE_CAPTURED));
}
entity.setRetreatedDirection(fleeDirection);
game.removeEntity(entity.getId(), IEntityRemovalConditions.REMOVE_IN_RETREAT);
send(createRemoveEntityPacket(entity.getId(), IEntityRemovalConditions.REMOVE_IN_RETREAT));
return;
}
if (md.contains(MovePath.STEP_EJECT)) {
if (entity instanceof Mech) {
r = new Report(2020);
r.subject = entity.getId();
r.add(entity.getCrew().getName());
r.addDesc(entity);
addReport(r);
} else if ((entity instanceof Tank) && !entity.isCarcass()) {
r = new Report(2025);
r.subject = entity.getId();
r.addDesc(entity);
addReport(r);
}
addReport(ejectEntity(entity, false));
return;
}
if ( md.contains(MovePath.STEP_CAREFUL_STAND) ) {
entity.setCarefulStand(true);
}
// okay, proceed with movement calculations
Coords lastPos = entity.getPosition();
Coords curPos = entity.getPosition();
int curFacing = entity.getFacing();
int curVTOLElevation = entity.getElevation();
int startElevation = entity.getElevation();
int curElevation = entity.getElevation();
int lastElevation = entity.getElevation();
// if the entity already used some MPs,
// it previously tried to get up and fell,
// and then got another turn. set moveType
// and overallMoveType accordingly
// (these are all cleared by Entity.newRound)
int distance = entity.delta_distance;
int mpUsed = entity.mpUsed;
int moveType = entity.moved;
int overallMoveType = entity.moved;
boolean firstStep;
boolean wasProne;
boolean fellDuringMovement = false;
boolean turnOver;
boolean wigeStartedLanded = false;
int prevFacing = curFacing;
IHex prevHex = null;
final boolean isInfantry = entity instanceof Infantry;
AttackAction charge = null;
RamAttackAction ram = null;
PilotingRollData rollTarget;
// cache this here, otherwise changing MP in the turn causes
// errorneous gravity PSRs
int cachedGravityLimit = -1;
int thrustUsed = 0;
int j = 0;
boolean didMove = false;
boolean recovered = false;
Entity loader = null;
// get a list of coordinates that the unit passed through this turn
// so that I can later recover potential bombing targets
// it may already have some values
Vector<Coords> passedThrough = entity.getPassedThrough();
passedThrough.add(curPos);
// Compile the move
md.compile(game, entity);
// if advanced movement is being used then set the new vectors based on
// movepath
entity.setVectors(md.getFinalVectors());
overallMoveType = md.getLastStepMovementType();
// check for starting in liquid magma
if ((game.getBoard().getHex(entity.getPosition()).terrainLevel(Terrains.MAGMA) == 2) && (entity.getElevation() == 0)) {
doMagmaDamage(entity, false);
}
// set acceleration used to default
if (entity instanceof Aero) {
Aero a = (Aero) entity;
a.setAccLast(false);
}
// iterate through steps
firstStep = true;
turnOver = false;
/* Bug 754610: Revert fix for bug 702735. */
MoveStep prevStep = null;
Vector<UnitLocation> movePath = new Vector<UnitLocation>();
for (final Enumeration<MoveStep> i = md.getSteps(); i.hasMoreElements();) {
final MoveStep step = i.nextElement();
wasProne = entity.isProne();
boolean isPavementStep = step.isPavementStep();
entity.inReverse = step.isThisStepBackwards();
boolean entityFellWhileAttemptingToStand = false;
// stop for illegal movement
if (step.getMovementType() == IEntityMovementType.MOVE_ILLEGAL) {
break;
}
// stop if the entity already killed itself
if (entity.isDestroyed() || entity.isDoomed()) {
break;
}
if (firstStep && (entity.getMovementMode() == IEntityMovementMode.WIGE) && (entity.getElevation() == 0)) {
wigeStartedLanded = true;
}
// check for MASC failure on first step
if (firstStep && (entity instanceof Mech)) {
HashMap<Integer, CriticalSlot> crits = new HashMap<Integer, CriticalSlot>();
Vector<Report> vReport = new Vector<Report>();
if (((Mech) entity).checkForMASCFailure(md, vReport, crits)) {
addReport(vReport);
for (Integer loc : crits.keySet()) {
CriticalSlot cs = crits.get(loc);
addReport(applyCriticalHit(entity, loc, cs, true));
}
// do any PSR immediately
addReport(resolvePilotingRolls(entity));
game.resetPSRs(entity);
// let the player replot their move as MP might be changed
md.clear();
fellDuringMovement = true; // so they get a new turn
} else {
addReport(vReport);
}
}
// did the entity move?
didMove = step.getDistance() > distance;
// check for aero stuff
if (entity instanceof Aero) {
Aero a = (Aero) entity;
j++;
// TODO: change the way this check is made
if (!didMove && (md.length() != j)) {
thrustUsed += step.getMp();
} else {
// if this was the last move and distance was zero, then add
// thrust
if (!didMove && (md.length() == j)) {
thrustUsed += step.getMp();
}
// then we moved to a new hex or the last step so check
// conditions
// structural damage
rollTarget = a.checkThrustSI(thrustUsed, overallMoveType);
if ((rollTarget.getValue() != TargetRoll.CHECK_FALSE) && !(entity instanceof FighterSquadron) && !game.useVectorMove()) {
if (!doSkillCheckInSpace(entity, rollTarget)) {
a.setSI(a.getSI() - 1);
// check for destruction
if (a.getSI() == 0) {
destroyEntity(entity, "Structural Integrity Collapse", false);
}
}
}
// check for pilot damage
int hits = entity.getCrew().getHits();
int health = 6 - hits;
if ((thrustUsed > (2 * health)) && !game.useVectorMove() && !(entity instanceof TeleMissile)) {
int targetroll = 2 + (thrustUsed - 2 * health) + 2 * hits;
resistGForce(entity, targetroll);
}
thrustUsed = 0;
}
rollTarget = a.checkRolls(step, overallMoveType);
if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) {
game.addControlRoll(new PilotingRollData(a.getId(), 0, "excess roll"));
}
rollTarget = a.checkManeuver(step, overallMoveType);
if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) {
if (!doSkillCheckManeuver(entity, rollTarget)) {
a.setFailedManeuver(true);
int forward = Math.max(step.getVelocityLeft() / 2, 1);
if (forward < step.getVelocityLeft()) {
fellDuringMovement = true;
}
while (forward > 0) {
curPos = curPos.translated(step.getFacing());
forward--;
distance++;
// make sure it didn't fly off the map
if (!game.getBoard().contains(curPos)) {
r = new Report(9370, Report.PUBLIC);
r.indent();
r.addDesc(entity);
addReport(r);
game.removeEntity(entity.getId(), IEntityRemovalConditions.REMOVE_PUSHED);
send(createRemoveEntityPacket(entity.getId(), IEntityRemovalConditions.REMOVE_PUSHED));
forward = 0;
fellDuringMovement = false;
return;
// make sure it didn't crash
} else if (game.getBoard().getHex(curPos).ceiling() >= step.getElevation()) {
addReport(processCrash(entity, step.getVelocity()));
forward = 0;
fellDuringMovement = false;
}
}
break;
}
}
// if out of control, check for possible collision
if (didMove && a.isOutControlTotal()) {
Enumeration<Entity> targets = game.getEntities(step.getPosition());
if (targets.hasMoreElements()) {
// Somebody here so check to see if there is a collision
int checkroll = Compute.d6(2);
// TODO: change this to 11 for Large Craft
int targetroll = 11;
if ((a instanceof Dropship) || (entity instanceof Jumpship)) {
targetroll = 10;
}
if (checkroll >= targetroll) {
// this gets complicated, I need to check for each
// unit type
// by order of movement subphase
Vector<Integer> potentialSpaceStation;
Vector<Integer> potentialWarship;
Vector<Integer> potentialJumpship;
Vector<Integer> potentialDropship;
Vector<Integer> potentialSmallCraft;
Vector<Integer> potentialASF;
potentialSpaceStation = new Vector<Integer>();
potentialWarship = new Vector<Integer>();
potentialJumpship = new Vector<Integer>();
potentialDropship = new Vector<Integer>();
potentialSmallCraft = new Vector<Integer>();
potentialASF = new Vector<Integer>();
while (targets.hasMoreElements()) {
int id = targets.nextElement().getId();
Entity ce = game.getEntity(id);
if (ce instanceof SpaceStation) {
potentialSpaceStation.addElement(id);
} else if (ce instanceof Warship) {
potentialWarship.addElement(id);
} else if (ce instanceof Jumpship) {
potentialJumpship.addElement(id);
} else if (ce instanceof Dropship) {
potentialDropship.addElement(id);
} else if (ce instanceof SmallCraft) {
potentialSmallCraft.addElement(id);
} else {
potentialASF.addElement(id);
}
}
// ok now go through and see if these have anybody
// in them
if (potentialSpaceStation.size() > 0) {
int chosen = Compute.randomInt(potentialSpaceStation.size());
Entity target = game.getEntity(potentialSpaceStation.elementAt(chosen));
Coords dest = target.getPosition();
if (processCollision(entity, target, lastPos)) {
curPos = dest;
break;
}
} else if (potentialWarship.size() > 0) {
int chosen = Compute.randomInt(potentialWarship.size());
Entity target = game.getEntity(potentialWarship.elementAt(chosen));
Coords dest = target.getPosition();
if (processCollision(entity, target, lastPos)) {
curPos = dest;
break;
}
} else if (potentialJumpship.size() > 0) {
int chosen = Compute.randomInt(potentialJumpship.size());
Entity target = game.getEntity(potentialJumpship.elementAt(chosen));
Coords dest = target.getPosition();
if (processCollision(entity, target, lastPos)) {
curPos = dest;
break;
}
} else if (potentialDropship.size() > 0) {
int chosen = Compute.randomInt(potentialDropship.size());
Entity target = game.getEntity(potentialDropship.elementAt(chosen));
Coords dest = target.getPosition();
if (processCollision(entity, target, lastPos)) {
curPos = dest;
break;
}
} else if (potentialSmallCraft.size() > 0) {
int chosen = Compute.randomInt(potentialSmallCraft.size());
Entity target = game.getEntity(potentialSmallCraft.elementAt(chosen));
Coords dest = target.getPosition();
if (processCollision(entity, target, lastPos)) {
curPos = dest;
break;
}
} else if (potentialASF.size() > 0) {
int chosen = Compute.randomInt(potentialASF.size());
Entity target = game.getEntity(potentialASF.elementAt(chosen));
Coords dest = target.getPosition();
if (processCollision(entity, target, lastPos)) {
curPos = dest;
break;
}
}
}
}
}
// if in the atmosphere, check for a potential crash
if (game.getBoard().inAtmosphere() && (game.getBoard().getHex(step.getPosition()).ceiling() >= step.getElevation())) {
addReport(processCrash(entity, md.getFinalVelocity()));
// don't do the rest
break;
}
// handle fighter launching
if (step.getType() == MovePath.STEP_LAUNCH) {
TreeMap<Integer, Vector<Integer>> launched = step.getLaunched();
Set<Integer> bays = launched.keySet();
Iterator<Integer> bayIter = bays.iterator();
Bay currentBay;
while(bayIter.hasNext()) {
int bayId = bayIter.next();
currentBay = entity.getFighterBays().elementAt(bayId);
Vector<Integer> launches = launched.get(bayId);
int nLaunched = launches.size();
//need to make some decisions about how to handle the distribution
//of fighters to doors beyond the launch rate. The most sensible thing
//is probably to distribut them evenly.
int doors = currentBay.getDoors();
int[] distribution = new int[doors];
for(int l = 0; l < nLaunched; l++) {
distribution[l % doors] = distribution[l % doors] + 1;
}
//ok, now lets launch them
r = new Report(9380);
r.add(entity.getDisplayName());
r.subject = entity.getId();
r.newlines = 0;
r.add(nLaunched);
addReport(r);
int currentDoor = 0;
int fighterCount = 0;
boolean doorDamage = false;
for(int fighterId : launches) {
//check to see if we are in the same door
fighterCount++;
if(fighterCount > distribution[currentDoor]) {
//move to a new door
currentDoor++;
fighterCount = 0;
doorDamage = false;
}
int bonus = Math.max(0, distribution[currentDoor] - 2);
//check for door damage
if(!doorDamage && (distribution[currentDoor] > 2) && (Compute.d6(2) == 2)) {
doorDamage = true;
r = new Report(9390);
r.subject = entity.getId();
r.indent(1);
r.newlines = 0;
r.add(currentBay.getType());
addReport(r);
currentBay.destroyDoorNext();
}
Entity fighter = game.getEntity(fighterId);
if (!launchUnit(entity, fighter, curPos, curFacing, step.getVelocity(), step.getElevation(), step.getVectors(), bonus)) {
System.err.println("Error! Server was told to unload " + fighter.getDisplayName() + " from " + entity.getDisplayName() + " into " + curPos.getBoardNum());
}
}
}
// now apply any damage to bay doors
entity.resetBayDoors();
}
if (step.getType() == MovePath.STEP_OFF) {
//same as flee but different message.
//we can't use flee because if the unit is out of control, it needs to go through its
//to see if it collides with anything else
r = new Report(9370, Report.PUBLIC);
r.indent();
r.addDesc(entity);
addReport(r);
Coords pos = entity.getPosition();
int fleeDirection;
if (pos.x == 0) {
fleeDirection = IOffBoardDirections.WEST;
} else if (pos.y == 0) {
fleeDirection = IOffBoardDirections.SOUTH;
} else if (pos.x == game.getBoard().getWidth()) {
fleeDirection = IOffBoardDirections.EAST;
} else {
fleeDirection = IOffBoardDirections.NORTH;
}
// Is the unit carrying passengers?
final Vector<Entity> passengers = entity.getLoadedUnits();
if (!passengers.isEmpty()) {
for (Entity passenger : passengers) {
// Unit has fled the battlefield.
r = new Report(2010, Report.PUBLIC);
r.indent();
r.addDesc(passenger);
addReport(r);
passenger.setRetreatedDirection(fleeDirection);
game.removeEntity(passenger.getId(), IEntityRemovalConditions.REMOVE_IN_RETREAT);
send(createRemoveEntityPacket(passenger.getId(), IEntityRemovalConditions.REMOVE_IN_RETREAT));
}
}
// Handle any picked up MechWarriors
for (Integer mechWarriorId : entity.getPickedUpMechWarriors()) {
Entity mw = game.getEntity(mechWarriorId.intValue());
// Is the MechWarrior an enemy?
int condition = IEntityRemovalConditions.REMOVE_IN_RETREAT;
r = new Report(2010);
if (mw.isCaptured()) {
r = new Report(2015);
condition = IEntityRemovalConditions.REMOVE_CAPTURED;
} else {
mw.setRetreatedDirection(fleeDirection);
}
game.removeEntity(mw.getId(), condition);
send(createRemoveEntityPacket(mw.getId(), condition));
r.addDesc(mw);
r.indent();
addReport(r);
}
// Is the unit being swarmed?
final int swarmerId = entity.getSwarmAttackerId();
if (Entity.NONE != swarmerId) {
final Entity swarmer = game.getEntity(swarmerId);
// Has the swarmer taken a turn?
if (!swarmer.isDone()) {
// Dead entities don't take turns.
game.removeTurnFor(swarmer);
send(createTurnVectorPacket());
} // End swarmer-still-to-move
// Unit has fled the battlefield.
swarmer.setSwarmTargetId(Entity.NONE);
entity.setSwarmAttackerId(Entity.NONE);
r = new Report(2015, Report.PUBLIC);
r.indent();
r.addDesc(swarmer);
addReport(r);
game.removeEntity(swarmerId, IEntityRemovalConditions.REMOVE_CAPTURED);
send(createRemoveEntityPacket(swarmerId, IEntityRemovalConditions.REMOVE_CAPTURED));
}
entity.setRetreatedDirection(fleeDirection);
game.removeEntity(entity.getId(), IEntityRemovalConditions.REMOVE_IN_RETREAT);
send(createRemoveEntityPacket(entity.getId(), IEntityRemovalConditions.REMOVE_IN_RETREAT));
return;
}
}
// check piloting skill for getting up
rollTarget = entity.checkGetUp(step);
if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) {
entity.heatBuildup += 1;
entity.setProne(false);
//entity.setHullDown(false);
wasProne = false;
game.resetPSRs(entity);
entityFellWhileAttemptingToStand = !doSkillCheckInPlace(entity, rollTarget);
}
// did the entity just fall?
if (entityFellWhileAttemptingToStand) {
moveType = step.getMovementType();
curFacing = entity.getFacing();
curPos = entity.getPosition();
mpUsed = step.getMpUsed();
fellDuringMovement = true;
if ( !entity.isCarefulStand() ) {
break;
}
} else {
entity.setHullDown(false);
}
if (step.getType() == MovePath.STEP_UNJAM_RAC) {
entity.setUnjammingRAC(true);
game.addAction(new UnjamAction(entity.getId()));
// for Aeros this will end movement prematurely
// if we break
if (!(entity instanceof Aero)) {
break;
}
}
if (step.getType() == MovePath.STEP_LAY_MINE) {
layMine(entity, step.getMineToLay(), step.getPosition());
break;
}
if (step.getType() == MovePath.STEP_CLEAR_MINEFIELD) {
ClearMinefieldAction cma = new ClearMinefieldAction(entity.getId(), step.getMinefield());
entity.setClearingMinefield(true);
game.addAction(cma);
break;
}
if ((step.getType() == MovePath.STEP_SEARCHLIGHT) && entity.hasSpotlight()) {
final boolean SearchOn = !entity.isUsingSpotlight();
entity.setSpotlightState(SearchOn);
sendServerChat(entity.getDisplayName() + " switched searchlight " + (SearchOn ? "on" : "off") + '.');
}
// set most step parameters
moveType = step.getMovementType();
distance = step.getDistance();
mpUsed = step.getMpUsed();
if (cachedGravityLimit < 0) {
cachedGravityLimit = IEntityMovementType.MOVE_JUMP == moveType ? entity.getJumpMP(false) : entity.getRunMP(false, false);
}
// check for charge
if (step.getType() == MovePath.STEP_CHARGE) {
if (entity.canCharge()) {
checkExtremeGravityMovement(entity, step, curPos, cachedGravityLimit);
Targetable target = step.getTarget(game);
ChargeAttackAction caa = new ChargeAttackAction(entity.getId(), target.getTargetType(), target.getTargetId(), target.getPosition());
entity.setDisplacementAttack(caa);
game.addCharge(caa);
charge = caa;
} else {
sendServerChat("Illegal charge!! I don't think " + entity.getDisplayName() + " should be allowed to charge," + " but the client of " + entity.getOwner().getName() + " disagrees.");
sendServerChat("Please make sure " + entity.getOwner().getName() + " is running MegaMek " + MegaMek.VERSION + ", or if that is already the case, submit a bug report at http://megamek.sf.net/");
return;
}
break;
}
// check for dfa
if (step.getType() == MovePath.STEP_DFA) {
if (entity.canDFA()) {
checkExtremeGravityMovement(entity, step, curPos, cachedGravityLimit);
Targetable target = step.getTarget(game);
DfaAttackAction daa = new DfaAttackAction(entity.getId(), target.getTargetType(), target.getTargetId(), target.getPosition());
entity.setDisplacementAttack(daa);
game.addCharge(daa);
charge = daa;
} else {
sendServerChat("Illegal DFA!! I don't think " + entity.getDisplayName() + " should be allowed to DFA," + " but the client of " + entity.getOwner().getName() + " disagrees.");
sendServerChat("Please make sure " + entity.getOwner().getName() + " is running MegaMek " + MegaMek.VERSION + ", or if that is already the case, submit a bug report at http://megamek.sf.net/");
return;
}
break;
}
// check for ram
if (step.getType() == MovePath.STEP_RAM) {
if (entity.canRam()) {
Targetable target = step.getTarget(game);
RamAttackAction raa = new RamAttackAction(entity.getId(), target.getTargetType(), target.getTargetId(), target.getPosition());
entity.setRamming(true);
game.addRam(raa);
ram = raa;
} else {
sendServerChat("Illegal ram!! I don't think " + entity.getDisplayName() + " should be allowed to charge," + " but the client of " + entity.getOwner().getName() + " disagrees.");
sendServerChat("Please make sure " + entity.getOwner().getName() + " is running MegaMek " + MegaMek.VERSION + ", or if that is already the case, submit a bug report at http://megamek.sf.net/");
return;
}
break;
}
if ((step.getType() == MovePath.STEP_ACC) || (step.getType() == MovePath.STEP_ACCN)) {
if (entity instanceof Aero) {
Aero a = (Aero) entity;
if (step.getType() == MovePath.STEP_ACCN) {
a.setAccLast(true);
a.setNextVelocity(a.getNextVelocity() + 1);
} else {
a.setAccDecNow(true);
a.setCurrentVelocity(a.getCurrentVelocity() + 1);
a.setNextVelocity(a.getNextVelocity() + 1);
}
}
}
if ((step.getType() == MovePath.STEP_DEC) || (step.getType() == MovePath.STEP_DECN)) {
if (entity instanceof Aero) {
Aero a = (Aero) entity;
if (step.getType() == MovePath.STEP_DECN) {
a.setAccLast(true);
a.setNextVelocity(a.getNextVelocity() - 1);
} else {
a.setAccDecNow(true);
a.setCurrentVelocity(a.getCurrentVelocity() - 1);
a.setNextVelocity(a.getNextVelocity() - 1);
}
}
}
if (step.getType() == MovePath.STEP_EVADE) {
entity.setEvading(true);
}
if (step.getType() == MovePath.STEP_STALL) {
// TODO: check VSTOL status
r = new Report(9391);
r.subject = entity.getId();
r.addDesc(entity);
r.newlines = 0;
addReport(r);
game.addControlRoll(new PilotingRollData(entity.getId(), 0, "stalled out"));
// check for crash
if (game.getBoard().getHex(step.getPosition()).ceiling() >= step.getElevation()) {
addReport(processCrash(entity, 0));
// don't do the rest
break;
}
}
if (step.getType() == MovePath.STEP_ROLL) {
if (entity instanceof Aero) {
Aero a = (Aero) entity;
if (a.isRolled()) {
a.setRolled(false);
} else {
a.setRolled(true);
}
}
}
// check for dig in or fortify
if (entity instanceof Infantry) {
Infantry inf = (Infantry) entity;
if (step.getType() == MovePath.STEP_DIG_IN) {
inf.setDugIn(Infantry.DUG_IN_WORKING);
continue;
} else if (step.getType() == MovePath.STEP_FORTIFY) {
if (!entity.hasWorkingMisc(MiscType.F_TOOLS, MiscType.S_VIBROSHOVEL)) {
sendServerChat(entity.getDisplayName() + " failed to fortify because it is missing suitable equipment");
}
inf.setDugIn(Infantry.DUG_IN_FORTIFYING1);
continue;
} else if ((step.getType() != MovePath.STEP_TURN_LEFT) && (step.getType() != MovePath.STEP_TURN_RIGHT)) {
// other movement clears dug in status
inf.setDugIn(Infantry.DUG_IN_NONE);
}
}
// set last step parameters
curPos = step.getPosition();
if ((moveType != IEntityMovementType.MOVE_JUMP) || (entity.getJumpType() != Mech.JUMP_BOOSTER)) {
curFacing = step.getFacing();
}
// check if a building PSR will be needed later, before setting the
// new elevation
int buildingMove = entity.checkMovementInBuilding(step, prevStep, curPos, lastPos);
curVTOLElevation = step.getElevation();
curElevation = step.getElevation();
// set elevation in case of collapses
entity.setElevation(step.getElevation());
IHex curHex = game.getBoard().getHex(curPos);
// check for automatic unstick
if (entity.canUnstickByJumping() && entity.isStuck() && (moveType == IEntityMovementType.MOVE_JUMP)) {
entity.setStuck(false);
entity.setCanUnstickByJumping(false);
}
// Check for skid.
rollTarget = entity.checkSkid(moveType, prevHex, overallMoveType, prevStep, prevFacing, curFacing, lastPos, curPos, isInfantry, distance-1);
if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) {
// Have an entity-meaningful PSR message.
boolean psrFailed = true;
if (entity instanceof Mech) {
psrFailed = (0 < doSkillCheckWhileMoving(entity, lastPos, lastPos, rollTarget, true));
} else {
psrFailed = (0 < doSkillCheckWhileMoving(entity, lastPos, lastPos, rollTarget, false));
}
// Does the entity skid?
if (psrFailed) {
if (entity instanceof Tank) {
addReport(vehicleMotiveDamage((Tank) entity, 0));
}
curPos = lastPos;
int skidDistance = (int)Math.round((double)(distance-1)/2);
int skidDirection = prevFacing;
// All charge damage is based upon
// the pre-skid move distance.
entity.delta_distance = distance - 1;
// Attacks against a skidding target have additional +2.
moveType = IEntityMovementType.MOVE_SKID;
// What is the first hex in the skid?
if (step.isThisStepBackwards()) {
skidDirection = (skidDirection + 3) % 6;
}
if (processSkid(entity, curPos, prevStep.getElevation(), skidDirection, skidDistance, prevStep)) {
return;
}
// set entity parameters
curFacing = entity.getFacing();
curPos = entity.getPosition();
entity.setSecondaryFacing(curFacing);
// skid consumes all movement
if (md.hasActiveMASC()) {
mpUsed = entity.getRunMP();
} else {
mpUsed = entity.getRunMPwithoutMASC();
}
entity.moved = moveType;
fellDuringMovement = true;
turnOver = true;
distance = entity.delta_distance;
break;
} // End failed-skid-psr
} // End need-skid-psr
// check sideslip
if ((entity instanceof VTOL) || (entity.getMovementMode() == IEntityMovementMode.HOVER) || (entity.getMovementMode() == IEntityMovementMode.WIGE)) {
rollTarget = entity.checkSideSlip(moveType, prevHex, overallMoveType, prevStep, prevFacing, curFacing, lastPos, curPos, distance);
if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) {
int MoF = doSkillCheckWhileMoving(entity, lastPos, curPos, rollTarget, false);
if (MoF > 0) {
// maximum distance is hexes moved / 2
int sideslipDistance = Math.min(MoF, distance - 1);
if (sideslipDistance > 0) {
int skidDirection = prevFacing;
// report sideslip
sideslipped = true;
r = new Report(2100);
r.subject = entity.getId();
r.addDesc(entity);
r.add(sideslipDistance);
addReport(r);
if (processSkid(entity, lastPos, prevStep.getElevation(), skidDirection, sideslipDistance, prevStep)) {
return;
}
if (!entity.isDestroyed() && !entity.isDoomed() && (mpUsed < entity.getRunMP())) {
fellDuringMovement = true; // No, but it should
// work...
}
if ((entity.getElevation() == 0) && ((entity.getMovementMode() == IEntityMovementMode.VTOL) || (entity.getMovementMode() == IEntityMovementMode.WIGE))) {
turnOver = true;
}
// set entity parameters
curFacing = step.getFacing();
curPos = entity.getPosition();
entity.setSecondaryFacing(curFacing);
break;
}
}
}
}
// check if we've moved into rubble
rollTarget = entity.checkRubbleMove(step, curHex, lastPos, curPos);
if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) {
doSkillCheckWhileMoving(entity, lastPos, curPos, rollTarget, true);
}
//check if we are using reckless movement
rollTarget = entity.checkRecklessMove(step, curHex, lastPos, curPos, prevHex);
if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) {
if(entity instanceof Mech) {
doSkillCheckWhileMoving(entity, lastPos, curPos, rollTarget, true);
} else if (entity instanceof Tank) {
if(0 < doSkillCheckWhileMoving(entity, lastPos, curPos, rollTarget, false)) {
//assume VTOLs in flight are always in clear terrain
if((0 == curHex.terrainsPresent()) || (step.getElevation() > 0)) {
r = new Report(2206);
r.addDesc(entity);
r.subject = entity.getId();
addReport(r);
mpUsed = step.getMpUsed() + 1;
fellDuringMovement = true;
break;
} else {
r = new Report(2207);
r.addDesc(entity);
r.subject = entity.getId();
addReport(r);
//until we get a rules clarification assume that the entity is both giver and taker
//for charge damage
HitData hit = entity.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT);
addReport(damageEntity(entity, hit, ChargeAttackAction.getDamageTakenBy(entity, entity)));
turnOver = true;
break;
}
}
}
}
// check for breaking magma crust
if ((curHex.terrainLevel(Terrains.MAGMA) == 1) && (step.getElevation() == 0) && (step.getMovementType() != IEntityMovementType.MOVE_JUMP)) {
int roll = Compute.d6(1);
r = new Report(2395);
r.addDesc(entity);
r.add(roll);
r.subject = entity.getId();
addReport(r);
if (roll == 6) {
curHex.removeTerrain(Terrains.MAGMA);
curHex.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.MAGMA, 2));
sendChangedHex(curPos);
for (Enumeration<Entity> e = game.getEntities(curPos); e.hasMoreElements();) {
Entity en = e.nextElement();
if (en != entity) {
doMagmaDamage(en, false);
}
}
}
}
// check for entering liquid magma
if ((curHex.terrainLevel(Terrains.MAGMA) == 2) && (step.getElevation() == 0) && (step.getMovementType() != IEntityMovementType.MOVE_JUMP)) {
doMagmaDamage(entity, false);
}
// check if we've moved into a swamp
rollTarget = entity.checkBogDown(step, curHex, lastPos, curPos, lastElevation, isPavementStep);
if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) {
if (0 < doSkillCheckWhileMoving(entity, lastPos, curPos, rollTarget, false)) {
entity.setStuck(true);
entity.setCanUnstickByJumping(true);
r = new Report(2081);
r.add(entity.getDisplayName());
r.subject = entity.getId();
addReport(r);
//check for quicksand
addReport(checkQuickSand(curPos));
// check for accidental stacking violation
Entity violation = Compute.stackingViolation(game, entity.getId(), curPos);
if (violation != null) {
// target gets displaced, because of low elevation
int direction = lastPos.direction(curPos);
Coords targetDest = Compute.getValidDisplacement(game, entity.getId(), curPos, direction);
addReport(doEntityDisplacement(violation, curPos, targetDest, new PilotingRollData(violation.getId(), 0, "domino effect")));
// Update the violating entity's postion on the client.
entityUpdate(violation.getId());
}
break;
}
}
// check to see if we are a mech and we've moved OUT of fire
IHex lastHex = game.getBoard().getHex(lastPos);
if (entity instanceof Mech) {
if (!lastPos.equals(curPos) && (prevStep != null) && ((lastHex.containsTerrain(Terrains.FIRE) && (prevStep.getElevation() <= 1)) || (lastHex.containsTerrain(Terrains.MAGMA) && (prevStep.getElevation() == 0))) && ((step.getMovementType() != IEntityMovementType.MOVE_JUMP)
// Bug #828741 -- jumping bypasses fire, but not on the
// first step
// getMpUsed -- total MP used to this step
// getMp -- MP used in this step
// the difference will always be 0 on the "first step"
// of a jump,
// and >0 on a step in the midst of a jump
|| (0 == step.getMpUsed() - step.getMp()))) {
int heat = 0;
if (lastHex.containsTerrain(Terrains.FIRE)) {
heat += 2;
}
if (lastHex.terrainLevel(Terrains.MAGMA) == 1) {
heat += 2;
} else if (lastHex.terrainLevel(Terrains.MAGMA) == 2) {
heat += 5;
}
entity.heatFromExternal += heat;
r = new Report(2115);
r.subject = entity.getId();
r.addDesc(entity);
r.add(heat);
addReport(r);
}
}
// check to see if we are not a mech and we've moved INTO fire
if (!(entity instanceof Mech)) {
if (game.getBoard().getHex(curPos).containsTerrain(Terrains.FIRE) && !lastPos.equals(curPos) && (step.getMovementType() != IEntityMovementType.MOVE_JUMP) && (step.getElevation() <= 1)) {
doFlamingDamage(entity);
}
}
// check for extreme gravity movement
if (!i.hasMoreElements() && !firstStep) {
checkExtremeGravityMovement(entity, step, curPos, cachedGravityLimit);
}
// check for minefields. have to check both new hex and new elevation
// VTOLs may land and submarines may rise or lower into a minefield
if (!lastPos.equals(curPos) || (lastElevation != curElevation)) {
boolean boom = false;
boolean isOnGround = !i.hasMoreElements();
isOnGround |= step.getMovementType() != IEntityMovementType.MOVE_JUMP;
isOnGround &= step.getElevation() < 1;
if(isOnGround) {
boom = checkVibrabombs(entity, curPos, false, lastPos, curPos, vPhaseReport);
}
if (game.containsMinefield(curPos)) {
// set the new position temporarily, because
// infantry otherwise would get double damage
// when moving from clear into mined woods
entity.setPosition(curPos);
if(enterMinefield(entity, curPos, step.getElevation(), isOnGround, vPhaseReport)) {
//resolve any piloting rolls from damage unless unit was jumping
if(step.getMovementType() != IEntityMovementType.MOVE_JUMP) {
addReport(resolvePilotingRolls(entity));
game.resetPSRs(entity);
}
boom = true;
}
if(wasProne || !entity.isProne()) {
entity.setPosition(lastPos);
}
}
//did anything go boom?
if(boom) {
//set fell during movement so that entity will get another chance to move with any motive damage
//taken account of (functions the same as MASC failure)
//only do this if they had more steps (and they were not jumping
if(i.hasMoreElements() && (step.getMovementType() != IEntityMovementType.MOVE_JUMP)) {
md.clear();
fellDuringMovement = true;
}
//reset mines if anything detonated
resetMines();
}
}
// infantry discovers minefields if they end their move
// in a minefield.
if (!lastPos.equals(curPos) && !i.hasMoreElements() && isInfantry) {
if (game.containsMinefield(curPos)) {
Player owner = entity.getOwner();
for (Minefield mf : game.getMinefields(curPos)) {
if (!owner.containsMinefield(mf)) {
r = new Report(2120);
r.subject = entity.getId();
r.add(entity.getShortName(), true);
addReport(r);
revealMinefield(game.getTeamForPlayer(owner), mf);
}
}
}
}
// check if we've moved into water
rollTarget = entity.checkWaterMove(step, curHex, lastPos, curPos, isPavementStep);
if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) {
// Swarmers need special handling.
final int swarmerId = entity.getSwarmAttackerId();
boolean swarmerDone = true;
Entity swarmer = null;
if (Entity.NONE != swarmerId) {
swarmer = game.getEntity(swarmerId);
swarmerDone = swarmer.isDone();
}
// Now do the skill check.
doSkillCheckWhileMoving(entity, lastPos, curPos, rollTarget, true);
// Swarming infantry platoons may drown.
if (curHex.terrainLevel(Terrains.WATER) > 1) {
drownSwarmer(entity, curPos);
}
// Do we need to remove a game turn for the swarmer
if (!swarmerDone && (swarmer != null) && (swarmer.isDoomed() || swarmer.isDestroyed())) {
// We have to diddle with the swarmer's
// status to get its turn removed.
swarmer.setDone(false);
swarmer.setUnloaded(false);
// Dead entities don't take turns.
game.removeTurnFor(swarmer);
send(createTurnVectorPacket());
// Return the original status.
swarmer.setDone(true);
swarmer.setUnloaded(true);
}
// check for inferno wash-off
checkForWashedInfernos(entity, curPos);
}
// In water, may or may not be a new hex, neccessary to
// check during movement, for breach damage, and always
// set dry if appropriate
// TODO: possibly make the locations local and set later
addReport(doSetLocationsExposure(entity, curHex, step.getMovementType() == IEntityMovementType.MOVE_JUMP, step.getElevation()));
// check for breaking ice by breaking through from below
if ((lastElevation < 0) && (step.getElevation() == 0)
&& lastHex.containsTerrain(Terrains.ICE) && lastHex.containsTerrain(Terrains.WATER)
&& (step.getMovementType() != IEntityMovementType.MOVE_JUMP) && !lastPos.equals(curPos)) {
//need to temporarily reset entity's position so it doesn't fall in the ice
entity.setPosition(curPos);
r = new Report(2410);
r.addDesc(entity);
addReport(r);
addReport(resolveIceBroken(lastPos));
//ok now set back
entity.setPosition(lastPos);
}
// check for breaking ice by stepping on it
if (curHex.containsTerrain(Terrains.ICE) && curHex.containsTerrain(Terrains.WATER)
&& (step.getMovementType() != IEntityMovementType.MOVE_JUMP)
&& !lastPos.equals(curPos)
&& !(entity instanceof Infantry)
&& !(step.isPavementStep() && curHex.containsTerrain(Terrains.BRIDGE))) {
if (step.getElevation() == 0) {
int roll = Compute.d6(1);
r = new Report(2118);
r.addDesc(entity);
r.add(roll);
r.subject = entity.getId();
addReport(r);
if (roll == 6) {
entity.setPosition(curPos);
addReport(resolveIceBroken(curPos));
curPos = entity.getPosition();
}
}
// or intersecting it
else if (step.getElevation() + entity.height() == 0) {
r = new Report(2410);
r.addDesc(entity);
addReport(r);
addReport(resolveIceBroken(curPos));
}
}
// Handle loading units.
if (step.getType() == MovePath.STEP_LOAD) {
// Find the unit being loaded.
Entity loaded = null;
Enumeration<Entity> entities = game.getEntities(curPos);
while (entities.hasMoreElements()) {
// Is the other unit friendly and not the current entity?
loaded = entities.nextElement();
if (!entity.isEnemyOf(loaded) && !entity.equals(loaded)) {
// The moving unit should be able to load the other
// unit and the other should be able to have a turn.
if (!entity.canLoad(loaded) || !loaded.isLoadableThisTurn()) {
// Something is fishy in Denmark.
System.err.println(entity.getShortName() + " can not load " + loaded.getShortName());
loaded = null;
} else {
// Have the deployed unit load the indicated unit.
loadUnit(entity, loaded);
// Stop looking.
break;
}
} else {
// Nope. Discard it.
loaded = null;
}
} // Handle the next entity in this hex.
// We were supposed to find someone to load.
if (loaded == null) {
System.err.println("Could not find unit for " + entity.getShortName() + " to load in " + curPos);
}
} // End STEP_LOAD
// handle fighter recovery
if (step.getType() == MovePath.STEP_RECOVER) {
loader = game.getEntity(step.getRecoveryUnit());
PilotingRollData psr = entity.getBasePilotingRoll(overallMoveType);
if (loader.mpUsed > 0) {
psr.addModifier(5, "carrier used thrust");
}
int ctrlroll = Compute.d6(2);
r = new Report(9381);
r.subject = entity.getId();
r.add(entity.getDisplayName());
r.add(loader.getDisplayName());
r.add(psr.getValue());
r.add(ctrlroll);
r.newlines = 0;
r.indent(1);
if (ctrlroll < psr.getValue()) {
r.choose(false);
addReport(r);
// damage unit
Aero a = (Aero) entity;
HitData hit = a.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT);
addReport(damageEntity(entity, hit, 2 * (psr.getValue() - ctrlroll)));
} else {
r.choose(true);
addReport(r);
recovered = true;
}
// check for door damage
if (ctrlroll == 2) {
loader.damageDoorRecovery(entity);
r = new Report(9384);
r.subject = entity.getId();
r.indent(0);
r.add(loader.getDisplayName());
addReport(r);
}
}
//handle fighter squadron joining
if (step.getType() == MovePath.STEP_JOIN) {
loader = game.getEntity(step.getRecoveryUnit());
recovered = true;
}
// Handle unloading units.
if (step.getType() == MovePath.STEP_UNLOAD) {
Targetable unloaded = step.getTarget(game);
if (!unloadUnit(entity, unloaded, curPos, curFacing, step.getElevation())) {
System.err.println("Error! Server was told to unload " + unloaded.getDisplayName() + " from " + entity.getDisplayName() + " into " + curPos.getBoardNum());
}
}
if (((step.getType() == MovePath.STEP_BACKWARDS) || (step.getType() == MovePath.STEP_LATERAL_LEFT_BACKWARDS) || (step.getType() == MovePath.STEP_LATERAL_RIGHT_BACKWARDS)) && (game.getBoard().getHex(lastPos).getElevation() != curHex.getElevation()) && !(entity instanceof VTOL)) {
PilotingRollData psr = entity.getBasePilotingRoll(overallMoveType);
int roll = Compute.d6(2);
if (entity instanceof Tank) {
r = new Report(2435);
} else {
r = new Report(2430);
}
r.subject = entity.getId();
r.addDesc(entity);
r.add(psr.getValue());
r.add(roll);
addReport(r);
if (roll < psr.getValue()) {
if (entity instanceof Mech) {
if (curHex.getElevation() < game.getBoard().getHex(lastPos).getElevation()) {
entity.setElevation(Integer.MIN_VALUE);
addReport(doEntityFallsInto(entity, lastPos, curPos, entity.getBasePilotingRoll(overallMoveType), false));
} else {
// Tac Ops p.22
// the change in levels is not taken into
// consideration for determining the levels fallen.
entity.setElevation(Integer.MIN_VALUE);
addReport(doEntityFallsInto(entity, curPos, lastPos, entity.getBasePilotingRoll(overallMoveType), false));
}
} else if (entity instanceof Tank) {
curPos = lastPos;
}
}
}
// Handle non-infantry moving into a building.
if (buildingMove > 0) {
// Get the building being exited.
Building bldgExited = null;
if ((buildingMove & 1) == 1) {
bldgExited = game.getBoard().getBuildingAt(lastPos);
}
// Get the building being entered.
Building bldgEntered = null;
if ((buildingMove & 2) == 2) {
bldgEntered = game.getBoard().getBuildingAt(curPos);
}
// Get the building being stepped on.
Building bldgStepped = null;
if ((buildingMove & 4) == 4) {
bldgStepped = game.getBoard().getBuildingAt(curPos);
}
boolean collapsed = false;
// are we passing through a building wall?
if ((bldgEntered != null)) {
// If we're not leaving a building, just handle the
// "entered".
String reason;
if (bldgExited == null) {
reason = "entering";
}
// If we're moving within the same building, just handle
// the "within".
else if (bldgExited.equals(bldgEntered) && !(entity instanceof Protomech) && !(entity instanceof Infantry)) {
reason = "moving in";
}
// If we have different buildings, roll for each.
else {
reason = "entering";
}
collapsed = passBuildingWall(entity, bldgEntered, lastPos, curPos, distance, reason, step.isThisStepBackwards(), step.getParent().getLastStepMovementType());
addAffectedBldg(bldgEntered, collapsed);
}
// stepping on roof, no PSR just check for over weight
if (bldgStepped != null) {
collapsed = checkBuildingCollapseWhileMoving(bldgStepped, entity, curPos);
addAffectedBldg(bldgStepped, collapsed);
}
// Clean up the entity if it has been destroyed.
if (entity.isDoomed()) {
entity.setDestroyed(true);
game.moveToGraveyard(entity.getId());
send(createRemoveEntityPacket(entity.getId()));
// The entity's movement is completed.
return;
}
// TODO: what if a building collapses into rubble?
}
// did the entity just fall?
if (!wasProne && entity.isProne()) {
curFacing = entity.getFacing();
curPos = entity.getPosition();
mpUsed = step.getMpUsed();
fellDuringMovement = true;
break;
}
// dropping prone intentionally?
if (step.getType() == MovePath.STEP_GO_PRONE) {
mpUsed = step.getMpUsed();
rollTarget = entity.checkDislodgeSwarmers(step);
if (rollTarget.getValue() == TargetRoll.CHECK_FALSE) {
// Not being swarmed
entity.setProne(true);
// check to see if we washed off infernos
checkForWashedInfernos(entity, curPos);
} else {
// Being swarmed
entity.setPosition(curPos);
if (doDislodgeSwarmerSkillCheck(entity, rollTarget, curPos)) {
// Entity falls
curFacing = entity.getFacing();
curPos = entity.getPosition();
fellDuringMovement = true;
break;
}
// roll failed, go prone but don't dislodge swarmers
entity.setProne(true);
// check to see if we washed off infernos
checkForWashedInfernos(entity, curPos);
break;
}
}
// going hull down
if (step.getType() == MovePath.STEP_HULL_DOWN) {
mpUsed = step.getMpUsed();
entity.setHullDown(true);
}
// Track this step's location.
movePath.addElement(new UnitLocation(entity.getId(), curPos, curFacing, step.getElevation()));
// if the lastpos is not the same as the current position
// then add the current position to the list of places passed
// through
if (!curPos.equals(lastPos)) {
passedThrough.add(curPos);
}
// update lastPos, prevStep, prevFacing & prevHex
lastPos = new Coords(curPos);
lastElevation = curElevation;
prevStep = step;
/*
* Bug 754610: Revert fix for bug 702735. if (prevHex != null &&
* !curHex.equals(prevHex)) {
*/
if (!curHex.equals(prevHex)) {
prevFacing = curFacing;
}
prevHex = curHex;
firstStep = false;
}
// set entity parameters
entity.setPosition(curPos);
entity.setFacing(curFacing);
entity.setSecondaryFacing(curFacing);
entity.delta_distance = distance;
entity.moved = moveType;
entity.mpUsed = mpUsed;
if (!sideslipped && !fellDuringMovement) {
entity.setElevation(curVTOLElevation);
}
entity.setClimbMode(md.getFinalClimbMode());
// add a list of places passed through
entity.setPassedThrough(passedThrough);
// if we ran with destroyed hip or gyro, we need a psr
rollTarget = entity.checkRunningWithDamage(overallMoveType);
if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) {
doSkillCheckInPlace(entity, rollTarget);
}
if (entity instanceof Aero) {
Aero a = (Aero) entity;
int thrust = md.getMpUsed();
// set straight movement
if (null != md.getLastStep()) {
a.setStraightMoves(md.getLastStep().getNStraight());
} else {
a.setStraightMoves(0);
}
// consume fuel
if (((entity instanceof Aero) && game.getOptions().booleanOption("fuel_consumption")) || (entity instanceof TeleMissile)) {
int fuelUsed = ((Aero) entity).getFuelUsed(thrust);
a.useFuel(fuelUsed);
}
//jumpships and space stations need to reduce accumulated thrust if they spend some
if(entity instanceof Jumpship) {
Jumpship js = (Jumpship)entity;
double penalty = 0.0;
//jumpships do not accumulate thrust when they make a turn or change velocity
if(md.contains(MovePath.STEP_TURN_LEFT) || md.contains(MovePath.STEP_TURN_RIGHT)) {
//I need to subtract the station keeping thrust from their accumulated thrust
//because they did not actually use it
penalty = js.getStationKeepingThrust();
}
if(thrust > 0) {
penalty = thrust;
}
if(penalty > 0.0) {
js.setAccumulatedThrust(Math.max(0, js.getAccumulatedThrust() - penalty));
}
}
// check to see if thrust exceeded SI
rollTarget = a.checkThrustSITotal(thrust, overallMoveType);
if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) {
game.addControlRoll(new PilotingRollData(a.getId(), 0, "Thrust spent during turn exceeds SI"));
}
if (game.getBoard().inAtmosphere()) {
rollTarget = a.checkVelocityDouble(md.getFinalVelocity(), overallMoveType);
if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) {
game.addControlRoll(new PilotingRollData(a.getId(), 0, "Velocity greater than 2x safe thrust"));
}
rollTarget = a.checkDown(md.getFinalNDown(), overallMoveType);
if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) {
game.addControlRoll(new PilotingRollData(a.getId(), md.getFinalNDown(), "descended more than two altitudes"));
}
// check for hovering
rollTarget = a.checkHover(md);
if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) {
game.addControlRoll(new PilotingRollData(a.getId(), 0, "hovering"));
}
// if this is a spheroid then check to see if it loses elevation
// TODO: I am currently awaiting a rules decision on the forums
// regarding
// exactly when this elevation loss occurs. For the moment lets
// assume it occurs
// if there was no elevation change during the turn and no hover
// step
if ((a.isSpheroid() || game.getPlanetaryConditions().isVacuum()) && (startElevation == curElevation) && !md.contains(MovePath.STEP_HOVER)) {
a.setElevation(a.getElevation() - 1);
// check for crash
if (game.getBoard().getHex(a.getPosition()).ceiling() >= a.getElevation()) {
addReport(processCrash(entity, md.getFinalVelocity()));
}
}
}
}
// If the entity is being swarmed, erratic movement may dislodge the
// fleas.
final int swarmerId = entity.getSwarmAttackerId();
if ((Entity.NONE != swarmerId) && md.contains(MovePath.STEP_SHAKE_OFF_SWARMERS)) {
final Entity swarmer = game.getEntity(swarmerId);
final PilotingRollData roll = entity.getBasePilotingRoll(overallMoveType);
entity.addPilotingModifierForTerrain(roll);
// Add a +4 modifier.
if (md.getLastStepMovementType() == IEntityMovementType.MOVE_VTOL_RUN) {
roll.addModifier(2, "dislodge swarming infantry with VTOL movement");
} else {
roll.addModifier(4, "dislodge swarming infantry");
}
// If the swarmer has Assault claws, give a 1 modifier.
// We can stop looking when we find our first match.
for (Mounted mount : swarmer.getMisc()) {
EquipmentType equip = mount.getType();
if (BattleArmor.ASSAULT_CLAW.equals(equip.getInternalName())) {
roll.addModifier(1, "swarmer has assault claws");
break;
}
}
// okay, print the info
r = new Report(2125);
r.subject = entity.getId();
r.addDesc(entity);
addReport(r);
// roll
final int diceRoll = Compute.d6(2);
r = new Report(2130);
r.subject = entity.getId();
r.add(roll.getValueAsString());
r.add(roll.getDesc());
r.add(diceRoll);
if (diceRoll < roll.getValue()) {
r.choose(false);
addReport(r);
} else {
// Dislodged swarmers don't get turns.
game.removeTurnFor(swarmer);
send(createTurnVectorPacket());
// Update the report and the swarmer's status.
r.choose(true);
addReport(r);
entity.setSwarmAttackerId(Entity.NONE);
swarmer.setSwarmTargetId(Entity.NONE);
IHex curHex = game.getBoard().getHex(curPos);
// Did the infantry fall into water?
if (curHex.terrainLevel(Terrains.WATER) > 0) {
// Swarming infantry die.
swarmer.setPosition(curPos);
r = new Report(2135);
r.subject = entity.getId();
r.indent();
r.addDesc(swarmer);
addReport(r);
addReport(destroyEntity(swarmer, "a watery grave", false));
} else {
// Swarming infantry take a 3d6 point hit.
// ASSUMPTION : damage should not be doubled.
r = new Report(2140);
r.subject = entity.getId();
r.indent();
r.addDesc(swarmer);
r.add("3d6");
addReport(r);
addReport(damageEntity(swarmer, swarmer.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT), Compute.d6(3)));
addNewLines();
swarmer.setPosition(curPos);
}
entityUpdate(swarmerId);
} // End successful-PSR
} // End try-to-dislodge-swarmers
// but the danger isn't over yet! landing from a jump can be risky!
if ((overallMoveType == IEntityMovementType.MOVE_JUMP) && !entity.isMakingDfa()) {
final IHex curHex = game.getBoard().getHex(curPos);
// check for damaged criticals
rollTarget = entity.checkLandingWithDamage(overallMoveType);
if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) {
doSkillCheckInPlace(entity, rollTarget);
}
// jumped into water?
int waterLevel = curHex.terrainLevel(Terrains.WATER);
if (curHex.containsTerrain(Terrains.ICE) && (waterLevel > 0)) {
if(!(entity instanceof Infantry)) {
waterLevel = 0;
// check for breaking ice
int roll = Compute.d6(1);
r = new Report(2122);
r.add(entity.getDisplayName(), true);
r.add(roll);
r.subject = entity.getId();
addReport(r);
if (roll >= 4) {
// oops!
entity.setPosition(curPos);
addReport(resolveIceBroken(curPos));
curPos = entity.getPosition();
} else {
//TacOps: immediate PSR with +4 for terrain. If you fall then may break the ice after all
rollTarget = entity.checkLandingOnIce(overallMoveType, curHex);
if(!doSkillCheckInPlace(entity, rollTarget)) {
//apply damage now, or it will show up as a possible breach, if ice is broken
entity.applyDamage();
roll = Compute.d6(1);
r = new Report(2118);
r.addDesc(entity);
r.add(roll);
r.subject = entity.getId();
addReport(r);
if(roll == 6) {
entity.setPosition(curPos);
addReport(resolveIceBroken(curPos));
curPos = entity.getPosition();
}
}
}
}
} else if (!(prevStep.climbMode() && curHex.containsTerrain(Terrains.BRIDGE))) {
rollTarget = entity.checkWaterMove(waterLevel, overallMoveType);
if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) {
doSkillCheckInPlace(entity, rollTarget);
}
if (waterLevel > 1) {
// Any swarming infantry will be destroyed.
drownSwarmer(entity, curPos);
}
}
// check for building collapse
Building bldg = game.getBoard().getBuildingAt(curPos);
if (bldg != null) {
checkForCollapse(bldg, game.getPositionMap(), curPos, true);
}
// check for breaking magma crust
if (curHex.terrainLevel(Terrains.MAGMA) == 1) {
int roll = Compute.d6(1);
r = new Report(2395);
r.addDesc(entity);
r.add(roll);
r.subject = entity.getId();
addReport(r);
if (roll == 6) {
curHex.removeTerrain(Terrains.MAGMA);
curHex.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.MAGMA, 2));
sendChangedHex(curPos);
for (Enumeration<Entity> e = game.getEntities(curPos); e.hasMoreElements();) {
Entity en = e.nextElement();
if (en != entity) {
doMagmaDamage(en, false);
}
}
}
}
// check for entering liquid magma
if (curHex.terrainLevel(Terrains.MAGMA) == 2) {
doMagmaDamage(entity, false);
}
// jumped into swamp? maybe stuck!
if (curHex.getBogDownModifier(entity.getMovementMode(), entity instanceof LargeSupportTank) != TargetRoll.AUTOMATIC_SUCCESS) {
if (entity instanceof Mech) {
entity.setStuck(true);
r = new Report(2121);
r.add(entity.getDisplayName(), true);
r.subject = entity.getId();
addReport(r);
//check for quicksand
addReport(checkQuickSand(curPos));
} else {
PilotingRollData roll = new PilotingRollData(entity.getId(), 5, "entering boggy terrain");
roll.append(new PilotingRollData(entity.getId(), curHex.getBogDownModifier(entity.getMovementMode(), entity instanceof LargeSupportTank), "avoid bogging down"));
if (0 < doSkillCheckWhileMoving(entity, curPos, curPos, roll, false)) {
entity.setStuck(true);
r = new Report(2081);
r.add(entity.getDisplayName());
r.subject = entity.getId();
addReport(r);
//check for quicksand
addReport(checkQuickSand(curPos));
}
}
}
// If the entity is being swarmed, jumping may dislodge the fleas.
if (Entity.NONE != swarmerId) {
final Entity swarmer = game.getEntity(swarmerId);
final PilotingRollData roll = entity.getBasePilotingRoll(overallMoveType);
entity.addPilotingModifierForTerrain(roll);
// Add a +4 modifier.
roll.addModifier(4, "dislodge swarming infantry");
// If the swarmer has Assault claws, give a 1 modifier.
// We can stop looking when we find our first match.
for (Mounted mount : swarmer.getMisc()) {
EquipmentType equip = mount.getType();
if (BattleArmor.ASSAULT_CLAW.equals(equip.getInternalName())) {
roll.addModifier(1, "swarmer has assault claws");
break;
}
}
// okay, print the info
r = new Report(2125);
r.subject = entity.getId();
r.addDesc(entity);
addReport(r);
// roll
final int diceRoll = Compute.d6(2);
r = new Report(2130);
r.subject = entity.getId();
r.add(roll.getValueAsString());
r.add(roll.getDesc());
r.add(diceRoll);
if (diceRoll < roll.getValue()) {
r.choose(false);
addReport(r);
} else {
// Dislodged swarmers don't get turns.
game.removeTurnFor(swarmer);
send(createTurnVectorPacket());
// Update the report and the swarmer's status.
r.choose(true);
addReport(r);
entity.setSwarmAttackerId(Entity.NONE);
swarmer.setSwarmTargetId(Entity.NONE);
// Did the infantry fall into water?
if (curHex.terrainLevel(Terrains.WATER) > 0) {
// Swarming infantry die.
swarmer.setPosition(curPos);
r = new Report(2135);
r.subject = entity.getId();
r.indent();
r.addDesc(swarmer);
addReport(r);
addReport(destroyEntity(swarmer, "a watery grave", false));
} else {
// Swarming infantry take a 3d6 point hit.
// ASSUMPTION : damage should not be doubled.
r = new Report(2140);
r.subject = entity.getId();
r.indent();
r.addDesc(swarmer);
r.add("3d6");
addReport(r);
addReport(damageEntity(swarmer, swarmer.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT), Compute.d6(3)));
addNewLines();
swarmer.setPosition(curPos);
}
entityUpdate(swarmerId);
} // End successful-PSR
} // End try-to-dislodge-swarmers
// one more check for inferno wash-off
checkForWashedInfernos(entity, curPos);
} // End entity-is-jumping
// update entity's locations' exposure
doSetLocationsExposure(entity, game.getBoard().getHex(curPos), false, entity.getElevation());
// Check the falls_end_movement option to see if it should be able to
// move on.
if (!(game.getOptions().booleanOption("falls_end_movement") && (entity instanceof Mech)) && fellDuringMovement && !turnOver && (entity.mpUsed < entity.getRunMP()) && entity.isSelectableThisTurn() && !entity.isDoomed()) {
entity.applyDamage();
entity.setDone(false);
GameTurn newTurn = new GameTurn.SpecificEntityTurn(entity.getOwner().getId(), entity.getId());
game.insertNextTurn(newTurn);
// brief everybody on the turn update
send(createTurnVectorPacket());
// let everyone know about what just happened
send(entity.getOwner().getId(), createSpecialReportPacket());
} else {
if ((entity.getMovementMode() == IEntityMovementMode.WIGE) && (entity.getElevation() > 0)) {
if (!wigeStartedLanded && (entity.delta_distance < 5)) {
// try to land safely
r = new Report(2123);
r.addDesc(entity);
r.subject = entity.getId();
vPhaseReport.add(r);
// when no clear or pavement, crash
IHex hex = game.getBoard().getHex(curPos);
if (!hex.hasPavement() && (hex.terrainsPresent() > 0)) {
// crash
r = new Report(2124);
r.addDesc(entity);
r.subject = entity.getId();
vPhaseReport.add(r);
vPhaseReport.addAll(crashVTOLorWiGE((Tank) entity));
} else {
entity.setElevation(0);
}
} else {
// we didn't land, so we go to elevation 1
// it might have been higher than one due to the extra MPs
// it can spend to stay higher during movement, but should
// end up at one
entity.setElevation(1);
}
}
entity.setDone(true);
}
// If the entity is being swarmed, update the attacker's position.
if (Entity.NONE != swarmerId) {
final Entity swarmer = game.getEntity(swarmerId);
swarmer.setPosition(curPos);
// If the hex is on fire, and the swarming infantry is
// *not* Battle Armor, it drops off.
if (!(swarmer instanceof BattleArmor) && game.getBoard().getHex(curPos).containsTerrain(Terrains.FIRE)) {
swarmer.setSwarmTargetId(Entity.NONE);
entity.setSwarmAttackerId(Entity.NONE);
r = new Report(2145);
r.subject = entity.getId();
r.indent();
r.add(swarmer.getShortName(), true);
addReport(r);
}
entityUpdate(swarmerId);
}
// Update the entitiy's position,
// unless it is off the game map.
if (!game.isOutOfGame(entity)) {
entityUpdate(entity.getId(), movePath);
if (entity.isDoomed()) {
send(createRemoveEntityPacket(entity.getId(), entity.getRemovalCondition()));
}
}
// recovered units should now be recovered and dealt with
if ((entity instanceof Aero) && recovered && (loader != null)) {
if(loader.isCapitalFighter()) {
if(!(loader instanceof FighterSquadron)) {
//this is a solo capital fighter so we need to add a new squadron and load both the loader and loadee
FighterSquadron fs = new FighterSquadron();
fs.setDeployed(true);
fs.setId(getFreeEntityId());
fs.setCurrentVelocity(((Aero)loader).getCurrentVelocity());
fs.setNextVelocity(((Aero)loader).getNextVelocity());
fs.setVectors(loader.getVectors());
fs.setFacing(loader.getFacing());
fs.setOwner(entity.getOwner());
// set velocity and heading the same as parent entity
game.addEntity(fs.getId(), fs);
send(createAddEntityPacket(fs.getId()));
// make him not get a move this turn
fs.setDone(true);
// place on board
fs.setPosition(loader.getPosition());
loadUnit(fs, loader);
loader = fs;
entityUpdate(fs.getId());
}
loader.load(entity);
} else {
loader.recover(entity);
entity.setRecoveryTurn(5);
}
// The loaded unit is being carried by the loader.
entity.setTransportId(loader.getId());
// Remove the loaded unit from the screen.
entity.setPosition(null);
// Update the loaded unit.
entityUpdate(entity.getId());
}
// even if load was unsuccessful, I may need to update the loader
if (null != loader) {
entityUpdate(loader.getId());
}
// if using double blind, update the player on new units he might see
if (doBlind()) {
send(entity.getOwner().getId(), createFilteredEntitiesPacket(entity.getOwner()));
}
// if we generated a charge attack, report it now
if (charge != null) {
send(createAttackPacket(charge, 1));
}
// if we generated a ram attack, report it now
if (ram != null) {
send(createAttackPacket(ram, 1));
}
if ((entity instanceof Mech) && ((Mech)entity).isIndustrial() && !((Mech)entity).hasEnvironmentalSealing() && (entity.getEngine().getEngineType() == Engine.COMBUSTION_ENGINE)) {
if ((!entity.isProne() && (game.getBoard().getHex(entity.getPosition()).terrainLevel(Terrains.WATER) >= 2)) || (entity.isProne() && (game.getBoard().getHex(entity.getPosition()).terrainLevel(Terrains.WATER) == 1))) {
((Mech)entity).setJustMovedIntoIndustrialKillingWater(true);
} else {
((Mech)entity).setJustMovedIntoIndustrialKillingWater(false);
}
}
}
/**
* Delivers a thunder-aug shot to the targetted hex area. Thunder-Augs are 7
* hexes, though, so...
*/
public void deliverThunderAugMinefield(Coords coords, int playerId, int damage, int entityId) {
Coords mfCoord = null;
//divide damage in half
damage = damage / 2 + damage % 2;
for (int dir = 0; dir < 7; dir++) {
switch (dir) {
case 6:
// The targeted hex.
mfCoord = new Coords(coords);
break;
default:
// The hex in the dir direction from the targeted hex.
mfCoord = coords.translated(dir);
break;
}
// Only if this is on the board...
if (game.getBoard().contains(mfCoord)) {
Minefield minefield = null;
Enumeration<Minefield> minefields = game.getMinefields(mfCoord).elements();
// Check if there already are Thunder minefields in the hex.
while (minefields.hasMoreElements()) {
Minefield mf = minefields.nextElement();
if (mf.getType() == Minefield.TYPE_CONVENTIONAL) {
minefield = mf;
break;
}
}
// Did we find a Thunder minefield in the hex?
// N.B. damage Thunder minefields equals the number of
// missiles, divided by two, rounded up.
if (minefield == null) {
// Nope. Create a new Thunder minefield
minefield = Minefield.createMinefield(mfCoord, playerId, Minefield.TYPE_CONVENTIONAL, damage);
game.addMinefield(minefield);
checkForRevealMinefield(minefield, game.getEntity(entityId));
} else if (minefield.getDensity() < Minefield.MAX_DAMAGE) {
// Yup. Replace the old one.
removeMinefield(minefield);
int newDamage = damage / 2 + damage % 2;
newDamage += minefield.getDensity();
// Damage from Thunder minefields are capped.
if (newDamage > Minefield.MAX_DAMAGE) {
newDamage = Minefield.MAX_DAMAGE;
}
minefield.setDensity(newDamage);
game.addMinefield(minefield);
checkForRevealMinefield(minefield, game.getEntity(entityId));
}
} // End coords-on-board
} // Handle the next coords
}
/**
* Adds a Thunder minefield to the hex.
*
* @param coords
* @param playerId
* @param damage
*/
public void deliverThunderMinefield(Coords coords, int playerId, int damage, int entityId) {
Minefield minefield = null;
Enumeration<Minefield> minefields = game.getMinefields(coords).elements();
// Check if there already are Thunder minefields in the hex.
while (minefields.hasMoreElements()) {
Minefield mf = minefields.nextElement();
if (mf.getType() == Minefield.TYPE_CONVENTIONAL) {
minefield = mf;
break;
}
}
// Create a new Thunder minefield
if (minefield == null) {
minefield = Minefield.createMinefield(coords, playerId, Minefield.TYPE_CONVENTIONAL, damage);
game.addMinefield(minefield);
checkForRevealMinefield(minefield, game.getEntity(entityId));
} else if (minefield.getDensity() < Minefield.MAX_DAMAGE) {
// Add to the old one
removeMinefield(minefield);
int oldDamage = minefield.getDensity();
damage += oldDamage;
damage = damage > Minefield.MAX_DAMAGE ? Minefield.MAX_DAMAGE : damage;
minefield.setDensity(damage);
game.addMinefield(minefield);
checkForRevealMinefield(minefield, game.getEntity(entityId));
}
}
/**
* Adds a Thunder Inferno minefield to the hex.
*
* @param coords
* @param playerId
* @param damage
*/
public void deliverThunderInfernoMinefield(Coords coords, int playerId, int damage, int entityId) {
Minefield minefield = null;
Enumeration<Minefield> minefields = game.getMinefields(coords).elements();
// Check if there already are Thunder minefields in the hex.
while (minefields.hasMoreElements()) {
Minefield mf = minefields.nextElement();
if (mf.getType() == Minefield.TYPE_INFERNO) {
minefield = mf;
break;
}
}
// Create a new Thunder Inferno minefield
if (minefield == null) {
minefield = Minefield.createMinefield(coords, playerId, Minefield.TYPE_INFERNO, damage);
game.addMinefield(minefield);
checkForRevealMinefield(minefield, game.getEntity(entityId));
} else if (minefield.getDensity() < Minefield.MAX_DAMAGE) {
// Add to the old one
removeMinefield(minefield);
int oldDamage = minefield.getDensity();
damage += oldDamage;
damage = damage > Minefield.MAX_DAMAGE ? Minefield.MAX_DAMAGE : damage;
minefield.setDensity(damage);
game.addMinefield(minefield);
checkForRevealMinefield(minefield, game.getEntity(entityId));
}
}
/**
* Delivers a Arrow IV FASCAM shot to the targetted hex area.
*/
public void deliverFASCAMMinefield(Coords coords, int playerId, int damage, int entityId) {
//round up damage to next highest increment of five
damage = (int)(5 * Math.ceil(damage / 5.0));
// Only if this is on the board...
if (game.getBoard().contains(coords)) {
Minefield minefield = null;
Enumeration<Minefield> minefields = game.getMinefields(coords).elements();
// Check if there already are Thunder minefields in the hex.
while (minefields.hasMoreElements()) {
Minefield mf = minefields.nextElement();
if (mf.getType() == Minefield.TYPE_CONVENTIONAL) {
minefield = mf;
break;
}
}
// Did we find a Thunder minefield in the hex?
// N.B. damage of FASCAM minefields is 30
if (minefield == null) {
minefield = Minefield.createMinefield(coords, playerId, Minefield.TYPE_CONVENTIONAL, 30);
}
removeMinefield(minefield);
minefield.setDensity(30);
game.addMinefield(minefield);
checkForRevealMinefield(minefield, game.getEntity(entityId));
} // End coords-on-board
}
/**
* Adds a Thunder-Active minefield to the hex.
*/
public void deliverThunderActiveMinefield(Coords coords, int playerId, int damage, int entityId) {
Minefield minefield = null;
Enumeration<Minefield> minefields = game.getMinefields(coords).elements();
// Check if there already are Thunder minefields in the hex.
while (minefields.hasMoreElements()) {
Minefield mf = minefields.nextElement();
if (mf.getType() == Minefield.TYPE_ACTIVE) {
minefield = mf;
break;
}
}
// Create a new Thunder-Active minefield
if (minefield == null) {
minefield = Minefield.createMinefield(coords, playerId, Minefield.TYPE_ACTIVE, damage);
game.addMinefield(minefield);
checkForRevealMinefield(minefield, game.getEntity(entityId));
} else if (minefield.getDensity() < Minefield.MAX_DAMAGE) {
// Add to the old one
removeMinefield(minefield);
int oldDamage = minefield.getDensity();
damage += oldDamage;
damage = damage > Minefield.MAX_DAMAGE ? Minefield.MAX_DAMAGE : damage;
minefield.setDensity(damage);
game.addMinefield(minefield);
checkForRevealMinefield(minefield, game.getEntity(entityId));
}
}
/**
* Adds a Thunder-Vibrabomb minefield to the hex.
*/
public void deliverThunderVibraMinefield(Coords coords, int playerId, int damage, int sensitivity, int entityId) {
Minefield minefield = null;
Enumeration<Minefield> minefields = game.getMinefields(coords).elements();
// Check if there already are Thunder minefields in the hex.
while (minefields.hasMoreElements()) {
Minefield mf = minefields.nextElement();
if (mf.getType() == Minefield.TYPE_VIBRABOMB) {
minefield = mf;
break;
}
}
// Create a new Thunder-Vibra minefield
if (minefield == null) {
minefield = Minefield.createMinefield(coords, playerId, Minefield.TYPE_VIBRABOMB, damage, sensitivity);
game.addVibrabomb(minefield);
checkForRevealMinefield(minefield, game.getEntity(entityId));
} else if (minefield.getDensity() < Minefield.MAX_DAMAGE) {
// Add to the old one
removeMinefield(minefield);
int oldDamage = minefield.getDensity();
damage += oldDamage;
damage = damage > Minefield.MAX_DAMAGE ? Minefield.MAX_DAMAGE : damage;
minefield.setDensity(damage);
game.addVibrabomb(minefield);
checkForRevealMinefield(minefield, game.getEntity(entityId));
}
}
/**
* Creates an artillery flare of the given radius above the target
*/
public void deliverArtilleryFlare(Coords coords, int radius) {
Flare flare = new Flare(coords, 5, radius, Flare.F_DRIFTING);
game.addFlare(flare);
}
/**
* deliver artillery smoke
*
* @param coords
* the <code>Coords</code> where to deliver
*/
public void deliverArtillerySmoke(Coords coords, Vector<Report> vPhaseReport) {
IHex h = game.getBoard().getHex(coords);
// Unless there is a heavy smoke in the hex already, add one.
if (h.terrainLevel(Terrains.SMOKE) < 2) {
Report r = new Report(5185, Report.PUBLIC);
r.indent(2);
r.add(coords.getBoardNum());
vPhaseReport.add(r);
createSmoke(coords);
sendChangedHex(coords);
}
}
/**
* deliver artillery inferno
*
* @param coords
* the <code>Coords</code> where to deliver
* @param ae
* the attacking <code>entity<code>
* @param subjectId
* the <code>int</code> id of the target
*/
public void deliverArtilleryInferno(Coords coords, Entity ae, int subjectId, Vector<Report> vPhaseReport) {
IHex h = game.getBoard().getHex(coords);
Report r;
// Unless there is a fire in the hex already, start one.
if (!h.containsTerrain(Terrains.FIRE)) {
ignite(coords, true, vPhaseReport);
}
//possibly melt ice and snow
if(h.containsTerrain(Terrains.ICE) || h.containsTerrain(Terrains.SNOW)) {
vPhaseReport.addAll(meltIceAndSnow(coords, subjectId));
}
for (Enumeration<Entity> impactHexHits = game.getEntities(coords); impactHexHits.hasMoreElements();) {
Entity entity = impactHexHits.nextElement();
//TacOps, p. 356 - treat as if hit by 5 inferno missiles
r = new Report(6695);
r.indent(3);
r.add(entity.getDisplayName());
r.subject = entity.getId();
r.newlines = 0;
vPhaseReport.add(r);
if(entity instanceof Tank) {
Report.addNewline(vPhaseReport);
}
Vector<Report> vDamageReport = deliverInfernoMissiles(ae, entity, 5);
Report.indentAll(vDamageReport, 2);
vPhaseReport.addAll(vDamageReport);
}
for (int dir = 0; dir <= 5; dir++) {
Coords tempcoords = coords.translated(dir);
if (!game.getBoard().contains(tempcoords)) {
continue;
}
if (coords.equals(tempcoords)) {
continue;
}
h = game.getBoard().getHex(tempcoords);
// Unless there is a fire in the hex already, start one.
if (!h.containsTerrain(Terrains.FIRE)) {
ignite(tempcoords, true, vPhaseReport);
}
//possibly melt ice and snow
if(h.containsTerrain(Terrains.ICE) || h.containsTerrain(Terrains.SNOW)) {
vPhaseReport.addAll(meltIceAndSnow(tempcoords, subjectId));
}
for (Enumeration<Entity> splashHexHits = game.getEntities(tempcoords); splashHexHits.hasMoreElements();) {
Entity entity = splashHexHits.nextElement();
r = new Report(6695);
r.indent(3);
r.add(entity.getDisplayName());
r.newlines = 0;
r.subject = entity.getId();
vPhaseReport.add(r);
if(entity instanceof Tank) {
Report.addNewline(vPhaseReport);
}
Vector<Report> vDamageReport = deliverInfernoMissiles(ae, entity, 5);
Report.indentAll(vDamageReport, 2);
vPhaseReport.addAll(vDamageReport);
}
}
}
public void deliverScreen(Coords coords, Vector<Report> vPhaseReport) {
IHex h = game.getBoard().getHex(coords);
Report r;
Report.addNewline(vPhaseReport);
r = new Report(9070, Report.PUBLIC);
r.indent(2);
r.add(coords.getBoardNum());
vPhaseReport.add(r);
// use level to count the number of screens (since level does not matter
// in space)
int nscreens = h.terrainLevel(Terrains.SCREEN);
if (nscreens > 0) {
h.removeTerrain(Terrains.SCREEN);
h.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.SCREEN, nscreens + 1));
} else {
h.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.SCREEN, 1));
}
sendChangedHex(coords);
}
/*
* deploys a new tele-missile entity onto the map
*/
public void deployTeleMissile(Entity ae, AmmoType atype, int wId, int capMisMod, Vector<Report> vPhaseReport) {
Report r = new Report(9080);
r.subject = ae.getId();
r.addDesc(ae);
r.indent(2);
r.newlines = 0;
r.add(atype.getName());
vPhaseReport.add(r);
TeleMissile tele = new TeleMissile(ae, atype.getDamagePerShot(), atype.getTonnage(ae), atype.getAmmoType(), capMisMod);
tele.setDeployed(true);
tele.setId(getFreeEntityId());
if (ae instanceof Aero) {
Aero a = (Aero) ae;
tele.setCurrentVelocity(a.getCurrentVelocity());
tele.setNextVelocity(a.getNextVelocity());
tele.setVectors(a.getVectors());
tele.setFacing(a.getFacing());
}
// set velocity and heading the same as parent entity
game.addEntity(tele.getId(), tele);
send(createAddEntityPacket(tele.getId()));
// make him not get a move this turn
tele.setDone(true);
// place on board
tele.setPosition(ae.getPosition());
// Update the entity
entityUpdate(tele.getId());
// check to see if the launching of this missile removes control of any
// prior missiles
if (ae.getTMTracker().containsLauncher(wId)) {
Entity priorMissile = game.getEntity(ae.getTMTracker().getMissile(wId));
if ((null != priorMissile) && (priorMissile instanceof TeleMissile)) {
((TeleMissile) priorMissile).setOutContact(true);
// remove this from the tracker for good measure
ae.getTMTracker().removeMissile(wId);
}
}
// track this missile on the entity
ae.getTMTracker().addMissile(wId, tele.getId());
}
/**
* deliver inferno missiles
*
* @param ae
* the <code>Entity</code> that fired the missiles
* @param t
* the <code>Targetable</code> that is the target
* @param missiles
* the <code>int</code> amount of missiles
*/
public Vector<Report> deliverInfernoMissiles(Entity ae, Targetable t, int missiles) {
IHex hex = game.getBoard().getHex(t.getPosition());
Report r;
Vector<Report> vPhaseReport = new Vector<Report>();
switch (t.getTargetType()) {
case Targetable.TYPE_HEX_ARTILLERY:
// used for BA inferno explosion
for (Enumeration<Entity> entities = game.getEntities(t.getPosition()); entities.hasMoreElements();) {
Entity e = entities.nextElement();
if (e.getElevation() > hex.terrainLevel(Terrains.BLDG_ELEV)) {
r = new Report(6685);
r.subject = e.getId();
r.addDesc(e);
vPhaseReport.add(r);
vPhaseReport.addAll(deliverInfernoMissiles(ae, e, missiles));
} else {
int roll = Compute.d6();
r = new Report(3570);
r.subject = e.getId();
r.addDesc(e);
r.add(roll);
vPhaseReport.add(r);
if (roll >= 5) {
vPhaseReport.addAll(deliverInfernoMissiles(ae, e, missiles));
}
}
}
if (game.getBoard().getBuildingAt(t.getPosition()) != null) {
Vector<Report> vBuildingReport = damageBuilding(game.getBoard().getBuildingAt(t.getPosition()), 2 * missiles, t.getPosition());
for (Report report : vBuildingReport) {
report.subject = ae.getId();
}
vPhaseReport.addAll(vBuildingReport);
}
// fall through
case Targetable.TYPE_HEX_CLEAR:
case Targetable.TYPE_HEX_IGNITE:
vPhaseReport.addAll(tryClearHex(t.getPosition(), missiles * 4, ae.getId()));
tryIgniteHex(t.getPosition(), ae.getId(), false, true, new TargetRoll(0, "inferno"), -1, vPhaseReport);
break;
case Targetable.TYPE_BLDG_IGNITE:
case Targetable.TYPE_BUILDING:
for (Enumeration<Entity> entities = game.getEntities(t.getPosition()); entities.hasMoreElements();) {
Entity e = entities.nextElement();
if (e.getElevation() > hex.terrainLevel(Terrains.BLDG_ELEV)) {
continue;
}
int roll = Compute.d6();
r = new Report(3570);
r.subject = e.getId();
r.addDesc(e);
r.add(roll);
vPhaseReport.add(r);
if (roll >= 5) {
vPhaseReport.addAll(deliverInfernoMissiles(ae, e, missiles));
}
}
Vector<Report> vBuildingReport = damageBuilding(game.getBoard().getBuildingAt(t.getPosition()), 2 * missiles, t.getPosition());
for (Report report : vBuildingReport) {
report.subject = ae.getId();
}
vPhaseReport.addAll(vBuildingReport);
break;
case Targetable.TYPE_ENTITY:
Entity te = (Entity) t;
if (te instanceof Mech) {
// Bug #1585497: Check for partial cover
int m = missiles;
for (int i = 0; i < m; i++) {
int roll = Compute.d6(2);
LosEffects le = LosEffects.calculateLos(game, ae.getId(), t);
if (te.removePartialCoverHits(roll, le.getTargetCover(), Compute.targetSideTable(ae, t))) {
missiles--;
}
}
if (missiles != m) {
r = new Report(3403);
r.add(m - missiles);
r.indent(2);
r.subject = te.getId();
vPhaseReport.add(r);
}
r = new Report(3400);
r.add(2 * missiles);
r.subject = te.getId();
r.indent(2);
r.choose(true);
vPhaseReport.add(r);
te.heatFromExternal += 2 * missiles;
} else if (te instanceof Tank) {
int direction = Compute.targetSideTable(ae, te);
while (missiles-- > 0) {
HitData hit = te.rollHitLocation(ToHitData.HIT_NORMAL, direction);
vPhaseReport.addAll(criticalEntity(te, hit.getLocation(), -2));
}
} else if (te instanceof Protomech) {
te.heatFromExternal += missiles;
while (te.heatFromExternal >= 3) {
te.heatFromExternal -= 3;
HitData hit = te.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT);
if (hit.getLocation() == Protomech.LOC_NMISS) {
r = new Report(6035);
r.subject = te.getId();
r.newlines = 0;
vPhaseReport.add(r);
} else {
r = new Report(6690);
r.subject = te.getId();
r.indent(2);
r.add(te.getLocationName(hit));
vPhaseReport.add(r);
te.destroyLocation(hit.getLocation());
// Handle Protomech pilot damage
// due to location destruction
int hits = Protomech.POSSIBLE_PILOT_DAMAGE[hit.getLocation()] - ((Protomech) te).getPilotDamageTaken(hit.getLocation());
if (hits > 0) {
vPhaseReport.addAll(damageCrew(te, hits));
((Protomech) te).setPilotDamageTaken(hit.getLocation(), Protomech.POSSIBLE_PILOT_DAMAGE[hit.getLocation()]);
}
if (te.getTransferLocation(hit).getLocation() == Entity.LOC_DESTROYED) {
vPhaseReport.addAll(destroyEntity(te, "flaming inferno death", false, true));
Report.addNewline(vPhaseReport);
}
}
}
} else if (te instanceof BattleArmor) {
for (Mounted equip : te.getMisc()) {
if (equip.getType().hasFlag(MiscType.F_FIRE_RESISTANT)) {
r = new Report(3395);
r.indent(2);
r.subject = te.getId();
r.addDesc(te);
vPhaseReport.add(r);
return vPhaseReport;
}
}
te.heatFromExternal += missiles;
while (te.heatFromExternal >= 3) {
te.heatFromExternal -= 3;
HitData hit = te.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT);
hit.setEffect(HitData.EFFECT_CRITICAL);
vPhaseReport.addAll(damageEntity(te, hit, 1));
Report.addNewline(vPhaseReport);
}
} else if (te instanceof Infantry) {
HitData hit = new HitData(Infantry.LOC_INFANTRY);
if (te.getInternal(hit) > 3 * missiles) {
// internal structure absorbs all damage
te.setInternal(te.getInternal(hit) - 3 * missiles, hit);
r = new Report(6065);
r.addDesc(te);
r.add(3 * missiles);
r.indent(2);
r.add(te.getLocationAbbr(hit));
r.newlines = 0;
r.subject = te.getId();
vPhaseReport.add(r);
r = new Report(6095);
r.add(te.getInternal(hit));
r.subject = te.getId();
r.indent(2);
vPhaseReport.add(r);
} else {
vPhaseReport.addAll(destroyEntity(te, "damage", false));
Report.addNewline(vPhaseReport);
}
} else {
// gun emplacements
int direction = Compute.targetSideTable(ae, te);
while (missiles-- > 0) {
HitData hit = te.rollHitLocation(ToHitData.HIT_NORMAL, direction);
vPhaseReport.addAll(damageEntity(te, hit, 2));
}
}
}
return vPhaseReport;
}
/**
* Check for any detonations when an entity enters a minefield, except a vibrabomb.
*
* @param entity - the <code>entity</code> who entered the minefield
* @param c - the <code>Coords</code> of the minefield
* @param curElev - an <code>int</code> for the elevation of the entity entering the minefield (used for underwater sea mines)
* @param isOnGround - <code>true</code> if the entity is not in the middle of a jump
* @param vMineReport - the report vector that reports will be added to
* @return - <code>true</code> if the entity set off any mines
*/
private boolean enterMinefield(Entity entity, Coords c, int curElev, boolean isOnGround, Vector<Report> vMineReport) {
return enterMinefield(entity, c, curElev, isOnGround, vMineReport, -1);
}
/**
* Check for any detonations when an entity enters a minefield, except a vibrabomb.
*
* @param entity - the <code>entity</code> who entered the minefield
* @param c - the <code>Coords</code> of the minefield
* @param curElev - an <code>int</code> for the elevation of the entity entering the minefield (used for underwater sea mines)
* @param isOnGround - <code>true</code> if the entity is not in the middle of a jump
* @param vMineReport - the report vector that reports will be added to
* @param target - the <code>int</code> target number for detonation. If this will be determined by density, it should be -1
* @return - <code>true</code> if the entity set off any mines
*/
private boolean enterMinefield(Entity entity, Coords c, int curElev, boolean isOnGround, Vector<Report> vMineReport, int target) {
Report r;
boolean trippedMine = false;
//flying units cannot trip a mine
if(curElev > 0) {
return false;
}
//loop through mines in this hex
for(Minefield mf : game.getMinefields(c)) {
//vibrabombs are handled differently
if(mf.getType() == Minefield.TYPE_VIBRABOMB) {
continue;
}
//if we are in the water, then the sea mine will only blow up if at the right depth
if(game.getBoard().getHex(mf.getCoords()).containsTerrain(Terrains.WATER)) {
if((Math.abs(curElev) != mf.getDepth())
&& (Math.abs(curElev + entity.getHeight()) != mf.getDepth())) {
continue;
}
}
//check whether we have an active mine
if((mf.getType() == Minefield.TYPE_ACTIVE) && isOnGround) {
continue;
}
if((mf.getType() != Minefield.TYPE_ACTIVE) && !isOnGround) {
continue;
}
//set the target number
if(target == -1) {
target = mf.getTrigger();
if(mf.getType() == Minefield.TYPE_ACTIVE) {
target = 9;
}
if(entity instanceof Infantry) {
target += 1;
}
if((entity.getMovementMode() == IEntityMovementMode.HOVER) || (entity.getMovementMode() == IEntityMovementMode.WIGE)) {
target = 12;
}
}
if (Compute.d6(2) < target) {
continue;
}
//apply damage
trippedMine = true;
//explodedMines.add(mf);
mf.setDetonated(true);
if(mf.getType() == Minefield.TYPE_INFERNO) {
//report hitting an inferno mine
r = new Report(2155);
r.subject = entity.getId();
r.add(entity.getShortName(), true);
r.add(mf.getCoords().getBoardNum(), true);
vMineReport.add(r);
vMineReport.addAll(deliverInfernoMissiles(entity, entity, mf.getDensity() / 2));
} else {
r = new Report(2150);
r.subject = entity.getId();
r.add(entity.getShortName(), true);
r.add(mf.getCoords().getBoardNum(), true);
vMineReport.add(r);
int damage = mf.getDensity();
while(damage > 0) {
int cur_damage = Math.min(5, damage);
damage = damage - cur_damage;
HitData hit = entity.rollHitLocation(Minefield.TO_HIT_TABLE, Minefield.TO_HIT_SIDE);
vMineReport.addAll(damageEntity(entity, hit, cur_damage));
}
if(entity instanceof Tank) {
vMineReport.addAll(vehicleMotiveDamage((Tank)entity, 2));
}
}
//check the direct reduction
mf.checkReduction(0, true);
revealMinefield(mf);
}
return trippedMine;
}
/**
* cycle through all mines on the board, check to see whether they should do collateral damage
* to other mines due to detonation, resets detonation to false, and removes any mines whose density has
* been reduced to zero.
*/
private void resetMines() {
Enumeration<Coords> mineLoc = game.getMinedCoords();
while(mineLoc.hasMoreElements()) {
Coords c = mineLoc.nextElement();
Enumeration<Minefield> minefields = game.getMinefields(c).elements();
while (minefields.hasMoreElements()) {
Minefield minefield = minefields.nextElement();
if(minefield.hasDetonated()) {
minefield.setDetonated(false);
Enumeration<Minefield> otherMines = game.getMinefields(c).elements();
while (otherMines.hasMoreElements()) {
Minefield otherMine = otherMines.nextElement();
if(otherMine.equals(minefield)) {
continue;
}
int bonus = 0;
if(otherMine.getDensity() > minefield.getDensity()) {
bonus = 1;
}
if(otherMine.getDensity() < minefield.getDensity()) {
bonus = -1;
}
otherMine.checkReduction(bonus, false);
}
}
}
//cycle through a second time to see if any mines at these coords need to be removed
ArrayList<Minefield> mfRemoved = new ArrayList<Minefield>();
Enumeration<Minefield> mines = game.getMinefields(c).elements();
while (mines.hasMoreElements()) {
Minefield mine = mines.nextElement();
if(mine.getDensity() < 5) {
mfRemoved.add(mine);
}
}
//we have to do it this way to avoid a concurrent error problem
for(Minefield mf : mfRemoved) {
removeMinefield(mf);
}
//update the mines at these coords
sendChangedMines(c);
}
}
/**
* attempt to clear a minefield
* @param mf - a <code>Minefield</code> to clear
* @param en - <code>entity</code> doing the clearing
* @param target - <code>int</code> needed to roll for a successful clearance
* @return <code>true</code> if clearance successful
*/
public boolean clearMinefield(Minefield mf, Entity en, int target, Vector<Report> vClearReport) {
return clearMinefield(mf, en, target, -1, vClearReport);
}
/**
* attempt to clear a minefield
* We don't actually remove the minefield here, because if this is called up from within
* a loop, that will cause problems
* @param mf - a <code>Minefield</code> to clear
* @param en - <code>entity</code> doing the clearing
* @param target - <code>int</code> needed to roll for a successful clearance
* @param botch - <code>int</code> that indicates an accidental detonation
* @return <code>true</code> if clearance successful
*/
public boolean clearMinefield(Minefield mf, Entity en, int target, int botch, Vector<Report> vClearReport) {
Report r;
int roll = Compute.d6(2);
if(roll >= target) {
r = new Report(2250);
r.subject = en.getId();
r.add(Minefield.getDisplayableName(mf.getType()));
r.indent(1);
vClearReport.add(r);
return true;
}
else if(roll <= botch) {
//TODO: detonate the minefield
r = new Report(2255);
r.subject = en.getId();
r.indent(1);
r.add(Minefield.getDisplayableName(mf.getType()));
vClearReport.add(r);
//The detonation damages any units that were also attempting to
//clear mines in the same hex
for (Enumeration<Entity> e = game.getEntities(mf.getCoords()); e.hasMoreElements();) {
Entity victim = e.nextElement();
Report rVictim;
if(victim.isClearingMinefield()) {
rVictim = new Report(2265);
rVictim.subject = victim.getId();
rVictim.add(victim.getShortName(), true);
rVictim.indent(2);
vClearReport.add(rVictim);
int damage = mf.getDensity();
while(damage > 0) {
int cur_damage = Math.min(5, damage);
damage = damage - cur_damage;
HitData hit = victim.rollHitLocation(Minefield.TO_HIT_TABLE, Minefield.TO_HIT_SIDE);
vClearReport.addAll(damageEntity(victim, hit, cur_damage));
}
}
}
//reduction works differently here
if(mf.getType() == Minefield.TYPE_CONVENTIONAL) {
mf.setDensity(Math.max(5, mf.getDensity() - 5));
} else {
//congratulations, you cleared the mine by blowing yourself up
return true;
}
} else {
// failure
r = new Report(2260);
r.subject = en.getId();
r.indent(1);
r.add(Minefield.getDisplayableName(mf.getType()));
vClearReport.add(r);
}
return false;
}
/**
* Clear any detonated mines at these coords
*/
private void clearDetonatedMines(Coords c, int target) {
Enumeration<Minefield> minefields = game.getMinefields(c).elements();
ArrayList<Minefield> mfRemoved = new ArrayList<Minefield>();
while (minefields.hasMoreElements()) {
Minefield minefield = minefields.nextElement();
if(minefield.hasDetonated() && (Compute.d6(2) >= target)) {
mfRemoved.add(minefield);
}
}
//we have to do it this way to avoid a concurrent error problem
for(Minefield mf : mfRemoved) {
removeMinefield(mf);
}
}
/**
* Checks to see if an entity sets off any vibrabombs.
*/
private boolean checkVibrabombs(Entity entity, Coords coords, boolean displaced, Vector<Report> vMineReport) {
return checkVibrabombs(entity, coords, displaced, null, null, vMineReport);
}
/**
* Checks to see if an entity sets off any vibrabombs.
*/
private boolean checkVibrabombs(Entity entity, Coords coords, boolean displaced, Coords lastPos, Coords curPos, Vector<Report> vMineReport) {
boolean boom = false;
// Only mechs can set off vibrabombs.
if (!(entity instanceof Mech)) {
return boom;
}
int mass = (int) entity.getWeight();
Enumeration<Minefield> e = game.getVibrabombs().elements();
while (e.hasMoreElements()) {
Minefield mf = e.nextElement();
// Bug 954272: Mines shouldn't work underwater, and BMRr says
// Vibrabombs are mines
if (game.getBoard().getHex(mf.getCoords()).containsTerrain(Terrains.WATER) && !game.getBoard().getHex(mf.getCoords()).containsTerrain(Terrains.PAVEMENT) && !game.getBoard().getHex(mf.getCoords()).containsTerrain(Terrains.ICE)) {
continue;
}
// Mech weighing 10 tons or less can't set off the bomb
if (mass <= mf.getSetting() - 10) {
continue;
}
int effectiveDistance = (mass - mf.getSetting()) / 10;
int actualDistance = coords.distance(mf.getCoords());
if (actualDistance <= effectiveDistance) {
Report r = new Report(2156);
r.subject = entity.getId();
r.add(entity.getShortName(), true);
r.add(mf.getCoords().getBoardNum(), true);
vMineReport.add(r);
explodeVibrabomb(mf, vMineReport, false);
}
// Hack; when moving, the Mech isn't in the hex during
// the movement.
if (!displaced && (actualDistance == 0)) {
// report getting hit by vibrabomb
Report r = new Report(2160);
r.subject = entity.getId();
r.add(entity.getShortName(), true);
vMineReport.add(r);
int damage = mf.getDensity();
while(damage > 0) {
int cur_damage = Math.min(5, damage);
damage = damage - cur_damage;
HitData hit = entity.rollHitLocation(Minefield.TO_HIT_TABLE, Minefield.TO_HIT_SIDE);
vMineReport.addAll(damageEntity(entity, hit, cur_damage));
}
vMineReport.addAll(resolvePilotingRolls(entity, true, lastPos, curPos));
// we need to apply Damage now, in case the entity lost a leg,
// otherwise it won't get a leg missing mod if it hasn't yet
// moved and lost a leg, see bug 1071434 for an example
entity.applyDamage();
}
//don't check for reduction until the end or units in the same hex through
//movement will get the reduced damage
if(mf.hasDetonated()) {
boom = true;
mf.checkReduction(0, true);
}
}
return boom;
}
/**
* Remove all minefields in the specified coords from the game
*
* @param coords
* The <code>Coords</code> from which to remove minefields
*/
/*
public void removeMinefieldsFrom(Coords coords) {
Vector<Minefield> v = game.getMinefields(coords);
while (v.elements().hasMoreElements()) {
Minefield mf = v.elements().nextElement();
removeMinefield(mf);
}
}
*/
/**
* Removes the minefield from the game.
*
* @param mf
* The <code>Minefield</code> to remove
*/
public void removeMinefield(Minefield mf) {
if (game.containsVibrabomb(mf)) {
game.removeVibrabomb(mf);
}
game.removeMinefield(mf);
Enumeration<Player> players = game.getPlayers();
while (players.hasMoreElements()) {
Player player = players.nextElement();
removeMinefield(player, mf);
}
}
/**
* Removes the minfield from a player.
*
* @param player
* The <code>Player</code> who's minefield should be removed
* @param mf
* The <code>Minefield</code> to be removed
*/
private void removeMinefield(Player player, Minefield mf) {
if (player.containsMinefield(mf)) {
player.removeMinefield(mf);
send(player.getId(), new Packet(Packet.COMMAND_REMOVE_MINEFIELD, mf));
}
}
/**
* Reveals a minefield for all players.
*
* @param mf
* The <code>Minefield</code> to be revealed
*/
private void revealMinefield(Minefield mf) {
Enumeration<Team> teams = game.getTeams();
while (teams.hasMoreElements()) {
Team team = teams.nextElement();
revealMinefield(team, mf);
}
}
/**
* Reveals a minefield for all players on a team.
*
* @param team
* The <code>team</code> who's minefiled should be revealed
* @param mf
* The <code>Minefield</code> to be revealed
*/
private void revealMinefield(Team team, Minefield mf) {
Enumeration<Player> players = team.getPlayers();
while (players.hasMoreElements()) {
Player player = players.nextElement();
if (!player.containsMinefield(mf)) {
player.addMinefield(mf);
send(player.getId(), new Packet(Packet.COMMAND_REVEAL_MINEFIELD, mf));
}
}
}
/**
* checks whether a newly set mine should be revealed to players based on
* LOS. If so, then it reveals the mine
*/
private void checkForRevealMinefield(Minefield mf, Entity layer) {
Enumeration<Team> teams = game.getTeams();
//loop through each team and determine if they can see the mine, then loop through players on team
//and reveal the mine
while(teams.hasMoreElements()) {
Team team = teams.nextElement();
boolean canSee = false;
//the players own team can always see the mine
if(team.equals(game.getTeamForPlayer(game.getPlayer(mf.getPlayerId())))) {
canSee = true;
} else {
//need to loop through all entities on this team and find the one with the best shot of seeing
//the mine placement
int target = Integer.MAX_VALUE;
Enumeration<Entity> entities = game.getEntities();
while(entities.hasMoreElements()) {
Entity en = entities.nextElement();
//are we on the right team?
if(!team.equals(game.getTeamForPlayer(en.getOwner()))) {
continue;
}
if(LosEffects.calculateLos(game, en.getId(),
new HexTarget(mf.getCoords(), game.getBoard(), Targetable.TYPE_HEX_CLEAR)).canSee()) {
target = 0;
break;
}
LosEffects los = LosEffects.calculateLos(game, en.getId(), layer);
if(los.canSee()) {
//TODO: need to add mods
ToHitData current = new ToHitData(4, "base");
current.append(Compute.getAttackerMovementModifier(game, en.getId()));
current.append(Compute.getTargetMovementModifier(game, layer.getId()));
current.append(los.losModifiers(game));
if(current.getValue() < target) {
target = current.getValue();
}
}
}
if(Compute.d6(2) >= target) {
canSee = true;
}
}
if(canSee) {
revealMinefield(team, mf);
}
}
}
/**
* Explodes a vibrabomb.
*
* @param mf
* The <code>Minefield</code> to explode
*/
private void explodeVibrabomb(Minefield mf, Vector<Report> vBoomReport, boolean reduce) {
Enumeration<Entity> targets = game.getEntities(mf.getCoords());
Report r;
while (targets.hasMoreElements()) {
Entity entity = targets.nextElement();
// check for the "no_premove_vibra" option
// If it's set, and the target has not yet moved,
// it doesn't get damaged.
if (!entity.isDone() && game.getOptions().booleanOption("no_premove_vibra")) {
r = new Report(2157);
r.subject = entity.getId();
r.add(entity.getShortName(), true);
vBoomReport.add(r);
continue;
}
// report hitting vibrabomb
r = new Report(2160);
r.subject = entity.getId();
r.add(entity.getShortName(), true);
vBoomReport.add(r);
int damage = mf.getDensity();
while(damage > 0) {
int cur_damage = Math.min(5, damage);
damage = damage - cur_damage;
HitData hit = entity.rollHitLocation(Minefield.TO_HIT_TABLE, Minefield.TO_HIT_SIDE);
vBoomReport.addAll(damageEntity(entity, hit, cur_damage));
}
Report.addNewline(vBoomReport);
if(entity instanceof Tank) {
vBoomReport.addAll(vehicleMotiveDamage((Tank)entity, 2));
}
vBoomReport.addAll(resolvePilotingRolls(entity, true, entity.getPosition(), entity.getPosition()));
// we need to apply Damage now, in case the entity lost a leg,
// otherwise it won't get a leg missing mod if it hasn't yet
// moved and lost a leg, see bug 1071434 for an example
game.resetPSRs(entity);
entity.applyDamage();
Report.addNewline(vBoomReport);
entityUpdate(entity.getId());
}
//check the direct reduction of mine
if(reduce) {
mf.checkReduction(0, true);
}
mf.setDetonated(true);
}
/**
* drowns any units swarming the entity
*
* @param entity
* The <code>Entity</code> that is being swarmed
* @param pos
* The <code>Coords</code> the entity is at
*/
private void drownSwarmer(Entity entity, Coords pos) {
// Any swarming infantry will be destroyed.
final int swarmerId = entity.getSwarmAttackerId();
if (Entity.NONE != swarmerId) {
final Entity swarmer = game.getEntity(swarmerId);
// Only *platoons* drown while swarming.
if (!(swarmer instanceof BattleArmor)) {
swarmer.setSwarmTargetId(Entity.NONE);
entity.setSwarmAttackerId(Entity.NONE);
swarmer.setPosition(pos);
Report r = new Report(2165);
r.subject = entity.getId();
r.indent();
r.add(entity.getShortName(), true);
addReport(r);
addReport(destroyEntity(swarmer, "a watery grave", false));
entityUpdate(swarmerId);
}
}
}
/**
* Checks to see if we may have just washed off infernos. Call after a step
* which may have done this.
*
* @param entity
* The <code>Entity</code> that is being checked
* @param coords
* The <code>Coords</code> the entity is at
*/
void checkForWashedInfernos(Entity entity, Coords coords) {
IHex hex = game.getBoard().getHex(coords);
int waterLevel = hex.terrainLevel(Terrains.WATER);
// Mech on fire with infernos can wash them off.
if (!(entity instanceof Mech) || !entity.infernos.isStillBurning()) {
return;
}
// Check if entering depth 2 water or prone in depth 1.
if ((waterLevel > 0) && (entity.absHeight() < 0)) {
washInferno(entity, coords);
}
}
/**
* Washes off an inferno from a mech and adds it to the (water) hex.
*
* @param entity
* The <code>Entity</code> that is taking a bath
* @param coords
* The <code>Coords</code> the entity is at
*/
void washInferno(Entity entity, Coords coords) {
game.getBoard().addInfernoTo(coords, InfernoTracker.STANDARD_ROUND, 1);
entity.infernos.clear();
// Start a fire in the hex?
IHex hex = game.getBoard().getHex(coords);
Report r = new Report(2170);
r.subject = entity.getId();
r.addDesc(entity);
if (!hex.containsTerrain(Terrains.FIRE)) {
r.messageId = 2175;
ignite(coords, true, null);
}
addReport(r);
}
/**
* Add heat from the movement phase
*/
public void addMovementHeat() {
for (Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
Entity entity = i.nextElement();
if ((entity.getMovementMode() == IEntityMovementMode.BIPED_SWIM) || (entity.getMovementMode() == IEntityMovementMode.QUAD_SWIM)) {
return;
}
// build up heat from movement
if(entity.isEvading() && !(entity instanceof Aero)) {
entity.heatBuildup += entity.getRunHeat() + 2;
} else if (entity.moved == IEntityMovementType.MOVE_NONE) {
entity.heatBuildup += entity.getStandingHeat();
} else if ((entity.moved == IEntityMovementType.MOVE_WALK) || (entity.moved == IEntityMovementType.MOVE_VTOL_WALK) || (entity.moved == IEntityMovementType.MOVE_CAREFUL_STAND)) {
entity.heatBuildup += entity.getWalkHeat();
} else if ((entity.moved == IEntityMovementType.MOVE_RUN) || (entity.moved == IEntityMovementType.MOVE_VTOL_RUN) || (entity.moved == IEntityMovementType.MOVE_SKID)) {
entity.heatBuildup += entity.getRunHeat();
} else if (entity.moved == IEntityMovementType.MOVE_JUMP) {
entity.heatBuildup += entity.getJumpHeat(entity.delta_distance);
}
}
}
/**
* Set the locationsexposure of an entity
*
* @param entity
* The <code>Entity</code> who's exposure is being set
* @param hex
* The <code>IHex</code> the entity is in
* @param isJump
* a <code>boolean</code> value wether the entity is jumping
* @param elevation
* the elevation the entity should be at.
*/
public Vector<Report> doSetLocationsExposure(Entity entity, IHex hex, boolean isJump, int elevation) { Vector<Report> vPhaseReport = new Vector<Report>();
if ((hex.terrainLevel(Terrains.WATER) > 0) && !isJump && (elevation < 0)) {
if ((entity instanceof Mech) && !entity.isProne() && (hex.terrainLevel(Terrains.WATER) == 1)) {
for (int loop = 0; loop < entity.locations(); loop++) {
if (game.getPlanetaryConditions().isVacuum()) {
entity.setLocationStatus(loop, ILocationExposureStatus.VACUUM);
} else {
entity.setLocationStatus(loop, ILocationExposureStatus.NORMAL);
}
}
entity.setLocationStatus(Mech.LOC_RLEG, ILocationExposureStatus.WET);
entity.setLocationStatus(Mech.LOC_LLEG, ILocationExposureStatus.WET);
vPhaseReport.addAll(breachCheck(entity, Mech.LOC_RLEG, hex));
vPhaseReport.addAll(breachCheck(entity, Mech.LOC_LLEG, hex));
if (entity instanceof QuadMech) {
entity.setLocationStatus(Mech.LOC_RARM, ILocationExposureStatus.WET);
entity.setLocationStatus(Mech.LOC_LARM, ILocationExposureStatus.WET);
vPhaseReport.addAll(breachCheck(entity, Mech.LOC_RARM, hex));
vPhaseReport.addAll(breachCheck(entity, Mech.LOC_LARM, hex));
}
} else {
for (int loop = 0; loop < entity.locations(); loop++) {
entity.setLocationStatus(loop, ILocationExposureStatus.WET);
vPhaseReport.addAll(breachCheck(entity, loop, hex));
}
}
} else {
for (int loop = 0; loop < entity.locations(); loop++) {
if (game.getPlanetaryConditions().isVacuum()) {
entity.setLocationStatus(loop, ILocationExposureStatus.VACUUM);
} else {
entity.setLocationStatus(loop, ILocationExposureStatus.NORMAL);
}
}
}
return vPhaseReport;
}
/**
* Do a roll to avoid pilot damage from g-forces
*
* @param entity
* The <code>Entity</code> that should make the PSR
* @param roll
* The <code>PilotingRollData</code> to be used for this PSR.
*
* @return true if check succeeds, false otherwise.
*
*/
private void resistGForce(Entity entity, int targetroll) {
// okay, print the info
Report r = new Report(9330);
r.subject = entity.getId();
r.addDesc(entity);
addReport(r);
// roll
final int diceRoll = Compute.d6(2);
r = new Report(9335);
r.subject = entity.getId();
r.add(Integer.toString(targetroll));
r.add(diceRoll);
if (diceRoll < targetroll) {
r.choose(false);
addReport(r);
addReport(damageCrew(entity, 1));
} else {
r.choose(true);
addReport(r);
}
}
/**
* Do a piloting skill check in space to avoid structural damage
*
* @param entity
* The <code>Entity</code> that should make the PSR
* @param roll
* The <code>PilotingRollData</code> to be used for this PSR.
*
* @return true if check succeeds, false otherwise.
*
*/
private boolean doSkillCheckInSpace(Entity entity, PilotingRollData roll) {
if (roll.getValue() == TargetRoll.AUTOMATIC_SUCCESS) {
return true;
}
// okay, print the info
Report r = new Report(9320);
r.subject = entity.getId();
r.addDesc(entity);
r.add(roll.getLastPlainDesc(), true);
addReport(r);
// roll
final int diceRoll = Compute.d6(2);
r = new Report(9325);
r.subject = entity.getId();
r.add(roll.getValueAsString());
r.add(roll.getDesc());
r.add(diceRoll);
boolean suc;
if (diceRoll < roll.getValue()) {
r.choose(false);
addReport(r);
suc = false;
} else {
r.choose(true);
addReport(r);
suc = true;
}
return suc;
}
/**
* Do a piloting skill check in space to do a successful maneuver Failure
* means moving forward half velocity
*
* @param entity
* The <code>Entity</code> that should make the PSR
* @param roll
* The <code>PilotingRollData</code> to be used for this PSR.
*
* @return true if check succeeds, false otherwise.
*
*/
private boolean doSkillCheckManeuver(Entity entity, PilotingRollData roll) {
if (roll.getValue() == TargetRoll.AUTOMATIC_SUCCESS) {
return true;
}
// okay, print the info
Report r = new Report(9600);
r.subject = entity.getId();
r.addDesc(entity);
r.add(roll.getLastPlainDesc(), true);
addReport(r);
// roll
final int diceRoll = Compute.d6(2);
r = new Report(9601);
r.subject = entity.getId();
r.add(roll.getValueAsString());
r.add(roll.getDesc());
r.add(diceRoll);
boolean suc;
if (diceRoll < roll.getValue()) {
r.choose(false);
addReport(r);
suc = false;
} else {
r.choose(true);
addReport(r);
suc = true;
}
return suc;
}
/**
* Do a piloting skill check while standing still (during the movement
* phase).
*
* @param entity
* The <code>Entity</code> that should make the PSR
* @param roll
* The <code>PilotingRollData</code> to be used for this PSR.
* @return true if check succeeds, false otherwise.
*/
private boolean doSkillCheckInPlace(Entity entity, PilotingRollData roll) {
if (roll.getValue() == TargetRoll.AUTOMATIC_SUCCESS) {
return true;
}
if (entity.isProne()) {
return true;
}
// okay, print the info
Report r = new Report(2180);
r.subject = entity.getId();
r.addDesc(entity);
r.add(roll.getLastPlainDesc(), true);
addReport(r);
// roll
final int diceRoll = Compute.d6(2);
r = new Report(2185);
r.subject = entity.getId();
r.add(roll.getValueAsString());
r.add(roll.getDesc());
r.add(diceRoll);
boolean suc;
if (diceRoll < roll.getValue() ) {
r.choose(false);
addReport(r);
if ( (entity instanceof Mech)
&& game.getOptions().booleanOption("tacops_falling_expanded")
&& (entity.getCrew().getPiloting() < 6)
&& !entity.isHullDown()
&& entity.canGoHullDown() ){
if ( (entity.getCrew().getPiloting() > 1) && (roll.getValue() - diceRoll < 2)){
entity.setHullDown(true);
}else if ( (entity.getCrew().getPiloting() <= 1) && (roll.getValue() - diceRoll < 3) ){
entity.setHullDown(true);
}
}
if ( !entity.isHullDown() || ( entity.isHullDown() && !entity.canGoHullDown()) ) {
addReport(doEntityFall(entity, roll));
}
else{
r = new Report (2317);
r.subject = entity.getId();
r.add(entity.getDisplayName());
addReport(r);
}
suc = false;
// failed a PSR, possibly check for engine stalling
entity.doCheckEngineStallRoll(vPhaseReport);
} else {
r.choose(true);
addReport(r);
suc = true;
}
return suc;
}
/**
* Do a Piloting Skill check to dislogde swarming infantry.
*
* @param entity
* The <code>Entity</code> that is doing the dislodging.
* @param roll
* The <code>PilotingRollData</code> for this PSR.
* @param curPos
* The <code>Coords</code> the entity is at.
* @return <code>true</code> if the dislodging is successful.
*/
private boolean doDislodgeSwarmerSkillCheck(Entity entity, PilotingRollData roll, Coords curPos) {
// okay, print the info
Report r = new Report(2180);
r.subject = entity.getId();
r.addDesc(entity);
r.add(roll.getLastPlainDesc(), true);
addReport(r);
// roll
final int diceRoll = Compute.d6(2);
r = new Report(2190);
r.subject = entity.getId();
r.add(roll.getValueAsString());
r.add(roll.getDesc());
r.add(diceRoll);
if (diceRoll < roll.getValue()) {
r.choose(false);
addReport(r);
// failed a PSR, possibly check for engine stalling
entity.doCheckEngineStallRoll(vPhaseReport);
return false;
}
// Dislodged swarmers don't get turns.
int swarmerId = entity.getSwarmAttackerId();
final Entity swarmer = game.getEntity(swarmerId);
if (!swarmer.isDone()) {
swarmer.setDone(true);
game.removeTurnFor(swarmer);
send(createTurnVectorPacket());
}
// Update the report and cause a fall.
r.choose(true);
addReport(r);
entity.setPosition(curPos);
addReport(doEntityFallsInto(entity, curPos, curPos, roll, false));
return true;
}
/**
* Do a piloting skill check while moving.
*
* @param entity -
* the <code>Entity</code> that must roll.
* @param src -
* the <code>Coords</code> the entity is moving from.
* @param dest -
* the <code>Coords</code> the entity is moving to. This value
* can be the same as src for in-place checks.
* @param roll -
* the <code>PilotingRollData</code> that is causing this
* check.
* @param isFallRoll -
* a <code>boolean</code> flag that indicates that failure will
* result in a fall or not. Falls will be processed.
* @return Margin of Failure if the pilot fails the skill check, 0 if they
* pass.
*/
private int doSkillCheckWhileMoving(Entity entity, Coords src, Coords dest, PilotingRollData roll, boolean isFallRoll) {
boolean fallsInPlace;
// Start the info for this roll.
Report r = new Report(1210);
r.subject = entity.getId();
r.addDesc(entity);
// Will the entity fall in the source or destination hex?
if (src.equals(dest)) {
fallsInPlace = true;
r.messageId = 2195;
r.add(src.getBoardNum(), true);
} else {
fallsInPlace = false;
r.messageId = 2200;
r.add(src.getBoardNum(), true);
r.add(dest.getBoardNum(), true);
}
// Finish the info.
r.add(roll.getLastPlainDesc(), true);
addReport(r);
// roll
final int diceRoll = Compute.d6(2);
r = new Report(2185);
r.subject = entity.getId();
r.add(roll.getValueAsString());
r.add(roll.getDesc());
r.add(diceRoll);
if (diceRoll < roll.getValue()) {
// Does failing the PSR result in a fall.
if (isFallRoll) {
r.choose(false);
addReport(r);
addReport(doEntityFallsInto(entity, fallsInPlace ? dest : src, fallsInPlace ? src : dest, roll));
} else {
r.messageId = 2190;
r.choose(false);
addReport(r);
entity.setPosition(fallsInPlace ? src : dest);
}
// failed a PSR, possibly check for engine stalling
entity.doCheckEngineStallRoll(vPhaseReport);
return roll.getValue() - diceRoll;
}
r.choose(true);
addReport(r);
return 0;
}
/**
* The entity falls into the hex specified. Check for any conflicts and
* resolve them. Deal damage to faller.
*
* @param entity
* The <code>Entity</code> that is falling.
* @param src
* The <code>Coords</code> of the source hex.
* @param dest
* The <code>Coords</code> of the destination hex.
* @param roll
* The <code>PilotingRollData</code> to be used for PSRs
* induced by the falling.
*/
private Vector<Report> doEntityFallsInto(Entity entity, Coords src, Coords dest, PilotingRollData roll) {
return doEntityFallsInto(entity, src, dest, roll, true);
}
/**
* The entity falls into the hex specified. Check for any conflicts and
* resolve them. Deal damage to faller.
*
* @param entity
* The <code>Entity</code> that is falling.
* @param src
* The <code>Coords</code> of the source hex.
* @param dest
* The <code>Coords</code> of the destination hex.
* @param roll
* The <code>PilotingRollData</code> to be used for PSRs
* induced by the falling.
* @param causeAffa
* The <code>boolean</code> value wether this fall should be
* able to cause an accidental fall from above
*/
private Vector<Report> doEntityFallsInto(Entity entity, Coords src, Coords dest, PilotingRollData roll, boolean causeAffa) {
Vector<Report> vPhaseReport = new Vector<Report>();
final IHex srcHex = game.getBoard().getHex(src);
final IHex destHex = game.getBoard().getHex(dest);
final int srcHeightAboveFloor = entity.getElevation() + srcHex.depth();
final int fallElevation = Math.max(0, srcHex.floor() + srcHeightAboveFloor - (destHex.containsTerrain(Terrains.ICE) ? destHex.surface() : destHex.floor()));
int direction;
if (src.equals(dest)) {
direction = Compute.d6() - 1;
} else {
direction = src.direction(dest);
}
Report r;
// check entity in target hex
Entity affaTarget = game.getAffaTarget(dest, entity);
// falling mech falls
r = new Report(2205);
r.subject = entity.getId();
r.addDesc(entity);
r.add(fallElevation);
r.add(dest.getBoardNum(), true);
vPhaseReport.add(r);
// if hex was empty, deal damage and we're done
if (affaTarget == null) {
vPhaseReport.addAll(doEntityFall(entity, dest, fallElevation, roll));
return vPhaseReport;
}
// hmmm... somebody there... problems.
if ((fallElevation >= 2) && causeAffa) {
// accidental fall from above: havoc!
r = new Report(2210);
r.subject = entity.getId();
r.addDesc(affaTarget);
vPhaseReport.add(r);
// determine to-hit number
ToHitData toHit = new ToHitData(7, "base");
if (affaTarget instanceof Tank) {
toHit = new ToHitData(TargetRoll.AUTOMATIC_SUCCESS, "Target is a Tank");
} else {
toHit.append(Compute.getTargetMovementModifier(game, affaTarget.getId()));
toHit.append(Compute.getTargetTerrainModifier(game, affaTarget));
}
if (toHit.getValue() != TargetRoll.AUTOMATIC_FAIL) {
// collision roll
final int diceRoll = Compute.d6(2);
if (toHit.getValue() == TargetRoll.AUTOMATIC_SUCCESS) {
r = new Report(2212);
r.add(toHit.getValue());
r.indent();
} else {
r = new Report(2215);
r.subject = entity.getId();
r.add(toHit.getValue());
r.add(diceRoll);
r.newlines = 0;
r.indent();
}
vPhaseReport.add(r);
if (diceRoll >= toHit.getValue()) {
// deal damage to target
int damage = Compute.getAffaDamageFor(entity);
r = new Report(2220);
r.subject = affaTarget.getId();
r.addDesc(affaTarget);
r.add(damage);
vPhaseReport.add(r);
while (damage > 0) {
int cluster = Math.min(5, damage);
HitData hit = affaTarget.rollHitLocation(ToHitData.HIT_PUNCH, ToHitData.SIDE_FRONT);
hit.setGeneralDamageType(HitData.DAMAGE_PHYSICAL);
vPhaseReport.addAll(damageEntity(affaTarget, hit, cluster));
damage -= cluster;
}
// attacker falls as normal, on his back
// only given a modifier, so flesh out into a full piloting
// roll
PilotingRollData pilotRoll = entity.getBasePilotingRoll();
pilotRoll.append(roll);
//entity.addPilotingModifierForTerrain(pilotRoll, dest);
vPhaseReport.addAll(doEntityFall(entity, dest, fallElevation, 3, pilotRoll));
vPhaseReport.addAll(doEntityDisplacementMinefieldCheck(entity, src, dest, entity.getElevation()));
// defender pushed away, or destroyed, if there is a
// stacking violation
Entity violation = Compute.stackingViolation(game, entity.getId(), dest);
if (violation != null) {
Coords targetDest = Compute.getValidDisplacement(game, violation.getId(), dest, direction);
if (targetDest != null) {
vPhaseReport.addAll(doEntityDisplacement(affaTarget, dest, targetDest, new PilotingRollData(violation.getId(), 2, "fallen on")));
// Update the violating entity's postion on the
// client.
entityUpdate(affaTarget.getId());
} else {
// ack! automatic death! Tanks
// suffer an ammo/power plant hit.
// TODO : a Mech suffers a Head Blown Off crit.
vPhaseReport.addAll(destroyEntity(affaTarget, "impossible displacement", violation instanceof Mech, violation instanceof Mech));
}
}
return vPhaseReport;
}
} else {
// automatic miss
r = new Report(2213);
r.add(toHit.getDesc());
vPhaseReport.add(r);
}
// ok, we missed, let's fall into a valid other hex and not cause an
// AFFA while doing so
Coords targetDest = Compute.getValidDisplacement(game, entity.getId(), dest, direction);
if (targetDest != null) {
vPhaseReport.addAll(doEntityFallsInto(entity, src, targetDest, new PilotingRollData(entity.getId(), TargetRoll.IMPOSSIBLE, "pushed off a cliff"), false));
// Update the entity's postion on the client.
entityUpdate(entity.getId());
} else {
// ack! automatic death! Tanks
// suffer an ammo/power plant hit.
// TODO : a Mech suffers a Head Blown Off crit.
vPhaseReport.addAll(destroyEntity(entity, "impossible displacement", entity instanceof Mech, entity instanceof Mech));
}
} else {
// damage as normal
vPhaseReport.addAll(doEntityFall(entity, dest, fallElevation, roll));
Entity violation = Compute.stackingViolation(game, entity.getId(), dest);
if (violation != null) {
// target gets displaced, because of low elevation
Coords targetDest = Compute.getValidDisplacement(game, entity.getId(), dest, direction);
vPhaseReport.addAll(doEntityDisplacement(violation, dest, targetDest, new PilotingRollData(violation.getId(), 0, "domino effect")));
// Update the violating entity's postion on the client.
entityUpdate(violation.getId());
}
}
return vPhaseReport;
}
/**
* Displace a unit in the direction specified. The unit moves in that
* direction, and the piloting skill roll is used to determine if it falls.
* The roll may be unnecessary as certain situations indicate an automatic
* fall. Rolls are added to the piloting roll list.
*/
private Vector<Report> doEntityDisplacement(Entity entity, Coords src, Coords dest, PilotingRollData roll) {
Vector<Report> vPhaseReport = new Vector<Report>();
Report r;
if (!game.getBoard().contains(dest)) {
// set position anyway, for pushes moving through and stuff like
// that
entity.setPosition(dest);
if (!entity.isDoomed()) {
game.removeEntity(entity.getId(), IEntityRemovalConditions.REMOVE_PUSHED);
send(createRemoveEntityPacket(entity.getId(), IEntityRemovalConditions.REMOVE_PUSHED));
// entity forced from the field
r = new Report(2230);
r.subject = entity.getId();
r.addDesc(entity);
vPhaseReport.add(r);
// TODO: remove passengers and swarmers.
}
return vPhaseReport;
}
final IHex srcHex = game.getBoard().getHex(src);
final IHex destHex = game.getBoard().getHex(dest);
final int direction = src.direction(dest);
// Handle null hexes.
if ((srcHex == null) || (destHex == null)) {
System.err.println("Can not displace " + entity.getShortName() + " from " + src + " to " + dest + '.');
return vPhaseReport;
}
int fallElevation = entity.elevationOccupied(srcHex) - entity.elevationOccupied(destHex);
if (fallElevation > 1) {
if (roll == null) {
roll = entity.getBasePilotingRoll();
}
vPhaseReport.addAll(doEntityFallsInto(entity, src, dest, roll));
return vPhaseReport;
}
// unstick the entity if it was stuck in swamp
boolean wasStuck = entity.isStuck();
entity.setStuck(false);
int oldElev = entity.elevationOccupied(srcHex);
// move the entity into the new location gently
entity.setPosition(dest);
entity.setElevation(entity.elevationOccupied(destHex) - destHex.surface());
Building bldg = game.getBoard().getBuildingAt(dest);
if (bldg != null) {
if (destHex.terrainLevel(Terrains.BLDG_ELEV) > oldElev) {
// woops, into the building we go
passBuildingWall(entity, game.getBoard().getBuildingAt(dest), src, dest, 1, "displaced into", Math.abs(entity.getFacing() - src.direction(dest)) == 3, entity.moved);
} else {
// woops, we step on the roof
checkBuildingCollapseWhileMoving(bldg, entity, dest);
}
}
Entity violation = Compute.stackingViolation(game, entity.getId(), dest);
if (violation == null) {
// move and roll normally
r = new Report(2235);
r.indent();
r.subject = entity.getId();
r.addDesc(entity);
r.add(dest.getBoardNum(), true);
vPhaseReport.add(r);
} else {
// domino effect: move & displace target
r = new Report(2240);
r.indent();
r.subject = entity.getId();
r.addDesc(entity);
r.add(dest.getBoardNum(), true);
r.addDesc(violation);
vPhaseReport.add(r);
}
// trigger any special things for moving to the new hex
vPhaseReport.addAll(doEntityDisplacementMinefieldCheck(entity, src, dest, entity.getElevation()));
vPhaseReport.addAll(doSetLocationsExposure(entity, destHex, false, entity.getElevation()));
//mechs that were stuck will automatically fall in their new hex
if(wasStuck && (entity instanceof Mech) && !entity.isProne()) {
if (roll == null) {
roll = entity.getBasePilotingRoll();
}
vPhaseReport.addAll(doEntityFall(entity, dest, 0, roll));
}
//check bog-down conditions
vPhaseReport.addAll(doEntityDisplacementBogDownCheck(entity, dest, entity.getElevation()));
if (roll != null) {
game.addPSR(roll);
}
// Update the entity's postion on the client.
entityUpdate(entity.getId());
if (violation != null) {
vPhaseReport.addAll(doEntityDisplacement(violation, dest, dest.translated(direction), new PilotingRollData(violation.getId(), 0, "domino effect")));
// Update the violating entity's postion on the client,
// if it didn't get displaced off the board.
if (!game.isOutOfGame(violation)) {
entityUpdate(violation.getId());
}
}
return vPhaseReport;
}
private Vector<Report> doEntityDisplacementMinefieldCheck(Entity entity, Coords src, Coords dest, int elev) {
Vector<Report> vPhaseReport = new Vector<Report>();
boolean boom = checkVibrabombs(entity, dest, true, vPhaseReport);
if (game.containsMinefield(dest)) {
boom = enterMinefield(entity, dest, elev, true, vPhaseReport) || boom;
}
if(boom) {
resetMines();
}
return vPhaseReport;
}
private Vector<Report> doEntityDisplacementBogDownCheck(Entity entity, Coords c, int elev) {
Vector<Report> vReport = new Vector<Report>();
Report r;
IHex destHex = game.getBoard().getHex(c);
int bgMod = destHex.getBogDownModifier(entity.getMovementMode(), entity instanceof LargeSupportTank);
if ((bgMod != TargetRoll.AUTOMATIC_SUCCESS)
&& (entity.getMovementMode() != IEntityMovementMode.HOVER)
&& (entity.getMovementMode() != IEntityMovementMode.WIGE)
&& (elev == 0)) {
PilotingRollData roll = entity.getBasePilotingRoll();
roll.append(new PilotingRollData(entity.getId(), bgMod, "avoid bogging down"));
int stuckroll = Compute.d6(2);
if(stuckroll < roll.getValue()) {
entity.setStuck(true);
r = new Report(2081);
r.subject = entity.getId();
r.add(entity.getDisplayName(), true);
vReport.add(r);
//check for quicksand
vReport.addAll(checkQuickSand(c));
}
}
return vReport;
}
/**
* Receive a deployment packet. If valid, execute it and end the current
* turn.
*/
private void receiveDeployment(Packet packet, int connId) {
Entity entity = game.getEntity(packet.getIntValue(0));
Coords coords = (Coords) packet.getObject(1);
int nFacing = packet.getIntValue(2);
// Handle units that deploy loaded with other units.
int loadedCount = packet.getIntValue(3);
Vector<Entity> loadVector = new Vector<Entity>();
for (int i = 0; i < loadedCount; i++) {
int loadedId = packet.getIntValue(5 + i);
loadVector.addElement(game.getEntity(loadedId));
}
// is this the right phase?
if (game.getPhase() != IGame.Phase.PHASE_DEPLOYMENT) {
System.err.println("error: server got deployment packet in wrong phase");
return;
}
// can this player/entity act right now?
final boolean assaultDrop = packet.getBooleanValue(4);
if (!game.getTurn().isValid(connId, entity, game) || !(game.getBoard().isLegalDeployment(coords, entity.getOwner()) || (assaultDrop && game.getOptions().booleanOption("assault_drop") && entity.canAssaultDrop()))) {
System.err.println("error: server got invalid deployment packet");
return;
}
// looks like mostly everything's okay
processDeployment(entity, coords, nFacing, loadVector, assaultDrop);
// Update visibility indications if using double blind.
if (doBlind()) {
updateVisibilityIndicator();
}
endCurrentTurn(entity);
}
/**
* Process a deployment packet by... deploying the entity! We load any other
* specified entities inside of it too. Also, check that the deployment is
* valid.
*/
private void processDeployment(Entity entity, Coords coords, int nFacing, Vector<Entity> loadVector, boolean assaultDrop) {
for (Entity loaded : loadVector) {
if ((loaded == null) || (loaded.getPosition() != null) || (loaded.getTransportId() != Entity.NONE)) {
// Something is fishy in Denmark.
System.err.println("error: " + entity + " can not load entity #" + loaded);
break;
}
// Have the deployed unit load the indicated unit.
loadUnit(entity, loaded);
}
/*
* deal with starting velocity for advanced movement. Probably not the
* best place to do it, but what are you going to do
*/
if ((entity instanceof Aero) && game.useVectorMove()) {
Aero a = (Aero) entity;
if (a.getCurrentVelocityActual() > 0) {
int[] v = { 0, 0, 0, 0, 0, 0 };
v[nFacing] = a.getCurrentVelocityActual();
entity.setVectors(v);
}
}
entity.setPosition(coords);
entity.setFacing(nFacing);
entity.setSecondaryFacing(nFacing);
IHex hex = game.getBoard().getHex(coords);
if (assaultDrop) {
entity.setElevation(hex.ceiling() - hex.surface() + 100); // falling
// from
// the
// sky!
entity.setAssaultDropInProgress(true);
} else if (entity instanceof VTOL) {
// We should let players pick, but this simplifies a lot.
// Only do it for VTOLs, though; assume everything else is on the
// ground.
entity.setElevation(hex.ceiling() - hex.surface() + 1);
while ((Compute.stackingViolation(game, entity, coords, null) != null) && (entity.getElevation() <= 50)) {
entity.setElevation(entity.getElevation() + 1);
}
if (entity.getElevation() > 50) {
throw new IllegalStateException("Entity #" + entity.getId() + " appears to be in an infinite loop trying to get a legal elevation.");
}
} else if (entity instanceof Aero) {
if (game.getBoard().inAtmosphere()) {
// all spheroid craft should have velocity of zero in atmosphere
// regardless of
// what was entered
Aero a = (Aero) entity;
if (a.isSpheroid() || game.getPlanetaryConditions().isVacuum()) {
a.setCurrentVelocity(0);
a.setNextVelocity(0);
}
} else if (game.getBoard().inSpace()) {
entity.setElevation(0);
} else {
entity.setElevation(hex.floor() - hex.surface());
}
} else if (entity.getMovementMode() == IEntityMovementMode.SUBMARINE) {
// TODO: Submarines should have a selectable height.
// For now, pretend they're regular naval.
entity.setElevation(0);
} else if ((entity.getMovementMode() == IEntityMovementMode.HOVER) || (entity.getMovementMode() == IEntityMovementMode.WIGE) || (entity.getMovementMode() == IEntityMovementMode.NAVAL) || (entity.getMovementMode() == IEntityMovementMode.HYDROFOIL)) {
// For now, assume they're on the surface.
// entity elevation is relative to hex surface
entity.setElevation(0);
} else if (hex.containsTerrain(Terrains.ICE)) {
entity.setElevation(0);
} else if (hex.containsTerrain(Terrains.BRIDGE)) {
entity.setElevation(hex.terrainLevel(Terrains.BRIDGE_ELEV));
} else {
Building bld = game.getBoard().getBuildingAt(entity.getPosition());
if ((bld != null) && (bld.getType() == Building.WALL)) {
entity.setElevation(hex.terrainLevel(Terrains.BLDG_ELEV));
} else {
// For anything else, assume they're on the floor.
// entity elevation is relative to hex surface
entity.setElevation(hex.floor() - hex.surface());
}
}
entity.setDone(true);
entity.setDeployed(true);
entityUpdate(entity.getId());
}
/**
* receive a packet that contains hexes that are automatically hit by
* artillery
*
* @param packet
* @param connId
*/
@SuppressWarnings("unchecked")
private void receiveArtyAutoHitHexes(Packet packet, int connId) {
PlayerIDandList<Coords> artyAutoHitHexes = (PlayerIDandList<Coords>) packet.getObject(0);
int playerId = artyAutoHitHexes.getPlayerID();
// is this the right phase?
if (game.getPhase() != IGame.Phase.PHASE_SET_ARTYAUTOHITHEXES) {
System.err.println("error: server got set artyautohithexespacket in wrong phase");
return;
}
game.getPlayer(playerId).setArtyAutoHitHexes(artyAutoHitHexes);
for (Coords coord : artyAutoHitHexes) {
game.getBoard().addSpecialHexDisplay(coord, new SpecialHexDisplay(SpecialHexDisplay.Type.ARTILLERY_AUTOHIT, SpecialHexDisplay.NO_ROUND, getPlayer(playerId).getName(), "ArtyAutoHit Hex, better text later"));
}
endCurrentTurn(null);
}
/**
* receive a packet that contains minefields
*
* @param packet
* @param connId
*/
@SuppressWarnings("unchecked")
private void receiveDeployMinefields(Packet packet, int connId) {
Vector<Minefield> minefields = (Vector<Minefield>) packet.getObject(0);
// is this the right phase?
if (game.getPhase() != IGame.Phase.PHASE_DEPLOY_MINEFIELDS) {
System.err.println("error: server got deploy minefields packet in wrong phase");
return;
}
// looks like mostly everything's okay
processDeployMinefields(minefields);
endCurrentTurn(null);
}
/**
* process deployment of minefields
*
* @param minefields
*/
private void processDeployMinefields(Vector<Minefield> minefields) {
int playerId = Player.PLAYER_NONE;
for (int i = 0; i < minefields.size(); i++) {
Minefield mf = minefields.elementAt(i);
playerId = mf.getPlayerId();
game.addMinefield(mf);
if (mf.getType() == Minefield.TYPE_VIBRABOMB) {
game.addVibrabomb(mf);
}
}
Player player = game.getPlayer(playerId);
if (null != player) {
int teamId = player.getTeam();
if (teamId != Player.TEAM_NONE) {
Enumeration<Team> teams = game.getTeams();
while (teams.hasMoreElements()) {
Team team = teams.nextElement();
if (team.getId() == teamId) {
Enumeration<Player> players = team.getPlayers();
while (players.hasMoreElements()) {
Player teamPlayer = players.nextElement();
if (teamPlayer.getId() != player.getId()) {
send(teamPlayer.getId(), new Packet(Packet.COMMAND_DEPLOY_MINEFIELDS, minefields));
}
teamPlayer.addMinefields(minefields);
}
break;
}
}
} else {
player.addMinefields(minefields);
}
}
}
/**
* Gets a bunch of entity attacks from the packet. If valid, processess them
* and ends the current turn.
*/
@SuppressWarnings("unchecked")
private void receiveAttack(Packet packet, int connId) {
Entity entity = game.getEntity(packet.getIntValue(0));
Vector<EntityAction> vector = (Vector<EntityAction>) packet.getObject(1);
// is this the right phase?
if ((game.getPhase() != IGame.Phase.PHASE_FIRING) && (game.getPhase() != IGame.Phase.PHASE_PHYSICAL) && (game.getPhase() != IGame.Phase.PHASE_TARGETING) && (game.getPhase() != IGame.Phase.PHASE_OFFBOARD)) {
System.err.println("error: server got attack packet in wrong phase");
return;
}
// can this player/entity act right now?
GameTurn turn = game.getTurn();
if(game.isPhaseSimultaneous()) {
turn = game.getTurnForPlayer(connId);
}
if ((turn==null) || !turn.isValid(connId, entity, game)) {
System.err.println("error: server got invalid attack packet");
send(connId, createTurnVectorPacket());
send(connId, createTurnIndexPacket());
return;
}
// looks like mostly everything's okay
processAttack(entity, vector);
// Update visibility indications if using double blind.
if (doBlind()) {
updateVisibilityIndicator();
}
endCurrentTurn(entity);
}
/**
* Process a batch of entity attack (or twist) actions by adding them to the
* proper list to be processed later.
*/
private void processAttack(Entity entity, Vector<EntityAction> vector) {
// Not **all** actions take up the entity's turn.
boolean setDone = !((game.getTurn() instanceof GameTurn.TriggerAPPodTurn) || (game.getTurn() instanceof GameTurn.TriggerBPodTurn)) ;
for (EntityAction ea : vector) {
// is this the right entity?
if (ea.getEntityId() != entity.getId()) {
System.err.println("error: attack packet has wrong attacker");
continue;
}
// Anti-mech and pointblank attacks from
// hiding may allow the target to respond.
if (ea instanceof WeaponAttackAction) {
final WeaponAttackAction waa = (WeaponAttackAction) ea;
final String weaponName = entity.getEquipment(waa.getWeaponId()).getType().getInternalName();
if (Infantry.SWARM_MEK.equals(weaponName) || Infantry.LEG_ATTACK.equals(weaponName)) {
// Does the target have any AP Pods available?
final Entity target = game.getEntity(waa.getTargetId());
for (Mounted equip : target.getMisc()) {
if (equip.getType().hasFlag(MiscType.F_AP_POD) && equip.canFire()) {
// Yup. Insert a game turn to handle AP pods.
// ASSUMPTION : AP pod declarations come
// immediately after the attack declaration.
game.insertNextTurn(new GameTurn.TriggerAPPodTurn(target.getOwnerId(), target.getId()));
send(createTurnVectorPacket());
// We can stop looking.
break;
} // end found-available-ap-pod
} // Check the next piece of equipment on the target.
for (Mounted weapon : target.getWeaponList()) {
if ( weapon.getType().hasFlag(WeaponType.F_B_POD) && weapon.canFire() ) {
// Yup. Insert a game turn to handle B pods.
// ASSUMPTION : B pod declarations come
// immediately after the attack declaration.
game.insertNextTurn(new GameTurn.TriggerBPodTurn(target.getOwnerId(), target.getId(), weaponName));
send(createTurnVectorPacket());
// We can stop looking.
break;
} // end found-available-b-pod
} // Check the next piece of equipment on the target.
} // End check-for-available-ap-pod
}
// If attacker breaks grapple, defender may counter
if (ea instanceof BreakGrappleAttackAction) {
final BreakGrappleAttackAction bgaa = (BreakGrappleAttackAction) ea;
final Entity att = (game.getEntity(bgaa.getEntityId()));
if (att.isGrappleAttacker()) {
final Entity def = (game.getEntity(bgaa.getTargetId()));
// Remove existing break grapple by defender (if exists)
if (def.isDone()) {
game.removeActionsFor(def.getId());
} else {
game.removeTurnFor(def);
def.setDone(true);
}
// Add a turn to declare counterattack
game.insertNextTurn(new GameTurn.CounterGrappleTurn(def.getOwnerId(), def.getId()));
send(createTurnVectorPacket());
}
}
if (ea instanceof ArtilleryAttackAction) {
boolean firingAtNewHex = false;
final ArtilleryAttackAction aaa = (ArtilleryAttackAction) ea;
final Entity firingEntity = game.getEntity(aaa.getEntityId());
for (Enumeration<AttackHandler> j = game.getAttacks(); !firingAtNewHex && j.hasMoreElements();) {
WeaponHandler wh = (WeaponHandler) j.nextElement();
if (wh.waa instanceof ArtilleryAttackAction) {
ArtilleryAttackAction oaaa = (ArtilleryAttackAction) wh.waa;
if ((oaaa.getEntityId() == aaa.getEntityId()) && !oaaa.getTarget(game).getPosition().equals(aaa.getTarget(game).getPosition())) {
firingAtNewHex = true;
}
}
if (firingAtNewHex) {
clearArtillerySpotters(firingEntity.getId(), aaa.getWeaponId());
}
Enumeration<Entity> spotters = game.getSelectedEntities(new EntitySelector() {
public int player = firingEntity.getOwnerId();
public Targetable target = aaa.getTarget(game);
public boolean accept(Entity entity) {
return ((player == entity.getOwnerId()) && !((LosEffects.calculateLos(game, entity.getId(), target)).isBlocked()) && entity.isActive());
}
});
Vector<Integer> spotterIds = new Vector<Integer>();
while (spotters.hasMoreElements()) {
Integer id = new Integer(spotters.nextElement().getId());
spotterIds.addElement(id);
}
aaa.setSpotterIds(spotterIds);
}
}
// The equipment type of a club needs to be restored.
if (ea instanceof ClubAttackAction) {
ClubAttackAction caa = (ClubAttackAction) ea;
Mounted club = caa.getClub();
club.restore();
}
if (ea instanceof PushAttackAction) {
// push attacks go the end of the displacement attacks
PushAttackAction paa = (PushAttackAction) ea;
entity.setDisplacementAttack(paa);
game.addCharge(paa);
} else if (ea instanceof DodgeAction) {
entity.dodging = true;
} else if (ea instanceof SpotAction) {
entity.setSpotting(true);
entity.setSpotTargetId(((SpotAction) ea).getTargetId());
} else {
// add to the normal attack list.
game.addAction(ea);
}
// Mark any AP Pod as used in this turn.
if (ea instanceof TriggerAPPodAction) {
TriggerAPPodAction tapa = (TriggerAPPodAction) ea;
Mounted pod = entity.getEquipment(tapa.getPodId());
pod.setUsedThisRound(true);
}
// Mark any B Pod as used in this turn.
if (ea instanceof TriggerBPodAction) {
TriggerBPodAction tba = (TriggerBPodAction) ea;
Mounted pod = entity.getEquipment(tba.getPodId());
pod.setUsedThisRound(true);
}
}
// Unless otherwise stated,
// this entity is done for the round.
if (setDone) {
entity.setDone(true);
}
entityUpdate(entity.getId());
Packet p = createAttackPacket(vector,0);
if(game.isPhaseSimultaneous()) {
// Update attack only to player who declared it & observers
for(Player player:game.getPlayersVector()) {
if(player.canSeeAll() || player.isObserver() || (entity.getOwnerId()==player.getId())) {
send(player.getId(),p);
}
}
} else {
// update all players on the attacks. Don't worry about pushes being a
// "charge" attack. It doesn't matter to the client.
send(p);
}
}
/**
* Auto-target active AMS systems
*/
private void assignAMS() {
// sort all missile-based attacks by the target
Hashtable<Entity, Vector<WeaponHandler>> htAttacks = new Hashtable<Entity, Vector<WeaponHandler>>();
for (AttackHandler ah : game.getAttacksVector()) {
WeaponHandler wh = (WeaponHandler) ah;
WeaponAttackAction waa = wh.waa;
// ignore artillery attacks, here the attacking entity
// might no longer be in the game
if (waa instanceof ArtilleryAttackAction) {
continue;
}
Mounted weapon = game.getEntity(waa.getEntityId()).getEquipment(waa.getWeaponId());
// Only entities can have AMS.
if (Targetable.TYPE_ENTITY != waa.getTargetType()) {
continue;
}
// AMS is only used against attacks that hit (TW p129)
if (wh.roll < wh.toHit.getValue()) {
continue;
}
// Can only use AMS versus missles.
if (((WeaponType) weapon.getType()).getDamage() == WeaponType.DAMAGE_MISSILE) {
Entity target = game.getEntity(waa.getTargetId());
Vector<WeaponHandler> v = htAttacks.get(target);
if (v == null) {
v = new Vector<WeaponHandler>();
htAttacks.put(target, v);
}
v.addElement(wh);
}
}
// let each target assign its AMS
for (Entity e : htAttacks.keySet()) {
Vector<WeaponHandler> vAttacks = htAttacks.get(e);
e.assignAMS(vAttacks);
}
}
/**
* Called during the weapons fire phase. Resolves anything other than
* weapons fire that happens. Torso twists, for example.
*/
private void resolveAllButWeaponAttacks() {
Vector<EntityAction> triggerPodActions = new Vector<EntityAction>();
// loop thru actions and handle everything we expect except attacks
for (Enumeration<EntityAction> i = game.getActions(); i.hasMoreElements();) {
EntityAction ea = i.nextElement();
Entity entity = game.getEntity(ea.getEntityId());
if (ea instanceof TorsoTwistAction) {
TorsoTwistAction tta = (TorsoTwistAction) ea;
if (entity.canChangeSecondaryFacing()) {
entity.setSecondaryFacing(tta.getFacing());
}
} else if (ea instanceof FlipArmsAction) {
FlipArmsAction faa = (FlipArmsAction) ea;
entity.setArmsFlipped(faa.getIsFlipped());
} else if (ea instanceof FindClubAction) {
resolveFindClub(entity);
} else if (ea instanceof UnjamAction) {
resolveUnjam(entity);
} else if (ea instanceof ClearMinefieldAction) {
resolveClearMinefield(entity, ((ClearMinefieldAction)ea).getMinefield());
} else if (ea instanceof TriggerAPPodAction) {
TriggerAPPodAction tapa = (TriggerAPPodAction) ea;
// Don't trigger the same pod twice.
if (!triggerPodActions.contains(tapa)) {
triggerAPPod(entity, tapa.getPodId());
triggerPodActions.addElement(tapa);
} else {
System.err.print("AP Pod #");
System.err.print(tapa.getPodId());
System.err.print(" on ");
System.err.print(entity.getDisplayName());
System.err.println(" was already triggered this round!!");
}
} else if (ea instanceof TriggerBPodAction) {
TriggerBPodAction tba = (TriggerBPodAction) ea;
// Don't trigger the same pod twice.
if (!triggerPodActions.contains(tba)) {
triggerBPod(entity, tba.getPodId(), game.getEntity(tba.getTargetId()));
triggerPodActions.addElement(tba);
} else {
System.err.print("B Pod #");
System.err.print(tba.getPodId());
System.err.print(" on ");
System.err.print(entity.getDisplayName());
System.err.println(" was already triggered this round!!");
}
} else if (ea instanceof SearchlightAttackAction) {
SearchlightAttackAction saa = (SearchlightAttackAction) ea;
addReport(saa.resolveAction(game));
} else if (ea instanceof UnjamTurretAction) {
if (entity instanceof Tank) {
((Tank) entity).unjamTurret();
Report r = new Report(3033);
r.addDesc(entity);
addReport(r);
} else {
System.err.println("Non-Tank tried to unjam turret");
}
} else if (ea instanceof RepairWeaponMalfunctionAction) {
if (entity instanceof Tank) {
Mounted m = entity.getEquipment(((RepairWeaponMalfunctionAction) ea).getWeaponId());
m.setJammed(false);
((Tank) entity).getJammedWeapons().remove(m);
Report r = new Report(3034);
r.addDesc(entity);
r.add(m.getName());
addReport(r);
} else {
System.err.println("Non-Tank tried to repair weapon malfunction");
}
}
}
}
private void reportGhostTargetRolls() {
//run through an enumeration of deployed game entities. If they have ghost targets, then check the roll
//and report it
Report r;
for (Enumeration<Entity> e = game.getEntities(); e.hasMoreElements();) {
Entity ent = e.nextElement();
if(ent.isDeployed() && ent.hasGhostTargets(false)) {
r = new Report(3630);
r.addDesc(ent);
int target = ent.getCrew().getPiloting() + 2;
int roll = ent.getGhostTargetRoll();
r.add(target);
r.add(roll);
if(roll >= target) {
r.choose(true);
} else {
r.choose(false);
}
addReport(r);
}
}
}
private void reportLargeCraftECCMRolls() {
//run through an enumeration of deployed game entities. If they are large craft in space, then check the roll
//and report it
if(!game.getBoard().inSpace() || !game.getOptions().booleanOption("stratops_ecm")) {
return;
}
Report r;
for (Enumeration<Entity> e = game.getEntities(); e.hasMoreElements();) {
Entity ent = e.nextElement();
if(ent.isDeployed() && ent.isLargeCraft()) {
r = new Report(3635);
r.addDesc(ent);
int target = ((Aero)ent).getECCMTarget();
int roll = ((Aero)ent).getECCMRoll();
r.add(roll);
r.add(target);
int mod = ((Aero)ent).getECCMBonus();
r.add(mod);
addReport(r);
}
}
}
private void resolveClearMinefield(Entity ent, Minefield mf) {
if((null == mf) || (null == ent) || ent.isDoomed() || ent.isDestroyed()) {
return;
}
Coords pos = mf.getCoords();
int clear = Minefield.CLEAR_NUMBER_INFANTRY;
int boom = Minefield.CLEAR_NUMBER_INFANTRY_ACCIDENT;
// Does the entity has a minesweeper?
/*this is no longer the way this is handled in TacOps
* for infantry, we need to check for specialized minesweeping
* for vees, this is totally different
for (Mounted mounted : ent.getMisc()) {
if (mounted.getType().hasFlag(MiscType.F_TOOLS) && mounted.getType().hasSubType(MiscType.S_MINESWEEPER)) {
int sweeperType = mounted.getType().getToHitModifier();
clear = Minefield.CLEAR_NUMBER_SWEEPER[sweeperType];
boom = Minefield.CLEAR_NUMBER_SWEEPER_ACCIDENT[sweeperType];
break;
}
}
*/
// mine clearing roll
Report r = new Report(2245);
r.subject = ent.getId();
r.add(ent.getShortName(), true);
r.add(Minefield.getDisplayableName(mf.getType()));
r.add(pos.getBoardNum(), true);
addReport(r);
if(clearMinefield(mf, ent, clear, boom, vPhaseReport)) {
removeMinefield(mf);
}
//some mines might have blown up
resetMines();
}
/**
* Called during the fire phase to resolve all (and only) weapon attacks
*/
private void resolveOnlyWeaponAttacks() {
// loop thru received attack actions, getting attack handlers
for (Enumeration<EntityAction> i = game.getActions(); i.hasMoreElements();) {
EntityAction ea = i.nextElement();
if (ea instanceof WeaponAttackAction) {
WeaponAttackAction waa = (WeaponAttackAction) ea;
Entity ae = game.getEntity(waa.getEntityId());
Mounted m = ae.getEquipment(waa.getWeaponId());
Weapon w = (Weapon) m.getType();
AttackHandler ah = w.fire(waa, game, this);
if (ah != null) {
game.addAttack(ah);
}
}
}
// and clear the attacks Vector
game.resetActions();
}
/**
* Trigger the indicated AP Pod of the entity.
*
* @param entity
* the <code>Entity</code> triggering the AP Pod.
* @param podId
* the <code>int</code> ID of the AP Pod.
*/
private void triggerAPPod(Entity entity, int podId) {
// Get the mount for this pod.
Mounted mount = entity.getEquipment(podId);
// Confirm that this is, indeed, an AP Pod.
if (null == mount) {
System.err.print("Expecting to find an AP Pod at ");
System.err.print(podId);
System.err.print(" on the unit, ");
System.err.print(entity.getDisplayName());
System.err.println(" but found NO equipment at all!!!");
return;
}
EquipmentType equip = mount.getType();
if (!(equip instanceof MiscType) || !equip.hasFlag(MiscType.F_AP_POD)) {
System.err.print("Expecting to find an AP Pod at ");
System.err.print(podId);
System.err.print(" on the unit, ");
System.err.print(entity.getDisplayName());
System.err.print(" but found ");
System.err.print(equip.getName());
System.err.println(" instead!!!");
return;
}
// Now confirm that the entity can trigger the pod.
// Ignore the "used this round" flag.
boolean oldFired = mount.isUsedThisRound();
mount.setUsedThisRound(false);
boolean canFire = mount.canFire();
mount.setUsedThisRound(oldFired);
if (!canFire) {
System.err.print("Can not trigger the AP Pod at ");
System.err.print(podId);
System.err.print(" on the unit, ");
System.err.print(entity.getDisplayName());
System.err.println("!!!");
return;
}
Report r;
// Mark the pod as fired and log the action.
mount.setFired(true);
r = new Report(3010);
r.newlines = 0;
r.subject = entity.getId();
r.addDesc(entity);
addReport(r);
// Walk through ALL entities in the triggering entity's hex.
Enumeration<Entity> targets = game.getEntities(entity.getPosition());
while (targets.hasMoreElements()) {
final Entity target = targets.nextElement();
// Is this an unarmored infantry platoon?
if ((target instanceof Infantry) && !(target instanceof BattleArmor)) {
// Roll d6-1 for damage.
final int damage = Math.max(1, Compute.d6() - 1);
// Damage the platoon.
addReport(damageEntity(target, new HitData(Infantry.LOC_INFANTRY), damage));
// Damage from AP Pods is applied immediately.
target.applyDamage();
} // End target-is-unarmored
// Nope, the target is immune.
// Don't make a log entry for the triggering entity.
else if (!entity.equals(target)) {
r = new Report(3020);
r.indent(2);
r.subject = target.getId();
r.addDesc(target);
addReport(r);
}
} // Check the next entity in the triggering entity's hex.
}
/**
* Trigger the indicated B Pod of the entity.
*
* @param entity
* the <code>Entity</code> triggering the B Pod.
* @param podId
* the <code>int</code> ID of the B Pod.
*/
private void triggerBPod(Entity entity, int podId, Entity target) {
// Get the mount for this pod.
Mounted mount = entity.getEquipment(podId);
// Confirm that this is, indeed, an Anti-BA Pod.
if (null == mount) {
System.err.print("Expecting to find an B Pod at ");
System.err.print(podId);
System.err.print(" on the unit, ");
System.err.print(entity.getDisplayName());
System.err.println(" but found NO equipment at all!!!");
return;
}
EquipmentType equip = mount.getType();
if (!(equip instanceof WeaponType) || !equip.hasFlag(WeaponType.F_B_POD)) {
System.err.print("Expecting to find an B Pod at ");
System.err.print(podId);
System.err.print(" on the unit, ");
System.err.print(entity.getDisplayName());
System.err.print(" but found ");
System.err.print(equip.getName());
System.err.println(" instead!!!");
return;
}
// Now confirm that the entity can trigger the pod.
// Ignore the "used this round" flag.
boolean oldFired = mount.isUsedThisRound();
mount.setUsedThisRound(false);
boolean canFire = mount.canFire();
mount.setUsedThisRound(oldFired);
if (!canFire) {
System.err.print("Can not trigger the B Pod at ");
System.err.print(podId);
System.err.print(" on the unit, ");
System.err.print(entity.getDisplayName());
System.err.println("!!!");
return;
}
Report r;
// Mark the pod as fired and log the action.
mount.setFired(true);
r = new Report(3011);
r.newlines = 0;
r.subject = entity.getId();
r.addDesc(entity);
addReport(r);
// Is this an unarmored infantry platoon?
if ((target instanceof Infantry) && !(target instanceof BattleArmor)) {
// Roll d6 for damage.
final int damage = Compute.d6();
// Damage the platoon.
addReport(damageEntity(target, new HitData(Infantry.LOC_INFANTRY), damage));
// Damage from AP Pods is applied immediately.
target.applyDamage();
// End target-is-unarmored
} else if ( target instanceof BattleArmor ){
// 20 damage in 5 point clusters
final int damage = 5;
// Damage the squad.
addReport(damageEntity(target, target.rollHitLocation(0,0), damage));
addReport(damageEntity(target, target.rollHitLocation(0,0), damage));
addReport(damageEntity(target, target.rollHitLocation(0,0), damage));
addReport(damageEntity(target, target.rollHitLocation(0,0), damage));
// Damage from B Pods is applied immediately.
target.applyDamage();
}
// Nope, the target is immune.
// Don't make a log entry for the triggering entity.
else if (!entity.equals(target)) {
r = new Report(3020);
r.indent(2);
r.subject = target.getId();
r.addDesc(target);
addReport(r);
}
}
/**
* Resolve an Unjam Action object
*/
private void resolveUnjam(Entity entity) {
Report r;
final int TN = entity.getCrew().getGunnery() + 3;
r = new Report(3025);
r.subject = entity.getId();
r.addDesc(entity);
addReport(r);
for (Mounted mounted : entity.getTotalWeaponList()) {
if (mounted.isJammed()) {
WeaponType wtype = (WeaponType) mounted.getType();
if (wtype.getAmmoType() == AmmoType.T_AC_ROTARY) {
int roll = Compute.d6(2);
r = new Report(3030);
r.indent();
r.subject = entity.getId();
r.add(wtype.getName());
r.add(TN);
r.add(roll);
if (roll >= TN) {
r.choose(true);
mounted.setJammed(false);
} else {
r.choose(false);
}
addReport(r);
}
}
}
}
private void resolveFindClub(Entity entity) {
EquipmentType clubType = null;
entity.setFindingClub(true);
// Get the entity's current hex.
Coords coords = entity.getPosition();
IHex curHex = game.getBoard().getHex(coords);
Report r;
// Is there a blown off arm in the hex?
if (curHex.terrainLevel(Terrains.ARMS) > 0) {
clubType = EquipmentType.get("Limb Club");
curHex.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.ARMS, curHex.terrainLevel(Terrains.ARMS) - 1));
sendChangedHex(entity.getPosition());
r = new Report(3035);
r.subject = entity.getId();
r.addDesc(entity);
addReport(r);
}
// Is there a blown off leg in the hex?
else if (curHex.terrainLevel(Terrains.LEGS) > 0) {
clubType = EquipmentType.get("Limb Club");
curHex.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.LEGS, curHex.terrainLevel(Terrains.LEGS) - 1));
sendChangedHex(entity.getPosition());
r = new Report(3040);
r.subject = entity.getId();
r.addDesc(entity);
addReport(r);
}
// Is there the rubble of a medium, heavy,
// or hardened building in the hex?
else if (Building.LIGHT < curHex.terrainLevel(Terrains.RUBBLE)) {
// Finding a club is not guaranteed. The chances are
// based on the type of building that produced the
// rubble.
boolean found = false;
int roll = Compute.d6(2);
switch (curHex.terrainLevel(Terrains.RUBBLE)) {
case Building.MEDIUM:
if (roll >= 7) {
found = true;
}
break;
case Building.HEAVY:
if (roll >= 6) {
found = true;
}
break;
case Building.HARDENED:
if (roll >= 5) {
found = true;
}
break;
case Building.WALL:
if (roll >= 13) {
found = true;
}
break;
default:
//we must be in ultra
if(roll >= 4) {
found = true;
}
}
// Let the player know if they found a club.
if (found) {
clubType = EquipmentType.get("Girder Club");
r = new Report(3045);
r.subject = entity.getId();
r.addDesc(entity);
addReport(r);
} else {
// Sorry, no club for you.
r = new Report(3050);
r.subject = entity.getId();
r.addDesc(entity);
addReport(r);
}
}
// Are there woods in the hex?
else if (curHex.containsTerrain(Terrains.WOODS) || curHex.containsTerrain(Terrains.JUNGLE)) {
clubType = EquipmentType.get("Tree Club");
r = new Report(3055);
r.subject = entity.getId();
r.addDesc(entity);
addReport(r);
}
// add the club
try {
if (clubType != null) {
entity.addEquipment(clubType, Entity.LOC_NONE);
}
} catch (LocationFullException ex) {
// unlikely...
r = new Report(3060);
r.subject = entity.getId();
r.addDesc(entity);
addReport(r);
}
}
/**
* Try to ignite the hex, taking into account exisiting fires and the
* effects of Inferno rounds.
*
* @param c -
* the <code>Coords</code> of the hex being lit.
* @param entityId -
* the <code>int</code> id of the entity involved.
* @param bInferno -
* <code>true</code> if the weapon igniting the hex is an
* Inferno round. If some other weapon or ammo is causing the
* roll, this should be <code>false</code>.
* @param bHotGun -
* <code>true</code> if the weapon is plasma/flamer/incendiary
* LRM/etc
* @param nTargetRoll -
* the <code>TargetRoll</code> for the ignition roll.
* @param bReportAttempt -
* <code>true</code> if the attempt roll should be added to the
* report.
* @param accidentTarget -
* <code>int</code> the target number below which a roll has to
* be made in order to try igniting a hex accidently. -1 for
* intentional
*/
public boolean tryIgniteHex(Coords c, int entityId, boolean bHotGun, boolean bInferno, TargetRoll nTargetRoll,
boolean bReportAttempt, int accidentTarget, Vector<Report> vPhaseReport) {
IHex hex = game.getBoard().getHex(c);
Report r;
// Ignore bad coordinates.
if (hex == null) {
return false;
}
// Ignore if fire is not enabled as a game option
if (!game.getOptions().booleanOption("tacops_start_fire")) {
return false;
}
// is the hex ignitable (how are infernos handled?)
if (!hex.isIgnitable() && !bInferno) {
return false;
}
// first for accidental ignitions, make the necessary roll
if (accidentTarget > -1) {
// if this hex is in snow, then accidental ignitions are not
// possible
if (hex.containsTerrain(Terrains.SNOW)) {
return false;
}
nTargetRoll.addModifier(2, "accidental");
int accidentRoll = Compute.d6(2);
r = new Report(3066);
r.subject = entityId;
r.add(accidentTarget);
r.add(accidentRoll);
r.indent(2);
if (accidentRoll > accidentTarget) {
r.choose(false);
vPhaseReport.add(r);
return false;
} else {
r.choose(true);
vPhaseReport.add(r);
}
}
int terrainMod = hex.getIgnitionModifier();
if (terrainMod != 0) {
nTargetRoll.addModifier(terrainMod, "terrain");
}
// building modifiers
Building bldg = game.getBoard().getBuildingAt(c);
if (null != bldg) {
nTargetRoll.addModifier(bldg.getType() - 1, "building");
}
// add in any modifiers for planetary conditions
int weatherMod = game.getPlanetaryConditions().getIgniteModifiers();
if (weatherMod != 0) {
nTargetRoll.addModifier(weatherMod, "conditions");
}
// if there is snow on the ground and this a hotgun or inferno, it may
// melt the snow instead
if ((hex.containsTerrain(Terrains.SNOW) || hex.containsTerrain(Terrains.ICE)) && (bHotGun || bInferno)) {
boolean melted = false;
int meltCheck = Compute.d6(2);
if ((hex.terrainLevel(Terrains.SNOW) > 1) && (meltCheck == 12)) {
melted = true;
} else if (hex.containsTerrain(Terrains.ICE) && (meltCheck > 9)) {
melted = true;
} else if (hex.containsTerrain(Terrains.SNOW) && (meltCheck > 7)) {
melted = true;
}
if (bInferno) {
melted = true;
}
if (melted) {
vPhaseReport.addAll(meltIceAndSnow(c, entityId));
return false;
}
}
// inferno always ignites
// ERRATA not if targeting clear hexes for ignition is disabled.
if (bInferno && !game.getOptions().booleanOption("no_ignite_clear")) {
nTargetRoll = new TargetRoll(0, "inferno");
}
// no lighting fires in tornados
if (game.getPlanetaryConditions().getWindStrength() > PlanetaryConditions.WI_STORM) {
nTargetRoll = new TargetRoll(TargetRoll.AUTOMATIC_FAIL, "tornado");
}
// The hex may already be on fire.
if (hex.containsTerrain(Terrains.FIRE)) {
if (bReportAttempt) {
r = new Report(3065);
r.indent(2);
r.subject = entityId;
vPhaseReport.add(r);
}
} else if(checkIgnition(c, nTargetRoll, bInferno, entityId, vPhaseReport)){
return true;
}
return false;
}
/**
* Try to ignite the hex, taking into account exisiting fires and the
* effects of Inferno rounds. This version of the method will not report the
* attempt roll.
*
* @param c -
* the <code>Coords</code> of the hex being lit.
* @param entityId -
* the <code>int</code> id of the entity involved.
* @param bInferno -
* <code>true</code> if the weapon igniting the hex is an
* Inferno round. If some other weapon or ammo is causing the
* roll, this should be <code>false</code>.
* @param nTargetRoll -
* the <code>int</code> roll target for the attempt.
*/
public boolean tryIgniteHex(Coords c, int entityId, boolean bHotGun, boolean bInferno, TargetRoll nTargetRoll, int accidentTarget, Vector<Report> vPhaseReport) {
return tryIgniteHex(c, entityId, bHotGun, bInferno, nTargetRoll, false, accidentTarget, vPhaseReport);
}
public Vector<Report> tryClearHex(Coords c, int nDamage, int entityId) {
Vector<Report> vPhaseReport = new Vector<Report>();
IHex h = game.getBoard().getHex(c);
if (h == null) {
return vPhaseReport;
}
ITerrain woods = h.getTerrain(Terrains.WOODS);
ITerrain jungle = h.getTerrain(Terrains.JUNGLE);
ITerrain ice = h.getTerrain(Terrains.ICE);
ITerrain magma = h.getTerrain(Terrains.MAGMA);
Report r;
if (woods != null) {
int tf = woods.getTerrainFactor() - nDamage;
int level = woods.getLevel();
if (tf <= 0) {
h.removeTerrain(Terrains.WOODS);
h.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.ROUGH, 1));
// light converted to rough
r = new Report(3090);
r.subject = entityId;
vPhaseReport.add(r);
} else if ((tf <= 50) && (level > 1)) {
h.removeTerrain(Terrains.WOODS);
h.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.WOODS, 1));
woods = h.getTerrain(Terrains.WOODS);
// heavy converted to light
r = new Report(3085);
r.subject = entityId;
vPhaseReport.add(r);
} else if ((tf <= 90) && (level > 2)) {
h.removeTerrain(Terrains.WOODS);
h.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.WOODS, 2));
woods = h.getTerrain(Terrains.WOODS);
// ultra heavy converted to heavy
r = new Report(3082);
r.subject = entityId;
vPhaseReport.add(r);
}
woods.setTerrainFactor(tf);
}
if (jungle != null) {
int tf = jungle.getTerrainFactor() - nDamage;
int level = jungle.getLevel();
if (tf < 0) {
h.removeTerrain(Terrains.JUNGLE);
h.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.ROUGH, 1));
// light converted to rough
r = new Report(3091);
r.subject = entityId;
vPhaseReport.add(r);
} else if ((tf <= 50) && (level > 1)) {
h.removeTerrain(Terrains.JUNGLE);
h.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.JUNGLE, 1));
jungle = h.getTerrain(Terrains.JUNGLE);
// heavy converted to light
r = new Report(3086);
r.subject = entityId;
vPhaseReport.add(r);
} else if ((tf <= 90) && (level > 2)) {
h.removeTerrain(Terrains.JUNGLE);
h.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.JUNGLE, 2));
jungle = h.getTerrain(Terrains.JUNGLE);
// ultra heavy converted to heavy
r = new Report(3083);
r.subject = entityId;
vPhaseReport.add(r);
}
jungle.setTerrainFactor(tf);
}
if (ice != null) {
int tf = ice.getTerrainFactor() - nDamage;
if (tf <= 0) {
// ice melted
r = new Report(3092);
r.subject = entityId;
vPhaseReport.add(r);
vPhaseReport.addAll(resolveIceBroken(c));
} else {
ice.setTerrainFactor(tf);
}
}
if ((magma != null) && (magma.getLevel() == 1)) {
int tf = magma.getTerrainFactor() - nDamage;
if (tf <= 0) {
// magma crust destroyed
r = new Report(3093);
r.subject = entityId;
vPhaseReport.add(r);
h.removeTerrain(Terrains.MAGMA);
h.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.MAGMA, 2));
for (Enumeration<Entity> e = getGame()
.getEntities(c); e.hasMoreElements();) {
doMagmaDamage(e.nextElement(), false);
}
} else {
magma.setTerrainFactor(tf);
}
}
sendChangedHex(c);
//any attempt to clear an heavy industrial hex may cause an exposion
checkExplodeIndustrialZone(c, vPhaseReport);
return vPhaseReport;
}
/**
* Handle all physical attacks for the round
*/
private void resolvePhysicalAttacks() {
// Physical phase header
addReport(new Report(4000, Report.PUBLIC));
// add any pending charges
for (Enumeration<AttackAction> i = game.getCharges(); i.hasMoreElements();) {
game.addAction(i.nextElement());
}
game.resetCharges();
// add any pending rams
for (Enumeration<AttackAction> i = game.getRams(); i.hasMoreElements();) {
game.addAction(i.nextElement());
}
game.resetRams();
// add any pending Tele Missile Attacks
for (Enumeration<AttackAction> i = game.getTeleMissileAttacks(); i.hasMoreElements();) {
game.addAction(i.nextElement());
}
game.resetTeleMissileAttacks();
// remove any duplicate attack declarations
cleanupPhysicalAttacks();
// loop thru received attack actions
for (Enumeration<EntityAction> i = game.getActions(); i.hasMoreElements();) {
Object o = i.nextElement();
// verify that the attacker is still active
AttackAction aa = (AttackAction) o;
if (!game.getEntity(aa.getEntityId()).isActive() && !(o instanceof DfaAttackAction)) {
continue;
}
AbstractAttackAction aaa = (AbstractAttackAction) o;
// do searchlights immediately
if (aaa instanceof SearchlightAttackAction) {
SearchlightAttackAction saa = (SearchlightAttackAction) aaa;
addReport(saa.resolveAction(game));
} else {
physicalResults.addElement(preTreatPhysicalAttack(aaa));
}
}
int cen = Entity.NONE;
for (PhysicalResult pr : physicalResults) {
resolvePhysicalAttack(pr, cen);
cen = pr.aaa.getEntityId();
}
physicalResults.removeAllElements();
}
/**
* Cleans up the attack declarations for the physical phase by removing all
* attacks past the first for any one mech. Also clears out attacks by dead
* or disabled mechs.
*/
private void cleanupPhysicalAttacks() {
for (Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
Entity entity = i.nextElement();
removeDuplicateAttacks(entity.getId());
}
removeDeadAttacks();
}
/**
* Removes any actions in the attack queue beyond the first by the specified
* entity.
*/
private void removeDuplicateAttacks(int entityId) {
boolean attacked = false;
Vector<EntityAction> toKeep = new Vector<EntityAction>(/* game.actionsSize() */);
for (Enumeration<EntityAction> i = game.getActions(); i.hasMoreElements();) {
EntityAction action = i.nextElement();
if (action.getEntityId() != entityId) {
toKeep.addElement(action);
} else if (!attacked) {
toKeep.addElement(action);
if (!(action instanceof SearchlightAttackAction)) {
attacked = true;
}
} else {
System.err.println("server: removing duplicate phys attack for id#" + entityId);
System.err.println(" action was " + action.toString());
}
}
// reset actions and re-add valid elements
game.resetActions();
for (EntityAction entityAction : toKeep) {
game.addAction(entityAction);
}
}
/**
* Removes all attacks by any dead entities. It does this by going through
* all the attacks and only keeping ones from active entities. DFAs are kept
* even if the pilot is unconscious, so that he can fail.
*/
private void removeDeadAttacks() {
Vector<EntityAction> toKeep = new Vector<EntityAction>(game.actionsSize());
for (Enumeration<EntityAction> i = game.getActions(); i.hasMoreElements();) {
EntityAction action = i.nextElement();
Entity entity = game.getEntity(action.getEntityId());
if ((entity != null) && !entity.isDestroyed() && (entity.isActive() || (action instanceof DfaAttackAction))) {
toKeep.addElement(action);
}
}
// reset actions and re-add valid elements
game.resetActions();
for (EntityAction entityAction : toKeep) {
game.addAction(entityAction);
}
}
/**
* Handle a punch attack
*/
private void resolvePunchAttack(PhysicalResult pr, int lastEntityId) {
final PunchAttackAction paa = (PunchAttackAction) pr.aaa;
final Entity ae = game.getEntity(paa.getEntityId());
final Targetable target = game.getTarget(paa.getTargetType(), paa.getTargetId());
Entity te = null;
if (target.getTargetType() == Targetable.TYPE_ENTITY) {
te = (Entity) target;
}
boolean throughFront = true;
if (te != null) {
throughFront = Compute.isThroughFrontHex(game, ae.getPosition(), te);
}
final String armName = paa.getArm() == PunchAttackAction.LEFT ? "Left Arm" : "Right Arm";
// get damage, ToHitData and roll from the PhysicalResult
int damage = paa.getArm() == PunchAttackAction.LEFT ? pr.damage : pr.damageRight;
final ToHitData toHit = paa.getArm() == PunchAttackAction.LEFT ? pr.toHit : pr.toHitRight;
int roll = paa.getArm() == PunchAttackAction.LEFT ? pr.roll : pr.rollRight;
final boolean targetInBuilding = Compute.isInBuilding(game, te);
final boolean glancing = game.getOptions().booleanOption("tacops_glancing_blows") && (roll == toHit.getValue());
Report r;
// Set Margin of Success/Failure.
toHit.setMoS(roll - Math.max(2, toHit.getValue()));
final boolean directBlow = game.getOptions().booleanOption("tacops_direct_blow") && ((toHit.getMoS() / 3) >= 1);
// Which building takes the damage?
Building bldg = game.getBoard().getBuildingAt(target.getPosition());
if (lastEntityId != paa.getEntityId()) {
// report who is making the attacks
r = new Report(4005);
r.subject = ae.getId();
r.addDesc(ae);
addReport(r);
}
r = new Report(4010);
r.subject = ae.getId();
r.indent();
r.add(armName);
r.add(target.getDisplayName());
r.newlines = 0;
addReport(r);
if (toHit.getValue() == TargetRoll.IMPOSSIBLE) {
r = new Report(4015);
r.subject = ae.getId();
r.add(toHit.getDesc());
addReport(r);
return;
} else if (toHit.getValue() == TargetRoll.AUTOMATIC_SUCCESS) {
r = new Report(4020);
r.subject = ae.getId();
r.add(toHit.getDesc());
r.newlines = 0;
addReport(r);
roll = Integer.MAX_VALUE;
} else {
r = new Report(4025);
r.subject = ae.getId();
r.add(toHit.getValue());
r.add(roll);
r.newlines = 0;
addReport(r);
if (glancing) {
r = new Report(4030);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
}
if (directBlow) {
r = new Report(4032);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
}
}
// do we hit?
if (roll < toHit.getValue()) {
// nope
r = new Report(4035);
r.subject = ae.getId();
addReport(r);
// If the target is in a building, the building absorbs the damage.
if (targetInBuilding && (bldg != null)) {
// Only report if damage was done to the building.
if (damage > 0) {
Vector<Report> buildingReport = damageBuilding(bldg, damage, target.getPosition());
for (Report report : buildingReport) {
report.subject = ae.getId();
}
addReport(buildingReport);
}
}
return;
}
// Targeting a building.
if ((target.getTargetType() == Targetable.TYPE_BUILDING) || (target.getTargetType() == Targetable.TYPE_FUEL_TANK)) {
// The building takes the full brunt of the attack.
r = new Report(4040);
r.subject = ae.getId();
addReport(r);
Vector<Report> buildingReport = damageBuilding(bldg, damage, target.getPosition());
for (Report report : buildingReport) {
report.subject = ae.getId();
}
addReport(buildingReport);
// Damage any infantry in the hex.
damageInfantryIn(bldg, damage, target.getPosition());
// And we're done!
return;
}
HitData hit = te.rollHitLocation(toHit.getHitTable(), toHit.getSideTable());
hit.setGeneralDamageType(HitData.DAMAGE_PHYSICAL);
r = new Report(4045);
r.subject = ae.getId();
r.add(toHit.getTableDesc());
r.add(te.getLocationAbbr(hit));
r.newlines = 0;
addReport(r);
// The building shields all units from a certain amount of damage.
// The amount is based upon the building's CF at the phase's start.
if (targetInBuilding && (bldg != null)) {
int bldgAbsorbs = (int) Math.ceil(bldg.getPhaseCF(target.getPosition()) / 10.0);
int toBldg = Math.min(bldgAbsorbs, damage);
damage -= toBldg;
addNewLines();
Vector<Report> buildingReport = damageBuilding(bldg, toBldg, target.getPosition());
for (Report report : buildingReport) {
report.subject = ae.getId();
}
addReport(buildingReport);
}
// A building may absorb the entire shot.
if (damage == 0) {
r = new Report(4050);
r.subject = ae.getId();
r.add(te.getShortName());
r.add(te.getOwner().getName());
r.newlines = 0;
addReport(r);
} else {
if (glancing) {
damage = (int) Math.floor(damage / 2.0);
}
if (directBlow) {
damage += toHit.getMoS() / 3;
hit.makeDirectBlow(toHit.getMoS() / 3);
}
if ((damage >= 1) && te.hasWorkingMisc(MiscType.F_SPIKES, -1, hit.getLocation())) {
r = new Report(4330);
r.indent(2);
r.newlines = 0;
r.subject = ae.getId();
addReport(r);
checkBreakSpikes(te, hit.getLocation());
damage = Math.max(1, damage - 4);
HitData ahit;
if (paa.getArm() == PunchAttackAction.LEFT) {
ahit = new HitData(Mech.LOC_LARM);
} else {
ahit = new HitData(Mech.LOC_RARM);
}
addReport(damageEntity(ae, ahit, 2, false, DamageType.NONE, false, false, false));
}
DamageType damageType = DamageType.NONE;
addReport(damageEntity(te, hit, damage, false, damageType, false, false, throughFront));
if (target instanceof VTOL) {
// destroy rotor
addReport(applyCriticalHit(te, VTOL.LOC_ROTOR, new CriticalSlot(CriticalSlot.TYPE_SYSTEM, VTOL.CRIT_ROTOR_DESTROYED), false));
}
//check for extending retractable blades
if(paa.isBladeExtended(paa.getArm())) {
addNewLines();
r = new Report(4455);
r.indent(2);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
//conventional infantry don't take crits and battle armor need to be handled differently
if(!(target instanceof Infantry)) {
addNewLines();
addReport(criticalEntity(te, hit.getLocation(), 0, true, false));
}
if((target instanceof BattleArmor)
&& (hit.getLocation() < te.locations()) && (te.getInternal(hit.getLocation()) > 0)) {
//TODO: we should really apply BA criticals through the critical
//hits methods. Right now they are applied in damageentity
HitData bahit = new HitData(hit.getLocation(), false, HitData.EFFECT_CRITICAL);
addReport(damageEntity(te,bahit,0));
}
//extend the blade
//since retracting/extending is a freebie in the movement phase, lets assume that the
//blade retracts to its original mode
//ae.extendBlade(paa.getArm());
//check for breaking a nail
if(Compute.d6(2) > 9) {
int armLoc = (paa.getArm() == PunchAttackAction.RIGHT) ? Mech.LOC_RARM : Mech.LOC_LARM;
addNewLines();
r = new Report(4456);
r.indent(2);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
ae.destroyRetractableBlade(armLoc);
}
}
}
addNewLines();
//if the target is an industrial mech, it needs to check for crits
//at the end of turn
if ((target instanceof Mech) && ((Mech)target).isIndustrial()) {
((Mech)target).setCheckForCrit(true);
}
}
/**
* Handle a kick attack
*/
private void resolveKickAttack(PhysicalResult pr, int lastEntityId) {
KickAttackAction kaa = (KickAttackAction) pr.aaa;
final Entity ae = game.getEntity(kaa.getEntityId());
final Targetable target = game.getTarget(kaa.getTargetType(), kaa.getTargetId());
Entity te = null;
if (target.getTargetType() == Targetable.TYPE_ENTITY) {
te = (Entity) target;
}
boolean throughFront = true;
if (te != null) {
throughFront = Compute.isThroughFrontHex(game, ae.getPosition(), te);
}
String legName = (kaa.getLeg() == KickAttackAction.LEFT) || (kaa.getLeg() == KickAttackAction.LEFTMULE) ? "Left " : "Right ";
if ((kaa.getLeg() == KickAttackAction.LEFTMULE) || (kaa.getLeg() == KickAttackAction.RIGHTMULE)) {
legName.concat(" rear ");
} else {
legName.concat(" front ");
}
legName.concat("leg");
Report r;
// get damage, ToHitData and roll from the PhysicalResult
int damage = pr.damage;
final ToHitData toHit = pr.toHit;
int roll = pr.roll;
final boolean targetInBuilding = Compute.isInBuilding(game, te);
final boolean glancing = game.getOptions().booleanOption("tacops_glancing_blows") && (roll == toHit.getValue());
// Set Margin of Success/Failure.
toHit.setMoS(roll - Math.max(2, toHit.getValue()));
final boolean directBlow = game.getOptions().booleanOption("tacops_direct_blow") && ((toHit.getMoS() / 3) >= 1);
// Which building takes the damage?
Building bldg = game.getBoard().getBuildingAt(target.getPosition());
if (lastEntityId != ae.getId()) {
// who is making the attacks
r = new Report(4005);
r.subject = ae.getId();
r.addDesc(ae);
addReport(r);
}
r = new Report(4055);
r.subject = ae.getId();
r.indent();
r.add(legName);
r.add(target.getDisplayName());
r.newlines = 0;
addReport(r);
if (toHit.getValue() == TargetRoll.IMPOSSIBLE) {
r = new Report(4060);
r.subject = ae.getId();
r.add(toHit.getDesc());
addReport(r);
game.addPSR(new PilotingRollData(ae.getId(), 0, "missed a kick"));
return;
} else if (toHit.getValue() == TargetRoll.AUTOMATIC_SUCCESS) {
r = new Report(4065);
r.subject = ae.getId();
r.add(toHit.getDesc());
r.newlines = 0;
addReport(r);
roll = Integer.MAX_VALUE;
} else {
r = new Report(4025);
r.subject = ae.getId();
r.add(toHit.getValue());
r.add(roll);
r.newlines = 0;
addReport(r);
if (glancing) {
r = new Report(4030);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
}
if (directBlow) {
r = new Report(4032);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
}
}
// do we hit?
if (roll < toHit.getValue()) {
// miss
r = new Report(4035);
r.subject = ae.getId();
addReport(r);
game.addPSR(new PilotingRollData(ae.getId(), 0, "missed a kick"));
// If the target is in a building, the building absorbs the damage.
if (targetInBuilding && (bldg != null)) {
// Only report if damage was done to the building.
if (damage > 0) {
Vector<Report> buildingReport = damageBuilding(bldg, damage, target.getPosition());
for (Report report : buildingReport) {
report.subject = ae.getId();
}
addReport(buildingReport);
}
}
return;
}
// Targeting a building.
if ((target.getTargetType() == Targetable.TYPE_BUILDING) || (target.getTargetType() == Targetable.TYPE_FUEL_TANK)) {
// The building takes the full brunt of the attack.
r = new Report(4040);
r.subject = ae.getId();
addReport(r);
Vector<Report> buildingReport = damageBuilding(bldg, damage, target.getPosition());
for (Report report : buildingReport) {
report.subject = ae.getId();
}
addReport(buildingReport);
// Damage any infantry in the hex.
damageInfantryIn(bldg, damage, target.getPosition());
// And we're done!
return;
}
HitData hit = te.rollHitLocation(toHit.getHitTable(), toHit.getSideTable());
hit.setGeneralDamageType(HitData.DAMAGE_PHYSICAL);
r = new Report(4045);
r.subject = ae.getId();
r.add(toHit.getTableDesc());
r.add(te.getLocationAbbr(hit));
r.newlines = 0;
addReport(r);
// The building shields all units from a certain amount of damage.
// The amount is based upon the building's CF at the phase's start.
if (targetInBuilding && (bldg != null)) {
int bldgAbsorbs = (int) Math.ceil(bldg.getPhaseCF(target.getPosition()) / 10.0);
int toBldg = Math.min(bldgAbsorbs, damage);
damage -= toBldg;
addNewLines();
Vector<Report> buildingReport = damageBuilding(bldg, damage, target.getPosition());
for (Report report : buildingReport) {
report.subject = ae.getId();
}
addReport(buildingReport);
}
// A building may absorb the entire shot.
if (damage == 0) {
r = new Report(4050);
r.subject = ae.getId();
r.add(te.getShortName());
r.add(te.getOwner().getName());
r.newlines = 0;
addReport(r);
} else {
if (glancing) {
damage = (int) Math.floor(damage / 2.0);
}
if (directBlow) {
damage += toHit.getMoS() / 3;
hit.makeDirectBlow(toHit.getMoS() / 3);
}
if ((damage >= 1) && te.hasWorkingMisc(MiscType.F_SPIKES, -1, hit.getLocation())) {
r = new Report(4330);
r.indent(2);
r.newlines = 0;
r.subject = ae.getId();
addReport(r);
checkBreakSpikes(te, hit.getLocation());
damage = Math.max(1, damage - 4);
HitData ahit;
switch (kaa.getLeg()) {
case KickAttackAction.LEFT:
if (ae instanceof QuadMech) {
ahit = new HitData(Mech.LOC_LARM);
} else {
ahit = new HitData(Mech.LOC_LLEG);
}
break;
case KickAttackAction.RIGHT:
if (ae instanceof QuadMech) {
ahit = new HitData(Mech.LOC_RARM);
} else {
ahit = new HitData(Mech.LOC_RLEG);
}
break;
case KickAttackAction.LEFTMULE:
ahit = new HitData(Mech.LOC_LLEG);
break;
case KickAttackAction.RIGHTMULE:
default:
ahit = new HitData(Mech.LOC_RLEG);
break;
}
addReport(damageEntity(ae, ahit, 2, false, DamageType.NONE, false, false, false));
}
DamageType damageType = DamageType.NONE;
addReport(damageEntity(te, hit, damage, false, damageType, false, false, throughFront));
if (target instanceof VTOL) {
// destroy rotor
addReport(applyCriticalHit(te, VTOL.LOC_ROTOR, new CriticalSlot(CriticalSlot.TYPE_SYSTEM, VTOL.CRIT_ROTOR_DESTROYED), false));
}
}
if ((te.getMovementMode() == IEntityMovementMode.BIPED) || (te.getMovementMode() == IEntityMovementMode.QUAD)) {
PilotingRollData kickPRD = getKickPushPSR(te, ae, te, "was kicked");
game.addPSR(kickPRD);
}
//if the target is an industrial mech, it needs to check for crits
//at the end of turn
if ((te instanceof Mech) && ((Mech)te).isIndustrial()) {
((Mech)te).setCheckForCrit(true);
}
addNewLines();
}
/**
* Handle a kick attack
*/
private void resolveJumpJetAttack(PhysicalResult pr, int lastEntityId) {
JumpJetAttackAction kaa = (JumpJetAttackAction) pr.aaa;
final Entity ae = game.getEntity(kaa.getEntityId());
final Targetable target = game.getTarget(kaa.getTargetType(), kaa.getTargetId());
Entity te = null;
if (target.getTargetType() == Targetable.TYPE_ENTITY) {
te = (Entity) target;
}
boolean throughFront = true;
if (te != null) {
throughFront = Compute.isThroughFrontHex(game, ae.getPosition(), te);
}
String legName = null;
switch (kaa.getLeg()) {
case JumpJetAttackAction.LEFT:
legName = "Left leg";
break;
case JumpJetAttackAction.RIGHT:
legName = "Right leg";
break;
default:
legName = "Both legs";
break;
}
Report r;
// get damage, ToHitData and roll from the PhysicalResult
int damage = pr.damage;
final ToHitData toHit = pr.toHit;
int roll = pr.roll;
final boolean targetInBuilding = Compute.isInBuilding(game, te);
final boolean glancing = game.getOptions().booleanOption("tacops_glancing_blows") && (roll == toHit.getValue());
// Set Margin of Success/Failure.
toHit.setMoS(roll - Math.max(2, toHit.getValue()));
final boolean directBlow = game.getOptions().booleanOption("tacops_direct_blow") && ((toHit.getMoS() / 3) >= 1);
// Which building takes the damage?
Building bldg = game.getBoard().getBuildingAt(target.getPosition());
if (lastEntityId != ae.getId()) {
// who is making the attacks
r = new Report(4005);
r.subject = ae.getId();
r.addDesc(ae);
addReport(r);
}
r = new Report(4290);
r.subject = ae.getId();
r.indent();
r.add(legName);
r.add(target.getDisplayName());
r.newlines = 0;
addReport(r);
if (toHit.getValue() == TargetRoll.IMPOSSIBLE) {
r = new Report(4075);
r.subject = ae.getId();
r.add(toHit.getDesc());
addReport(r);
return;
} else if (toHit.getValue() == TargetRoll.AUTOMATIC_SUCCESS) {
r = new Report(4080);
r.subject = ae.getId();
r.add(toHit.getDesc());
r.newlines = 0;
addReport(r);
roll = Integer.MAX_VALUE;
} else {
r = new Report(4025);
r.subject = ae.getId();
r.add(toHit.getValue());
r.add(roll);
r.newlines = 0;
addReport(r);
if (glancing) {
r = new Report(4030);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
}
if (directBlow) {
r = new Report(4032);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
}
}
// do we hit?
if (roll < toHit.getValue()) {
// miss
r = new Report(4035);
r.subject = ae.getId();
addReport(r);
// If the target is in a building, the building absorbs the damage.
if (targetInBuilding && (bldg != null)) {
damage += pr.damageRight;
// Only report if damage was done to the building.
if (damage > 0) {
Vector<Report> buildingReport = damageBuilding(bldg, damage, target.getPosition());
for (Report report : buildingReport) {
report.subject = ae.getId();
}
addReport(buildingReport);
}
}
return;
}
// Targeting a building.
if ((target.getTargetType() == Targetable.TYPE_BUILDING) || (target.getTargetType() == Targetable.TYPE_FUEL_TANK)) {
damage += pr.damageRight;
// The building takes the full brunt of the attack.
r = new Report(4040);
r.subject = ae.getId();
addReport(r);
Vector<Report> buildingReport = damageBuilding(bldg, damage, target.getPosition());
for (Report report : buildingReport) {
report.subject = ae.getId();
}
addReport(buildingReport);
// Damage any infantry in the hex.
damageInfantryIn(bldg, damage, target.getPosition());
// And we're done!
return;
}
r = new Report(4040);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
for (int leg = 0; leg < 2; leg++) {
if (leg == 1) {
damage = pr.damageRight;
if (damage == 0) {
break;
}
}
HitData hit = te.rollHitLocation(toHit.getHitTable(), toHit.getSideTable());
hit.setGeneralDamageType(HitData.DAMAGE_ENERGY);
// The building shields all units from a certain amount of damage.
// The amount is based upon the building's CF at the phase's start.
if (targetInBuilding && (bldg != null)) {
int bldgAbsorbs = (int) Math.ceil(bldg.getPhaseCF(target.getPosition()) / 10.0);
int toBldg = Math.min(bldgAbsorbs, damage);
damage -= toBldg;
addNewLines();
Vector<Report> buildingReport = damageBuilding(bldg, damage, target.getPosition());
for (Report report : buildingReport) {
report.subject = ae.getId();
}
addReport(buildingReport);
}
// A building may absorb the entire shot.
if (damage == 0) {
r = new Report(4050);
r.subject = ae.getId();
r.add(te.getShortName());
r.add(te.getOwner().getName());
r.newlines = 0;
addReport(r);
} else {
if (glancing) {
damage = (int) Math.floor(damage / 2.0);
}
if (directBlow) {
damage += toHit.getMoS() / 3;
hit.makeDirectBlow(toHit.getMoS() / 3);
}
addReport(damageEntity(te, hit, damage, false, DamageType.NONE, false, false, throughFront));
}
}
addNewLines();
//if the target is an industrial mech, it needs to check for crits
//at the end of turn
if ((target instanceof Mech) && ((Mech)target).isIndustrial()) {
((Mech)target).setCheckForCrit(true);
}
}
/**
* Handle a Protomech physicalattack
*/
private void resolveProtoAttack(PhysicalResult pr, int lastEntityId) {
final ProtomechPhysicalAttackAction ppaa = (ProtomechPhysicalAttackAction) pr.aaa;
final Entity ae = game.getEntity(ppaa.getEntityId());
// get damage, ToHitData and roll from the PhysicalResult
int damage = pr.damage;
final ToHitData toHit = pr.toHit;
int roll = pr.roll;
final Targetable target = game.getTarget(ppaa.getTargetType(), ppaa.getTargetId());
Entity te = null;
if (target.getTargetType() == Targetable.TYPE_ENTITY) {
te = (Entity) target;
}
boolean throughFront = true;
if (te != null) {
throughFront = Compute.isThroughFrontHex(game, ae.getPosition(), te);
}
final boolean targetInBuilding = Compute.isInBuilding(game, te);
final boolean glancing = game.getOptions().booleanOption("tacops_glancing_blows") && (roll == toHit.getValue());
// Set Margin of Success/Failure.
toHit.setMoS(roll - Math.max(2, toHit.getValue()));
final boolean directBlow = game.getOptions().booleanOption("tacops_direct_blow") && ((toHit.getMoS() / 3) >= 1);
Report r;
// Which building takes the damage?
Building bldg = game.getBoard().getBuildingAt(target.getPosition());
if (lastEntityId != ae.getId()) {
// who is making the attacks
r = new Report(4005);
r.subject = ae.getId();
r.addDesc(ae);
addReport(r);
}
r = new Report(4070);
r.subject = ae.getId();
r.indent();
r.add(target.getDisplayName());
r.newlines = 0;
addReport(r);
if (toHit.getValue() == TargetRoll.IMPOSSIBLE) {
r = new Report(4075);
r.subject = ae.getId();
r.add(toHit.getDesc());
addReport(r);
return;
} else if (toHit.getValue() == TargetRoll.AUTOMATIC_SUCCESS) {
r = new Report(4080);
r.subject = ae.getId();
r.add(toHit.getDesc());
r.newlines = 0;
addReport(r);
roll = Integer.MAX_VALUE;
} else {
// report the roll
r = new Report(4025);
r.subject = ae.getId();
r.add(toHit.getValue());
r.add(roll);
r.newlines = 0;
addReport(r);
if (glancing) {
r = new Report(4030);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
}
if (directBlow) {
r = new Report(4032);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
}
}
// do we hit?
if (roll < toHit.getValue()) {
// miss
r = new Report(4035);
r.subject = ae.getId();
addReport(r);
// If the target is in a building, the building absorbs the damage.
if (targetInBuilding && (bldg != null)) {
// Only report if damage was done to the building.
if (damage > 0) {
Vector<Report> buildingReport = damageBuilding(bldg, damage, target.getPosition());
for (Report report : buildingReport) {
report.subject = ae.getId();
}
addReport(buildingReport);
}
}
return;
}
// Targeting a building.
if ((target.getTargetType() == Targetable.TYPE_BUILDING) || (target.getTargetType() == Targetable.TYPE_FUEL_TANK)) {
// The building takes the full brunt of the attack.
r = new Report(4040);
r.subject = ae.getId();
addReport(r);
Vector<Report> buildingReport = damageBuilding(bldg, damage, target.getPosition());
for (Report report : buildingReport) {
report.subject = ae.getId();
}
addReport(buildingReport);
// Damage any infantry in the hex.
damageInfantryIn(bldg, damage, target.getPosition());
// And we're done!
return;
}
HitData hit = te.rollHitLocation(toHit.getHitTable(), toHit.getSideTable());
hit.setGeneralDamageType(HitData.DAMAGE_PHYSICAL);
r = new Report(4045);
r.subject = ae.getId();
r.add(toHit.getTableDesc());
r.add(te.getLocationAbbr(hit));
r.newlines = 0;
addReport(r);
// The building shields all units from a certain amount of damage.
// The amount is based upon the building's CF at the phase's start.
if (targetInBuilding && (bldg != null)) {
int bldgAbsorbs = (int) Math.ceil(bldg.getPhaseCF(target.getPosition()) / 10.0);
int toBldg = Math.min(bldgAbsorbs, damage);
damage -= toBldg;
addNewLines();
Vector<Report> buildingReport = damageBuilding(bldg, damage, target.getPosition());
for (Report report : buildingReport) {
report.subject = ae.getId();
}
addReport(buildingReport);
}
// A building may absorb the entire shot.
if (damage == 0) {
r = new Report(4050);
r.subject = ae.getId();
r.add(te.getShortName());
r.add(te.getOwner().getName());
r.newlines = 0;
addReport(r);
} else {
if (glancing) {
damage = (int) Math.floor(damage / 2.0);
}
if (directBlow) {
damage += toHit.getMoS() / 3;
hit.makeDirectBlow(toHit.getMoS() / 3);
}
addReport(damageEntity(te, hit, damage, false, DamageType.NONE, false, false, throughFront));
}
addNewLines();
//if the target is an industrial mech, it needs to check for crits
//at the end of turn
if ((target instanceof Mech) && ((Mech)target).isIndustrial()) {
((Mech)target).setCheckForCrit(true);
}
}
/**
* Handle a brush off attack
*/
private void resolveBrushOffAttack(PhysicalResult pr, int lastEntityId) {
final BrushOffAttackAction baa = (BrushOffAttackAction) pr.aaa;
final Entity ae = game.getEntity(baa.getEntityId());
// PLEASE NOTE: buildings are *never* the target
// of a "brush off", but iNarc pods **are**.
Targetable target = game.getTarget(baa.getTargetType(), baa.getTargetId());
Entity te = null;
final String armName = baa.getArm() == BrushOffAttackAction.LEFT ? "Left Arm" : "Right Arm";
Report r;
if (target.getTargetType() == Targetable.TYPE_ENTITY) {
te = game.getEntity(baa.getTargetId());
}
// get damage, ToHitData and roll from the PhysicalResult
// ASSUMPTION: buildings can't absorb *this* damage.
int damage = baa.getArm() == BrushOffAttackAction.LEFT ? pr.damage : pr.damageRight;
final ToHitData toHit = baa.getArm() == BrushOffAttackAction.LEFT ? pr.toHit : pr.toHitRight;
int roll = baa.getArm() == BrushOffAttackAction.LEFT ? pr.roll : pr.rollRight;
if (lastEntityId != baa.getEntityId()) {
// who is making the attacks
r = new Report(4005);
r.subject = ae.getId();
r.addDesc(ae);
addReport(r);
}
r = new Report(4085);
r.subject = ae.getId();
r.indent();
r.add(target.getDisplayName());
r.add(armName);
r.newlines = 0;
addReport(r);
if (toHit.getValue() == TargetRoll.IMPOSSIBLE) {
r = new Report(4090);
r.subject = ae.getId();
r.add(toHit.getDesc());
addReport(r);
return;
}
// report the roll
r = new Report(4025);
r.subject = ae.getId();
r.add(toHit.getValue());
r.add(roll);
r.newlines = 0;
addReport(r);
// do we hit?
if (roll < toHit.getValue()) {
// miss
r = new Report(4035);
r.subject = ae.getId();
addReport(r);
// Missed Brush Off attacks cause punch damage to the attacker.
toHit.setHitTable(ToHitData.HIT_PUNCH);
toHit.setSideTable(ToHitData.SIDE_FRONT);
HitData hit = ae.rollHitLocation(toHit.getHitTable(), toHit.getSideTable());
hit.setGeneralDamageType(HitData.DAMAGE_PHYSICAL);
r = new Report(4095);
r.subject = ae.getId();
r.addDesc(ae);
r.add(ae.getLocationAbbr(hit));
r.newlines = 0;
addReport(r);
addReport(damageEntity(ae, hit, damage));
addNewLines();
//if this is an industrial mech, it needs to check for crits
//at the end of turn
if ((ae instanceof Mech) && ((Mech)ae).isIndustrial()) {
((Mech)ae).setCheckForCrit(true);
}
return;
}
// Different target types get different handling.
switch (target.getTargetType()) {
case Targetable.TYPE_ENTITY:
// Handle Entity targets.
HitData hit = te.rollHitLocation(toHit.getHitTable(), toHit.getSideTable());
hit.setGeneralDamageType(HitData.DAMAGE_PHYSICAL);
r = new Report(4045);
r.subject = ae.getId();
r.add(toHit.getTableDesc());
r.add(te.getLocationAbbr(hit));
r.newlines = 0;
addReport(r);
addReport(damageEntity(te, hit, damage));
addNewLines();
// Dislodge the swarming infantry.
ae.setSwarmAttackerId(Entity.NONE);
te.setSwarmTargetId(Entity.NONE);
r = new Report(4100);
r.subject = ae.getId();
r.add(te.getDisplayName());
addReport(r);
break;
case Targetable.TYPE_INARC_POD:
// Handle iNarc pod targets.
// TODO : check the return code and handle false appropriately.
ae.removeINarcPod((INarcPod) target);
// // TODO : confirm that we don't need to update the attacker.
// //killme
// entityUpdate( ae.getId() ); // killme
r = new Report(4105);
r.subject = ae.getId();
r.add(target.getDisplayName());
addReport(r);
break;
// TODO : add a default: case and handle it appropriately.
}
}
/**
* Handle a thrash attack
*/
private void resolveThrashAttack(PhysicalResult pr, int lastEntityId) {
final ThrashAttackAction taa = (ThrashAttackAction) pr.aaa;
final Entity ae = game.getEntity(taa.getEntityId());
// get damage, ToHitData and roll from the PhysicalResult
int hits = pr.damage;
final ToHitData toHit = pr.toHit;
int roll = pr.roll;
final boolean glancing = game.getOptions().booleanOption("tacops_glancing_blows") && (roll == toHit.getValue());
// Set Margin of Success/Failure.
toHit.setMoS(roll - Math.max(2, toHit.getValue()));
final boolean directBlow = game.getOptions().booleanOption("tacops_direct_blow") && ((toHit.getMoS() / 3) >= 1);
// PLEASE NOTE: buildings are *never* the target of a "thrash".
final Entity te = game.getEntity(taa.getTargetId());
Report r;
if (lastEntityId != taa.getEntityId()) {
// who is making the attacks
r = new Report(4005);
r.subject = ae.getId();
r.addDesc(ae);
addReport(r);
}
r = new Report(4110);
r.subject = ae.getId();
r.indent();
r.addDesc(te);
r.newlines = 0;
addReport(r);
if (toHit.getValue() == TargetRoll.IMPOSSIBLE) {
r = new Report(4115);
r.subject = ae.getId();
r.add(toHit.getDesc());
addReport(r);
return;
}
// Thrash attack may hit automatically
if (toHit.getValue() == TargetRoll.AUTOMATIC_SUCCESS) {
r = new Report(4120);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
} else {
// report the roll
r = new Report(4025);
r.subject = ae.getId();
r.add(toHit.getValue());
r.add(roll);
r.newlines = 0;
addReport(r);
// do we hit?
if (roll < toHit.getValue()) {
// miss
r = new Report(4035);
r.subject = ae.getId();
addReport(r);
return;
}
r = new Report(4125);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
}
// Standard damage loop in 5 point clusters.
if (glancing) {
hits = (int) Math.floor(hits / 2.0);
}
if (directBlow) {
hits += toHit.getMoS() / 3;
}
r = new Report(4130);
r.subject = ae.getId();
r.add(hits);
r.newlines = 0;
addReport(r);
if (glancing) {
r = new Report(4030);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
}
if (directBlow) {
r = new Report(4032);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
}
while (hits > 0) {
int damage = Math.min(5, hits);
hits -= damage;
HitData hit = te.rollHitLocation(toHit.getHitTable(), toHit.getSideTable());
hit.setGeneralDamageType(HitData.DAMAGE_PHYSICAL);
r = new Report(4135);
r.subject = ae.getId();
r.add(te.getLocationAbbr(hit));
r.newlines = 0;
addReport(r);
addReport(damageEntity(te, hit, damage));
}
addNewLines();
// Thrash attacks cause PSRs. Failed PSRs cause falling damage.
// This fall damage applies even though the Thrashing Mek is prone.
PilotingRollData rollData = ae.getBasePilotingRoll();
ae.addPilotingModifierForTerrain(rollData);
rollData.addModifier(0, "thrashing at infantry");
r = new Report(4140);
r.subject = ae.getId();
r.addDesc(ae);
addReport(r);
final int diceRoll = Compute.d6(2);
r = new Report(2190);
r.subject = ae.getId();
r.add(rollData.getValueAsString());
r.add(rollData.getDesc());
r.add(diceRoll);
if (diceRoll < rollData.getValue()) {
r.choose(false);
addReport(r);
addReport(doEntityFall(ae, rollData));
} else {
r.choose(true);
addReport(r);
}
}
/**
* Handle a thrash attack
*/
private void resolveBAVibroClawAttack(PhysicalResult pr, int lastEntityId) {
final BAVibroClawAttackAction bvaa = (BAVibroClawAttackAction) pr.aaa;
final Entity ae = game.getEntity(bvaa.getEntityId());
// get damage, ToHitData and roll from the PhysicalResult
int hits = pr.damage;
final ToHitData toHit = pr.toHit;
int roll = pr.roll;
final boolean glancing = game.getOptions().booleanOption("tacops_glancing_blows") && (roll == toHit.getValue());
// Set Margin of Success/Failure.
toHit.setMoS(roll - Math.max(2, toHit.getValue()));
final boolean directBlow = game.getOptions().booleanOption("tacops_direct_blow") && ((toHit.getMoS() / 3) >= 1);
// PLEASE NOTE: buildings are *never* the target of a BA vibroclaw attack.
final Entity te = game.getEntity(bvaa.getTargetId());
Report r;
if (lastEntityId != bvaa.getEntityId()) {
// who is making the attacks
r = new Report(4005);
r.subject = ae.getId();
r.addDesc(ae);
addReport(r);
}
r = new Report(4146);
r.subject = ae.getId();
r.indent();
r.addDesc(te);
r.newlines = 0;
addReport(r);
if (toHit.getValue() == TargetRoll.IMPOSSIBLE) {
r = new Report(4147);
r.subject = ae.getId();
r.add(toHit.getDesc());
addReport(r);
return;
}
// we may hit automatically
if (toHit.getValue() == TargetRoll.AUTOMATIC_SUCCESS) {
r = new Report(4120);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
} else {
// report the roll
r = new Report(4025);
r.subject = ae.getId();
r.add(toHit.getValue());
r.add(roll);
r.newlines = 0;
addReport(r);
// do we hit?
if (roll < toHit.getValue()) {
// miss
r = new Report(4035);
r.subject = ae.getId();
addReport(r);
return;
}
}
// Standard damage loop
if (glancing) {
hits = (int) Math.floor(hits / 2.0);
}
if (directBlow) {
hits += toHit.getMoS() / 3;
}
if ((te instanceof Infantry) && !(te instanceof BattleArmor)) {
r = new Report(4149);
r.subject = ae.getId();
} else {
r = new Report(4148);
r.subject = ae.getId();
r.add(hits);
r.add(ae.getVibroClaws());
}
r.newlines = 0;
addReport(r);
if (glancing) {
r = new Report(4030);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
}
if (directBlow) {
r = new Report(4032);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
}
while (hits > 0) {
// BA get hit seperately by each attacking BA trooper
int damage = Math.min(ae.getVibroClaws(), hits);
// conv infantry get hit in one lump
if ((te instanceof Infantry) && !(te instanceof BattleArmor)) {
damage = hits;
}
hits -= damage;
HitData hit = te.rollHitLocation(toHit.getHitTable(), toHit.getSideTable());
hit.setGeneralDamageType(HitData.DAMAGE_PHYSICAL);
r = new Report(4135);
r.subject = ae.getId();
r.add(te.getLocationAbbr(hit));
r.newlines = 0;
addReport(r);
addReport(damageEntity(te, hit, damage));
}
addNewLines();
}
/**
* Handle a club attack
*/
private void resolveClubAttack(PhysicalResult pr, int lastEntityId) {
final ClubAttackAction caa = (ClubAttackAction) pr.aaa;
final Entity ae = game.getEntity(caa.getEntityId());
// get damage, ToHitData and roll from the PhysicalResult
int damage = pr.damage;
final ToHitData toHit = pr.toHit;
int roll = pr.roll;
final Targetable target = game.getTarget(caa.getTargetType(), caa.getTargetId());
Entity te = null;
if (target.getTargetType() == Targetable.TYPE_ENTITY) {
te = (Entity) target;
}
boolean throughFront = true;
if (te != null) {
throughFront = Compute.isThroughFrontHex(game, ae.getPosition(), te);
}
final boolean targetInBuilding = Compute.isInBuilding(game, te);
final boolean glancing = game.getOptions().booleanOption("tacops_glancing_blows") && (roll == toHit.getValue());
// Set Margin of Success/Failure.
toHit.setMoS(roll - Math.max(2, toHit.getValue()));
final boolean directBlow = game.getOptions().booleanOption("tacops_direct_blow") && ((toHit.getMoS() / 3) >= 1);
Report r;
// Which building takes the damage?
Building bldg = game.getBoard().getBuildingAt(target.getPosition());
// restore club attack
caa.getClub().restore();
// Shield bash causes 1 point of damage to the shield
if (((MiscType) caa.getClub().getType()).isShield()) {
((Mech) ae).shieldAbsorptionDamage(1, caa.getClub().getLocation(), false);
}
if (lastEntityId != caa.getEntityId()) {
// who is making the attacks
r = new Report(4005);
r.subject = ae.getId();
r.addDesc(ae);
addReport(r);
}
r = new Report(4145);
r.subject = ae.getId();
r.indent();
r.add(caa.getClub().getName());
r.add(target.getDisplayName());
r.newlines = 0;
addReport(r);
// Flail/Wrecking Ball auto misses on a 2 and hits themself.
if ((((MiscType) caa.getClub().getType()).hasSubType(MiscType.S_FLAIL)
|| ((MiscType) caa.getClub().getType()).hasSubType(MiscType.S_WRECKING_BALL))
&& (roll == 2)) {
// miss
r = new Report(4035);
r.subject = ae.getId();
addReport(r);
ToHitData newToHit = new ToHitData(TargetRoll.AUTOMATIC_SUCCESS, "hit with own flail/wrecking ball");
pr.damage /= 2;
newToHit.setHitTable(ToHitData.HIT_NORMAL);
newToHit.setSideTable(ToHitData.SIDE_FRONT);
pr.toHit = newToHit;
pr.aaa.setTargetId(ae.getId());
pr.aaa.setTargetType(Targetable.TYPE_ENTITY);
pr.roll = Integer.MAX_VALUE;
resolveClubAttack(pr, ae.getId());
game.addPSR(new PilotingRollData(ae.getId(), 0, "missed a flail/wrecking ball attack"));
return;
}
// Need to compute 2d6 damage. and add +3 heat build up.
if (((MiscType) (caa.getClub().getType())).hasSubType(MiscType.S_BUZZSAW)) {
damage = Compute.d6(2);
ae.heatBuildup += 3;
// Buzzsaw's blade will shatter on a roll of 2.
if (roll == 2) {
Mounted club = caa.getClub();
for (Mounted eq : ae.getWeaponList()) {
if ((eq.getLocation() == club.getLocation()) && (eq.getType() instanceof MiscType) && ((MiscType) eq.getType()).hasFlag(MiscType.F_CLUB) && ((MiscType) eq.getType()).hasSubType(MiscType.S_BUZZSAW)) {
eq.setDestroyed(true);
break;
}
}
r = new Report(4037);
r.subject = ae.getId();
addReport(r);
damage = 0;
return;
}
}
if (toHit.getValue() == TargetRoll.IMPOSSIBLE) {
r = new Report(4075);
r.subject = ae.getId();
r.add(toHit.getDesc());
addReport(r);
if (((MiscType) caa.getClub().getType()).hasSubType(MiscType.S_MACE_THB)) {
game.addPSR(new PilotingRollData(ae.getId(), 0, "missed a mace attack"));
}
if (((MiscType) caa.getClub().getType()).hasSubType(MiscType.S_MACE)) {
game.addPSR(new PilotingRollData(ae.getId(), 2, "missed a mace attack"));
}
return;
} else if (toHit.getValue() == TargetRoll.AUTOMATIC_SUCCESS) {
r = new Report(4080);
r.subject = ae.getId();
r.add(toHit.getDesc());
r.newlines = 0;
addReport(r);
roll = Integer.MAX_VALUE;
} else {
// report the roll
r = new Report(4025);
r.subject = ae.getId();
r.add(toHit.getValue());
r.add(roll);
r.newlines = 0;
addReport(r);
if (glancing) {
r = new Report(4030);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
}
if (directBlow) {
r = new Report(4032);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
}
}
// do we hit?
if (roll < toHit.getValue()) {
// miss
r = new Report(4035);
r.subject = ae.getId();
addReport(r);
if (((MiscType) caa.getClub().getType()).hasSubType(MiscType.S_MACE_THB)) {
game.addPSR(new PilotingRollData(ae.getId(), 0, "missed a mace attack"));
}
if (((MiscType) caa.getClub().getType()).hasSubType(MiscType.S_MACE)) {
game.addPSR(new PilotingRollData(ae.getId(), 2, "missed a mace attack"));
}
// If the target is in a building, the building absorbs the damage.
if (targetInBuilding && (bldg != null)) {
// Only report if damage was done to the building.
if (damage > 0) {
Vector<Report> buildingReport = damageBuilding(bldg, damage, target.getPosition());
for (Report report : buildingReport) {
report.subject = ae.getId();
}
addReport(buildingReport);
}
}
return;
}
// Targeting a building.
if ((target.getTargetType() == Targetable.TYPE_BUILDING)
|| (target.getTargetType() == Targetable.TYPE_FUEL_TANK)) {
// The building takes the full brunt of the attack.
r = new Report(4040);
r.subject = ae.getId();
addReport(r);
Vector<Report> buildingReport = damageBuilding(bldg, damage, target.getPosition());
for (Report report : buildingReport) {
report.subject = ae.getId();
}
addReport(buildingReport);
// Damage any infantry in the hex.
damageInfantryIn(bldg, damage, target.getPosition());
// And we're done!
return;
}
HitData hit = te.rollHitLocation(toHit.getHitTable(), toHit.getSideTable());
hit.setGeneralDamageType(HitData.DAMAGE_PHYSICAL);
r = new Report(4045);
r.subject = ae.getId();
r.add(toHit.getTableDesc());
r.add(te.getLocationAbbr(hit));
r.newlines = 0;
addReport(r);
// The building shields all units from a certain amount of damage.
// The amount is based upon the building's CF at the phase's start.
if (targetInBuilding && (bldg != null)) {
int bldgAbsorbs = (int) Math.ceil(bldg.getPhaseCF(target.getPosition()) / 10.0);
int toBldg = Math.min(bldgAbsorbs, damage);
damage -= toBldg;
addNewLines();
Vector<Report> buildingReport = damageBuilding(bldg, damage, target.getPosition());
for (Report report : buildingReport) {
report.subject = ae.getId();
}
addReport(buildingReport);
}
// A building may absorb the entire shot.
if (damage == 0) {
r = new Report(4050);
r.subject = ae.getId();
r.add(te.getShortName());
r.add(te.getOwner().getName());
r.newlines = 0;
addReport(r);
} else {
if (glancing) {
damage = (int) Math.floor(damage / 2.0);
}
if (directBlow) {
damage += toHit.getMoS() / 3;
hit.makeDirectBlow(toHit.getMoS() / 3);
}
/*if (damage >= 1 && te.hasWorkingMisc(MiscType.F_SPIKES, -1, hit.getLocation())) {
r = new Report(4330);
r.indent(2);
r.newlines = 0;
r.subject = ae.getId();
addReport(r);
checkBreakSpikes(te, hit.getLocation());
damage = Math.max(1, damage - 4);
int loc = caa.getClub().getLocation();
if (loc == Entity.LOC_NONE) {
addReport(damageEntity(ae, new HitData(Mech.LOC_LARM), 1, false, DamageType.NONE, false, false, false));
addReport(damageEntity(ae, new HitData(Mech.LOC_RARM), 1, false, DamageType.NONE, false, false, false));
} else {
addReport(damageEntity(ae, new HitData(loc), 2, false, DamageType.NONE, false, false, false));
}
}*/
DamageType damageType = DamageType.NONE;
addReport(damageEntity(te, hit, damage, false, damageType, false, false, throughFront));
if (target instanceof VTOL) {
// destroy rotor
addReport(applyCriticalHit(te, VTOL.LOC_ROTOR, new CriticalSlot(CriticalSlot.TYPE_SYSTEM, VTOL.CRIT_ROTOR_DESTROYED), false));
}
}
// On a roll of 10+ a lance hitting a mech/Vehicle can cause 1 point of
// internal damage
if (((MiscType) caa.getClub().getType()).hasSubType(MiscType.S_LANCE) && (te.getArmor(hit) > 0) && (te.getArmorType() != EquipmentType.T_ARMOR_HARDENED)) {
roll = Compute.d6(2);
// Pierce checking report
r = new Report(4021);
r.indent(2);
r.subject = ae.getId();
r.add(te.getLocationAbbr(hit));
r.add(roll);
r.newlines = 1;
addReport(r);
if (roll >= 10) {
hit.makeGlancingBlow();
addReport(damageEntity(te, hit, 1, false, DamageType.NONE, true, false, throughFront));
}
}
if (((MiscType) caa.getClub().getType()).hasSubType(MiscType.S_CHAIN_WHIP)
&& (te instanceof Mech)) {
addNewLines();
int loc = hit.getLocation();
int toHitNumber = toHit.getValue();
if ( ((loc == Mech.LOC_LLEG) || (loc == Mech.LOC_RLEG)) && (!te.hasActiveShield(loc) && !te.hasPassiveShield(loc))) {
roll = Compute.d6(2);
if ( (((Mech)ae).hasTSM() && (ae.heat >= 9))
&& (!((Mech)te).hasTSM() || ((((Mech)te).hasTSM()) && (te.heat < 9))) ) {
toHitNumber -= 2;
}
r = new Report(4450);
r.subject = ae.getId();
r.add(ae.getShortName());
r.add(te.getShortName());
r.add(toHitNumber);
r.add(roll);
r.indent(2);
r.newlines = 0;
addReport(r);
if ( roll >= toHit.getValue() ) {
r = new Report(2270);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
game.addPSR(new PilotingRollData(te.getId(),3,"Snared by chain whip"));
}else {
r = new Report(2357);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
}
} else if ( ((loc == Mech.LOC_RARM) || (loc == Mech.LOC_LARM))
&& (!te.hasActiveShield(loc) && !te.hasPassiveShield(loc) && !te.hasNoDefenseShield(loc)) ) {
GrappleAttackAction gaa = new GrappleAttackAction(ae.getId(),te.getId());
ToHitData grappleHit = GrappleAttackAction.toHit(game, ae.getId(), target);
PhysicalResult grappleResult = new PhysicalResult();
grappleResult.aaa = gaa;
grappleResult.toHit = grappleHit;
grappleResult.roll = Compute.d6(2);
resolveGrappleAttack(grappleResult, lastEntityId, hit.getLocation() == Mech.LOC_RARM ? Entity.GRAPPLE_RIGHT : Entity.GRAPPLE_LEFT );
}
}
addNewLines();
if (((MiscType) caa.getClub().getType()).hasSubType(MiscType.S_TREE_CLUB)) {
// the club breaks
r = new Report(4150);
r.subject = ae.getId();
r.add(caa.getClub().getName());
addReport(r);
ae.removeMisc(caa.getClub().getName());
}
//if the target is an industrial mech, it needs to check for crits
//at the end of turn
if ((target instanceof Mech) && ((Mech)target).isIndustrial()) {
((Mech)target).setCheckForCrit(true);
}
}
/**
* Handle a push attack
*/
private void resolvePushAttack(PhysicalResult pr, int lastEntityId) {
final PushAttackAction paa = (PushAttackAction) pr.aaa;
final Entity ae = game.getEntity(paa.getEntityId());
// PLEASE NOTE: buildings are *never* the target of a "push".
final Entity te = game.getEntity(paa.getTargetId());
// get roll and ToHitData from the PhysicalResult
int roll = pr.roll;
final ToHitData toHit = pr.toHit;
Report r;
// was this push resolved earlier?
if (pr.pushBackResolved) {
return;
}
// don't try this one again
pr.pushBackResolved = true;
if (lastEntityId != paa.getEntityId()) {
// who is making the attack
r = new Report(4005);
r.subject = ae.getId();
r.addDesc(ae);
addReport(r);
}
r = new Report(4155);
r.subject = ae.getId();
r.indent();
r.addDesc(te);
r.newlines = 0;
addReport(r);
if (toHit.getValue() == TargetRoll.IMPOSSIBLE) {
r = new Report(4160);
r.subject = ae.getId();
r.add(toHit.getDesc());
addReport(r);
return;
}
// report the roll
r = new Report(4025);
r.subject = ae.getId();
r.add(toHit.getValue());
r.add(roll);
r.newlines = 0;
addReport(r);
// check if our target has a push against us, too, and get it
PhysicalResult targetPushResult = null;
for (PhysicalResult tpr : physicalResults) {
if ((tpr.aaa.getEntityId() == te.getId()) && (tpr.aaa instanceof PushAttackAction) && (tpr.aaa.getTargetId() == ae.getId())) {
targetPushResult = tpr;
}
}
// if our target has a push against us,
// and we are hitting, we need to resolve both now
if ((targetPushResult != null) && !targetPushResult.pushBackResolved && (roll >= toHit.getValue())) {
targetPushResult.pushBackResolved = true;
// do they hit?
if (targetPushResult.roll >= targetPushResult.toHit.getValue()) {
r = new Report(4165);
r.subject = ae.getId();
r.addDesc(te);
r.addDesc(te);
r.addDesc(ae);
r.add(targetPushResult.toHit.getValue());
r.add(targetPushResult.roll);
r.addDesc(ae);
addReport(r);
PilotingRollData targetPushPRD = getKickPushPSR(te, ae, te, "was pushed");
PilotingRollData pushPRD = getKickPushPSR(ae, ae, te, "was pushed");
game.addPSR(pushPRD);
game.addPSR(targetPushPRD);
return;
}
// report the miss
r = new Report(4166);
r.subject = ae.getId();
r.addDesc(te);
r.addDesc(ae);
r.add(targetPushResult.toHit.getValue());
r.add(targetPushResult.roll);
addReport(r);
}
// do we hit?
if (roll < toHit.getValue()) {
// miss
r = new Report(4035);
r.subject = ae.getId();
addReport(r);
return;
}
// we hit...
int direction = ae.getFacing();
Coords src = te.getPosition();
Coords dest = src.translated(direction);
PilotingRollData pushPRD = getKickPushPSR(te, ae, te, "was pushed");
if (Compute.isValidDisplacement(game, te.getId(), te.getPosition(), direction)) {
r = new Report(4170);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
if (game.getBoard().contains(dest)) {
r = new Report(4175);
r.subject = ae.getId();
r.add(dest.getBoardNum(), true);
addReport(r);
} else {
// uh-oh, pushed off board
r = new Report(4180);
r.subject = ae.getId();
addReport(r);
}
addReport(doEntityDisplacement(te, src, dest, pushPRD));
// if push actually moved the target, attacker follows thru
if (!te.getPosition().equals(src)) {
ae.setPosition(src);
}
} else {
// targe imovable
r = new Report(4185);
r.subject = ae.getId();
addReport(r);
game.addPSR(pushPRD);
}
//if the target is an industrial mech, it needs to check for crits
//at the end of turn
if ((te instanceof Mech) && ((Mech)te).isIndustrial()) {
((Mech)te).setCheckForCrit(true);
}
addNewLines();
}
/**
* Handle a trip attack
*/
private void resolveTripAttack(PhysicalResult pr, int lastEntityId) {
final TripAttackAction paa = (TripAttackAction) pr.aaa;
final Entity ae = game.getEntity(paa.getEntityId());
// PLEASE NOTE: buildings are *never* the target of a "trip".
final Entity te = game.getEntity(paa.getTargetId());
// get roll and ToHitData from the PhysicalResult
int roll = pr.roll;
final ToHitData toHit = pr.toHit;
Report r;
if (lastEntityId != paa.getEntityId()) {
// who is making the attack
r = new Report(4005);
r.subject = ae.getId();
r.addDesc(ae);
addReport(r);
}
r = new Report(4280);
r.subject = ae.getId();
r.indent();
r.addDesc(te);
r.newlines = 0;
addReport(r);
if (toHit.getValue() == TargetRoll.IMPOSSIBLE) {
r = new Report(4285);
r.subject = ae.getId();
r.add(toHit.getDesc());
addReport(r);
return;
}
// report the roll
r = new Report(4025);
r.subject = ae.getId();
r.add(toHit.getValue());
r.add(roll);
r.newlines = 0;
addReport(r);
// do we hit?
if (roll < toHit.getValue()) {
// miss
r = new Report(4035);
r.subject = ae.getId();
addReport(r);
return;
}
// we hit...
PilotingRollData pushPRD = getKickPushPSR(te, ae, te, "was tripped");
game.addPSR(pushPRD);
r = new Report(4040);
r.subject = ae.getId();
addReport(r);
addNewLines();
//if the target is an industrial mech, it needs to check for crits
//at the end of turn
if ((te instanceof Mech) && ((Mech)te).isIndustrial()) {
((Mech)te).setCheckForCrit(true);
}
}
/**
* Handle a grapple attack
*/
private void resolveGrappleAttack(PhysicalResult pr, int lastEntityId) {
resolveGrappleAttack(pr, lastEntityId, Entity.GRAPPLE_BOTH);
}
private void resolveGrappleAttack(PhysicalResult pr, int lastEntityId, int grappleSide) {
final GrappleAttackAction paa = (GrappleAttackAction) pr.aaa;
final Entity ae = game.getEntity(paa.getEntityId());
// PLEASE NOTE: buildings are *never* the target of a "push".
final Entity te = game.getEntity(paa.getTargetId());
// get roll and ToHitData from the PhysicalResult
int roll = pr.roll;
final ToHitData toHit = pr.toHit;
Report r;
// same method as push, for counterattacks
if (pr.pushBackResolved) {
return;
}
if ((te.getGrappled() != Entity.NONE) || (ae.getGrappled() != Entity.NONE)) {
toHit.addModifier(TargetRoll.IMPOSSIBLE, "Already Grappled");
}
if (lastEntityId != paa.getEntityId()) {
// who is making the attack
r = new Report(4005);
r.subject = ae.getId();
r.addDesc(ae);
addReport(r);
}
r = new Report(4295);
r.subject = ae.getId();
r.indent();
r.addDesc(te);
r.newlines = 0;
addReport(r);
if (toHit.getValue() == TargetRoll.IMPOSSIBLE) {
r = new Report(4300);
r.subject = ae.getId();
r.add(toHit.getDesc());
addReport(r);
return;
}
// report the roll
r = new Report(4025);
r.subject = ae.getId();
r.add(toHit.getValue());
r.add(roll);
r.newlines = 0;
addReport(r);
// do we hit?
if (roll < toHit.getValue()) {
// miss
r = new Report(4035);
r.subject = ae.getId();
addReport(r);
return;
}
// we hit...
ae.setGrappled(te.getId(), true);
te.setGrappled(ae.getId(), false);
Coords pos = te.getPosition();
ae.setPosition(pos);
ae.setElevation(te.getElevation());
te.setFacing((ae.getFacing() + 3) % 6);
addReport(doSetLocationsExposure(ae, game.getBoard().getHex(pos), false, ae.getElevation()));
ae.setGrappleSide(grappleSide);
te.setGrappleSide(grappleSide);
r = new Report(4040);
r.subject = ae.getId();
addReport(r);
addNewLines();
//if the target is an industrial mech, it needs to check for crits
//at the end of turn
if ((te instanceof Mech) && ((Mech)te).isIndustrial()) {
((Mech)te).setCheckForCrit(true);
}
}
/**
* Handle a break grapple attack
*/
private void resolveBreakGrappleAttack(PhysicalResult pr, int lastEntityId) {
final BreakGrappleAttackAction paa = (BreakGrappleAttackAction) pr.aaa;
final Entity ae = game.getEntity(paa.getEntityId());
// PLEASE NOTE: buildings are *never* the target of a "push".
final Entity te = game.getEntity(paa.getTargetId());
// get roll and ToHitData from the PhysicalResult
int roll = pr.roll;
final ToHitData toHit = pr.toHit;
Report r;
if (lastEntityId != paa.getEntityId()) {
// who is making the attack
r = new Report(4005);
r.subject = ae.getId();
r.addDesc(ae);
addReport(r);
}
r = new Report(4305);
r.subject = ae.getId();
r.indent();
r.addDesc(te);
r.newlines = 0;
addReport(r);
if (toHit.getValue() == TargetRoll.IMPOSSIBLE) {
r = new Report(4310);
r.subject = ae.getId();
r.add(toHit.getDesc());
addReport(r);
return;
}
if (toHit.getValue() == TargetRoll.AUTOMATIC_SUCCESS) {
r = new Report(4320);
r.subject = ae.getId();
r.add(toHit.getDesc());
addReport(r);
} else {
// report the roll
r = new Report(4025);
r.subject = ae.getId();
r.add(toHit.getValue());
r.add(roll);
r.newlines = 0;
addReport(r);
// do we hit?
if (roll < toHit.getValue()) {
// miss
r = new Report(4035);
r.subject = ae.getId();
addReport(r);
return;
}
// hit
r = new Report(4040);
r.subject = ae.getId();
addReport(r);
}
// is there a counterattack?
PhysicalResult targetGrappleResult = null;
for (PhysicalResult tpr : physicalResults) {
if ((tpr.aaa.getEntityId() == te.getId()) && (tpr.aaa instanceof GrappleAttackAction) && (tpr.aaa.getTargetId() == ae.getId())) {
targetGrappleResult = tpr;
break;
}
}
if (targetGrappleResult != null) {
targetGrappleResult.pushBackResolved = true;
// counterattack
r = new Report(4315);
r.subject = te.getId();
r.newlines = 0;
r.addDesc(te);
addReport(r);
// report the roll
r = new Report(4025);
r.subject = te.getId();
r.add(targetGrappleResult.toHit.getValue());
r.add(targetGrappleResult.roll);
r.newlines = 0;
addReport(r);
// do we hit?
if (roll < toHit.getValue()) {
// miss
r = new Report(4035);
r.subject = ae.getId();
addReport(r);
} else {
// hit
r = new Report(4040);
r.subject = ae.getId();
addReport(r);
// exchange attacker and defender
ae.setGrappled(te.getId(), false);
te.setGrappled(ae.getId(), true);
return;
}
}
// score the adjacent hexes
Coords hexes[] = new Coords[6];
int scores[] = new int[6];
IHex curHex = game.getBoard().getHex(ae.getPosition());
for (int i = 0; i < 6; i++) {
hexes[i] = ae.getPosition().translated(i);
scores[i] = 0;
IHex hex = game.getBoard().getHex(hexes[i]);
if (hex.containsTerrain(Terrains.MAGMA)) {
scores[i] += 10;
}
if (hex.containsTerrain(Terrains.WATER)) {
scores[i] += hex.terrainLevel(Terrains.WATER);
}
if (curHex.surface() - hex.surface() >= 2) {
scores[i] += 2 * (curHex.surface() - hex.surface());
}
}
int bestScore = 99999;
int best = 0;
int worstScore = -99999;
int worst = 0;
for (int i = 0; i < 6; i++) {
if (bestScore > scores[i]) {
best = i;
bestScore = scores[i];
}
if (worstScore < scores[i]) {
worst = i;
worstScore = scores[i];
}
}
// attacker doesnt fall, unless off a cliff
if (ae.isGrappleAttacker()) {
// move self to least dangerous hex
PilotingRollData psr = ae.getBasePilotingRoll();
psr.addModifier(TargetRoll.AUTOMATIC_SUCCESS, "break grapple");
addReport(doEntityDisplacement(ae, ae.getPosition(), hexes[best], psr));
ae.setFacing(hexes[best].direction(te.getPosition()));
} else {
// move enemy to most dangerous hex
PilotingRollData psr = te.getBasePilotingRoll();
psr.addModifier(TargetRoll.AUTOMATIC_SUCCESS, "break grapple");
addReport(doEntityDisplacement(te, te.getPosition(), hexes[worst], psr));
te.setFacing(hexes[worst].direction(ae.getPosition()));
}
// grapple is broken
ae.setGrappled(Entity.NONE, false);
te.setGrappled(Entity.NONE, false);
addNewLines();
}
/**
* Handle a charge attack
*/
private void resolveChargeAttack(PhysicalResult pr, int lastEntityId) {
final ChargeAttackAction caa = (ChargeAttackAction) pr.aaa;
final Entity ae = game.getEntity(caa.getEntityId());
final Targetable target = game.getTarget(caa.getTargetType(), caa.getTargetId());
// get damage, ToHitData and roll from the PhysicalResult
int damage = pr.damage;
final ToHitData toHit = pr.toHit;
int roll = pr.roll;
Entity te = null;
if ((target != null) && (target.getTargetType() == Targetable.TYPE_ENTITY)) {
te = (Entity) target;
}
boolean throughFront = true;
if (te != null) {
throughFront = Compute.isThroughFrontHex(game, ae.getPosition(), te);
}
final boolean glancing = game.getOptions().booleanOption("tacops_glancing_blows") && (roll == toHit.getValue());
// Set Margin of Success/Failure.
toHit.setMoS(roll - Math.max(2, toHit.getValue()));
final boolean directBlow = game.getOptions().booleanOption("tacops_direct_blow") && ((toHit.getMoS() / 3) >= 1);
Report r;
// Which building takes the damage?
Building bldg = game.getBoard().getBuildingAt(caa.getTargetPos());
// is the attacker dead? because that sure messes up the calculations
if (ae == null) {
return;
}
final int direction = ae.getFacing();
// entity isn't charging any more
ae.setDisplacementAttack(null);
if (lastEntityId != caa.getEntityId()) {
// who is making the attack
r = new Report(4005);
r.subject = ae.getId();
r.addDesc(ae);
addReport(r);
}
// should we even bother?
if ((target == null) || ((target.getTargetType() == Targetable.TYPE_ENTITY) && (te.isDestroyed() || te.isDoomed() || te.crew.isDead()))) {
r = new Report(4190);
r.subject = ae.getId();
r.indent();
addReport(r);
// doEntityDisplacement(ae, ae.getPosition(), caa.getTargetPos(),
// null);
// Randall said that if a charge fails because of target
// destruction,
// the attacker stays in the hex he was in at the end of the
// movement phase
// See Bug 912094
return;
}
// attacker fell down?
if (ae.isProne()) {
r = new Report(4195);
r.subject = ae.getId();
r.indent();
addReport(r);
return;
}
// attacker immobile?
if (ae.isImmobile()) {
r = new Report(4200);
r.subject = ae.getId();
r.indent();
addReport(r);
return;
}
// target fell down, only for attacking Mechs, though
if ((te != null) && (te.isProne()) && (ae instanceof Mech)) {
r = new Report(4205);
r.subject = ae.getId();
r.indent();
addReport(r);
return;
}
r = new Report(4210);
r.subject = ae.getId();
r.indent();
r.add(target.getDisplayName());
r.newlines = 0;
addReport(r);
// target still in the same position?
if (!target.getPosition().equals(caa.getTargetPos())) {
r = new Report(4215);
r.subject = ae.getId();
addReport(r);
addReport(doEntityDisplacement(ae, ae.getPosition(), caa.getTargetPos(), null));
return;
}
// if the attacker's prone, fudge the roll
if (toHit.getValue() == TargetRoll.IMPOSSIBLE) {
roll = -12;
r = new Report(4220);
r.subject = ae.getId();
r.add(toHit.getDesc());
addReport(r);
} else if (toHit.getValue() == TargetRoll.AUTOMATIC_SUCCESS) {
roll = Integer.MAX_VALUE;
r = new Report(4225);
r.subject = ae.getId();
r.add(toHit.getDesc());
addReport(r);
} else {
// report the roll
r = new Report(4025);
r.subject = ae.getId();
r.add(toHit.getValue());
r.add(roll);
r.newlines = 0;
addReport(r);
if (glancing) {
r = new Report(4030);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
}
if (directBlow) {
r = new Report(4032);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
}
}
// do we hit?
if (roll < toHit.getValue()) {
Coords src = ae.getPosition();
Coords dest = Compute.getMissedChargeDisplacement(game, ae.getId(), src, direction);
// TODO: handle movement into/out of/through a building. Do it here?
// miss
r = new Report(4035);
r.subject = ae.getId();
addReport(r);
// move attacker to side hex
addReport(doEntityDisplacement(ae, src, dest, null));
} else if ((target.getTargetType() == Targetable.TYPE_BUILDING) || (target.getTargetType() == Targetable.TYPE_FUEL_TANK)) { // Targeting
// a building.
// The building takes the full brunt of the attack.
r = new Report(4040);
r.subject = ae.getId();
addReport(r);
Vector<Report> buildingReport = damageBuilding(bldg, damage, target.getPosition());
for (Report report : buildingReport) {
report.subject = ae.getId();
}
addReport(buildingReport);
// Damage any infantry in the hex.
damageInfantryIn(bldg, damage, target.getPosition());
// Apply damage to the attacker.
int toAttacker = ChargeAttackAction.getDamageTakenBy(ae, bldg, target.getPosition());
HitData hit = ae.rollHitLocation(ToHitData.HIT_NORMAL, ae.sideTable(target.getPosition()));
hit.setGeneralDamageType(HitData.DAMAGE_PHYSICAL);
addReport(damageEntity(ae, hit, toAttacker, false, DamageType.NONE, false, false, throughFront));
addNewLines();
entityUpdate(ae.getId());
// TODO: Does the attacker enter the building?
// TODO: What if the building collapses?
} else {
// Resolve the damage.
resolveChargeDamage(ae, te, toHit, direction, glancing, throughFront);
}
}
/**
* Handle a telemissile attack
*/
private void resolveTeleMissileAttack(PhysicalResult pr, int lastEntityId) {
final TeleMissileAttackAction taa = (TeleMissileAttackAction) pr.aaa;
final Entity ae = game.getEntity(taa.getEntityId());
if (!(ae instanceof TeleMissile)) {
return;
}
TeleMissile tm = (TeleMissile) ae;
final Targetable target = game.getTarget(taa.getTargetType(), taa.getTargetId());
final ToHitData toHit = pr.toHit;
int roll = pr.roll;
Entity te = null;
if ((target != null) && (target.getTargetType() == Targetable.TYPE_ENTITY)) {
te = (Entity) target;
}
boolean throughFront = true;
if (te != null) {
throughFront = Compute.isThroughFrontHex(game, ae.getPosition(), te);
}
Report r;
if (lastEntityId != taa.getEntityId()) {
// who is making the attack
r = new Report(4005);
r.subject = ae.getId();
r.addDesc(ae);
addReport(r);
}
// should we even bother?
if ((target == null) || ((target.getTargetType() == Targetable.TYPE_ENTITY) && (te.isDestroyed() || te.isDoomed() || te.crew.isDead()))) {
r = new Report(4190);
r.subject = ae.getId();
r.indent();
addReport(r);
return;
}
r = new Report(9031);
r.subject = ae.getId();
r.indent();
r.add(target.getDisplayName());
r.newlines = 0;
addReport(r);
// add some stuff to the to hit value
// need to add damage done modifier
if (ae.damageThisRound > 10) {
toHit.addModifier((int) (Math.floor(ae.damageThisRound / 10.0)), "damage taken");
}
// add modifiers for the originating unit missing CIC, FCS, or sensors
Entity ride = game.getEntity(tm.getOriginalRideId());
if ((null != ride) && (ride instanceof Aero)) {
Aero aride = (Aero) ride;
int cic = aride.getCICHits();
if (cic > 0) {
toHit.addModifier(cic * 2, "CIC damage");
}
// sensor hits
int sensors = aride.getSensorHits();
if ((sensors > 0) && (sensors < 3)) {
toHit.addModifier(sensors, "sensor damage");
}
if (sensors > 2) {
toHit.addModifier(+5, "sensors destroyed");
}
// FCS hits
int fcs = aride.getFCSHits();
if (fcs > 0) {
toHit.addModifier(fcs * 2, "fcs damage");
}
}
if (toHit.getValue() == TargetRoll.AUTOMATIC_SUCCESS) {
roll = Integer.MAX_VALUE;
r = new Report(4225);
r.subject = ae.getId();
r.add(toHit.getDesc());
addReport(r);
} else {
// report the roll
r = new Report(9032);
r.subject = ae.getId();
r.add(toHit.getValue());
r.add(toHit.getDesc());
r.add(roll);
r.newlines = 0;
addReport(r);
}
// do we hit?
if (roll < toHit.getValue()) {
// miss
r = new Report(4035);
r.subject = ae.getId();
addReport(r);
} else {
// Resolve the damage.
HitData hit = te.rollHitLocation(ToHitData.HIT_NORMAL, te.sideTable(ae.getPosition(), true));
hit.setCapital(true);
hit.setCapMisCritMod(tm.getCritMod());
addReport(damageEntity(te, hit, TeleMissileAttackAction.getDamageFor(ae), false, DamageType.NONE, false, false, throughFront));
destroyEntity(ae, "successful attack");
}
}
/**
* Handle a ramming attack
*/
private void resolveRamAttack(PhysicalResult pr, int lastEntityId) {
final RamAttackAction raa = (RamAttackAction) pr.aaa;
final Entity ae = game.getEntity(raa.getEntityId());
final Targetable target = game.getTarget(raa.getTargetType(), raa.getTargetId());
final ToHitData toHit = pr.toHit;
int roll = pr.roll;
Entity te = null;
if ((target != null) && (target.getTargetType() == Targetable.TYPE_ENTITY)) {
te = (Entity) target;
}
boolean throughFront = true;
if (te != null) {
throughFront = Compute.isThroughFrontHex(game, ae.getPosition(), te);
}
Report r;
boolean glancing = Compute.d6(1) == 6;
// entity isn't ramming any more
ae.setRamming(false);
if (lastEntityId != raa.getEntityId()) {
// who is making the attack
r = new Report(4005);
r.subject = ae.getId();
r.addDesc(ae);
addReport(r);
}
// should we even bother?
if ((target == null) || ((target.getTargetType() == Targetable.TYPE_ENTITY) && (te.isDestroyed() || te.isDoomed() || te.crew.isDead()))) {
r = new Report(4190);
r.subject = ae.getId();
r.indent();
addReport(r);
return;
}
// steel yourself for attack
int steelroll = Compute.d6(2);
r = new Report(9020);
r.subject = ae.getId();
r.add(steelroll);
if (steelroll > 11) {
r.choose(true);
addReport(r);
} else {
r.choose(false);
addReport(r);
return;
}
// attacker immobile?
if (ae.isImmobile()) {
r = new Report(4200);
r.subject = ae.getId();
r.indent();
addReport(r);
return;
}
r = new Report(9030);
r.subject = ae.getId();
r.indent();
r.add(target.getDisplayName());
r.newlines = 0;
addReport(r);
if (toHit.getValue() == TargetRoll.AUTOMATIC_SUCCESS) {
roll = Integer.MAX_VALUE;
r = new Report(4225);
r.subject = ae.getId();
r.add(toHit.getDesc());
addReport(r);
} else {
// report the roll
r = new Report(4025);
r.subject = ae.getId();
r.add(toHit.getValue());
r.add(roll);
r.newlines = 0;
addReport(r);
}
// do we hit?
if (roll < toHit.getValue()) {
// miss
r = new Report(4035);
r.subject = ae.getId();
addReport(r);
} else {
// Resolve the damage.
resolveRamDamage((Aero) ae, (Aero) te, toHit, glancing, throughFront);
}
}
/**
* Handle a ramming attack's damage
*/
private void resolveRamDamage(Aero ae, Aero te, ToHitData toHit, boolean glancing, boolean throughFront) {
int damage = RamAttackAction.getDamageFor(ae, te);
int damageTaken = RamAttackAction.getDamageTakenBy(ae, te);
if (glancing) {
damage = (int) Math.floor(damage / 2.0);
damageTaken = (int) Math.floor(damageTaken / 2.0);
}
// are they capital scale?
if (te.isCapitalScale()) {
damage = (int) Math.floor(damage / 10.0);
}
if (ae.isCapitalScale()) {
damageTaken = (int) Math.floor(damageTaken / 10.0);
}
Report r;
if (glancing) {
r = new Report(9015);
r.subject = ae.getId();
r.indent(1);
r.newlines = 0;
addReport(r);
}
// damage to attacker
r = new Report(4240);
r.subject = ae.getId();
r.add(damageTaken);
r.newlines = 0;
addReport(r);
HitData hit = ae.rollHitLocation(ToHitData.HIT_NORMAL, ae.sideTable(te.getPosition(), true));
// if the damage is greater than the initial armor then destroy the
// entity
if ((2 * ae.getOArmor(hit)) < damageTaken) {
addReport(destroyEntity(ae, "by massive ramming damage", false));
} else {
addReport(damageEntity(ae, hit, damageTaken, false, DamageType.NONE, false, false, throughFront));
}
r = new Report(4230);
r.subject = ae.getId();
r.add(damage);
r.add(toHit.getTableDesc());
r.newlines = 0;
addReport(r);
hit = te.rollHitLocation(toHit.getHitTable(), toHit.getSideTable());
if ((2 * te.getOArmor(hit)) < damage) {
addReport(destroyEntity(te, "by massive ramming damage", false));
} else {
addReport(damageEntity(te, hit, damage, false, DamageType.NONE, false, false, throughFront));
}
}
/**
* Handle a charge's damage
*/
private void resolveChargeDamage(Entity ae, Entity te, ToHitData toHit, int direction) {
resolveChargeDamage(ae, te, toHit, direction, false, true);
}
private void resolveChargeDamage(Entity ae, Entity te, ToHitData toHit, int direction, boolean glancing, boolean throughFront) {
// we hit...
PilotingRollData chargePSR = null;
// If we're upright, we may fall down.
if (!ae.isProne()) {
chargePSR = new PilotingRollData(ae.getId(), 2, "charging");
}
// move attacker and target, if possible
Coords src = te.getPosition();
Coords dest = src.translated(direction);
if (Compute.isValidDisplacement(game, te.getId(), te.getPosition(), direction)) {
addNewLines();
addReport(doEntityDisplacement(te, src, dest, new PilotingRollData(te.getId(), 2, "was charged")));
addReport(doEntityDisplacement(ae, ae.getPosition(), src, chargePSR));
}
// Damage To Target
int damage = ChargeAttackAction.getDamageFor(ae, te, game.getOptions().booleanOption("tacops_charge_damage"), toHit.getMoS());
// Damage to Attacker
int damageTaken = ChargeAttackAction.getDamageTakenBy(ae, te, game.getOptions().booleanOption("tacops_charge_damage"));
if (glancing) {
// Glancing Blow rule doesn't state whether damage to attacker on
// charge
// or DFA is halved as well, assume yes. TODO: Check with PM
damage = (int) Math.floor(damage / 2.0);
damageTaken = (int) Math.floor(damageTaken / 2.0);
}
boolean bDirect = false;
int directBlowCritMod = toHit.getMoS() / 3;
if (game.getOptions().booleanOption("tacops_direct_blow") && (toHit.getMoS() / 3 >= 1)) {
damage += toHit.getMoS() / 3;
bDirect = false;
}
// Is the target inside a building?
final boolean targetInBuilding = Compute.isInBuilding(game, te);
// Which building takes the damage?
Building bldg = game.getBoard().getBuildingAt(te.getPosition());
// The building shields all units from a certain amount of damage.
// The amount is based upon the building's CF at the phase's start.
int bldgAbsorbs = 0;
if (targetInBuilding && (bldg != null)) {
bldgAbsorbs = (int) Math.ceil(bldg.getPhaseCF(te.getPosition()) / 10.0);
}
Report r;
// damage to attacker
r = new Report(4240);
r.subject = ae.getId();
r.add(damageTaken);
r.newlines = 0;
addReport(r);
// work out which locations have spikes
int[] spikes = new int[ae.locations()];
for (int i = 0; i < ae.locations(); i++) {
spikes[i] = 0;
}
for (Mounted m : ae.getMisc()) {
if ((m.getLocation() != Entity.LOC_NONE) && m.getType().hasFlag(MiscType.F_SPIKES)) {
spikes[m.getLocation()] = 1;
}
}
int spikeDamage = 0;
while (damageTaken > 0) {
int cluster = Math.min(5, damageTaken);
HitData hit = ae.rollHitLocation(toHit.getHitTable(), ae.sideTable(te.getPosition()));
hit.setGeneralDamageType(HitData.DAMAGE_PHYSICAL);
if (spikes[hit.getLocation()] == 1) {
r = new Report(4335);
r.indent(2);
r.newlines = 0;
r.subject = ae.getId();
addReport(r);
spikes[hit.getLocation()] = 0;
checkBreakSpikes(ae, hit.getLocation());
spikeDamage += 2;
}
addReport(damageEntity(ae, hit, cluster, false, DamageType.NONE, false, false, throughFront));
damageTaken -= cluster;
}
// Damage to target
damage += spikeDamage;
r = new Report(4230);
r.subject = ae.getId();
r.add(damage);
r.add(toHit.getTableDesc());
r.newlines = 0;
addReport(r);
// work out which locations have spikes
spikes = new int[te.locations()];
for (int i = 0; i < te.locations(); i++) {
spikes[i] = 0;
}
for (Mounted m : te.getMisc()) {
if ((m.getLocation() != Entity.LOC_NONE) && m.getType().hasFlag(MiscType.F_SPIKES)) {
spikes[m.getLocation()] = 1;
}
}
spikeDamage = 0;
while (damage > 0) {
int cluster = Math.min(5, damage);
damage -= cluster;
if (bldgAbsorbs > 0) {
int toBldg = Math.min(bldgAbsorbs, cluster);
cluster -= toBldg;
addNewLines();
Vector<Report> buildingReport = damageBuilding(bldg, damage, te.getPosition());
for (Report report : buildingReport) {
report.subject = ae.getId();
}
addReport(buildingReport);
}
// A building may absorb the entire shot.
if (cluster == 0) {
r = new Report(4235);
r.subject = ae.getId();
r.addDesc(te);
r.newlines = 0;
addReport(r);
} else {
HitData hit = te.rollHitLocation(toHit.getHitTable(), toHit.getSideTable());
hit.setGeneralDamageType(HitData.DAMAGE_PHYSICAL);
if (bDirect) {
hit.makeDirectBlow(directBlowCritMod);
}
if (spikes[hit.getLocation()] == 1) {
r = new Report(4330);
r.indent(2);
r.newlines = 0;
r.subject = ae.getId();
addReport(r);
spikes[hit.getLocation()] = 0;
checkBreakSpikes(te, hit.getLocation());
cluster = 1;
spikeDamage += 2;
}
addReport(damageEntity(te, hit, cluster, false, DamageType.NONE, false, false, throughFront));
}
}
// finally apply spike damage to attacker
if (ae instanceof Mech) {
addReport(damageEntity(ae, new HitData(Mech.LOC_CT), spikeDamage, false, DamageType.NONE, false, false, throughFront));
} else if (ae instanceof Tank) {
addReport(damageEntity(ae, new HitData(Tank.LOC_FRONT), spikeDamage, false, DamageType.NONE, false, false, throughFront));
}
addNewLines();
//if the target is an industrial mech, it needs to check for crits
//at the end of turn
if ((te instanceof Mech) && ((Mech)te).isIndustrial()) {
((Mech)te).setCheckForCrit(true);
}
} // End private void resolveChargeDamage( Entity, Entity, ToHitData )
private void resolveLayExplosivesAttack(PhysicalResult pr, int lastEntityId) {
final LayExplosivesAttackAction laa = (LayExplosivesAttackAction) pr.aaa;
final Entity ae = game.getEntity(laa.getEntityId());
if (ae instanceof Infantry) {
Infantry inf = (Infantry) ae;
if (inf.turnsLayingExplosives < 0) {
inf.turnsLayingExplosives = 0;
Report r = new Report(4270);
r.subject = inf.getId();
r.addDesc(inf);
addReport(r);
} else {
Building building = game.getBoard().getBuildingAt(ae.getPosition());
if (building != null) {
building.addDemolitionCharge(ae.getOwner().getId(), pr.damage);
Report r = new Report(4275);
r.subject = inf.getId();
r.addDesc(inf);
r.add(pr.damage);
addReport(r);
}
inf.turnsLayingExplosives = -1;
}
}
}
/**
* Handle a death from above attack
*/
private void resolveDfaAttack(PhysicalResult pr, int lastEntityId) {
final DfaAttackAction daa = (DfaAttackAction) pr.aaa;
final Entity ae = game.getEntity(daa.getEntityId());
final Targetable target = game.getTarget(daa.getTargetType(), daa.getTargetId());
// get damage, ToHitData and roll from the PhysicalResult
int damage = pr.damage;
final ToHitData toHit = pr.toHit;
int roll = pr.roll;
Entity te = null;
if ((target != null) && (target.getTargetType() == Targetable.TYPE_ENTITY)) {
// Lets re-write around that horrible hack that was here before.
// So instead of asking if a specific location is wet and praying
// that it won't cause an NPE...
// We'll check 1) if the hex has water, and 2) if it's deep enough
// to cover the unit in question at its current elevation.
// It's especially important to make sure it's done this way,
// because some units (Sylph, submarines) can be at ANY elevation
// underwater, and VTOLs can be well above the surface.
te = (Entity) target;
IHex hex = game.getBoard().getHex(te.getPosition());
if (hex.containsTerrain(Terrains.WATER)) {
if (te.absHeight() < hex.getElevation()) {
damage = (int) Math.ceil(damage * 0.5f);
}
}
}
boolean throughFront = true;
if (te != null) {
throughFront = Compute.isThroughFrontHex(game, ae.getPosition(), te);
}
final boolean glancing = game.getOptions().booleanOption("tacops_glancing_blows") && (roll == toHit.getValue());
// Set Margin of Success/Failure.
toHit.setMoS(roll - Math.max(2, toHit.getValue()));
final boolean directBlow = game.getOptions().booleanOption("tacops_direct_blow") && ((toHit.getMoS() / 3) >= 1);
Report r;
// Which building takes the damage?
Building bldg = game.getBoard().getBuildingAt(daa.getTargetPos());
// is the attacker dead? because that sure messes up the calculations
if (ae == null) {
return;
}
final int direction = ae.getFacing();
if (lastEntityId != daa.getEntityId()) {
// who is making the attack
r = new Report(4005);
r.subject = ae.getId();
r.addDesc(ae);
addReport(r);
}
// entity isn't DFAing any more
ae.setDisplacementAttack(null);
// should we even bother?
if ((target == null) || ((target.getTargetType() == Targetable.TYPE_ENTITY) && (te.isDestroyed() || te.isDoomed() || te.crew.isDead()))) {
r = new Report(4245);
r.subject = ae.getId();
r.indent();
addReport(r);
if (ae.isProne()) {
// attacker prone during weapons phase
addReport(doEntityFall(ae, daa.getTargetPos(), 2, 3, ae.getBasePilotingRoll()));
} else {
// same effect as successful DFA
addReport(doEntityDisplacement(ae, ae.getPosition(), daa.getTargetPos(), new PilotingRollData(ae.getId(), 4, "executed death from above")));
}
return;
}
r = new Report(4246);
r.subject = ae.getId();
r.indent();
r.add(target.getDisplayName());
r.newlines = 0;
addReport(r);
// target still in the same position?
if (!target.getPosition().equals(daa.getTargetPos())) {
r = new Report(4215);
r.subject = ae.getId();
addReport(r);
addReport(doEntityFallsInto(ae, ae.getPosition(), daa.getTargetPos(), ae.getBasePilotingRoll()));
return;
}
// hack: if the attacker's prone, or incapacitated, fudge the roll
if (ae.isProne() || !ae.isActive()) {
roll = -12;
r = new Report(4250);
r.subject = ae.getId();
r.add(toHit.getDesc());
addReport(r);
} else if (toHit.getValue() == TargetRoll.IMPOSSIBLE) {
roll = -12;
r = new Report(4255);
r.subject = ae.getId();
r.add(toHit.getDesc());
addReport(r);
} else if (toHit.getValue() == TargetRoll.AUTOMATIC_SUCCESS) {
r = new Report(4260);
r.subject = ae.getId();
r.add(toHit.getDesc());
addReport(r);
roll = Integer.MAX_VALUE;
} else {
// report the roll
r = new Report(4025);
r.subject = ae.getId();
r.add(toHit.getValue());
r.add(roll);
r.newlines = 0;
addReport(r);
if (glancing) {
r = new Report(4030);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
}
if (directBlow) {
r = new Report(4032);
r.subject = ae.getId();
r.newlines = 0;
addReport(r);
}
}
// do we hit?
if (roll < toHit.getValue()) {
Coords dest = te.getPosition();
Coords targetDest = Compute.getPreferredDisplacement(game, te.getId(), dest, direction);
// miss
r = new Report(4035);
r.subject = ae.getId();
addReport(r);
if (targetDest != null) {
// attacker falls into destination hex
r = new Report(4265);
r.subject = ae.getId();
r.addDesc(ae);
r.add(dest.getBoardNum(), true);
addReport(r);
// move target to preferred hex
addReport(doEntityDisplacement(te, dest, targetDest, null));
int height = 2 + (game.getBoard().getHex(dest).containsTerrain(Terrains.BLDG_ELEV) ? game.getBoard().getHex(dest).terrainLevel(Terrains.BLDG_ELEV) : 0);
addReport(doEntityFall(ae, dest, height, 3, ae.getBasePilotingRoll()));
} else {
// attacker destroyed
// Tanks suffer an ammo/power plant hit.
// TODO : a Mech suffers a Head Blown Off crit.
addReport(destroyEntity(ae, "impossible displacement", ae instanceof Mech, ae instanceof Mech));
}
return;
}
// we hit...
// Target entities are pushed away or destroyed.
Coords dest = te.getPosition();
Coords targetDest = Compute.getValidDisplacement(game, te.getId(), dest, direction);
if (targetDest != null) {
addReport(doEntityDisplacement(te, dest, targetDest, new PilotingRollData(te.getId(), 2, "hit by death from above")));
} else {
// ack! automatic death! Tanks
// suffer an ammo/power plant hit.
// TODO : a Mech suffers a Head Blown Off crit.
addReport(destroyEntity(te, "impossible displacement", te instanceof Mech, te instanceof Mech));
}
// Can't DFA a target inside of a building.
int damageTaken = DfaAttackAction.getDamageTakenBy(ae);
r = new Report(4040);
r.subject = ae.getId();
addReport(r);
// Targeting a building.
if ((target.getTargetType() == Targetable.TYPE_BUILDING) || (target.getTargetType() == Targetable.TYPE_FUEL_TANK)) {
// The building takes the full brunt of the attack.
Vector<Report> buildingReport = damageBuilding(bldg, damage, target.getPosition());
for (Report report : buildingReport) {
report.subject = ae.getId();
}
addReport(buildingReport);
// Damage any infantry in the hex.
damageInfantryIn(bldg, damage, target.getPosition());
} else { // Target isn't building.
if (glancing) {
damage = (int) Math.floor(damage / 2.0);
}
if (directBlow) {
damage += toHit.getMoS() / 3;
}
// damage target
r = new Report(4230);
r.subject = ae.getId();
r.add(damage);
r.add(toHit.getTableDesc());
r.newlines = 0;
addReport(r);
// work out which locations have spikes
int[] spikes = new int[te.locations()];
for (int i = 0; i < te.locations(); i++) {
spikes[i] = 0;
}
for (Mounted m : te.getMisc()) {
if ((m.getLocation() != Entity.LOC_NONE) && m.getType().hasFlag(MiscType.F_SPIKES)) {
spikes[m.getLocation()] = 1;
}
}
int spikeDamage = 0;
while (damage > 0) {
int cluster = Math.min(5, damage);
HitData hit = te.rollHitLocation(toHit.getHitTable(), toHit.getSideTable());
hit.setGeneralDamageType(HitData.DAMAGE_PHYSICAL);
if (directBlow) {
hit.makeDirectBlow(toHit.getMoS() / 3);
}
if (spikes[hit.getLocation()] == 1) {
r = new Report(4330);
r.indent(2);
r.newlines = 0;
r.subject = ae.getId();
addReport(r);
cluster = 1;
spikes[hit.getLocation()] = 0;
checkBreakSpikes(te, hit.getLocation());
spikeDamage += 2;
}
addReport(damageEntity(te, hit, cluster, false, DamageType.NONE, false, false, throughFront));
damage -= 5;
}
if (spikeDamage > 0) {
if (ae instanceof QuadMech) {
addReport(damageEntity(ae, new HitData(Mech.LOC_LARM), (spikeDamage + 2) / 4, false, DamageType.NONE, false, false, false));
addReport(damageEntity(ae, new HitData(Mech.LOC_RARM), (spikeDamage + 2) / 4, false, DamageType.NONE, false, false, false));
if (spikeDamage > 2) {
addReport(damageEntity(ae, new HitData(Mech.LOC_LLEG), spikeDamage / 4, false, DamageType.NONE, false, false, false));
addReport(damageEntity(ae, new HitData(Mech.LOC_RLEG), spikeDamage / 4, false, DamageType.NONE, false, false, false));
}
} else {
addReport(damageEntity(ae, new HitData(Mech.LOC_LLEG), spikeDamage / 2, false, DamageType.NONE, false, false, false));
addReport(damageEntity(ae, new HitData(Mech.LOC_RLEG), spikeDamage / 2, false, DamageType.NONE, false, false, false));
}
}
if (target instanceof VTOL) {
// destroy rotor
addReport(applyCriticalHit(te, VTOL.LOC_ROTOR, new CriticalSlot(CriticalSlot.TYPE_SYSTEM, VTOL.CRIT_ROTOR_DESTROYED), false));
}
}
if (glancing) {
// Glancing Blow rule doesn't state whether damage to attacker on
// charge
// or DFA is halved as well, assume yes. TODO: Check with PM
damageTaken = (int) Math.floor(damageTaken / 2.0);
}
// damage attacker
r = new Report(4240);
r.subject = ae.getId();
r.add(damageTaken);
r.newlines = 0;
addReport(r);
while (damageTaken > 0) {
int cluster = Math.min(5, damageTaken);
HitData hit = ae.rollHitLocation(ToHitData.HIT_KICK, ToHitData.SIDE_FRONT);
hit.setGeneralDamageType(HitData.DAMAGE_PHYSICAL);
addReport(damageEntity(ae, hit, cluster));
damageTaken -= cluster;
}
addNewLines();
// That's it for target buildings.
// TODO: where do I put the attacker?!?
if ((target.getTargetType() == Targetable.TYPE_BUILDING) || (target.getTargetType() == Targetable.TYPE_FUEL_TANK)) {
return;
}
// HACK: to avoid automatic falls, displace from dest to dest
addReport(doEntityDisplacement(ae, dest, dest, new PilotingRollData(ae.getId(), 4, "executed death from above")));
//if the target is an industrial mech, it needs to check for crits
//at the end of turn
if ((target instanceof Mech) && ((Mech)target).isIndustrial()) {
((Mech)target).setCheckForCrit(true);
}
}
/**
* Get the Kick or Push PSR, modified by weight class
*
* @param psrEntity
* The <code>Entity</code> that should make a PSR
* @param attacker
* The attacking <code>Entity></code>
* @param target
* The target <code>Entity</code>
* @return The <code>PilotingRollData</code>
*/
private PilotingRollData getKickPushPSR(Entity psrEntity, Entity attacker, Entity target, String reason) {
int mod = 0;
PilotingRollData psr = new PilotingRollData(psrEntity.getId(), mod, reason);
if (game.getOptions().booleanOption("tacops_physical_psr")) {
switch (target.getWeightClass()) {
case EntityWeightClass.WEIGHT_LIGHT:
mod = 1;
break;
case EntityWeightClass.WEIGHT_MEDIUM:
mod = 0;
break;
case EntityWeightClass.WEIGHT_HEAVY:
mod = -1;
break;
case EntityWeightClass.WEIGHT_ASSAULT:
mod = -2;
break;
}
String reportStr;
if (mod > 0) {
reportStr = ("weight class modifier +")+mod;
} else {
reportStr = ("weight class modifier ")+mod;
}
psr.addModifier(mod, reportStr, false);
}
return psr;
}
/**
* Each mech sinks the amount of heat appropriate to its current heat
* capacity.
*/
private void resolveHeat() {
Report r;
// Heat phase header
addReport(new Report(5000, Report.PUBLIC));
for (Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
Entity entity = i.nextElement();
if (null == entity.getPosition()) {
continue;
}
IHex entityHex = game.getBoard().getHex(entity.getPosition());
// put in ASF heat build-up first because there are few differences
if (entity instanceof Aero) {
Aero a = (Aero) entity;
// should we even bother?
if (entity.isDestroyed() || entity.isDoomed() || entity.crew.isDoomed() || entity.crew.isDead()) {
continue;
}
// Engine hits cause excess heat for fighters
if (!((entity instanceof SmallCraft) || (entity instanceof Jumpship))) {
entity.heatBuildup += 2 * a.getEngineHits();
}
// add the heat we've built up so far.
entity.heat += entity.heatBuildup;
// how much heat can we sink?
int tosink = entity.getHeatCapacityWithWater();
tosink = Math.min(tosink, entity.heat);
entity.heat -= tosink;
r = new Report(5035);
r.subject = entity.getId();
r.addDesc(entity);
r.add(entity.heatBuildup);
r.add(tosink);
r.add(entity.heat);
addReport(r);
entity.heatBuildup = 0;
// add in the effects of heat
if ((entity instanceof Dropship) || (entity instanceof Jumpship)) {
// only check for a possible control roll
if (entity.heat > 0) {
int bonus = (int) Math.ceil(entity.heat / 100.0);
game.addControlRoll(new PilotingRollData(entity.getId(), bonus, "used too much heat"));
entity.heat = 0;
}
continue;
}
int autoShutDownHeat = 30;
boolean mtHeat = game.getOptions().booleanOption("tacops_heat");
if (mtHeat) {
autoShutDownHeat = 50;
}
// heat effects: start up
if ((entity.heat < autoShutDownHeat) && entity.isShutDown()) {
// only start up if not shut down by taser
if (entity.getTaserShutdownRounds() == 0) {
if (entity.heat < 14) {
// automatically starts up again
entity.setShutDown(false);
r = new Report(5045);
r.subject = entity.getId();
r.addDesc(entity);
addReport(r);
} else {
// roll for startup
int startup = 4 + (entity.heat - 14) / 4 * 2;
if (mtHeat) {
startup -= 5;
switch (entity.crew.getPiloting()) {
case 0:
case 1:
startup -= 2;
break;
case 2:
case 3:
startup -= 1;
break;
case 6:
case 7:
startup += 1;
break;
}
}
int suroll = Compute.d6(2);
r = new Report(5050);
r.subject = entity.getId();
r.addDesc(entity);
r.add(startup);
r.add(suroll);
if (suroll >= startup) {
// start 'er back up
entity.setShutDown(false);
r.choose(true);
} else {
r.choose(false);
}
addReport(r);
}
} else {
// if we're shutdown by a BA taser, we might activate
// again
if (entity.isBATaserShutdown()) {
int roll = Compute.d6(2);
if (roll >= 8) {
entity.setTaserShutdownRounds(0);
entity.setShutDown(false);
entity.setBATaserShutdown(false);
}
}
}
}
// heat effects: shutdown!
else if ((entity.heat >= 14) && !entity.isShutDown()) {
if (entity.heat >= autoShutDownHeat) {
r = new Report(5055);
r.subject = entity.getId();
r.addDesc(entity);
addReport(r);
// okay, now mark shut down
entity.setShutDown(true);
} else if (entity.heat >= 14) {
int shutdown = 4 + (entity.heat - 14) / 4 * 2;
if (mtHeat) {
shutdown -= 5;
switch (entity.crew.getPiloting()) {
case 0:
case 1:
shutdown -= 2;
break;
case 2:
case 3:
shutdown -= 1;
break;
case 6:
case 7:
shutdown += 1;
break;
}
}
int sdroll = Compute.d6(2);
r = new Report(5060);
r.subject = entity.getId();
r.addDesc(entity);
r.add(shutdown);
r.add(sdroll);
if (sdroll >= shutdown) {
// avoided
r.choose(true);
addReport(r);
} else {
// shutting down...
r.choose(false);
addReport(r);
// okay, now mark shut down
entity.setShutDown(true);
}
}
}
// heat effects: control effects (must make it unless already
// random moving)
if ((entity.heat >= 5) && !a.isRandomMove()) {
int controlavoid = 5 + (entity.heat >= 10 ? 1 : 0) + (entity.heat >= 15 ? 1 : 0) + (entity.heat >= 20 ? 1 : 0) + (entity.heat >= 25 ? 2 : 0);
int controlroll = Compute.d6(2);
r = new Report(9210);
r.subject = entity.getId();
r.addDesc(entity);
r.add(controlavoid);
r.add(controlroll);
if (controlroll >= controlavoid) {
// in control
r.choose(true);
addReport(r);
} else {
// out of control
r.choose(false);
addReport(r);
// if not already out of control, this may lead to
// elevation decline
if (!a.isOutControl() && game.getBoard().inAtmosphere()) {
int loss = Compute.d6(1);
r = new Report(9366);
r.newlines = 0;
r.subject = entity.getId();
r.addDesc(entity);
r.add(loss);
addReport(r);
// check for crash
if ((a.getElevation() - loss) <= game.getBoard().getHex(a.getPosition()).ceiling()) {
a.setElevation(0);
addReport(processCrash(entity, a.getCurrentVelocity()));
} else {
a.setElevation(a.getElevation() - loss);
}
}
// force unit out of control through heat
a.setOutCtrlHeat(true);
a.setRandomMove(true);
}
} //End of Entity Instance of Aero
// heat effects: ammo explosion!
if (entity.heat >= 19) {
int boom = 4 + (entity.heat >= 23 ? 2 : 0) + (entity.heat >= 28 ? 2 : 0);
if (mtHeat) {
boom += (entity.heat >= 35 ? 2 : 0) + (entity.heat >= 40 ? 2 : 0) + (entity.heat >= 45 ? 2 : 0);
// Last line is a crutch; 45 heat should be no roll
// but automatic explosion.
}
int boomroll = Compute.d6(2);
r = new Report(5065);
r.subject = entity.getId();
r.addDesc(entity);
r.add(boom);
r.add(boomroll);
if (boomroll >= boom) {
// mech is ok
r.choose(true);
addReport(r);
} else {
// boom!
r.choose(false);
addReport(r);
addReport(explodeAmmoFromHeat(entity));
}
}
// heat effects: pilot damage
if (entity.heat >= 21) {
int ouch = 6 + (entity.heat >= 27 ? 3 : 0);
int ouchroll = Compute.d6(2);
r = new Report(5075);
r.subject = entity.getId();
r.addDesc(entity);
r.add(ouch);
r.add(ouchroll);
if (ouchroll >= ouch) {
// pilot is ok
r.choose(true);
addReport(r);
} else {
// pilot is hurting
r.choose(false);
addReport(r);
addReport(damageCrew(entity, 1));
}
}
// The pilot may have just expired.
if ((entity.crew.isDead() || entity.crew.isDoomed()) && !entity.crew.isEjected()) {
r = new Report(5080);
r.subject = entity.getId();
r.addDesc(entity);
addReport(r);
addReport(destroyEntity(entity, "pilot death", true));
}
continue;
}
// heat doesn't matter for non-mechs
if (!(entity instanceof Mech)) {
entity.heatBuildup = 0;
entity.heatFromExternal = 0;
if (entity.infernos.isStillBurning()) {
doFlamingDamage(entity);
}
if (entity.getTaserShutdownRounds() == 0) {
entity.setShutDown(false);
entity.setBATaserShutdown(false);
} else {
// if we're shutdown by a BA taser, we might activate
// again
if (entity.isBATaserShutdown()) {
int roll = Compute.d6(2);
if (roll >= 8) {
entity.setTaserShutdownRounds(0);
entity.setShutDown(false);
entity.setBATaserShutdown(false);
}
}
}
continue;
}
// Meks gain heat from inferno hits.
if (entity.infernos.isStillBurning()) {
int infernoHeat = entity.infernos.getHeat();
entity.heatFromExternal += infernoHeat;
r = new Report(5010);
r.subject = entity.getId();
r.add(infernoHeat);
addReport(r);
}
// should we even bother?
if (entity.isDestroyed() || entity.isDoomed() || entity.crew.isDoomed() || entity.crew.isDead()) {
continue;
}
// engine hits add a lot of heat, provided the engine is on
entity.heatBuildup += entity.getEngineCritHeat();
// If a Mek had an active Stealth suite, add 10 heat.
if ((entity instanceof Mech) && entity.isStealthOn()) {
entity.heatBuildup += 10;
r = new Report(5015);
r.subject = entity.getId();
addReport(r);
}
//void sig adds 10 heat
if ((entity instanceof Mech) && entity.isVoidSigActive()) {
entity.heatBuildup += 10;
r = new Report(5016);
r.subject = entity.getId();
addReport(r);
}
//null sig adds 10 heat
if ((entity instanceof Mech) && entity.isNullSigActive()) {
entity.heatBuildup += 10;
r = new Report(5017);
r.subject = entity.getId();
addReport(r);
}
//chameleon polarization field adds 6
if ((entity instanceof Mech) && entity.isChameleonShieldActive()) {
entity.heatBuildup += 6;
r = new Report(5014);
r.subject = entity.getId();
addReport(r);
}
// If a Mek is in extreme Temperatures, add or subtract one
// heat per 10 degrees (or fraction of 10 degrees) above or
// below 50 or -30 degrees Celsius
if ((entity instanceof Mech) && (game.getPlanetaryConditions().getTemperatureDifference(50, -30) != 0) && !((Mech) entity).hasLaserHeatSinks()) {
if (game.getPlanetaryConditions().getTemperature() > 50) {
entity.heatFromExternal += game.getPlanetaryConditions().getTemperatureDifference(50, -30);
r = new Report(5020);
r.subject = entity.getId();
r.add(game.getPlanetaryConditions().getTemperatureDifference(50, -30));
addReport(r);
} else {
entity.heatFromExternal -= game.getPlanetaryConditions().getTemperatureDifference(50, -30);
r = new Report(5025);
r.subject = entity.getId();
r.add(game.getPlanetaryConditions().getTemperatureDifference(50, -30));
addReport(r);
}
}
// Add +5 Heat if the hex you're in is on fire
// and was on fire for the full round.
if (entityHex != null) {
if (entityHex.containsTerrain(Terrains.FIRE) && (entityHex.getFireTurn() > 0)
&& (entity.getElevation() <= 1)) {
entity.heatFromExternal += 5;
r = new Report(5030);
r.subject = entity.getId();
addReport(r);
}
int magma = entityHex.terrainLevel(Terrains.MAGMA);
if ((magma > 0) && (entity.getElevation() == 0)) {
entity.heatFromExternal += 5 * magma;
r = new Report(5032);
r.subject = entity.getId();
r.add(5 * magma);
addReport(r);
}
}
// Check the mech for vibroblades if so then check to see if any
// are active and what heat they will produce.
if (entity.hasVibroblades()) {
int vibroHeat = 0;
vibroHeat = entity.getActiveVibrobladeHeat(Mech.LOC_RARM);
vibroHeat += entity.getActiveVibrobladeHeat(Mech.LOC_LARM);
if (vibroHeat > 0) {
r = new Report(5018);
r.subject = entity.getId();
r.add(vibroHeat);
addReport(r);
entity.heatBuildup += vibroHeat;
}
}
int capHeat = 0;
for (Mounted m : entity.getEquipment()) {
if (m.hasChargedCapacitor() && !m.isUsedThisRound()) {
capHeat += 5;
}
}
if (capHeat > 0) {
r = new Report(5019);
r.subject = entity.getId();
r.add(capHeat);
addReport(r);
entity.heatBuildup += capHeat;
}
// Add heat from external sources to the heat buildup
entity.heatBuildup += Math.min(15, entity.heatFromExternal);
entity.heatFromExternal = 0;
// if heatbuildup is negative due to temperature, set it to 0
// for prettier turnreports
if (entity.heatBuildup < 0) {
entity.heatBuildup = 0;
}
// add the heat we've built up so far.
entity.heat += entity.heatBuildup;
// how much heat can we sink?
int tosink = entity.getHeatCapacityWithWater();
if ( entity.getCoolantFailureAmount() > 0 ){
int failureAmount = entity.getCoolantFailureAmount();
r = new Report(5520);
r.subject = entity.getId();
r.add(failureAmount);
tosink -= failureAmount;
}
// should we use a coolant pod?
int safeHeat = entity.hasInfernoAmmo() ? 9 : 13;
int possibleSinkage = ((Mech) entity).getNumberOfSinks() - entity.getCoolantFailureAmount();
for (Mounted m : entity.getEquipment()) {
if (m.getType() instanceof AmmoType) {
AmmoType at = (AmmoType) m.getType();
if ((at.getAmmoType() == AmmoType.T_COOLANT_POD) && m.isAmmoUsable()) {
EquipmentMode mode = m.curMode();
if (mode.equals("dump")) {
r = new Report(5260);
r.subject = entity.getId();
addReport(r);
m.setShotsLeft(0);
tosink += possibleSinkage;
break;
}
if (mode.equals("safe") && (entity.heat - tosink > safeHeat)) {
r = new Report(5265);
r.subject = entity.getId();
addReport(r);
m.setShotsLeft(0);
tosink += possibleSinkage;
break;
}
if (mode.equals("efficient") && (entity.heat - tosink >= possibleSinkage)) {
r = new Report(5270);
r.subject = entity.getId();
addReport(r);
m.setShotsLeft(0);
tosink += possibleSinkage;
break;
}
}
}
}
tosink = Math.min(tosink, entity.heat);
entity.heat -= tosink;
r = new Report(5035);
r.subject = entity.getId();
r.addDesc(entity);
r.add(entity.heatBuildup);
r.add(tosink);
r.add(entity.heat);
addReport(r);
entity.heatBuildup = 0;
// Does the unit have inferno ammo?
if (entity.hasInfernoAmmo()) {
// Roll for possible inferno ammo explosion.
if (entity.heat >= 10) {
int boom = 4 + (entity.heat >= 14 ? 2 : 0) + (entity.heat >= 19 ? 2 : 0) + (entity.heat >= 23 ? 2 : 0) + (entity.heat >= 28 ? 2 : 0);
int boomroll = Compute.d6(2);
r = new Report(5040);
r.subject = entity.getId();
r.addDesc(entity);
r.add(boom);
r.add(boomroll);
if (boomroll >= boom) {
// avoided
r.choose(true);
addReport(r);
} else {
r.choose(false);
addReport(r);
addReport(explodeInfernoAmmoFromHeat(entity));
}
}
} // End avoid-inferno-explosion
int autoShutDownHeat;
boolean mtHeat;
if (game.getOptions().booleanOption("tacops_heat")) {
autoShutDownHeat = 50;
mtHeat = true;
} else {
autoShutDownHeat = 30;
mtHeat = false;
}
// heat effects: start up
if ((entity.heat < autoShutDownHeat) && entity.isShutDown() && !entity.isStalled()) {
if (entity.getTaserShutdownRounds() == 0) {
if (entity.heat < 14) {
// automatically starts up again
entity.setShutDown(false);
r = new Report(5045);
r.subject = entity.getId();
r.addDesc(entity);
addReport(r);
} else {
// roll for startup
int startup = 4 + (entity.heat - 14) / 4 * 2;
if (mtHeat) {
startup -= 5;
switch (entity.crew.getPiloting()) {
case 0:
case 1:
startup -= 2;
break;
case 2:
case 3:
startup -= 1;
break;
case 6:
case 7:
startup += 1;
}
if (entity instanceof QuadMech) {
startup -= 2;
}
}
int suroll = Compute.d6(2);
r = new Report(5050);
r.subject = entity.getId();
r.addDesc(entity);
r.add(startup);
r.add(suroll);
if (suroll >= startup) {
// start 'er back up
entity.setShutDown(false);
r.choose(true);
} else {
r.choose(false);
}
addReport(r);
}
} else {
// if we're shutdown by a BA taser, we might activate
// again
if (entity.isBATaserShutdown()) {
int roll = Compute.d6(2);
if (roll >= 7) {
entity.setTaserShutdownRounds(0);
entity.setShutDown(false);
entity.setBATaserShutdown(false);
}
}
}
}
// heat effects: shutdown!
// Don't shut down if you just restarted.
else if ((entity.heat >= 14) && !entity.isShutDown()) {
if (entity.heat >= autoShutDownHeat) {
r = new Report(5055);
r.subject = entity.getId();
r.addDesc(entity);
addReport(r);
// add a piloting roll and resolve immediately
game.addPSR(new PilotingRollData(entity.getId(), 3, "reactor shutdown"));
addReport(resolvePilotingRolls());
// okay, now mark shut down
entity.setShutDown(true);
} else if (entity.heat >= 14) {
int shutdown = 4 + (entity.heat - 14) / 4 * 2;
if (mtHeat) {
shutdown -= 5;
switch (entity.crew.getPiloting()) {
case 0:
case 1:
shutdown -= 2;
break;
case 2:
case 3:
shutdown -= 1;
break;
case 6:
case 7:
shutdown += 1;
}
if (entity instanceof QuadMech) {
shutdown -= 2;
}
}
int sdroll = Compute.d6(2);
r = new Report(5060);
r.subject = entity.getId();
r.addDesc(entity);
r.add(shutdown);
r.add(sdroll);
if (sdroll >= shutdown) {
// avoided
r.choose(true);
addReport(r);
} else {
// shutting down...
r.choose(false);
addReport(r);
// add a piloting roll and resolve immediately
game.addPSR(new PilotingRollData(entity.getId(), 3, "reactor shutdown"));
addReport(resolvePilotingRolls());
// okay, now mark shut down
entity.setShutDown(true);
}
}
}
// heat effects: ammo explosion!
if (entity.heat >= 19) {
int boom = 4 + (entity.heat >= 23 ? 2 : 0) + (entity.heat >= 28 ? 2 : 0);
if (mtHeat) {
boom += (entity.heat >= 35 ? 2 : 0) + (entity.heat >= 40 ? 2 : 0) + (entity.heat >= 45 ? 2 : 0);
// Last line is a crutch; 45 heat should be no roll
// but automatic explosion.
}
if ((entity instanceof Mech) && ((Mech) entity).hasLaserHeatSinks()) {
boom--;
}
int boomroll = Compute.d6(2);
r = new Report(5065);
r.subject = entity.getId();
r.addDesc(entity);
r.add(boom);
r.add(boomroll);
if (boomroll >= boom) {
// mech is ok
r.choose(true);
addReport(r);
} else {
// boom!
r.choose(false);
addReport(r);
addReport(explodeAmmoFromHeat(entity));
}
}
// heat effects: mechwarrior damage
// N.B. The pilot may already be dead.
int lifeSupportCritCount = 0;
boolean torsoMountedCockpit = ((Mech) entity).getCockpitType() == Mech.COCKPIT_TORSO_MOUNTED;
if ((entity instanceof Mech) && torsoMountedCockpit) {
lifeSupportCritCount = entity.getHitCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_LIFE_SUPPORT, Mech.LOC_RT);
lifeSupportCritCount += entity.getHitCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_LIFE_SUPPORT, Mech.LOC_LT);
} else {
lifeSupportCritCount = entity.getHitCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_LIFE_SUPPORT, Mech.LOC_HEAD);
}
if ((lifeSupportCritCount > 0) && ((entity.heat >= 15) || (torsoMountedCockpit && (entity.heat >= 0))) && !entity.crew.isDead() && !entity.crew.isDoomed() && !entity.crew.isEjected()) {
int heatLimitDesc = 1;
int damageToCrew = 0;
if ((entity.heat >= 47) && mtHeat) {
// mechwarrior takes 5 damage
heatLimitDesc = 47;
damageToCrew = 5;
} else if ((entity.heat >= 39) && mtHeat) {
// mechwarrior takes 4 damage
heatLimitDesc = 39;
damageToCrew = 4;
} else if ((entity.heat >= 32) && mtHeat) {
// mechwarrior takes 3 damage
heatLimitDesc = 32;
damageToCrew = 3;
} else if (entity.heat >= 25) {
// mechwarrior takes 2 damage
heatLimitDesc = 25;
damageToCrew = 2;
} else if (entity.heat >= 15) {
// mechwarrior takes 1 damage
heatLimitDesc = 15;
damageToCrew = 1;
}
if ((entity.heat > 0) && (entity instanceof Mech) && (((Mech) entity).getCockpitType() == Mech.COCKPIT_TORSO_MOUNTED) && !entity.crew.getOptions().booleanOption("pain_shunt")) {
damageToCrew += 1;
}
r = new Report(5070);
r.subject = entity.getId();
r.addDesc(entity);
r.add(heatLimitDesc);
r.add(damageToCrew);
addReport(r);
addReport(damageCrew(entity, damageToCrew));
} else if (mtHeat && (entity.heat >= 32) && !entity.crew.isDead() && !entity.crew.isDoomed() && !entity.crew.getOptions().booleanOption("pain_shunt")) {
// Pilot may take damage from heat if MaxTech option is set
int heatroll = Compute.d6(2);
int avoidNumber = -1;
if (entity.heat >= 47) {
avoidNumber = 12;
} else if (entity.heat >= 39) {
avoidNumber = 10;
} else if (entity.heat >= 32) {
avoidNumber = 8;
}
r = new Report(5075);
r.subject = entity.getId();
r.addDesc(entity);
r.add(avoidNumber);
r.add(heatroll);
if (heatroll >= avoidNumber) {
// damage avoided
r.choose(true);
addReport(r);
} else {
r.choose(false);
addReport(r);
addReport(damageCrew(entity, 1));
}
}
// The pilot may have just expired.
if ((entity.crew.isDead() || entity.crew.isDoomed()) && !entity.crew.isEjected()) {
r = new Report(5080);
r.subject = entity.getId();
r.addDesc(entity);
addReport(r);
addReport(destroyEntity(entity, "crew death", true));
}
// With MaxTech Heat Scale, there may occur critical damage
if (mtHeat) {
if (entity.heat >= 36) {
int damageroll = Compute.d6(2);
int damageNumber = -1;
if (entity.heat >= 44) {
damageNumber = 10;
} else if (entity.heat >= 36) {
damageNumber = 8;
}
r = new Report(5085);
r.subject = entity.getId();
r.addDesc(entity);
r.add(damageNumber);
r.add(damageroll);
r.newlines = 0;
if (damageroll >= damageNumber) {
r.choose(true);
addReport(r);
} else {
r.choose(false);
addReport(r);
addReport(oneCriticalEntity(entity, Compute.randomInt(8)));
// add an empty report, for linebreaking
r = new Report(1210, Report.PUBLIC);
addReport(r);
}
}
}
if ( game.getOptions().booleanOption("tacops_coolant_failure")
&& (entity.getHeatCapacity() > entity.getCoolantFailureAmount())
&& (entity.heat >= 5)){
int roll = Compute.d6(2);
int hitNumber = 10;
hitNumber -= Math.max(0, (int)Math.ceil(entity.heat/5.0)-2);
r = new Report(5525);
r.subject = entity.getId();
r.add(entity.getShortName());
r.add(hitNumber);
r.add(roll);
r.newlines = 0;
addReport(r);
if ( roll >= hitNumber ){
r = new Report(5052);
r.subject = entity.getId();
addReport(r);
r = new Report(5526);
r.subject = entity.getId();
r.add(entity.getShortNameRaw());
addReport(r);
entity.addCoolantFailureAmount(1);
}else {
r = new Report(5041);
r.subject = entity.getId();
addReport(r);
}
}
}
if (vPhaseReport.size() == 1) {
// I guess nothing happened...
addReport(new Report(1205, Report.PUBLIC));
}
}
/**
* Resolve Flaming Damage for the given Entity Taharqa: This is now updated
* to TacOps rules which is much more lenient So I have change the name to
* Flaming Damage rather than flaming death
*
* @param entity
* The <code>Entity</code> that may experience flaming damage.
*/
private void doFlamingDamage(Entity entity) {
Report r;
int boomroll = Compute.d6(2);
// Infantry are unaffected by fire while they're still swarming.
if (Entity.NONE != entity.getSwarmTargetId()) {
return;
}
if ((entity.getMovementMode() == IEntityMovementMode.VTOL) && !entity.infernos.isStillBurning()) {
// VTOLs don't check as long as they are flying higher than
// the burning terrain. TODO: Check for rules conformity (ATPM?)
// according to maxtech, elevation 0 or 1 should be affected,
// this makes sense for level 2 as well
if (entity.getElevation() > 1) {
return;
}
}
// Battle Armor squads equipped with fire protection
// gear automatically avoid flaming damage
// TODO: can conv. infantry mount fire-resistant armor?
for (Mounted mount : entity.getMisc()) {
EquipmentType equip = mount.getType();
if (equip.hasFlag(MiscType.F_FIRE_RESISTANT)) {
r = new Report(5095);
r.subject = entity.getId();
r.indent(1);
r.addDesc(entity);
addReport(r);
return;
}
}
// mechs shouldn't be here, but just in case
// TODO: what to do with Aeros?
if (entity instanceof Mech) {
return;
}
// Must roll 8+ to survive...
r = new Report(5100);
r.subject = entity.getId();
r.newlines = 0;
r.addDesc(entity);
r.add(boomroll);
if (boomroll >= 8) {
// phew!
r.choose(true);
addReport(r);
} else {
// eek
r.choose(false);
r.newlines = 1;
addReport(r);
// Taharqa: TacOps rules, protos and vees no longer die instantly
// (hurray!)
if (entity instanceof Tank) {
int bonus = -2;
if ((entity instanceof SupportTank) || (entity instanceof SupportVTOL)) {
bonus = 0;
}
// roll a critical hit
Report.addNewline(vPhaseReport);
addReport(criticalTank((Tank) entity, Tank.LOC_FRONT, bonus));
} else if (entity instanceof Protomech) {
// this code is taken from inferno hits
HitData hit = entity.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT);
if (hit.getLocation() == Protomech.LOC_NMISS) {
r = new Report(6035);
r.subject = entity.getId();
r.indent(1);
addReport(r);
} else {
r = new Report(6690);
r.subject = entity.getId();
r.indent(1);
r.add(entity.getLocationName(hit));
addReport(r);
entity.destroyLocation(hit.getLocation());
// Handle Protomech pilot damage
// due to location destruction
int hits = Protomech.POSSIBLE_PILOT_DAMAGE[hit.getLocation()] - ((Protomech) entity).getPilotDamageTaken(hit.getLocation());
if (hits > 0) {
addReport(damageCrew(entity, hits));
((Protomech) entity).setPilotDamageTaken(hit.getLocation(), Protomech.POSSIBLE_PILOT_DAMAGE[hit.getLocation()]);
}
if (entity.getTransferLocation(hit).getLocation() == Entity.LOC_DESTROYED) {
addReport(destroyEntity(entity, "flaming death", false, true));
Report.addNewline(vPhaseReport);
}
}
} else {
// sucks to be you
addReport(destroyEntity(entity, "fire", false, false));
Report.addNewline(vPhaseReport);
}
}
}
/**
* Checks to see if any entity takes enough damage that requires them to
* make a piloting roll
*/
private void checkForPSRFromDamage() {
for (Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
final Entity entity = i.nextElement();
if (entity instanceof Mech) {
// if this mech has 20+ damage, add another roll to the list.
if (entity.damageThisPhase >= 20) {
if (game.getOptions().booleanOption("tacops_taking_damage")) {
PilotingRollData damPRD = new PilotingRollData(entity.getId());
int damMod = entity.damageThisPhase / 20;
damPRD.addModifier(damMod, damMod*20+"+ damage");
int weightMod = 0;
if (game.getOptions().booleanOption("tacops_physical_psr")) {
switch (entity.getWeightClass()) {
case EntityWeightClass.WEIGHT_LIGHT:
weightMod = 1;
break;
case EntityWeightClass.WEIGHT_MEDIUM:
weightMod = 0;
break;
case EntityWeightClass.WEIGHT_HEAVY:
weightMod = -1;
break;
case EntityWeightClass.WEIGHT_ASSAULT:
weightMod = -2;
break;
}
// the weight class PSR modifier is not cumulative
damPRD.addModifier(weightMod, "weight class modifier", false);
}game.addPSR(damPRD);
} else {
game.addPSR(new PilotingRollData(entity.getId(), 1, "20+ damage"));
}
}
}
if ((entity instanceof Aero) && game.getBoard().inAtmosphere()) {
// if this aero has any damage, add another roll to the list.
if (entity.damageThisPhase > 0) {
if(!game.getOptions().booleanOption("atmospheric_control")) {
int damMod = entity.damageThisPhase / 20;
StringBuffer reportStr = new StringBuffer();
reportStr.append(entity.damageThisPhase).append(" damage +").append(damMod);
PilotingRollData damPRD = new PilotingRollData(entity.getId(), damMod, reportStr.toString());
game.addControlRoll(damPRD);
} else {
//was the damage threshold exceeded this round?
if(((Aero)entity).wasCritThresh()) {
PilotingRollData damThresh = new PilotingRollData(entity.getId(), 0, "damage threshold exceeded");
game.addControlRoll(damThresh);
}
}
}
}
}
}
/**
* Checks to see if any non-mech units are standing in fire. Called at the
* end of the movement phase
*/
public void checkForFlamingDamage() {
for (Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
final Entity entity = i.nextElement();
if ((null == entity.getPosition()) || (entity instanceof Mech) || entity.isDoomed() || entity.isDestroyed() || entity.isOffBoard()) {
continue;
}
final IHex curHex = game.getBoard().getHex(entity.getPosition());
if (curHex.containsTerrain(Terrains.FIRE) && (entity.getElevation() <= 1)) {
doFlamingDamage(entity);
}
}
}
/**
* Checks to see if any tele-missiles are in a hex with enemy units. If so,
* then attack one.
*/
private void checkForTeleMissileAttacks() {
for (Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
final Entity entity = i.nextElement();
if (entity instanceof TeleMissile) {
// check for enemy units
Vector<Integer> potTargets = new Vector<Integer>();
for (Enumeration<Entity> j = game.getEntities(entity.getPosition()); j.hasMoreElements();) {
final Entity te = j.nextElement();
if (te.isEnemyOf(entity)) {
// then add it to a vector of potential targets
potTargets.add(te.getId());
}
}
if (potTargets.size() > 0) {
// determine randomly
Entity target = game.getEntity(potTargets.get(Compute.randomInt(potTargets.size())));
// report this and add a new TeleMissileAttackAction
Report r = new Report(9085);
r.subject = entity.getId();
r.addDesc(entity);
r.addDesc(target);
addReport(r);
game.addTeleMissileAttack(new TeleMissileAttackAction(entity, target));
}
}
}
}
/**
* Check to see if anyone dies due to being in certain planetary conditions.
*/
private void checkForConditionDeath() {
Report r;
for (Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
final Entity entity = i.nextElement();
if ((null == entity.getPosition()) || entity.isOffBoard()) {
// If it's not on the board - aboard something else, for
// example...
continue;
}
String reason = game.getPlanetaryConditions().whyDoomed(entity);
if (null != reason) {
r = new Report(6015);
r.subject = entity.getId();
r.addDesc(entity);
r.add(reason);
addReport(r);
addReport(destroyEntity(entity, reason, true, true));
}
}
}
/**
* Check to see if anyone dies due to being in atmosphere.
*/
private void checkForAtmosphereDeath() {
Report r;
for (Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
final Entity entity = i.nextElement();
if ((null == entity.getPosition()) || entity.isOffBoard()) {
// If it's not on the board - aboard something else, for
// example...
continue;
}
if (entity.doomedInAtmosphere()) {
r = new Report(6016);
r.subject = entity.getId();
r.addDesc(entity);
addReport(r);
addReport(destroyEntity(entity, "being in atmosphere where it can't survive", true, true));
}
}
}
/**
* checks if IndustrialMechs should die because they moved into to-deep
* water last round
*/
private void checkForIndustrialWaterDeath() {
for (Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
final Entity entity = i.nextElement();
if ((null == entity.getPosition()) || entity.isOffBoard()) {
// If it's not on the board - aboard something else, for
// example...
continue;
}
if ((entity instanceof Mech) && ((Mech)entity).isIndustrial() && ((Mech)entity).shouldDieAtEndOfTurnBecauseOfWater()) {
addReport(destroyEntity(entity, "being in water without environmental shielding", true, true));
}
}
}
private void checkForIndustrialEndOfTurn() {
checkForIndustrialWaterDeath();
checkForIndustrialUnstall();
checkForIndustrialCrit();
}
private void checkForIndustrialUnstall() {
for (Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
final Entity entity = i.nextElement();
entity.checkUnstall(vPhaseReport);
}
}
/**
* industrial mechs might need to check for critical damage
*/
private void checkForIndustrialCrit() {
for (Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
final Entity entity = i.nextElement();
if ((entity instanceof Mech) && ((Mech)entity).isIndustrial()) {
Mech mech = (Mech)entity;
// should we check for critical damage?
if (mech.isCheckForCrit()) {
Report r = new Report(5530);
r.addDesc(mech);
r.subject = mech.getId();
r.newlines = 0;
vPhaseReport.add(r);
// for being hit by a physical weapon
if (mech.getLevelsFallen() == 0) {
r = new Report(5531);
r.subject = mech.getId();
// or for falling
} else {
r = new Report(5532);
r.subject = mech.getId();
r.add(mech.getLevelsFallen());
}
vPhaseReport.add(r);
vPhaseReport.addAll(criticalEntity(mech,
mech.rollHitLocation(ToHitData.HIT_NORMAL,
ToHitData.SIDE_FRONT).getLocation(),
mech.getLevelsFallen()));
}
}
}
}
/**
* Check to see if anyone dies due to being in space.
*/
private void checkForSpaceDeath() {
Report r;
for (Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
final Entity entity = i.nextElement();
if ((null == entity.getPosition()) || entity.isOffBoard()) {
// If it's not on the board - aboard something else, for
// example...
continue;
}
if (entity.doomedInSpace()) {
r = new Report(6017);
r.subject = entity.getId();
r.addDesc(entity);
addReport(r);
addReport(destroyEntity(entity, "being in space where it can't survive", true, true));
}
}
}
/**
* Checks to see if any entities are underwater (or in vacuum) with damaged life support.
* Called during the end phase.
*/
private void checkForSuffocation() {
for (Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
final Entity entity = i.nextElement();
if ((null == entity.getPosition()) || entity.isOffBoard()) {
continue;
}
final IHex curHex = game.getBoard().getHex(entity.getPosition());
if ((((entity.getElevation() < 0) && ((curHex.terrainLevel(Terrains.WATER) > 1) || ((curHex.terrainLevel(Terrains.WATER) == 1) && entity.isProne())))
|| game.getPlanetaryConditions().isVacuum())
&& (entity.getHitCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_LIFE_SUPPORT, Mech.LOC_HEAD) > 0)) {
Report r = new Report(6020);
r.subject = entity.getId();
r.addDesc(entity);
addReport(r);
addReport(damageCrew(entity, 1));
}
}
}
/**
* Resolves all built up piloting skill rolls. Used at end of weapons,
* physical phases.
*/
private Vector<Report> resolvePilotingRolls() {
Vector<Report> vPhaseReport = new Vector<Report>();
for (Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
vPhaseReport.addAll(resolvePilotingRolls(i.nextElement()));
}
game.resetPSRs();
return vPhaseReport;
}
/**
* Resolves and reports all piloting skill rolls for a single mech.
*/
private Vector<Report> resolvePilotingRolls(Entity entity) {
return resolvePilotingRolls(entity, false, entity.getPosition(), entity.getPosition());
}
private Vector<Report> resolvePilotingRolls(Entity entity, boolean moving, Coords src, Coords dest) {
Vector<Report> vPhaseReport = new Vector<Report>();
// dead and undeployed and offboard units don't need to.
if (entity.isDoomed() || entity.isDestroyed() || entity.isOffBoard() || !entity.isDeployed()) {
return vPhaseReport;
}
Report r;
// first, do extreme gravity PSR, because non-mechs do these, too
PilotingRollData rollTarget = null;
for (Enumeration<PilotingRollData> i = game.getExtremeGravityPSRs(); i.hasMoreElements();) {
final PilotingRollData roll = i.nextElement();
if (roll.getEntityId() != entity.getId()) {
continue;
}
// found a roll, use it (there can be only 1 per entity)
rollTarget = roll;
game.resetExtremeGravityPSRs(entity);
}
if ((rollTarget != null) && (rollTarget.getValue() != TargetRoll.CHECK_FALSE)) {
// okay, print the info
r = new Report(2180);
r.subject = entity.getId();
r.addDesc(entity);
r.add(rollTarget.getLastPlainDesc());
vPhaseReport.add(r);
// roll
final int diceRoll = Compute.d6(2);
r = new Report(2190);
r.subject = entity.getId();
r.add(rollTarget.getValueAsString());
r.add(rollTarget.getDesc());
r.add(diceRoll);
if (diceRoll < rollTarget.getValue()) {
r.choose(false);
vPhaseReport.add(r);
// walking and running, 1 damage per MP used more than we would
// have normally
if ((entity.moved == IEntityMovementType.MOVE_WALK) || (entity.moved == IEntityMovementType.MOVE_VTOL_WALK) || (entity.moved == IEntityMovementType.MOVE_RUN) || (entity.moved == IEntityMovementType.MOVE_VTOL_RUN)) {
if (entity instanceof Mech) {
int j = entity.mpUsed;
int damage = 0;
while (j > entity.getRunMP(false, false)) {
j--;
damage++;
}
// Wee, direct internal damage
vPhaseReport.addAll(doExtremeGravityDamage(entity, damage));
} else if (entity instanceof Tank) {
// if we got a pavement bonus, take care of it
int k = entity.gotPavementBonus ? 1 : 0;
if (!entity.gotPavementBonus) {
int j = entity.mpUsed;
int damage = 0;
while (j > entity.getRunMP(false, false) + k) {
j--;
damage++;
}
vPhaseReport.addAll(doExtremeGravityDamage(entity, damage));
}
}
}
// jumping
if ((entity.moved == IEntityMovementType.MOVE_JUMP) && (entity instanceof Mech)) {
// low g, 1 damage for each hex jumped further than
// possible normally
if (game.getPlanetaryConditions().getGravity() < 1) {
int j = entity.mpUsed;
int damage = 0;
while (j > entity.getJumpMP(false)) {
j--;
damage++;
}
// Wee, direct internal damage
vPhaseReport.addAll(doExtremeGravityDamage(entity, damage));
}
// high g, 1 damage for each MP we have less than normally
else if (game.getPlanetaryConditions().getGravity() > 1) {
int damage = entity.getWalkMP(false, false) - entity.getWalkMP();
// Wee, direct internal damage
vPhaseReport.addAll(doExtremeGravityDamage(entity, damage));
}
}
// failed a PSR, check for ICE engine stalling
entity.doCheckEngineStallRoll(vPhaseReport);
} else {
r.choose(true);
vPhaseReport.add(r);
}
}
// non mechs and prone mechs can now return
if (!(entity instanceof Mech) || entity.isProne() || (entity.isHullDown() && entity.canGoHullDown()) ) {
return vPhaseReport;
}
// Mechs with UMU float and don't have to roll???
if (entity instanceof Mech) {
IHex hex = game.getBoard().getHex(dest);
int water = hex.terrainLevel(Terrains.WATER);
if ((water > 0) && (entity.getElevation() != -hex.depth()) && ((entity.getElevation() < 0) || ((entity.getElevation() == 0) && (hex.terrainLevel(Terrains.BRIDGE_ELEV) != 0) && !hex.containsTerrain(Terrains.ICE))) && !entity.isMakingDfa()) {
// mech is floating in water....
if (entity.hasUMU()) {
return vPhaseReport;
}
game.addPSR(new PilotingRollData(entity.getId(), TargetRoll.AUTOMATIC_FAIL, "lost buoyancy"));
}
}
// add all cumulative mods from other rolls to each PSR
// holds all rolls to make
Vector<PilotingRollData> rolls = new Vector<PilotingRollData>();
// holds the initial reason for each roll
StringBuffer reasons = new StringBuffer();
PilotingRollData base = entity.getBasePilotingRoll();
entity.addPilotingModifierForTerrain(base);
for (Enumeration<PilotingRollData> i = game.getPSRs(); i.hasMoreElements();) {
PilotingRollData psr = i.nextElement();
if (psr.getEntityId() != entity.getId()) {
continue;
}
// found a roll
if (reasons.length() > 0) {
reasons.append("; ");
}
reasons.append(psr.getPlainDesc());
PilotingRollData toUse = entity.getBasePilotingRoll();
entity.addPilotingModifierForTerrain(toUse);
toUse.append(psr);
// now, append all other roll's cumulative mods, not the non-cumulative
// ones
for (Enumeration<PilotingRollData> j = game.getPSRs(); j.hasMoreElements();) {
final PilotingRollData other = j.nextElement();
if ((other.getEntityId() != entity.getId()) || other.equals(psr)) {
continue;
}
toUse.append(other, false);
}
rolls.add(toUse);
}
// any rolls needed?
if (rolls.size() == 0) {
return vPhaseReport;
}
// is our base roll impossible?
if ((base.getValue() == TargetRoll.AUTOMATIC_FAIL) || (base.getValue() == TargetRoll.IMPOSSIBLE)) {
r = new Report(2275);
r.subject = entity.getId();
r.addDesc(entity);
r.add(rolls.size());
r.add(base.getDesc()); // international issue
vPhaseReport.add(r);
if (moving) {
vPhaseReport.addAll(doEntityFallsInto(entity, src, dest, base));
} else if ( (entity instanceof Mech)
&& game.getOptions().booleanOption("tacops_falling_expanded")
&& (entity.getCrew().getPiloting() < 6)
&& !entity.isHullDown()
&& entity.canGoHullDown()){
if ( entity.isHullDown() && entity.canGoHullDown() ){
r = new Report (2317);
r.subject = entity.getId();
r.add(entity.getDisplayName());
vPhaseReport.add(r);
}else{
vPhaseReport.addAll(doEntityFall(entity, base));
}
} else {
vPhaseReport.addAll(doEntityFall(entity, base));
}
// failed a PSR, check for ICE engine stalling
entity.doCheckEngineStallRoll(vPhaseReport);
return vPhaseReport;
}
// loop thru rolls we do have to make...
r = new Report(2280);
r.subject = entity.getId();
r.addDesc(entity);
r.add(rolls.size());
r.add(reasons.toString()); // international issue
vPhaseReport.add(r);
r = new Report(2285);
r.subject = entity.getId();
r.add(base.getValueAsString());
r.add(base.getDesc()); // international issue
vPhaseReport.add(r);
for (int i = 0; i < rolls.size(); i++) {
PilotingRollData roll = rolls.elementAt(i);
r = new Report(2290);
r.subject = entity.getId();
r.indent();
r.newlines = 0;
r.add(i + 1);
r.add(roll.getDesc()); // international issue
vPhaseReport.add(r);
if ((roll.getValue() == TargetRoll.AUTOMATIC_FAIL) || (roll.getValue() == TargetRoll.IMPOSSIBLE)) {
r = new Report(2295);
r.subject = entity.getId();
vPhaseReport.add(r);
if (moving) {
vPhaseReport.addAll(doEntityFallsInto(entity, src, dest, roll));
} else {
if ( (entity instanceof Mech)
&& game.getOptions().booleanOption("tacops_falling_expanded")
&& (entity.getCrew().getPiloting() < 6)
&& !entity.isHullDown()
&& entity.canGoHullDown()){
if ( entity.isHullDown() && entity.canGoHullDown() ){
r = new Report (2317);
r.subject = entity.getId();
r.add(entity.getDisplayName());
vPhaseReport.add(r);
}else{
vPhaseReport.addAll(doEntityFall(entity, roll));
}
}else {
vPhaseReport.addAll(doEntityFall(entity, roll));
}
}
// failed a PSR, check for ICE engine stalling
entity.doCheckEngineStallRoll(vPhaseReport);
return vPhaseReport;
} else {
int diceRoll = Compute.d6(2);
r = new Report(2300);
r.add(roll.getValueAsString());
r.add(diceRoll);
r.subject = entity.getId();
if (diceRoll < roll.getValue()) {
r.choose(false);
vPhaseReport.add(r);
if (moving) {
vPhaseReport.addAll(doEntityFallsInto(entity, src, dest, roll));
} else {
if ( (entity instanceof Mech)
&& game.getOptions().booleanOption("tacops_falling_expanded")
&& (entity.getCrew().getPiloting() < 6)
&& !entity.isHullDown()
&& entity.canGoHullDown()){
if ( (entity.getCrew().getPiloting() > 1) && (roll.getValue() - diceRoll < 2)){
entity.setHullDown(true);
}else if ( (entity.getCrew().getPiloting() <= 1) && (roll.getValue() - diceRoll < 3) ){
entity.setHullDown(true);
}
if ( entity.isHullDown() && entity.canGoHullDown() ){
r = new Report (2317);
r.subject = entity.getId();
r.add(entity.getDisplayName());
vPhaseReport.add(r);
}else{
vPhaseReport.addAll(doEntityFall(entity, roll));
}
}else {
vPhaseReport.addAll(doEntityFall(entity, roll));
}
}
// failed a PSR, check for ICE engine stalling
entity.doCheckEngineStallRoll(vPhaseReport);
return vPhaseReport;
}
r.choose(true);
vPhaseReport.add(r);
}
}
return vPhaseReport;
}
/**
* Resolves all built up control rolls. Used only during end phase
*/
private Vector<Report> resolveControlRolls() {
Vector<Report> vFullReport = new Vector<Report>();
vFullReport.add(new Report(5001, Report.PUBLIC));
for (Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
vFullReport.addAll(resolveControl(i.nextElement()));
}
game.resetControlRolls();
return vFullReport;
}
/**
* Resolves and reports all control skill rolls for a single aero.
*/
private Vector<Report> resolveControl(Entity e) {
Vector<Report> vReport = new Vector<Report>();
if (e.isDoomed() || e.isDestroyed() || e.isOffBoard() || !e.isDeployed()) {
return vReport;
}
Report r;
/*
* See forum answers on OOC
* http://forums.classicbattletech.com/index.php/topic,20424.0.html
*/
if (e instanceof Aero) {
Aero a = (Aero) e;
// they should get a shot at a recovery roll at the end of all this
// if they are already out of control
boolean canRecover = a.isOutControl();
// if the unit already is moving randomly then it can't get any
// worse
if (!a.isRandomMove()) {
// find control rolls and make them
Vector<PilotingRollData> rolls = new Vector<PilotingRollData>();
StringBuffer reasons = new StringBuffer();
PilotingRollData base = e.getBasePilotingRoll();
for (Enumeration<PilotingRollData> j = game.getControlRolls(); j.hasMoreElements();) {
final PilotingRollData modifier = j.nextElement();
if (modifier.getEntityId() != e.getId()) {
continue;
}
// found a roll, add it
rolls.addElement(modifier);
if (reasons.length() > 0) {
reasons.append("; ");
}
reasons.append(modifier.getCumulativePlainDesc());
base.append(modifier);
}
// any rolls needed?
if (rolls.size() > 0) {
// loop thru rolls we do have to make...
r = new Report(9310);
r.subject = e.getId();
r.addDesc(e);
r.add(rolls.size());
r.add(reasons.toString()); // international issue
vReport.add(r);
r = new Report(2285);
r.subject = e.getId();
r.add(base.getValueAsString());
r.add(base.getDesc()); // international issue
vReport.add(r);
for (int j = 0; j < rolls.size(); j++) {
PilotingRollData modifier = rolls.elementAt(j);
PilotingRollData target = base;
r = new Report(2290);
r.subject = e.getId();
r.indent();
r.newlines = 0;
r.add(j + 1);
r.add(modifier.getPlainDesc()); // international issue
vReport.add(r);
int diceRoll = Compute.d6(2);
// different reports depending on out-of-control status
if (a.isOutControl()) {
r = new Report(9360);
r.subject = e.getId();
r.add(target.getValueAsString());
r.add(diceRoll);
if (diceRoll < (target.getValue() - 5)) {
r.choose(false);
vReport.add(r);
a.setRandomMove(true);
} else {
r.choose(true);
vReport.add(r);
}
} else {
r = new Report(9315);
r.subject = e.getId();
r.add(target.getValueAsString());
r.add(diceRoll);
r.newlines = 1;
if (diceRoll < target.getValue()) {
r.choose(false);
vReport.add(r);
a.setOutControl(true);
// do we have random movement?
if ((target.getValue() - diceRoll) > 5) {
r = new Report(9365);
r.newlines = 0;
r.subject = e.getId();
vReport.add(r);
a.setRandomMove(true);
}
// if on the atmospheric map, then lose altitude
// and check
// for crash
if (game.getBoard().inAtmosphere()) {
int loss = Compute.d6(1);
r = new Report(9366);
r.newlines = 0;
r.subject = e.getId();
r.addDesc(e);
r.add(loss);
vReport.add(r);
// check for crash
if ((a.getElevation() - loss) <= game.getBoard().getHex(a.getPosition()).ceiling()) {
a.setElevation(game.getBoard().getHex(a.getPosition()).surface());
vReport.addAll(processCrash(e, a.getCurrentVelocity()));
} else {
a.setElevation(a.getElevation() - loss);
}
}
} else {
r.choose(true);
vReport.add(r);
}
}
}
}
}
// if they were out-of-control to start with, give them a chance to
// regain control
if (canRecover) {
PilotingRollData base = e.getBasePilotingRoll();
// is our base roll impossible?
if ((base.getValue() == TargetRoll.AUTOMATIC_FAIL) || (base.getValue() == TargetRoll.IMPOSSIBLE)) {
// report something
r = new Report(9340);
r.subject = e.getId();
r.addDesc(e);
r.add(base.getDesc()); // international issue
vReport.add(r);
return vReport;
}
r = new Report(9345);
r.subject = e.getId();
r.addDesc(e);
r.add(base.getDesc()); // international issue
vReport.add(r);
int diceRoll = Compute.d6(2);
r = new Report(9350);
r.subject = e.getId();
r.add(base.getValueAsString());
r.add(diceRoll);
if (diceRoll < base.getValue()) {
r.choose(false);
vReport.add(r);
} else {
r.choose(true);
vReport.add(r);
a.setOutControl(false);
a.setOutCtrlHeat(false);
a.setRandomMove(false);
}
}
}
return vReport;
}
/**
* Inflict damage on a pilot
*
* @param en
* The <code>Entity</code> who's pilot gets damaged.
* @param damage
* The <code>int</code> amount of damage.
*/
private Vector<Report> damageCrew(Entity en, int damage) {
Vector<Report> vDesc = new Vector<Report>();
Pilot crew = en.getCrew();
if (!crew.isDead() && !crew.isEjected() && !crew.isDoomed()) {
crew.setHits(crew.getHits() + damage);
Report r = new Report(6025);
r.subject = en.getId();
r.indent(2);
r.addDesc(en);
r.add(crew.getName());
r.add(damage);
r.newlines = 0;
vDesc.addElement(r);
if (Pilot.DEATH > crew.getHits()) {
vDesc.addAll(resolveCrewDamage(en, damage));
} else if (!crew.isDoomed()) {
crew.setDoomed(true);
vDesc.addAll(destroyEntity(en, "pilot death", true));
}
}
return vDesc;
}
/**
* resolves consciousness rolls for one entity
*/
private Vector<Report> resolveCrewDamage(Entity e, int damage) {
Vector<Report> vDesc = new Vector<Report>();
final int totalHits = e.getCrew().getHits();
if ((e instanceof MechWarrior) || !e.isTargetable() || !e.getCrew().isActive() || (damage == 0)) {
return vDesc;
}
// no consciousness roll for pain-shunted warriors
if (e.crew.getOptions().booleanOption("pain_shunt")) {
return vDesc;
}
//no consciousness roll for capital fighter pilots
if(e.isCapitalFighter()) {
return vDesc;
}
for (int hit = totalHits - damage + 1; hit <= totalHits; hit++) {
int rollTarget = Compute.getConsciousnessNumber(hit);
boolean edgeUsed = false;
do {
if (edgeUsed) {
e.crew.decreaseEdge();
}
int roll = Compute.d6(2);
if (e.getCrew().getOptions().booleanOption("pain_resistance")) {
roll = Math.min(12, roll + 1);
}
Report r = new Report(6030);
r.indent(2);
r.subject = e.getId();
r.addDesc(e);
r.add(e.getCrew().getName());
r.add(rollTarget);
r.add(roll);
if (roll >= rollTarget) {
e.crew.setKoThisRound(false);
r.choose(true);
} else {
e.crew.setKoThisRound(true);
r.choose(false);
if (e.crew.hasEdgeRemaining() && e.crew.getOptions().booleanOption("edge_when_ko")) {
edgeUsed = true;
vDesc.add(r);
r = new Report(6520);
r.subject = e.getId();
r.addDesc(e);
r.add(e.getCrew().getName());
r.add(e.crew.getOptions().intOption("edge"));
} // if
// return true;
} // else
vDesc.add(r);
} while (e.crew.hasEdgeRemaining() && e.crew.isKoThisRound() && e.crew.getOptions().booleanOption("edge_when_ko"));
// end of do-while
if (e.crew.isKoThisRound()) {
e.crew.setUnconscious(true);
return vDesc;
}
}
return vDesc;
}
/**
* Make the rolls indicating whether any unconscious crews wake up
*/
private void resolveCrewWakeUp() {
for (Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
final Entity e = i.nextElement();
// only unconscious pilots of mechs and protos and MechWarrirs
// can roll to wake up
if (!e.isTargetable() || !e.crew.isUnconscious() || e.crew.isKoThisRound() || !((e instanceof Mech) || (e instanceof Protomech) || (e instanceof MechWarrior))) {
continue;
}
int roll = Compute.d6(2);
if (e.getCrew().getOptions().booleanOption("pain_resistance")) {
roll = Math.min(12, roll + 1);
}
int rollTarget = Compute.getConsciousnessNumber(e.crew.getHits());
Report r = new Report(6029);
r.subject = e.getId();
r.addDesc(e);
r.add(e.getCrew().getName());
r.add(rollTarget);
r.add(roll);
if (roll >= rollTarget) {
r.choose(true);
e.crew.setUnconscious(false);
} else {
r.choose(false);
}
addReport(r);
}
}
/**
* Resolve any potential fatal damage to Capital Fighter after each individual attacker is finished
*/
private Vector<Report> checkFatalThresholds(int cen) {
Vector<Report> vDesc = new Vector<Report>();
for (Enumeration<Entity> e = game.getEntities(); e.hasMoreElements();) {
Entity en = e.nextElement();
if (!en.isCapitalFighter() || (cen == Entity.NONE)) {
continue;
}
Aero ship = (Aero) en;
int damage = ship.getCurrentDamage();
if (damage >= ship.getFatalThresh()) {
int roll = Compute.d6(2) + (int)Math.floor((damage - ship.getFatalThresh())/2.0);
if(roll > 9) {
vDesc.addAll(destroyEntity(ship, "fatal damage threshold"));
}
}
ship.setCurrentDamage(0);
}
return vDesc;
}
/**
* damage an Entity
*
* @param te
* the <code>Entity</code> to be damaged
* @param hit
* the corresponding <code>HitData</code>
* @param damage
* the <code>int</code> amount of damage
* @param ammoExplosion
* a <code>boolean</code> indicating if this is an
* ammoexplosion
* @return a <code>Vector<Report></code> containg the phasereports
*/
private Vector<Report> damageEntity(Entity te, HitData hit, int damage, boolean ammoExplosion) {
return damageEntity(te, hit, damage, ammoExplosion, DamageType.NONE, false, false);
}
/**
* Deals the listed damage to an entity. Returns a vector of Reports for the
* phase report
*
* @param te
* the target entity
* @param hit
* the hit data for the location hit
* @param damage
* the damage to apply
* @return a <code>Vector</code> of <code>Report</code>s
*/
public Vector<Report> damageEntity(Entity te, HitData hit, int damage) {
return damageEntity(te, hit, damage, false, DamageType.NONE, false, false);
}
/**
* Deals the listed damage to an entity. Returns a vector of Reports for the
* phase report
*
* @param te
* the target entity
* @param hit
* the hit data for the location hit
* @param damage
* the damage to apply
* @param ammoExplosion
* ammo explosion type damage is applied directly to the IS,
* hurts the pilot, causes auto-ejects, and can blow the unit to
* smithereens
* @param bFrag
* The DamageType of the attack.
* @param damageIS
* Should the target location's internal structure be damaged
* directly?
* @return a <code>Vector</code> of <code>Report</code>s
*/
public Vector<Report> damageEntity(Entity te, HitData hit, int damage, boolean ammoExplosion, DamageType bFrag, boolean damageIS) {
return damageEntity(te, hit, damage, ammoExplosion, bFrag, damageIS, false);
}
/**
* Deals the listed damage to an entity. Returns a vector of Reports for the
* phase report
*
* @param te
* the target entity
* @param hit
* the hit data for the location hit
* @param damage
* the damage to apply
* @param ammoExplosion
* ammo explosion type damage is applied directly to the IS,
* hurts the pilot, causes auto-ejects, and can blow the unit to
* smithereens
* @param bFrag
* The DamageType of the attack.
* @param damageIS
* Should the target location's internal structure be damaged
* directly?
* @param areaSatArty
* Is the damage from an area saturating artillery attack?
* @return a <code>Vector</code> of <code>Report</code>s
*/
private Vector<Report> damageEntity(Entity te, HitData hit, int damage, boolean ammoExplosion, DamageType bFrag, boolean damageIS, boolean areaSatArty) {
return damageEntity(te, hit, damage, ammoExplosion, bFrag, damageIS, areaSatArty, true);
}
/**
* Deals the listed damage to an entity. Returns a vector of Reports for the
* phase report
*
* @param te
* the target entity
* @param hit
* the hit data for the location hit
* @param damage
* the damage to apply
* @param ammoExplosion
* ammo explosion type damage is applied directly to the IS,
* hurts the pilot, causes auto-ejects, and can blow the unit to
* smithereens
* @param bFrag
* The DamageType of the attack.
* @param damageIS
* Should the target location's internal structure be damaged
* directly?
* @param areaSatArty
* Is the damage from an area saturating artillery attack?
* @param throughFront
* Is the damage coming through the hex the unit is facing?
* @return a <code>Vector</code> of <code>Report</code>s
*/
public Vector<Report> damageEntity(Entity te, HitData hit, int damage, boolean ammoExplosion, DamageType bFrag, boolean damageIS, boolean areaSatArty, boolean throughFront) {
return damageEntity(te, hit, damage, ammoExplosion, bFrag, damageIS, areaSatArty, throughFront, false, false);
}
/**
* Deals the listed damage to an entity. Returns a vector of Reports for the
* phase report
*
* @param te
* the target entity
* @param hit
* the hit data for the location hit
* @param damage
* the damage to apply
* @param ammoExplosion
* ammo explosion type damage is applied directly to the IS,
* hurts the pilot, causes auto-ejects, and can blow the unit to
* smithereens
* @param bFrag
* The DamageType of the attack.
* @param damageIS
* Should the target location's internal structure be damaged
* directly?
* @param areaSatArty
* Is the damage from an area saturating artillery attack?
* @param throughFront
* Is the damage coming through the hex the unit is facing?
* @param underWater
* Is the damage coming from an underwater attack
* @return a <code>Vector</code> of <code>Report</code>s
*/
public Vector<Report> damageEntity(Entity te, HitData hit, int damage, boolean ammoExplosion, DamageType bFrag, boolean damageIS, boolean areaSatArty, boolean throughFront, boolean underWater) {
return damageEntity(te, hit, damage, ammoExplosion, bFrag, damageIS, areaSatArty, throughFront, underWater, false);
}
/**
* Deals the listed damage to an entity. Returns a vector of Reports for the
* phase report
*
* @param te
* the target entity
* @param hit
* the hit data for the location hit
* @param damage
* the damage to apply
* @param ammoExplosion
* ammo explosion type damage is applied directly to the IS,
* hurts the pilot, causes auto-ejects, and can blow the unit to
* smithereens
* @param bFrag
* The DamageType of the attack.
* @param damageIS
* Should the target location's internal structure be damaged
* directly?
* @param areaSatArty
* Is the damage from an area saturating artillery attack?
* @param throughFront
* Is the damage coming through the hex the unit is facing?
* @param underWater
* Is the damage coming from an underwater attack?
* @param nukeS2s
* is this a ship-to-ship nuke?
* @return a <code>Vector</code> of <code>Report</code>s
*/
public Vector<Report> damageEntity(Entity te, HitData hit, int damage, boolean ammoExplosion, DamageType bFrag, boolean damageIS, boolean areaSatArty, boolean throughFront, boolean underWater, boolean nukeS2S) {
Vector<Report> vDesc = new Vector<Report>();
Report r;
int te_n = te.getId();
//if this is a fighter squadron then pick an active fighter and pass on the damage
if(te instanceof FighterSquadron) {
FighterSquadron fs = (FighterSquadron)te;
if(fs.getFighters().size() < 1) {
return vDesc;
}
Aero fighter = fs.getFighter(hit.getLocation());
HitData new_hit = fighter.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT);
new_hit.setBoxCars(hit.rolledBoxCars());
new_hit.setGeneralDamageType(hit.getGeneralDamageType());
new_hit.setCapital(hit.isCapital());
new_hit.setCapMisCritMod(hit.getCapMisCritMod());
new_hit.setSingleAV(hit.getSingleAV());
return damageEntity(fighter, new_hit, damage, ammoExplosion, bFrag, damageIS, areaSatArty, throughFront, underWater, nukeS2S);
}
// This is good for shields if a shield absorps the hit it shouldn't
// effect the pilot.
// TC SRM's that hit the head do external and internal damage but its
// one hit and shouldn't cause
// 2 hits to the pilot.
boolean isHeadHit = (te instanceof Mech) && (((Mech) te).getCockpitType() != Mech.COCKPIT_TORSO_MOUNTED) && (hit.getLocation() == Mech.LOC_HEAD) && ((hit.getEffect() & HitData.EFFECT_NO_CRITICALS) != HitData.EFFECT_NO_CRITICALS);
// booleans to indicate criticals for AT2
boolean critSI = false;
boolean critThresh = false;
//get the relevant damage for damage thresholding
int threshDamage = damage;
//weapon groups only get the damage of one weapon
if(hit.getSingleAV() > -1) {
threshDamage = hit.getSingleAV();
}
// is this capital-scale damage
boolean isCapital = hit.isCapital();
// check capital/standard damage
if (isCapital && !te.isCapitalScale()) {
damage = 10 * damage;
threshDamage = 10 * threshDamage;
}
if (!isCapital && te.isCapitalScale()) {
damage = (int)Math.round(damage / 10.0);
threshDamage = (int)Math.round(threshDamage / 10.0);
}
int damage_orig = damage;
// show Locations which have rerolled with Edge
HitData undoneLocation = hit.getUndoneLocation();
while (undoneLocation != null) {
r = new Report(6500);
r.subject = te_n;
r.indent(2);
r.newlines = 0;
r.addDesc(te);
r.add(te.getLocationAbbr(undoneLocation));
vDesc.addElement(r);
undoneLocation = undoneLocation.getUndoneLocation();
} // while
// if edge was uses, give at end overview of remainings
if (hit.getUndoneLocation() != null) {
r = new Report(6510);
r.subject = te_n;
r.indent(2);
r.newlines = 0;
r.addDesc(te);
r.add(te.crew.getOptions().intOption("edge"));
vDesc.addElement(r);
} // if
boolean autoEject = false;
if (ammoExplosion) {
if (te instanceof Mech) {
Mech mech = (Mech) te;
if (mech.isAutoEject()) {
autoEject = true;
vDesc.addAll(ejectEntity(te, true));
}
}
}
boolean isBattleArmor = te instanceof BattleArmor;
boolean isPlatoon = !isBattleArmor && (te instanceof Infantry);
boolean isFerroFibrousTarget = false;
boolean wasDamageIS = false;
boolean tookInternalDamage = damageIS;
IHex te_hex = null;
boolean hardenedArmor = false;
boolean reflectiveArmor = false;
boolean reactiveArmor = false;
if ( ((te instanceof Mech) || (te instanceof Tank)) && (te.getArmorType() == EquipmentType.T_ARMOR_HARDENED)) {
hardenedArmor = true;
}
if (((te instanceof Mech) || (te instanceof Tank)) && (te.getArmorType() == EquipmentType.T_ARMOR_REFLECTIVE)) {
reflectiveArmor = true;
}
if (((te instanceof Mech) || (te instanceof Tank)) && (te.getArmorType() == EquipmentType.T_ARMOR_REACTIVE)) {
reactiveArmor = true;
}
int crits = ((hit.getEffect() & HitData.EFFECT_CRITICAL) == HitData.EFFECT_CRITICAL) && !hardenedArmor ? 1 : 0;
int specCrits = ((hit.getEffect() & HitData.EFFECT_CRITICAL) == HitData.EFFECT_CRITICAL) && hardenedArmor ? 1 : 0;
HitData nextHit = null;
// Some "hits" on a Protomech are actually misses.
if ((te instanceof Protomech) && (hit.getLocation() == Protomech.LOC_NMISS)) {
r = new Report(6035);
r.subject = te_n;
r.indent(2);
r.newlines = 0;
vDesc.addElement(r);
return vDesc;
}
// check for critical hit/miss vs. a BA
if ((crits > 0) && (te instanceof BattleArmor)) {
// possible critical miss if the rerolled location isn't alive
if ((hit.getLocation() >= te.locations()) || (te.getInternal(hit.getLocation()) <= 0)) {
r = new Report(6037);
r.add(hit.getLocation());
r.subject = te_n;
r.indent(2);
r.newlines = 0;
vDesc.addElement(r);
return vDesc;
}
// otherwise critical hit
r = new Report(6225);
r.add(te.getLocationAbbr(hit));
r.subject = te_n;
r.indent(2);
r.newlines = 0;
vDesc.addElement(r);
crits = 0;
damage = Math.max(te.getInternal(hit.getLocation()) + te.getArmor(hit.getLocation()), damage);
}
if ((te.getArmor(hit) > 0) && ((te.getArmorType() == EquipmentType.T_ARMOR_FERRO_FIBROUS) || (te.getArmorType() == EquipmentType.T_ARMOR_LIGHT_FERRO) || (te.getArmorType() == EquipmentType.T_ARMOR_HEAVY_FERRO))) {
isFerroFibrousTarget = true;
}
// Is the infantry in the open?
if (isPlatoon && !te.isDestroyed()
&& !te.isDoomed() && (((Infantry) te).getDugIn() != Infantry.DUG_IN_COMPLETE)
&& !te.crew.getOptions().booleanOption("dermal_armor")) {
te_hex = game.getBoard().getHex(te.getPosition());
if ((te_hex != null) && !te_hex.containsTerrain(Terrains.WOODS) && !te_hex.containsTerrain(Terrains.JUNGLE) && !te_hex.containsTerrain(Terrains.ROUGH) && !te_hex.containsTerrain(Terrains.RUBBLE) && !te_hex.containsTerrain(Terrains.SWAMP) && !te_hex.containsTerrain(Terrains.BUILDING) && !te_hex.containsTerrain(Terrains.FUEL_TANK) && !te_hex.containsTerrain(Terrains.FORTIFIED) && !ammoExplosion) {
// PBI. Damage is doubled.
damage *= 2;
r = new Report(6040);
r.subject = te_n;
r.indent(2);
r.newlines = 0;
vDesc.addElement(r);
}
}
// Is the infantry in vacuum?
if ((isPlatoon || isBattleArmor) && !te.isDestroyed() && !te.isDoomed()
&& game.getPlanetaryConditions().isVacuum()) {
// PBI. Double damage.
damage *= 2;
r = new Report(6041);
r.subject = te_n;
r.indent(2);
r.newlines = 0;
vDesc.addElement(r);
}
// If dealing with fragmentation missiles,
// it does double damage to infantry...
// We're actually going to abuse this for AX-head warheads, too, so as
// to not add another parameter.
switch (bFrag) {
case ANTI_INFANTRY:
if(isPlatoon && te.crew.getOptions().booleanOption("dermal_armor")) {
int reduce = Math.min(damage - 1, Compute.d6());
damage -= reduce;
r = new Report(6042);
r.subject = te_n;
r.add(reduce);
r.add(damage);
r.indent(2);
r.newlines = 0;
vDesc.addElement(r);
}
break;
case FRAGMENTATION:
if (!isPlatoon) {
damage = 0;
r = new Report(6050);
r.subject = te_n;
r.indent(2);
r.newlines = 0;
vDesc.addElement(r);
}
break;
case FLECHETTE:
if (!isPlatoon && !isBattleArmor) {
damage /= 2;
r = new Report(6060);
r.subject = te_n;
r.indent(2);
r.newlines = 0;
vDesc.addElement(r);
} else if (isPlatoon && !isBattleArmor) {
r = new Report(6055);
r.subject = te_n;
r.indent(2);
r.newlines = 0;
vDesc.addElement(r);
}
break;
case ACID:
if (isFerroFibrousTarget) {
damage = te.getArmor(hit) >= 3 ? 3 : te.getArmor(hit);
r = new Report(6061);
r.subject = te_n;
r.indent(2);
r.newlines = 0;
r.add(damage);
vDesc.addElement(r);
} else {
r = new Report(6062);
r.subject = te_n;
r.indent(2);
r.newlines = 0;
vDesc.addElement(r);
}
break;
case INCENDIARY:
// Incendiary AC ammo does +2 damage to unarmoured infantry
if (isPlatoon) {
damage += 2;
r = new Report(6064);
r.subject = te_n;
r.indent(2);
r.newlines = 0;
vDesc.addElement(r);
}
break;
case ANTI_TSM:
te.hitThisRoundByAntiTSM = true;
default:
// We can ignore this.
break;
}
//check for infantry armor
/*
if(isPlatoon) {
damage = (int)Math.ceil(damage / 2.0);
r = new Report(6043);
r.subject = te_n;
r.indent(2);
r.newlines = 0;
r.add(damage);
vDesc.addElement(r);
}
*/
// adjust VTOL rotor damage
if ((te instanceof VTOL) && (hit.getLocation() == VTOL.LOC_ROTOR)) {
damage = (damage + 9) / 10;
}
// save EI status, in case sensors crit destroys it
final boolean eiStatus = te.hasActiveEiCockpit();
// BA using EI implants receive +1 damage from attacks
if (!(te instanceof Mech) && !(te instanceof Protomech) && eiStatus) {
damage += 1;
}
// check for case on Aeros
if (te instanceof Aero) {
Aero a = (Aero) te;
if (ammoExplosion && a.hasCase()) {
// damage should be reduced by a factor of 2 for ammo explosions
// according to p. 161, TW
damage /= 2;
r = new Report(9010);
r.subject = te_n;
r.add(damage);
r.indent(3);
r.newlines = 0;
vDesc.addElement(r);
}
}
// Allocate the damage
while (damage > 0) {
// first check for ammo explosions on aeros separately, because it
// must be done before
// standard to capital damage conversions
if ((te instanceof Aero) && (hit.getLocation() == Aero.LOC_AFT) && !damageIS) {
for (Mounted mAmmo : te.getAmmo()) {
if (mAmmo.isDumping() && !mAmmo.isDestroyed()
&& !mAmmo.isHit() && !(mAmmo.getType() instanceof BombType)) {
// doh. explode it
vDesc.addAll(explodeEquipment(te, mAmmo.getLocation(), mAmmo));
mAmmo.setHit(true);
}
}
}
if (te instanceof Aero) {
// chance of a critical if damage greater than threshold
Aero a = (Aero) te;
if ((threshDamage > a.getThresh(hit.getLocation())) && !te.isCapitalFighter()) {
critThresh = true;
a.setCritThresh(true);
}
if ((threshDamage >= 2) && te.isCapitalFighter()) {
critThresh = true;
a.setCritThresh(true);
}
}
//Capital fighters receive damage differently
if (te.isCapitalFighter()) {
Aero a = (Aero)te;
a.setCurrentDamage(a.getCurrentDamage() + damage);
a.setCapArmor(a.getCapArmor() - damage);
r = new Report(9065);
r.subject = te_n;
r.indent(2);
r.newlines = 0;
r.addDesc(te);
r.add(damage);
vDesc.addElement(r);
r = new Report(6085);
r.subject = te_n;
r.newlines = 0;
r.add(Math.max(a.getCapArmor(), 0));
vDesc.addElement(r);
// check to see if this detroyed the entity
if (a.getCapArmor() <= 0) {
vDesc.addAll(destroyEntity(te, "structural integrity collapse"));
a.setCapArmor(0);
}
damage = 0;
//check for crits
checkAeroCrits(vDesc, (Aero)te, hit, damage_orig, critThresh, critSI, ammoExplosion, nukeS2S);
return vDesc;
}
if (!((te instanceof Aero) && ammoExplosion)) {
// report something different for Aero ammo explosions
r = new Report(6065);
r.subject = te_n;
r.indent(2);
r.newlines = 0;
r.addDesc(te);
r.add(damage);
if (damageIS) {
r.messageId = 6070;
}
r.add(te.getLocationAbbr(hit));
vDesc.addElement(r);
}
// was the section destroyed earlier this phase?
if (te.getInternal(hit) == IArmorState.ARMOR_DOOMED) {
// cannot transfer a through armor crit if so
crits = 0;
}
// here goes the fun :)
// Shields take damage first then cowls then armor whee
// Shield does not protect from ammo explosions or falls.
if (!ammoExplosion && !hit.isFallDamage() && !damageIS && te.hasShield() && ((hit.getEffect() & HitData.EFFECT_NO_CRITICALS) != HitData.EFFECT_NO_CRITICALS)) {
Mech me = (Mech) te;
int damageNew = me.shieldAbsorptionDamage(damage, hit.getLocation(), hit.isRear());
// if a shield absorbed the damage then lets tell the world
// about it.
if (damageNew != damage) {
int absorb = damage - damageNew;
te.damageThisPhase += absorb;
damage = damageNew;
r = new Report(3530);
r.subject = te_n;
r.indent(3);
r.newlines = 0;
r.add(absorb);
vDesc.addElement(r);
if (damage <= 0) {
crits = 0;
specCrits = 0;
isHeadHit = false;
}
}
}
// Armored Cowl may absorb some damage from hit
if (te instanceof Mech) {
Mech me = (Mech) te;
if (me.hasCowl() && (hit.getLocation() == Mech.LOC_HEAD) && !throughFront) {
int damageNew = me.damageCowl(damage);
int damageDiff = damage - damageNew;
me.damageThisPhase += damageDiff;
damage = damageNew;
r = new Report(3520);
r.subject = te_n;
r.indent(3);
r.newlines = 0;
r.add(damageDiff);
vDesc.addElement(r);
}
}
if (!ammoExplosion && !damageIS && ((hit.getEffect() & HitData.EFFECT_NO_CRITICALS) != HitData.EFFECT_NO_CRITICALS)) {
damage = te.getDamageReductionFromModularArmor(hit.getLocation(), damage, vDesc);
}
// Destroy searchlights on 7+ (torso hits on mechs)
boolean spotlightHittable = false;
if (te.hasSpotlight()) {
spotlightHittable = true;
int loc = hit.getLocation();
if (te instanceof Mech) {
if ((loc != Mech.LOC_CT) && (loc != Mech.LOC_LT) && (loc != Mech.LOC_RT)) {
spotlightHittable = false;
}
} else if (te instanceof Tank) {
if ((loc != Tank.LOC_FRONT) && (loc != Tank.LOC_RIGHT) && (loc != Tank.LOC_LEFT)) {
spotlightHittable = false;
}
}
if (spotlightHittable) {
int spotroll = Compute.d6(2);
r = new Report(6072);
r.indent(2);
r.subject = te_n;
r.add(spotroll);
vDesc.addElement(r);
if (spotroll >= 7) {
r = new Report(6071);
r.subject = te_n;
r.indent(2);
vDesc.addElement(r);
te.setSpotlightState(false);
te.setSpotlight(false);
}
}
}
// Does an exterior passenger absorb some of the damage?
if (!damageIS) {
int nLoc = hit.getLocation();
Entity passenger = te.getExteriorUnitAt(nLoc, hit.isRear());
// Does an exterior passenger absorb some of the damage?
if (!ammoExplosion && (null != passenger) && (Compute.d6() >= 5) && !passenger.isDoomed() && (bFrag != DamageType.IGNORE_PASSENGER)) {
// Yup. Roll up some hit data for that passenger.
r = new Report(6075);
r.subject = passenger.getId();
r.indent(3);
r.addDesc(passenger);
vDesc.addElement(r);
HitData passHit = passenger.getTrooperAtLocation(hit, te);
// How much damage will the passenger absorb?
int absorb = 0;
HitData nextPassHit = passHit;
do {
if (0 < passenger.getArmor(nextPassHit)) {
absorb += passenger.getArmor(nextPassHit);
}
if (0 < passenger.getInternal(nextPassHit)) {
absorb += passenger.getInternal(nextPassHit);
}
nextPassHit = passenger.getTransferLocation(nextPassHit);
} while ((damage > absorb) && (nextPassHit.getLocation() >= 0));
// Damage the passenger.
vDesc.addAll(damageEntity(passenger, passHit, damage));
// Did some damage pass on?
if (damage > absorb) {
// Yup. Remove the absorbed damage.
damage -= absorb;
r = new Report(6080);
r.subject = te_n;
r.indent(1);
r.add(damage);
r.addDesc(te);
vDesc.addElement(r);
} else {
// Nope. Return our description.
return vDesc;
}
} // End nLoc-has-exterior-passenger
boolean bTorso = (nLoc == Mech.LOC_CT) || (nLoc == Mech.LOC_RT) || (nLoc == Mech.LOC_LT);
// Does a swarming unit absorb damage?
int swarmer = te.getSwarmAttackerId();
if ((!(te instanceof Mech) || bTorso) && (swarmer != Entity.NONE) && ((hit.getEffect() & HitData.EFFECT_CRITICAL) == 0) && (Compute.d6() >= 5) && (bFrag != DamageType.IGNORE_PASSENGER)) {
Entity swarm = game.getEntity(swarmer);
// Yup. Roll up some hit data for that passenger.
r = new Report(6076);
r.subject = swarmer;
r.indent(3);
r.addDesc(swarm);
vDesc.addElement(r);
HitData passHit = swarm.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT);
// How much damage will the swarm absorb?
int absorb = 0;
HitData nextPassHit = passHit;
do {
if (0 < swarm.getArmor(nextPassHit)) {
absorb += swarm.getArmor(nextPassHit);
}
if (0 < swarm.getInternal(nextPassHit)) {
absorb += swarm.getInternal(nextPassHit);
}
nextPassHit = swarm.getTransferLocation(nextPassHit);
} while ((damage > absorb) && (nextPassHit.getLocation() >= 0));
// Damage the swarm.
vDesc.addAll(damageEntity(swarm, passHit, damage));
// Did some damage pass on?
if (damage > absorb) {
// Yup. Remove the absorbed damage.
damage -= absorb;
r = new Report(6080);
r.subject = te_n;
r.indent(1);
r.add(damage);
r.addDesc(te);
vDesc.addElement(r);
} else {
// Nope. Return our description.
return vDesc;
}
}
// is this a mech dumping ammo being hit in the rear torso?
if (((te instanceof Mech) && hit.isRear() && bTorso) || ((te instanceof Tank) && (hit.getLocation() == Tank.LOC_REAR))) {
for (Mounted mAmmo : te.getAmmo()) {
if (mAmmo.isDumping() && !mAmmo.isDestroyed() && !mAmmo.isHit()) {
// doh. explode it
vDesc.addAll(explodeEquipment(te, mAmmo.getLocation(), mAmmo));
mAmmo.setHit(true);
}
}
}
}
// is there armor in the location hit?
if (!ammoExplosion && (te.getArmor(hit) > 0) && !damageIS) {
int tmpDamageHold = -1;
int origDamage = damage;
// If the target has hardened armor, we need to adjust damage.
if (hardenedArmor && (hit.getGeneralDamageType() != HitData.DAMAGE_ARMOR_PIERCING) && (hit.getGeneralDamageType() != HitData.DAMAGE_ARMOR_PIERCING_MISSILE)) {
tmpDamageHold = damage;
damage = (int) Math.ceil(((double) damage) / 2);
r = new Report(6069);
r.subject = te_n;
r.indent(2);
r.newlines = 0;
r.add(damage);
vDesc.addElement(r);
} else if (isPlatoon) {
// infantry armour works differently
int armor = te.getArmor(hit);
int men = te.getInternal(hit);
tmpDamageHold = damage % 2;
damage /= 2;
if ((tmpDamageHold == 1) && (armor >= men)) {
// extra 1 point of damage to armor
tmpDamageHold = damage;
damage++;
} else {
// extra 0 or 1 point of damage to men
tmpDamageHold += damage;
}
} else if (reflectiveArmor && (hit.getGeneralDamageType() == HitData.DAMAGE_PHYSICAL)) {
tmpDamageHold = damage;
damage *= 2;
r = new Report(6066);
r.subject = te_n;
r.indent(2);
r.newlines = 0;
r.add(damage);
vDesc.addElement(r);
} else if (reflectiveArmor && (hit.getGeneralDamageType() == HitData.DAMAGE_ENERGY)) {
tmpDamageHold = damage;
damage = (int) Math.ceil(((double) damage) / 2);
r = new Report(6067);
r.subject = te_n;
r.indent(2);
r.newlines = 0;
r.add(damage);
vDesc.addElement(r);
} else if (reactiveArmor && ( (hit.getGeneralDamageType() == HitData.DAMAGE_MISSILE) || (hit.getGeneralDamageType() == HitData.DAMAGE_ARMOR_PIERCING_MISSILE))) {
tmpDamageHold = damage;
damage = (int) Math.ceil(((double) damage) / 2);
r = new Report(6068);
r.subject = te_n;
r.indent(2);
r.newlines = 0;
r.add(damage);
vDesc.addElement(r);
}
if (te.getArmor(hit) >= damage) {
// armor absorbs all damage
te.setArmor(te.getArmor(hit) - damage, hit);
if (tmpDamageHold >= 0) {
te.damageThisPhase += tmpDamageHold;
} else {
te.damageThisPhase += damage;
}
damage = 0;
r = new Report(6085);
r.subject = te_n;
r.newlines = 0;
if (spotlightHittable) {
r.indent(3);
}
r.add(te.getArmor(hit));
vDesc.addElement(r);
// tele-missiles are destroyed if they lose all armor
if ((te instanceof TeleMissile) && (te.getArmor(hit) == damage)) {
vDesc.addAll(destroyEntity(te, "damage", false));
}
} else {
// damage goes on to internal
int absorbed = Math.max(te.getArmor(hit), 0);
if (reflectiveArmor && (hit.getGeneralDamageType() == HitData.DAMAGE_PHYSICAL)) {
absorbed = (int) Math.round(Math.ceil(absorbed / 2));
damage = tmpDamageHold;
tmpDamageHold = 0;
}
te.setArmor(IArmorState.ARMOR_DESTROYED, hit);
if (tmpDamageHold >= 0) {
te.damageThisPhase += 2 * absorbed;
} else {
te.damageThisPhase += absorbed;
}
damage -= absorbed;
r = new Report(6090);
r.subject = te_n;
r.newlines = 0;
if (spotlightHittable) {
r.indent(3);
}
vDesc.addElement(r);
if (te instanceof GunEmplacement) {
// gun emplacements have no internal,
// destroy the section
te.destroyLocation(hit.getLocation());
r = new Report(6115);
r.subject = te_n;
r.newlines = 0;
vDesc.addElement(r);
if (te.getTransferLocation(hit).getLocation() == Entity.LOC_DESTROYED) {
vDesc.addAll(destroyEntity(te, "damage", false));
}
}
}
// targets with BAR armor get crits, depending on damage and BAR rating
if (te.hasBARArmor()) {
if (origDamage > te.getBARRating()) {
if (te.hasArmoredChassis()) {
Report.addNewline(vDesc);
// crit roll with -1 mod
vDesc.addAll(criticalEntity(te, hit.getLocation(), -1));
} else {
Report.addNewline(vDesc);
vDesc.addAll(criticalEntity(te, hit.getLocation(), 0));
}
}
}
// If it has hardened armor, now we need to "correct" any
// remaining damage.
if (tmpDamageHold > 0) {
if (hardenedArmor) {
damage *= 2;
damage -= tmpDamageHold % 2;
} else if (isPlatoon) {
damage = tmpDamageHold;
}
}
}
// is there damage remaining?
if (damage > 0) {
// if this is an Aero then I need to apply internal damage
// to the SI after halving it. Return from here to prevent
// further processing
if (te instanceof Aero) {
Aero a = (Aero) te;
//check for overpenetration
if(game.getOptions().booleanOption("stratops_over_penetrate")) {
int opRoll = Compute.d6(1);
if((((te instanceof Jumpship) || (te instanceof SpaceStation)) && !(te instanceof Warship) && (opRoll > 3))
|| ((te instanceof Dropship) && (opRoll > 4))
|| ((te instanceof Warship) && (a.get0SI() <= 30) && (opRoll > 5))) {
//over-penetration happened
r = new Report(9090);
r.subject = te_n;
r.newlines = 0;
vDesc.addElement(r);
int new_loc = a.getOppositeLocation(hit.getLocation());
damage = Math.min(damage, te.getArmor(new_loc));
r = new Report(6065);
r.subject = te_n;
r.indent(2);
r.newlines = 0;
r.addDesc(te);
r.add(damage);
r.add(te.getLocationAbbr(new_loc));
vDesc.addElement(r);
te.setArmor(te.getArmor(new_loc) - damage, new_loc);
if((te instanceof Warship) || (te instanceof Jumpship)) {
damage = 1;
} else {
damage = 0;
}
//
}
}
// divide damage in half
// do not divide by half if it is an ammo exposion
if (!ammoExplosion && !nukeS2S) {
damage /= 2;
}
// this should result in a crit
// but only if it really did damage after rounding down
if (damage > 0) {
critSI = true;
}
// Now apply damage to the structural integrity
a.setSI(a.getSI() - damage);
te.damageThisPhase += damage;
// send the report
r = new Report(1210);
r.subject = te_n;
r.newlines = 0;
if (!ammoExplosion) {
r.messageId = 9005;
} else {
r.messageId = 9006;
}
r.add(damage);
r.add(Math.max(a.getSI(), 0));
vDesc.addElement(r);
damage = 0;
// check to see if this would destroy the ASF
if (a.getSI() <= 0) {
vDesc.addAll(destroyEntity(te, "structural integrity collapse"));
a.setSI(0);
}
checkAeroCrits(vDesc, a, hit, damage_orig, critThresh, critSI, ammoExplosion, nukeS2S);
return vDesc;
}
// Check for CASE II right away. if so reduce damage to 1
// and let it hit the IS.
// Also remove as much of the rear armor as allowed by the
// damage. If arm/leg/head
// Then they lose all their armor if its less then the
// explosion damage.
if (ammoExplosion && te.hasCASEII(hit.getLocation())) {
te.damageThisPhase += damage;
// 1 point of damage goes to IS
damage--;
// Remaining damage prevented by CASE II
r = new Report(6126);
r.subject = te_n;
r.add(damage);
r.indent(3);
r.newlines = 0;
vDesc.addElement(r);
boolean rearArmor = te.hasRearArmor(hit.getLocation());
if (damage > te.getArmor(hit.getLocation(), rearArmor)) {
te.setArmor(IArmorState.ARMOR_DESTROYED, hit.getLocation(), rearArmor);
} else {
te.setArmor(te.getArmor(hit.getLocation(), rearArmor) - damage, hit.getLocation(), rearArmor);
}
if (te.getInternal(hit) > 0) {
// Mek takes 1 point of IS damage
damage = 1;
} else {
damage = 0;
}
int roll = Compute.d6(2);
r = new Report(6127);
r.subject = te.getId();
r.add(roll);
r.newlines = 0;
vDesc.add(r);
if (roll >= 8) {
hit.setEffect(HitData.EFFECT_NO_CRITICALS);
}
}
// check for tank CASE here: damage to rear armor, excess
// dissipating, and a crew stunned crit
if (ammoExplosion && (te instanceof Tank) && te.locationHasCase(Tank.LOC_BODY)) {
te.damageThisPhase += damage;
r = new Report(6124);
r.subject = te_n;
r.indent(2);
r.add(damage);
vDesc.add(r);
if (damage > te.getArmor(Tank.LOC_REAR)) {
te.setArmor(IArmorState.ARMOR_DESTROYED, Tank.LOC_REAR);
r = new Report(6090);
} else {
te.setArmor(te.getArmor(Tank.LOC_REAR) - damage, Tank.LOC_REAR);
r = new Report(6085);
r.add(te.getArmor(Tank.LOC_REAR));
}
r.subject = te_n;
r.newlines = 0;
r.indent(2);
vDesc.add(r);
damage = 0;
int critIndex;
if (((Tank) te).isCommanderHit() && ((Tank) te).isDriverHit()) {
critIndex = Tank.CRIT_CREW_KILLED;
} else {
critIndex = Tank.CRIT_CREW_STUNNED;
}
vDesc.addAll(applyCriticalHit(te, Entity.NONE, new CriticalSlot(0, critIndex), true));
}
// is there internal structure in the location hit?
if (te.getInternal(hit) > 0) {
// Now we need to consider alternate structure types!
int tmpDamageHold = -1;
if ((te instanceof Mech) && ((Mech) te).hasCompositeStructure()) {
tmpDamageHold = damage;
damage *= 2;
}
if ((te instanceof Mech) && ((Mech) te).hasReinforcedStructure()) {
tmpDamageHold = damage;
damage /= 2;
damage += tmpDamageHold % 2;
}
if ((te.getInternal(hit) > damage) && (damage > 0)) {
// internal structure absorbs all damage
te.setInternal(te.getInternal(hit) - damage, hit);
// Triggers a critical hit on Vehicles and Mechs.
if (!isPlatoon && !isBattleArmor) {
crits++;
}
tookInternalDamage = true;
te.damageThisPhase += damage;
damage = 0;
r = new Report(1210);
r.subject = te_n;
r.newlines = 0;
// Infantry platoons have men not "Internals".
if (isPlatoon) {
r.messageId = 6095;
} else {
r.messageId = 6100;
}
r.add(te.getInternal(hit));
vDesc.addElement(r);
} else if (damage > 0) {
// Triggers a critical hit on Vehicles and Mechs.
if (!isPlatoon && !isBattleArmor) {
crits++;
}
// damage transfers, maybe
int absorbed = Math.max(te.getInternal(hit), 0);
// Handle Protomech pilot damage
// due to location destruction
if (te instanceof Protomech) {
int hits = Protomech.POSSIBLE_PILOT_DAMAGE[hit.getLocation()] - ((Protomech) te).getPilotDamageTaken(hit.getLocation());
if (hits > 0) {
vDesc.addAll(damageCrew(te, hits));
((Protomech) te).setPilotDamageTaken(hit.getLocation(), Protomech.POSSIBLE_PILOT_DAMAGE[hit.getLocation()]);
}
}
// Platoon, Trooper, or Section destroyed message
r = new Report(1210);
r.subject = te_n;
r.newlines = 0;
if (isPlatoon) {
// Infantry have only one section, and
// are therefore destroyed.
r.messageId = 6105;
} else if (isBattleArmor) {
r.messageId = 6110;
} else {
r.messageId = 6115;
}
vDesc.addElement(r);
// If a sidetorso got destroyed, and the
// corresponding arm is not yet destroyed, add
// it as a club to that hex (p.35 BMRr)
if ((te instanceof Mech) && (((hit.getLocation() == Mech.LOC_RT) && (te.getInternal(Mech.LOC_RARM) > 0)) || ((hit.getLocation() == Mech.LOC_LT) && (te.getInternal(Mech.LOC_LARM) > 0)))) {
int blownOffLocation = -1; // good initial value?
if (hit.getLocation() == Mech.LOC_RT) {
blownOffLocation = Mech.LOC_RARM;
} else {
blownOffLocation = Mech.LOC_LARM;
}
r = new Report(6120);
r.subject = te_n;
r.add(te.getLocationName(blownOffLocation));
r.newlines = 0;
vDesc.addElement(r);
IHex h = game.getBoard().getHex(te.getPosition());
if (te instanceof BipedMech) {
if (!h.containsTerrain(Terrains.ARMS)) {
h.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.ARMS, 1));
} else {
h.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.ARMS, h.terrainLevel(Terrains.ARMS) + 1));
}
} else if (!h.containsTerrain(Terrains.LEGS)) {
h.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.LEGS, 1));
} else {
h.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.LEGS, h.terrainLevel(Terrains.LEGS) + 1));
}
sendChangedHex(te.getPosition());
}
// Troopers riding on a location
// all die when the location is destroyed.
if ((te instanceof Mech) || (te instanceof Tank)) {
Entity passenger = te.getExteriorUnitAt(hit.getLocation(), hit.isRear());
if ((null != passenger) && !passenger.isDoomed()) {
HitData passHit = passenger.getTrooperAtLocation(hit, te);
// ensures a kill
passHit.setEffect(HitData.EFFECT_CRITICAL);
if (passenger.getInternal(passHit) > 0) {
vDesc.addAll(damageEntity(passenger, passHit, damage));
}
passHit = new HitData(hit.getLocation(), !hit.isRear());
passHit = passenger.getTrooperAtLocation(passHit, te);
// ensures a kill
passHit.setEffect(HitData.EFFECT_CRITICAL);
if (passenger.getInternal(passHit) > 0) {
vDesc.addAll(damageEntity(passenger, passHit, damage));
}
}
}
// BA inferno explosions
if (te instanceof BattleArmor) {
int infernos = 0;
for (Mounted m : te.getEquipment()) {
if (m.getType() instanceof AmmoType) {
AmmoType at = (AmmoType) m.getType();
if (((at.getAmmoType() == AmmoType.T_SRM) || (at.getAmmoType() == AmmoType.T_MML)) && (at.getMunitionType() == AmmoType.M_INFERNO)) {
infernos += at.getRackSize() * m.getShotsLeft();
}
} else if (m.getType().hasFlag(MiscType.F_FIRE_RESISTANT)) {
// immune to inferno explosion
infernos = 0;
break;
}
}
if (infernos > 0) {
int roll = Compute.d6(2);
r = new Report(6680);
r.add(roll);
vDesc.add(r);
if (roll >= 8) {
Coords c = te.getPosition();
if (c == null) {
Entity transport = game.getEntity(te.getTransportId());
if (transport != null) {
c = transport.getPosition();
}
vPhaseReport.addAll(deliverInfernoMissiles(te, te, infernos));
}
if (c != null) {
vPhaseReport.addAll(deliverInfernoMissiles(te, new HexTarget(c, game.getBoard(), Targetable.TYPE_HEX_ARTILLERY), infernos));
}
}
}
}
// Destroy the location.
te.destroyLocation(hit.getLocation());
te.damageThisPhase += absorbed;
damage -= absorbed;
// Now we need to consider alternate structure types!
if (tmpDamageHold > 0) {
if (((Mech) te).hasCompositeStructure()) {
// If there's a remainder, we can actually
// ignore it.
damage /= 2;
} else if (((Mech) te).hasReinforcedStructure()) {
damage *= 2;
damage -= tmpDamageHold % 2;
}
}
if ((te instanceof Mech) && ((hit.getLocation() == Mech.LOC_RT) || (hit.getLocation() == Mech.LOC_LT))) {
boolean engineExploded = false;
int numEngineHits = 0;
numEngineHits += te.getHitCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_ENGINE, Mech.LOC_CT);
numEngineHits += te.getHitCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_ENGINE, Mech.LOC_RT);
numEngineHits += te.getHitCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_ENGINE, Mech.LOC_LT);
engineExploded = checkEngineExplosion(te, vDesc, numEngineHits);
if (!engineExploded && (numEngineHits > 2)) {
// third engine hit
vDesc.addAll(destroyEntity(te, "engine destruction"));
if (game.getOptions().booleanOption("auto_abandon_unit")) {
vDesc.addAll(abandonEntity(te));
}
}
}
if ((te instanceof VTOL) && (hit.getLocation() == VTOL.LOC_ROTOR)) {
// if rotor is destroyed, movement goes bleh.
// I think this will work?
te.setOriginalWalkMP(0);
vDesc.addAll(crashVTOLorWiGE((VTOL) te));
}
}
}
if (te.getInternal(hit) <= 0) {
// internal structure is gone, what are the transfer
// potentials?
nextHit = te.getTransferLocation(hit);
if (nextHit.getLocation() == Entity.LOC_DESTROYED) {
if (te instanceof Mech) {
// add all non-destroyed engine crits
te.engineHitsThisRound += te.getGoodCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_ENGINE, hit.getLocation());
// and substract those that where hit previously
// this round
// hackish, but works.
te.engineHitsThisRound -= te.getHitCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_ENGINE, hit.getLocation());
}
boolean engineExploded = false;
engineExploded = checkEngineExplosion(te, vDesc, te.engineHitsThisRound);
if (!engineExploded && !((te instanceof VTOL) && (hit.getLocation() == VTOL.LOC_ROTOR))) {
// Entity destroyed. Ammo explosions are
// neither survivable nor salvagable.
// Only ammo explosions in the CT are devastating.
vDesc.addAll(destroyEntity(te, "damage", !ammoExplosion, !((ammoExplosion || areaSatArty) && ((te instanceof Tank) || ((te instanceof Mech) && (hit.getLocation() == Mech.LOC_CT))))));
// If the head is destroyed, kill the crew.
if ((te instanceof Mech) && (hit.getLocation() == Mech.LOC_HEAD)
&& !te.getCrew().isDead()
&& !te.getCrew().isDoomed()
&& game.getOptions().booleanOption("tacops_skin_of_the_teeth_ejection") ) {
Mech mech = (Mech) te;
if (mech.isAutoEject()) {
if ( mech.getCrew().getHits() < 5) {
Report.addNewline(vDesc);
mech.setDoomed(false);
vDesc.addAll(damageCrew(te, 5-mech.getCrew().getHits()));
mech.setDoomed(true);
}
autoEject = true;
vDesc.addAll(ejectEntity(te, true));
}
}
if ((hit.getLocation() == Mech.LOC_HEAD) || ((hit.getLocation() == Mech.LOC_CT) && ((ammoExplosion && !autoEject) || areaSatArty))) {
te.getCrew().setDoomed(true);
}
if (game.getOptions().booleanOption("auto_abandon_unit")) {
vDesc.addAll(abandonEntity(te));
}
}
// nowhere for further damage to go
damage = 0;
} else if (nextHit.getLocation() == Entity.LOC_NONE) {
// Rest of the damage is wasted.
damage = 0;
} else if (ammoExplosion && te.locationHasCase(hit.getLocation())) {
// Remaining damage prevented by CASE
r = new Report(6125);
r.subject = te_n;
r.add(damage);
r.indent(3);
r.newlines = 0;
vDesc.addElement(r);
// ... but page 21 of the Ask The Precentor Martial FAQ
// www.classicbattletech.com/PDF/AskPMForumArchiveandFAQ.pdf
// says that the damage counts for making PSRs.
te.damageThisPhase += damage;
// The target takes no more damage from the explosion.
damage = 0;
} else if (damage > 0) {
// remaining damage transfers
r = new Report(6130);
r.subject = te_n;
r.indent(2);
r.newlines = 0;
r.add(damage);
r.add(te.getLocationAbbr(nextHit));
vDesc.addElement(r);
// If there are split weapons in this location, mark it
// as
// destroyed, even if it took no criticals.
for (Mounted m : te.getWeaponList()) {
if (m.isSplit()) {
if ((m.getLocation() == hit.getLocation()) || (m.getLocation() == nextHit.getLocation())) {
te.setWeaponDestroyed(m);
}
}
}
}
}
} else if (hit.getSpecCritMod() < 0) {
// If there ISN'T any armor left but we did damage, then there's
// a chance of a crit, using Armor Piercing.
specCrits++;
}
// check for breaching
vDesc.addAll(breachCheck(te, hit.getLocation(), null, underWater));
// resolve special results
if ((hit.getEffect() & HitData.EFFECT_VEHICLE_MOVE_DAMAGED) == HitData.EFFECT_VEHICLE_MOVE_DAMAGED) {
vDesc.addAll(vehicleMotiveDamage((Tank) te, hit.getMotiveMod()));
} else if ((hit.getEffect() & HitData.EFFECT_GUN_EMPLACEMENT_WEAPONS) == HitData.EFFECT_GUN_EMPLACEMENT_WEAPONS) {
r = new Report(6146);
r.subject = te_n;
r.indent(3);
vDesc.addElement(r);
for (Mounted mounted : te.getWeaponList()) {
mounted.setDestroyed(true);
}
} else if ((hit.getEffect() & HitData.EFFECT_GUN_EMPLACEMENT_TURRET) == HitData.EFFECT_GUN_EMPLACEMENT_TURRET) {
r = new Report(6145);
r.subject = te_n;
r.indent(3);
vDesc.addElement(r);
((GunEmplacement) te).setTurretLocked(true);
} else if ((hit.getEffect() & HitData.EFFECT_GUN_EMPLACEMENT_CREW) == HitData.EFFECT_GUN_EMPLACEMENT_CREW) {
r = new Report(6148);
r.subject = te_n;
r.indent(3);
vDesc.addElement(r);
((GunEmplacement) te).getCrew().setDoomed(true);
}
// roll all critical hits against this location
// unless the section destroyed in a previous phase?
// Cause a crit.
if ((te.getInternal(hit) != IArmorState.ARMOR_DESTROYED) && ((hit.getEffect() & HitData.EFFECT_NO_CRITICALS) != HitData.EFFECT_NO_CRITICALS)) {
for (int i = 0; i < crits; i++) {
vDesc.elementAt(vDesc.size() - 1).newlines++;
vDesc.addAll(criticalEntity(te, hit.getLocation(), hit.glancingMod()));
}
crits = 0;
for (int i = 0; i < specCrits; i++) {
vDesc.elementAt(vDesc.size() - 1).newlines++;
// against BAR armor, we get a +2 mod
int critMod = te.hasBARArmor()?2:0;
if (hardenedArmor) {
// against hardened armor, it's a flat -2 mod
critMod = -2;
} else {
// otherwise, use the normal mods
critMod += hit.getSpecCritMod();
critMod += hit.glancingMod();
}
vDesc.addAll(criticalEntity(te, hit.getLocation(), critMod));
}
specCrits = 0;
}
// resolve Aero crits
if (te instanceof Aero) {
checkAeroCrits(vDesc, (Aero)te, hit, damage_orig, critThresh, critSI, ammoExplosion, nukeS2S);
}
if (isHeadHit && !te.crew.getOptions().booleanOption("dermal_armor")) {
Report.addNewline(vDesc);
vDesc.addAll(damageCrew(te, 1));
}
// loop to next location
hit = nextHit;
if (damageIS) {
wasDamageIS = true;
damageIS = false;
}
}
// Mechs using EI implants take pilot damage each time a hit
// inflicts IS damage
if (tookInternalDamage && ((te instanceof Mech) || (te instanceof Protomech)) && te.hasActiveEiCockpit()) {
Report.addNewline(vDesc);
int roll = Compute.d6(2);
r = new Report(5075);
r.subject = te.getId();
r.addDesc(te);
r.add(7);
r.add(roll);
r.choose(roll >= 7);
r.indent(2);
vDesc.add(r);
if (roll < 7) {
vDesc.addAll(damageCrew(te, 1));
}
}
// if using VDNI (but not buffered), check for damage on an internal hit
if (tookInternalDamage && te.crew.getOptions().booleanOption("vdni") && !te.crew.getOptions().booleanOption("bvdni") && !te.crew.getOptions().booleanOption("pain_shunt")) {
Report.addNewline(vDesc);
int roll = Compute.d6(2);
r = new Report(3580);
r.subject = te.getId();
r.addDesc(te);
r.add(7);
r.add(roll);
r.choose(roll >= 8);
r.indent(2);
vDesc.add(r);
if (roll >= 8) {
vDesc.addAll(damageCrew(te, 1));
}
}
// damage field guns on infantry platoons if there arent enough men left
// to man it
if (isPlatoon) {
float tons = 0.0f;
for (Mounted weap : te.getWeaponList()) {
WeaponType wtype = (WeaponType) weap.getType();
if (!wtype.hasFlag(WeaponType.F_INFANTRY)) {
tons += wtype.getTonnage(te);
if (tons > te.getInternal(Infantry.LOC_INFANTRY)) {
weap.setDestroyed(true);
}
}
}
}
// TacOps p.78 Ammo booms can hurt other units in same and adjcent hexes
if (ammoExplosion
&& game.getOptions().booleanOption("tacops_ammunition")
&& (damage > 0)
&& (damage_orig/10 > 0)) {
Report.addNewline(vDesc);
r = new Report(5068, Report.PUBLIC);
r.subject = te.getId();
r.addDesc(te);
r.indent(2);
vDesc.add(r);
Report.addNewline(vDesc);
r = new Report(5400, Report.PUBLIC);
r.subject = te.getId();
r.indent(2);
vDesc.add(r);
int[] damages = { (int) Math.floor(damage_orig / 10), (int) Math.floor(damage_orig / 20) };
doExplosion(damages, false, te.getPosition(), true, vDesc, null,5);
Report.addNewline(vDesc);
r = new Report(5410, Report.PUBLIC);
r.subject = te.getId();
r.indent(2);
vDesc.add(r);
}
// This flag indicates the hit was directly to IS
if (wasDamageIS) {
Report.addNewline(vDesc);
}
return vDesc;
}
/**
* Check to see if the entity's engine explodes. Rules for ICE explosions
* are different to fusion engines.
*
* @param en -
* the <code>Entity</code> in question. This value must not be
* <code>null</code>.
* @param vDesc -
* the <code>Vector</code> that this function should add its
* <code>Report<code>s to. It may be empty, but not
* <code>null</code>.
* @param hits - the number of criticals on the engine
* @return <code>true</code> if the unit's engine exploded,
* <code>false</code> if not.
*/
private boolean checkEngineExplosion(Entity en, Vector<Report> vDesc, int hits) {
if (!(en instanceof Mech)
&& !(en instanceof QuadMech)
&& !(en instanceof BipedMech)
&& !(en instanceof Aero)
&& !(en instanceof Tank)) {
return false;
}
if (en.isDestroyed()) {
return false;
}
int explosionBTH = 10;
int hitsPerRound = 4;
Engine engine = null;
if (en instanceof Mech) {
engine = ((Mech) en).getEngine();
} else if (en instanceof Tank) {
explosionBTH = 12;
hitsPerRound = 1;
engine = ((Tank) en).getEngine();
} else {
engine = ((Aero) en).getEngine();
explosionBTH = 12;
hitsPerRound = 1;
}
// Non mechs and mechs that already rolled are safe
if (en.rolledForEngineExplosion || !(en instanceof Mech)) {
return false;
}
// ICE can always explode and roll every time hit
if (engine.isFusion()
&& (!game.getOptions().booleanOption("tacops_engine_explosions")
|| (en.engineHitsThisRound < hitsPerRound))) {
return false;
}
if (!engine.isFusion()) {
switch (hits) {
case 0:
return false;
case 1:
explosionBTH = 10;
break;
case 2:
explosionBTH = 7;
break;
case 3:
default:
explosionBTH = 4;
break;
}
}
int explosionRoll = Compute.d6(2);
boolean didExplode = explosionRoll >= explosionBTH;
Report r;
r = new Report(6150);
r.subject = en.getId();
r.indent(2);
r.addDesc(en);
r.add(en.engineHitsThisRound);
vDesc.addElement(r);
r = new Report(6155);
r.subject = en.getId();
r.indent(2);
r.add(explosionBTH);
r.add(explosionRoll);
vDesc.addElement(r);
if (!didExplode) {
// whew!
if (engine.isFusion()) {
en.rolledForEngineExplosion = true;
}
// fusion engines only roll 1/phase but ICE roll every time damaged
r = new Report(6160);
r.subject = en.getId();
r.indent(2);
vDesc.addElement(r);
} else {
en.rolledForEngineExplosion = true;
r = new Report(6165, Report.PUBLIC);
r.subject = en.getId();
r.indent(2);
vDesc.addElement(r);
vDesc.addAll(destroyEntity(en, "engine explosion", false, false));
// kill the crew
en.getCrew().setDoomed(true);
// This is a hack so MM.NET marks the mech as not salvageable
if (en instanceof Mech) {
en.destroyLocation(Mech.LOC_CT);
}
// ICE explosions don't hurt anyone else, but fusion do
if (engine.isFusion()) {
int engineRating = en.getEngine().getRating();
Report.addNewline(vDesc);
r = new Report(5400, Report.PUBLIC);
r.subject = en.getId();
r.indent(2);
vDesc.add(r);
if (en instanceof Mech) {
Mech mech = (Mech) en;
if (mech.isAutoEject()) {
vDesc.addAll(ejectEntity(en, true));
}
}
doFusionEngineExplosion(engineRating, en.getPosition(), vDesc, null);
Report.addNewline(vDesc);
r = new Report(5410, Report.PUBLIC);
r.subject = en.getId();
r.indent(2);
vDesc.add(r);
}
}
return didExplode;
}
/**
* Extract explosion functionality for generalized explosions in areas.
*/
public void doFusionEngineExplosion(int engineRating, Coords position, Vector<Report> vDesc, Vector<Integer> vUnits) {
int[] myDamages = { engineRating, (engineRating / 10), (engineRating / 20), (engineRating / 40) };
doExplosion(myDamages, true, position, false, vDesc, vUnits,5);
}
/**
* General function to cause explosions in areas.
*/
public void doExplosion(int damage, int degredation, boolean autoDestroyInSameHex, Coords position, boolean allowShelter, Vector<Report> vDesc, Vector<Integer> vUnits) {
if (degredation < 1) {
return;
}
int[] myDamages = new int[damage / degredation];
if (myDamages.length < 1) {
return;
}
myDamages[0] = damage;
for (int x = 1; x < myDamages.length; x++) {
myDamages[x] = myDamages[x - 1] - degredation;
}
doExplosion(myDamages, autoDestroyInSameHex, position, allowShelter, vDesc, vUnits,5);
}
/**
* General function to cause explosions in areas.
*/
public void doExplosion(int[] damages, boolean autoDestroyInSameHex, Coords position, boolean allowShelter, Vector<Report> vDesc, Vector<Integer> vUnits, int clusterAmt) {
if (vDesc == null) {
vDesc = new Vector<Report>();
}
if (vUnits == null) {
vUnits = new Vector<Integer>();
}
Report r;
HashSet<Entity> entitiesHit = new HashSet<Entity>();
// We need to damage buildings.
Enumeration<Building> bldgs = game.getBoard().getBuildings();
while (bldgs.hasMoreElements()) {
final Building bldg = bldgs.nextElement();
// Lets find the closest hex from the building.
Enumeration<Coords> hexes = bldg.getCoords();
while (hexes.hasMoreElements()) {
final Coords coords = hexes.nextElement();
int dist = position.distance(coords);
if (dist < damages.length) {
Vector<Report> buildingReport = damageBuilding(bldg, damages[dist], coords);
for (Report report : buildingReport) {
report.type = Report.PUBLIC;
}
vDesc.addAll(buildingReport);
}
}
}
// Now we damage people near the explosion.
ArrayList<Entity> loaded = new ArrayList<Entity>();
for (Enumeration<Entity> entities = game.getEntities(); entities.hasMoreElements();) {
Entity entity = entities.nextElement();
if (entitiesHit.contains(entity)) {
continue;
}
if (entity.isDestroyed() || !entity.isDeployed()) {
// FIXME
// IS this the behavior we want?
// This means, incidentally, that salvage is never affected by
// explosions
// as long as it was destroyed before the explosion.
continue;
}
if ( (entity instanceof MechWarrior) && !((MechWarrior)entity).hasLanded() ){
//MechWarrior is still up in the air ejecting their for safe from this explosion.
continue;
}
Coords entityPos = entity.getPosition();
if (entityPos == null) {
// maybe its loaded?
Entity transport = game.getEntity(entity.getTransportId());
if (transport != null) {
loaded.add(entity);
}
continue;
}
int range = position.distance(entityPos);
if (range >= damages.length) {
// Yeah, this is fine. It's outside the blast radius.
continue;
}
// We might need to nuke everyone in the explosion hex. If so...
if ((range == 0) && autoDestroyInSameHex) {
// Add the reports
vDesc.addAll(destroyEntity(entity, "explosion proximity", false, false));
// Add it to the "blasted units" list
vUnits.add(entity.getId());
// Kill the crew
entity.getCrew().setDoomed(true);
entitiesHit.add(entity);
continue;
}
int damage = damages[range];
if (allowShelter && canShelter(entityPos, position, entity.absHeight())) {
if (isSheltered()) {
r = new Report(6545);
r.addDesc(entity);
r.subject = entity.getId();
vDesc.addElement(r);
continue;
}
// If shelter is allowed but didn't work, report that.
r = new Report(6546);
r.subject = entity.getId();
r.addDesc(entity);
vDesc.addElement(r);
}
// Since it's taking damage, add it to the list of units hit.
vUnits.add(entity.getId());
r = new Report(6175);
r.subject = entity.getId();
r.indent(2);
r.addDesc(entity);
r.add(damage);
r.newlines = 0;
vDesc.addElement(r);
while (damage > 0) {
int cluster = Math.min(clusterAmt, damage);
HitData hit = entity.rollHitLocation(ToHitData.HIT_NORMAL, Compute.targetSideTable(position, entity));
vDesc.addAll(damageEntity(entity, hit, cluster, false, DamageType.IGNORE_PASSENGER, false, true));
damage -= cluster;
}
Report.addNewline(vDesc);
}
// now deal with loaded units...
for (Entity e : loaded) {
// This can be null, if the transport died from damage
final Entity transporter = game.getEntity(e.getTransportId());
if ((transporter == null) || transporter.getExternalUnits().contains(e)) {
// Its external or transport was destroyed - hit it.
final Coords entityPos = (transporter == null ? e.getPosition() : transporter.getPosition());
final int range = position.distance(entityPos);
if (range >= damages.length) {
// Yeah, this is fine. It's outside the blast radius.
continue;
}
int damage = damages[range];
if (allowShelter) {
final int absHeight = (transporter == null ? e.absHeight() : transporter.absHeight());
if ((entityPos != null) && canShelter(entityPos, position, absHeight)) {
if (isSheltered()) {
r = new Report(6545);
r.addDesc(e);
r.subject = e.getId();
vDesc.addElement(r);
continue;
}
// If shelter is allowed but didn't work, report that.
r = new Report(6546);
r.subject = e.getId();
r.addDesc(e);
vDesc.addElement(r);
}
}
// No shelter
// Since it's taking damage, add it to the list of units hit.
vUnits.add(e.getId());
r = new Report(6175);
r.subject = e.getId();
r.indent(2);
r.addDesc(e);
r.add(damage);
r.newlines = 0;
vDesc.addElement(r);
while (damage > 0) {
int cluster = Math.min(5, damage);
HitData hit = e.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT);
vDesc.addAll(damageEntity(e, hit, cluster, false, DamageType.IGNORE_PASSENGER, false, true));
damage -= cluster;
}
Report.addNewline(vDesc);
}
}
}
/**
* Check if an Entity of the passed height can find shelter from a nukeblast
*
* @param entityPosition
* the <code>Coords</code> the Entity is at
* @param position
* the <code>Coords</code> of the explosion
* @param entityAbsHeight
* the <code>int</code> height of the entity
* @return a <code>boolean</code> value indicating if the entity of the
* given height can find shelter
*/
public boolean canShelter(Coords entityPosition, Coords position, int entityAbsHeight) {
// What is the next hex in the direction of the blast?
Coords shelteringCoords = Coords.nextHex(entityPosition, position);
IHex shelteringHex = game.getBoard().getHex(shelteringCoords);
// This is an error condition. It really shouldn't ever happen.
if (shelteringHex == null) {
return false;
}
// Now figure out the height to which that hex will provide shelter.
// It's worth noting, this assumes that any building in the hex has
// already survived the bomb blast. In the case where a building
// won't survive the blast but hasn't actually taken the damage
// yet, this will be wrong.
int shelterLevel = shelteringHex.floor();
if (shelteringHex.containsTerrain(Terrains.BUILDING)) {
shelterLevel = shelteringHex.ceiling();
}
// Get the absolute height of the unit relative to level 0.
entityAbsHeight += game.getBoard().getHex(entityPosition).surface();
// Now find the height that needs to be sheltered, and compare.
if (entityAbsHeight < shelterLevel) {
return true;
}
// Well, if the above isn't true... Give up.
return false;
}
/**
* @return true if the unit succeeds a shelter roll
*/
private boolean isSheltered() {
if (Compute.d6(2) >= 9) {
return true;
}
return false;
}
/**
* add a nuke to be exploded in the next weapons attack phase
*
* @param nuke
* the nuke paramater either 3 or 6 ints 0 and 1 being X and Y
* when 3 ints: third is nuketype (from HS:3070) if 6 ints: third
* is damage fourth is degradation fifth is secondary radius
* sixth is crater depth
*/
public void addScheduledNuke(int[] nuke) {
scheduledNukes.add(nuke);
}
/**
* explode any scheduled nukes
*/
private void resolveScheduledNukes() {
for (int[] nuke : scheduledNukes) {
if (nuke.length == 3) {
doNuclearExplosion(new Coords(nuke[0] - 1, nuke[1] - 1), nuke[2], vPhaseReport);
}
if (nuke.length == 6) {
doNuclearExplosion(new Coords(nuke[0] - 1, nuke[1] - 1), nuke[2], nuke[3], nuke[4], nuke[5], vPhaseReport);
}
}
scheduledNukes.clear();
}
/**
* do a nuclear explosion
*
* @param position
* @param nukeType
* @param vDesc
*/
public void doNuclearExplosion(Coords position, int nukeType, Vector<Report> vDesc) {
// Throws a nuke for one of the pre-defined types.
switch (nukeType) {
case 0:
case 1:
doNuclearExplosion(position, 100, 5, 40, 0, vDesc);
break;
case 2:
doNuclearExplosion(position, 1000, 23, 86, 1, vDesc);
break;
case 3:
doNuclearExplosion(position, 10000, 109, 184, 3, vDesc);
break;
case 4:
doNuclearExplosion(position, 100000, 505, 396, 5, vDesc);
break;
default:
// This isn't a valid nuke type by HS:3070 rules.
// And since that's our only current source...
return;
}
}
/**
* explode a nuke
*
* @param position
* @param baseDamage
* @param degredation
* @param secondaryRadius
* @param craterDepth
* @param vDesc
*/
public void doNuclearExplosion(Coords position, int baseDamage, int degredation, int secondaryRadius, int craterDepth, Vector<Report> vDesc) {
// Just in case.
if (vDesc == null) {
vDesc = new Vector<Report>();
}
// First, crater the terrain.
// All terrain, units, buildings... EVERYTHING in here is just gone.
// Gotta love nukes.
Report r = new Report(1215, Report.PUBLIC);
r.indent();
r.add(position.getBoardNum(), true);
vDesc.add(r);
int curDepth = game.getBoard().getHex(position).floor() - craterDepth;
int range = 0;
while (range < 2 * craterDepth) {
// Get the set of hexes at this range.
Enumeration<Coords> hexSet = game.getBoard().getHexesAtDistance(position, range);
// Iterate through the hexes.
while (hexSet.hasMoreElements()) {
Coords myHexCoords = hexSet.nextElement();
IHex myHex = game.getBoard().getHex(myHexCoords);
// In each hex, first, sink the terrain if necessary.
myHex.setElevation(myHex.getElevation() - craterDepth + (range / 2));
// Then, remove ANY terrains here.
// I mean ALL of them; they're all just gone.
// No ruins, no water, no rough, no nothing.
if (myHex.containsTerrain(Terrains.WATER)) {
myHex.setElevation(myHex.floor());
}
myHex.removeAllTerrains();
myHex.clearExits();
sendChangedHex(myHexCoords);
}
// Now that the hexes are dealt with, increment the distance.
range++;
// Lastly, if the next distance is a multiple of 2...
// The crater depth goes down one.
if ((range > 0) && (range % 2 == 0)) {
curDepth++;
}
}
// This is technically part of cratering, but...
// Now we destroy all the units inside the cratering range.
Enumeration<Entity> entitiesInCrater = game.getEntities();
while (entitiesInCrater.hasMoreElements()) {
Entity entity = entitiesInCrater.nextElement();
// loaded units and off board units don't have a position,
// so we don't count 'em here
if ((entity.getTransportId() != Entity.NONE) || (entity.getPosition() == null)) {
continue;
}
// If it's too far away for this...
if (position.distance(entity.getPosition()) >= range) {
continue;
}
// If it's already destroyed...
if (entity.isDestroyed()) {
continue;
}
vDesc.addAll(destroyEntity(entity, "nuclear explosion proximity", false, false));
// Kill the crew
entity.getCrew().setDoomed(true);
}
entitiesInCrater = null;
// Then, do actual blast damage.
// Use the standard blast function for this.
Vector<Report> tmpV = new Vector<Report>();
Vector<Integer> blastedUnitsVec = new Vector<Integer>();
doExplosion(baseDamage, degredation, true, position, true, tmpV, blastedUnitsVec);
Report.indentAll(tmpV, 2);
vDesc.addAll(tmpV);
// Everything that was blasted by the explosion has to make a piloting
// check at +6.
for (Object i : blastedUnitsVec) {
Entity o = game.getEntity((Integer) i);
if (o instanceof Mech) {
Mech bm = (Mech) o;
// Needs a piloting check at +6 to avoid falling over.
// Obviously not if it's already prone, though.
if (!bm.isProne()) {
game.addPSR(new PilotingRollData(bm.getId(), 6, "hit by nuclear blast"));
}
} else if (o instanceof VTOL) {
// Needs a piloting check at +6 to avoid crashing.
// Wheeeeee!
VTOL vt = (VTOL) o;
// Check only applies if it's in the air.
// FIXME: is this actually correct? What about
// buildings/bridges?
if (vt.getElevation() > 0) {
game.addPSR(new PilotingRollData(vt.getId(), 6, "hit by nuclear blast"));
}
} else if (o instanceof Tank) {
// As per official answer on the rules questions board...
// Needs a piloting check at +6 to avoid a 1-level fall...
// But ONLY if a hover-tank.
// FIXME
}
}
// Just get rid of it for the balance of the function...
tmpV = null;
// This ISN'T part of the blast, but if there's ANYTHING in the ground
// zero hex, destroy it.
Building tmpB = game.getBoard().getBuildingAt(position);
if (tmpB != null) {
r = new Report(2415);
r.add(tmpB.getName());
addReport(r);
tmpB.setCurrentCF(0, position);
}
IHex gzHex = game.getBoard().getHex(position);
if (gzHex.containsTerrain(Terrains.WATER)) {
gzHex.setElevation(gzHex.floor());
}
gzHex.removeAllTerrains();
// Next, for whatever's left, do terrain effects
// such as clearing, roughing, and boiling off water.
boolean damageFlag = true;
int damageAtRange = baseDamage - (degredation * range);
if (damageAtRange > 0) {
for (int x = range; damageFlag; x++) {
// Damage terrain as necessary.
// Get all the hexes, and then iterate through them.
Enumeration<Coords> hexSet = game.getBoard().getHexesAtDistance(position, x);
// Iterate through the hexes.
while (hexSet.hasMoreElements()) {
Coords myHexCoords = hexSet.nextElement();
IHex myHex = game.getBoard().getHex(myHexCoords);
// For each 3000 damage, water level is reduced by 1.
if ((damageAtRange >= 3000) && (myHex.containsTerrain(Terrains.WATER))) {
int numCleared = damageAtRange / 3000;
int oldLevel = myHex.terrainLevel(Terrains.WATER);
myHex.removeTerrain(Terrains.WATER);
if (oldLevel > numCleared) {
myHex.addTerrain(new Terrain(Terrains.WATER, oldLevel - numCleared));
}
}
// ANY non-water hex that takes 200 becomes rough.
if ((damageAtRange >= 200) && (!myHex.containsTerrain(Terrains.WATER))) {
myHex.removeAllTerrains();
myHex.clearExits();
myHex.addTerrain(new Terrain(Terrains.ROUGH, 1));
} else if ((damageAtRange >= 20) && ((myHex.containsTerrain(Terrains.WOODS)) || (myHex.containsTerrain(Terrains.JUNGLE)))) {
// Each 20 clears woods by 1 level.
int numCleared = damageAtRange / 20;
int terrainType = (myHex.containsTerrain(Terrains.WOODS) ? Terrains.WOODS : Terrains.JUNGLE);
int oldLevel = myHex.terrainLevel(terrainType);
myHex.removeTerrain(terrainType);
if (oldLevel > numCleared) {
myHex.addTerrain(new Terrain(terrainType, oldLevel - numCleared));
}
}
sendChangedHex(myHexCoords);
}
// Initialize for the next iteration.
damageAtRange = baseDamage - (degredation * x + 1);
// If the damage is less than 20, it has no terrain effect.
if (damageAtRange < 20) {
damageFlag = false;
}
}
}
// Lastly, do secondary effects.
Enumeration<Entity> entitiesInSecondaryRange = game.getEntities();
while (entitiesInSecondaryRange.hasMoreElements()) {
Entity entity = entitiesInSecondaryRange.nextElement();
// loaded units and off board units don't have a position,
// so we don't count 'em here
if ((entity.getTransportId() != Entity.NONE) || (entity.getPosition() == null)) {
continue;
}
// If it's already destroyed...
if ((entity.isDoomed()) || (entity.isDestroyed())) {
continue;
}
// If it's too far away for this...
if (position.distance(entity.getPosition()) > secondaryRadius) {
continue;
}
// Actually do secondary effects against it.
// Since the effects are unit-dependant, we'll just define it in the
// entity.
applySecondaryNuclearEffects(entity, position, vDesc);
}
// All right. We're done.
r = new Report(1216, Report.PUBLIC);
r.indent();
r.newlines = 2;
vDesc.add(r);
}
/**
* Handles secondary effects from nuclear blasts against all units in range.
*
* @param entity
* The entity to affect.
* @param position
* The coordinates of the nuclear blast, for to-hit directions.
* @param vDesc
* a description vector to use for reports.
*/
public void applySecondaryNuclearEffects(Entity entity, Coords position, Vector<Report> vDesc) {
// If it's already destroyed, give up. We really don't care.
if (entity.isDestroyed()) {
return;
}
// Check to see if the infantry is in a protective structure.
boolean inHardenedBuilding = (Compute.isInBuilding(game, entity) && (game.getBoard().getHex(entity.getPosition()).terrainLevel(Terrains.BUILDING) == 4));
// Roll 2d6.
int roll = Compute.d6(2);
Report r = new Report(6555);
r.subject = entity.getId();
r.add(entity.getDisplayName());
r.add(roll);
// If they are in protective structure, add 2 to the roll.
if (inHardenedBuilding) {
roll += 2;
r.add(" + 2 (unit is in hardened building)");
} else {
r.add("");
}
// Also, if the entity is "hardened" against EMI, it gets a +2.
// For these purposes, I'm going to hand this off to the Entity itself
// to tell us.
// Right now, it IS based purely on class, but I won't rule out the idea
// of
// "nuclear hardening" as equipment for a support vehicle, for example.
if (entity.isNuclearHardened()) {
roll += 2;
r.add(" + 2 (unit is hardened against EMI)");
} else {
r.add("");
}
r.indent(2);
vDesc.add(r);
// Now, compare it to the table, and apply the effects.
if (roll <= 4) {
// The unit is destroyed.
// Sucks, doesn't it?
// This applies to all units.
// Yup, just sucks.
vDesc.addAll(destroyEntity(entity, "nuclear explosion secondary effects", false, false));
// Kill the crew
entity.getCrew().setDoomed(true);
} else if (roll <= 6) {
if (entity instanceof BattleArmor) {
// It takes 50% casualties, rounded up.
BattleArmor myBA = (BattleArmor) entity;
int numDeaths = (int) (Math.ceil((myBA.getNumberActiverTroopers())) / 2.0);
for (int x = 0; x < numDeaths; x++) {
vDesc.addAll(applyCriticalHit(entity, 0, null, false));
}
} else if (entity instanceof Infantry) {
// Standard infantry are auto-killed in this band, unless
// they're in a building.
if (game.getBoard().getHex(entity.getPosition()).containsTerrain(Terrains.BUILDING)) {
// 50% casualties, rounded up.
int damage = (int) (Math.ceil((((Infantry) entity).getInternal(Infantry.LOC_INFANTRY)) / 2.0));
vDesc.addAll(damageEntity(entity, new HitData(Infantry.LOC_INFANTRY), damage, true));
} else {
vDesc.addAll(destroyEntity(entity, "nuclear explosion secondary effects", false, false));
entity.getCrew().setDoomed(true);
}
} else if (entity instanceof Tank) {
// All vehicles suffer two critical hits...
HitData hd = entity.rollHitLocation(ToHitData.HIT_NORMAL, entity.sideTable(position));
vDesc.addAll(oneCriticalEntity(entity, hd.getLocation()));
hd = entity.rollHitLocation(ToHitData.HIT_NORMAL, entity.sideTable(position));
vDesc.addAll(oneCriticalEntity(entity, hd.getLocation()));
// ...and a Crew Killed hit.
vDesc.addAll(applyCriticalHit(entity, 0, new CriticalSlot(0, Tank.CRIT_CREW_KILLED), false));
} else if ((entity instanceof Mech) || (entity instanceof Protomech)) {
// 'Mechs suffer two critical hits...
HitData hd = entity.rollHitLocation(ToHitData.HIT_NORMAL, entity.sideTable(position));
vDesc.addAll(oneCriticalEntity(entity, hd.getLocation()));
hd = entity.rollHitLocation(ToHitData.HIT_NORMAL, entity.sideTable(position));
vDesc.addAll(oneCriticalEntity(entity, hd.getLocation()));
// and four pilot hits.
vDesc.addAll(damageCrew(entity, 4));
} else {
// Buildings and gun emplacements and such are only effected by
// the EMI.
// No auto-crits or anything.
}
} else if (roll <= 10) {
if (entity instanceof BattleArmor) {
// It takes 25% casualties, rounded up.
BattleArmor myBA = (BattleArmor) entity;
int numDeaths = (int) (Math.ceil(((myBA.getNumberActiverTroopers())) / 4.0));
for (int x = 0; x < numDeaths; x++) {
vDesc.addAll(applyCriticalHit(entity, 0, null, false));
}
} else if (entity instanceof Infantry) {
if (game.getBoard().getHex(entity.getPosition()).containsTerrain(Terrains.BUILDING)) {
// 25% casualties, rounded up.
int damage = (int) (Math.ceil((((Infantry) entity).getInternal(Infantry.LOC_INFANTRY)) / 4.0));
vDesc.addAll(damageEntity(entity, new HitData(Infantry.LOC_INFANTRY), damage, true));
} else {
// 50% casualties, rounded up.
int damage = (int) (Math.ceil((((Infantry) entity).getInternal(Infantry.LOC_INFANTRY)) / 2.0));
vDesc.addAll(damageEntity(entity, new HitData(Infantry.LOC_INFANTRY), damage, true));
}
} else if (entity instanceof Tank) {
// It takes one crit...
HitData hd = entity.rollHitLocation(ToHitData.HIT_NORMAL, entity.sideTable(position));
vDesc.addAll(oneCriticalEntity(entity, hd.getLocation()));
// Plus a Crew Stunned critical.
vDesc.addAll(applyCriticalHit(entity, 0, new CriticalSlot(0, Tank.CRIT_CREW_STUNNED), false));
} else if ((entity instanceof Mech) || (entity instanceof Protomech)) {
// 'Mechs suffer a critical hit...
HitData hd = entity.rollHitLocation(ToHitData.HIT_NORMAL, entity.sideTable(position));
vDesc.addAll(oneCriticalEntity(entity, hd.getLocation()));
// and two pilot hits.
vDesc.addAll(damageCrew(entity, 2));
} else {
// Buildings and gun emplacements and such are only effected by
// the EMI.
// No auto-crits or anything.
}
}
// If it's 11+, there are no secondary effects beyond EMI.
// Lucky bastards.
// And lastly, the unit is now affected by electromagnetic interference.
entity.setEMI(true);
}
/**
* Apply a single critical hit. The following private member of Server are
* accessed from this function, preventing it from being factored out of the
* Server class: destroyEntity() destroyLocation() checkEngineExplosion()
* damageCrew() explodeEquipment() game
*
* @param en
* the <code>Entity</code> that is being damaged. This value
* may not be <code>null</code>.
* @param loc
* the <code>int</code> location of critical hit. This value
* may be <code>Entity.NONE</code> for hits to
* <code>Tank</code>s and for hits to a <code>Protomech</code>
* torso weapon.
* @param cs
* the <code>CriticalSlot</code> being damaged. This value may
* not be <code>null</code>. For critical hits on a
* <code>Tank</code>, the index of the slot should be the
* index of the critical hit table.
* @param secondaryEffects
* the <code>boolean</code> flag that indicates whether to
* allow critical hits to cause secondary effects (such as
* triggering an ammo explosion, sending hovercraft to watery
* graves, or damaging Protomech torso weapons). This value is
* normally <code>true</code>, but it will be
* <code>false</code> when the hit is being applied from a
* saved game or scenario.
*/
public Vector<Report> applyCriticalHit(Entity en, int loc, CriticalSlot cs, boolean secondaryEffects) {
return applyCriticalHit(en, loc, cs, secondaryEffects, -1, false);
}
/**
* Apply a single critical hit. The following private member of Server are
* accessed from this function, preventing it from being factored out of the
* Server class: destroyEntity() destroyLocation() checkEngineExplosion()
* damageCrew() explodeEquipment() game
*
* @param en
* the <code>Entity</code> that is being damaged. This value
* may not be <code>null</code>.
* @param loc
* the <code>int</code> location of critical hit. This value
* may be <code>Entity.NONE</code> for hits to
* <code>Tank</code>s and for hits to a <code>Protomech</code>
* torso weapon.
* @param cs
* the <code>CriticalSlot</code> being damaged. This value may
* not be <code>null</code>. For critical hits on a
* <code>Tank</code>, the index of the slot should be the
* index of the critical hit table.
* @param secondaryEffects
* the <code>boolean</code> flag that indicates whether to
* allow critical hits to cause secondary effects (such as
* triggering an ammo explosion, sending hovercraft to watery
* graves, or damaging Protomech torso weapons). This value is
* normally <code>true</code>, but it will be
* <code>false</code> when the hit is being applied from a
* saved game or scenario.
* @param damageCaused
* the amount of damage causing this critical. Necessary for
* Aeros, but may be -1 for other units.
* @param isCapital
* whether it was capital scale damage that caused critical
*/
public Vector<Report> applyCriticalHit(Entity en, int loc, CriticalSlot cs, boolean secondaryEffects, int damageCaused, boolean isCapital) {
Vector<Report> vDesc = new Vector<Report>();
Report r;
// Handle hits on "critical slots" of tanks.
if (en instanceof Tank) {
Tank t = (Tank) en;
HitData hit;
switch (cs.getIndex()) {
case Tank.CRIT_NONE:
// no effect
r = new Report(6005);
r.subject = t.getId();
vDesc.add(r);
break;
case Tank.CRIT_AMMO:
// ammo explosion
r = new Report(6610);
r.subject = t.getId();
vDesc.add(r);
int damage = 0;
for (Mounted m : t.getAmmo()) {
m.setHit(true);
int tmp = m.getShotsLeft() * ((AmmoType) m.getType()).getDamagePerShot() * ((AmmoType) m.getType()).getRackSize();
m.setShotsLeft(0);
// non-explosive ammo can't explode
if (!m.getType().isExplosive()) {
continue;
}
damage += tmp;
r = new Report(6390);
r.subject = t.getId();
r.add(m.getName());
r.add(tmp);
r.newlines = 0;
vDesc.add(r);
}
hit = new HitData(loc);
vDesc.addAll(damageEntity(t, hit, damage, true));
break;
case Tank.CRIT_CARGO:
// Cargo/infantry damage
r = new Report(6615);
r.subject = t.getId();
vDesc.add(r);
Vector<Entity> passengers = t.getLoadedUnits();
Entity target = passengers.get(Compute.randomInt(passengers.size()));
hit = target.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT);
// FIXME should be original weapon damage
vDesc.addAll(damageEntity(target, hit, 5));
break;
case Tank.CRIT_COMMANDER:
if (en.crew.getOptions().booleanOption("vdni") || en.crew.getOptions().booleanOption("bvdni")) {
r = new Report(6191);
r.subject = t.getId();
vDesc.add(r);
vDesc.addAll(damageCrew(en, 1));
} else {
if (en.crew.getOptions().booleanOption("pain_shunt") && !t.isCommanderHitPS()) {
r = new Report(6606);
r.subject = t.getId();
vDesc.add(r);
t.setCommanderHitPS(true);
} else {
r = new Report(6605);
r.subject = t.getId();
vDesc.add(r);
t.setCommanderHit(true);
}
}
// fall through here, because effects of crew stunned also apply
case Tank.CRIT_CREW_STUNNED:
if (en.crew.getOptions().booleanOption("vdni") || en.crew.getOptions().booleanOption("bvdni")) {
r = new Report(6191);
r.subject = t.getId();
vDesc.add(r);
vDesc.addAll(damageCrew(en, 1));
} else {
if (en.crew.getOptions().booleanOption("pain_shunt")
|| en.crew.getOptions().booleanOption("dermal_armor")) {
r = new Report(6186);
r.subject = t.getId();
vDesc.add(r);
} else {
t.stunCrew();
r = new Report(6185);
r.add(t.getStunnedTurns() - 1);
r.subject = t.getId();
vDesc.add(r);
}
}
break;
case Tank.CRIT_DRIVER:
if (en.crew.getOptions().booleanOption("vdni") || en.crew.getOptions().booleanOption("bvdni")) {
r = new Report(6191);
r.subject = t.getId();
vDesc.add(r);
vDesc.addAll(damageCrew(en, 1));
} else {
if (en.crew.getOptions().booleanOption("pain_shunt") && !t.isDriverHitPS()) {
r = new Report(6601);
r.subject = t.getId();
vDesc.add(r);
t.setDriverHitPS(true);
} else {
r = new Report(6600);
r.subject = t.getId();
vDesc.add(r);
t.setDriverHit(true);
}
}
break;
case Tank.CRIT_CREW_KILLED:
if (en.crew.getOptions().booleanOption("vdni") || en.crew.getOptions().booleanOption("bvdni")) {
r = new Report(6191);
r.subject = t.getId();
vDesc.add(r);
vDesc.addAll(damageCrew(en, 1));
} else {
if (en.crew.getOptions().booleanOption("pain_shunt") && !t.isCrewHitPS()) {
r = new Report(6191);
r.subject = t.getId();
vDesc.add(r);
t.setCrewHitPS(true);
} else {
r = new Report(6190);
r.subject = t.getId();
vDesc.add(r);
t.getCrew().setDoomed(true);
}
}
break;
case Tank.CRIT_ENGINE:
r = new Report(6210);
r.subject = t.getId();
vDesc.add(r);
t.engineHit();
t.engineHitsThisRound++;
boolean engineExploded = checkEngineExplosion(t, vDesc, 1);
if (engineExploded) {
vDesc.addAll(destroyEntity(en, "engine destruction", true, true));
}
if (t instanceof VTOL) {
PilotingRollData psr = t.getBasePilotingRoll();
IHex hex = game.getBoard().getHex(t.getPosition());
psr.addModifier(4, "forced landing");
int elevation = Math.max(hex.terrainLevel(Terrains.BLDG_ELEV), hex.terrainLevel(Terrains.BRIDGE_ELEV));
elevation = Math.max(elevation, 0);
elevation = Math.min(elevation, t.getElevation());
if (t.getElevation() > elevation) {
if (!hex.containsTerrain(Terrains.FUEL_TANK) && !hex.containsTerrain(Terrains.JUNGLE) && !hex.containsTerrain(Terrains.MAGMA) && !hex.containsTerrain(Terrains.MUD) && !hex.containsTerrain(Terrains.RUBBLE) && !hex.containsTerrain(Terrains.WATER) && !hex.containsTerrain(Terrains.WOODS)) {
r = new Report(2180);
r.subject = t.getId();
r.addDesc(t);
r.add(psr.getLastPlainDesc(), true);
vDesc.add(r);
// roll
final int diceRoll = Compute.d6(2);
r = new Report(2185);
r.subject = t.getId();
r.add(psr.getValueAsString());
r.add(psr.getDesc());
r.add(diceRoll);
if (diceRoll < psr.getValue()) {
r.choose(false);
vDesc.add(r);
vDesc.addAll(crashVTOLorWiGE(t));
} else {
r.choose(true);
vDesc.add(r);
t.setElevation(elevation);
}
} else {
vDesc.addAll(crashVTOLorWiGE(t));
}
}
}
break;
case Tank.CRIT_FUEL_TANK:
r = new Report(6215);
r.subject = t.getId();
vDesc.add(r);
vDesc.addAll(destroyEntity(t, "fuel explosion", false, false));
break;
case Tank.CRIT_SENSOR:
r = new Report(6620);
r.subject = t.getId();
vDesc.add(r);
t.setSensorHits(t.getSensorHits() + 1);
break;
case Tank.CRIT_STABILIZER:
r = new Report(6625);
r.subject = t.getId();
vDesc.add(r);
t.setStabiliserHit(loc);
break;
case Tank.CRIT_TURRET_DESTROYED:
r = new Report(6630);
r.subject = t.getId();
vDesc.add(r);
t.destroyLocation(Tank.LOC_TURRET);
vDesc.addAll(destroyEntity(t, "turret blown off", true, true));
break;
case Tank.CRIT_TURRET_JAM:
if (t.isTurretEverJammed()) {
r = new Report(6640);
r.subject = t.getId();
vDesc.add(r);
t.lockTurret();
break;
}
r = new Report(6635);
r.subject = t.getId();
vDesc.add(r);
t.jamTurret();
break;
case Tank.CRIT_TURRET_LOCK:
r = new Report(6640);
r.subject = t.getId();
vDesc.add(r);
t.lockTurret();
break;
case Tank.CRIT_WEAPON_DESTROYED: {
r = new Report(6305);
r.subject = t.getId();
ArrayList<Mounted> weapons = new ArrayList<Mounted>();
for (Mounted weap : t.getWeaponList()) {
if ((weap.getLocation() == loc) && !weap.isHit() && !weap.isDestroyed()) {
weapons.add(weap);
}
}
// sort weapons by BV
Collections.sort(weapons, new WeaponComparator());
int roll = Compute.d6();
Mounted weapon;
if (roll < 4) {
// defender should choose, we'll just use the lowest BV
// weapon
weapon = weapons.get(weapons.size() - 1);
} else {
// attacker chooses, we'll use the highest BV weapon
weapon = weapons.get(0);
}
weapon.setHit(true);
r.add(weapon.getName());
vDesc.add(r);
// explosive weapons e.g. gauss now explode
vDesc.addAll(explodeEquipment(t, loc, weapon));
weapon.setDestroyed(true);
break;
}
case Tank.CRIT_WEAPON_JAM: {
r = new Report(6645);
r.subject = t.getId();
ArrayList<Mounted> weapons = new ArrayList<Mounted>();
for (Mounted weap : t.getWeaponList()) {
if ((weap.getLocation() == loc) && !weap.isJammed() && !weap.isHit() && !weap.isDestroyed()) {
weapons.add(weap);
}
}
Mounted weapon = weapons.get(Compute.randomInt(weapons.size()));
weapon.setJammed(true);
t.addJammedWeapon(weapon);
r.add(weapon.getName());
vDesc.add(r);
break;
}
case VTOL.CRIT_PILOT:
r = new Report(6650);
r.subject = t.getId();
vDesc.add(r);
t.setDriverHit(true);
PilotingRollData psr = t.getBasePilotingRoll();
psr.addModifier(0, "pilot injury");
if (!doSkillCheckInPlace(t, psr)) {
r = new Report(6675);
r.subject = t.getId();
r.addDesc(t);
vDesc.add(r);
boolean crash = true;
if (t.canGoDown()) {
t.setElevation(t.getElevation() - 1);
crash = !t.canGoDown();
}
if (crash) {
vDesc.addAll(crashVTOLorWiGE(t));
}
}
break;
case VTOL.CRIT_COPILOT:
r = new Report(6655);
r.subject = t.getId();
vDesc.add(r);
t.setCommanderHit(true);
break;
case VTOL.CRIT_ROTOR_DAMAGE: {
r = new Report(6660);
r.subject = t.getId();
vDesc.add(r);
int mp = t.getOriginalWalkMP();
if (mp > 1) {
t.setOriginalWalkMP(mp - 1);
} else if (mp == 1) {
t.setOriginalWalkMP(0);
vDesc.addAll(crashVTOLorWiGE(t));
}
break;
}
case VTOL.CRIT_ROTOR_DESTROYED:
r = new Report(6670);
r.subject = t.getId();
vDesc.add(r);
t.immobilize();
t.destroyLocation(VTOL.LOC_ROTOR);
vDesc.addAll(crashVTOLorWiGE(t));
break;
case VTOL.CRIT_FLIGHT_STABILIZER:
r = new Report(6665);
r.subject = t.getId();
vDesc.add(r);
t.setStabiliserHit(VTOL.LOC_ROTOR);
break;
}
} else if (en instanceof Aero) {
Aero a = (Aero) en;
Jumpship js = new Jumpship();
if (en instanceof Jumpship) {
js = (Jumpship) en;
} else {
js = null;
}
switch (cs.getIndex()) {
case Aero.CRIT_NONE:
// no effect
r = new Report(6005);
r.subject = a.getId();
r.newlines = 0;
vDesc.add(r);
break;
case Aero.CRIT_FCS:
// Fire control system
r = new Report(9105);
r.subject = a.getId();
r.newlines = 0;
vDesc.add(r);
a.setFCSHits(a.getFCSHits() + 1);
break;
case Aero.CRIT_SENSOR:
// sensors
r = new Report(6620);
r.subject = a.getId();
r.newlines = 0;
vDesc.add(r);
a.setSensorHits(a.getSensorHits() + 1);
break;
case Aero.CRIT_AVIONICS:
// avionics
r = new Report(9110);
r.subject = a.getId();
r.newlines = 0;
vDesc.add(r);
a.setAvionicsHits(a.getAvionicsHits() + 1);
if(a.isPartOfFighterSquadron()) {
game.addControlRoll(new PilotingRollData(a.getTransportId(), 1, "avionics hit"));
} else if(a.isCapitalFighter()) {
game.addControlRoll(new PilotingRollData(a.getId(), 1, "avionics hit"));
} else {
game.addControlRoll(new PilotingRollData(a.getId(), 0, "avionics hit"));
}
break;
case Aero.CRIT_CONTROL:
// force control roll
r = new Report(9115);
r.subject = a.getId();
r.newlines = 0;
vDesc.add(r);
if(a.isPartOfFighterSquadron()) {
game.addControlRoll(new PilotingRollData(a.getTransportId(), 1, "critical hit"));
} else if(a.isCapitalFighter()) {
game.addControlRoll(new PilotingRollData(a.getId(), 1, "critical hit"));
} else {
game.addControlRoll(new PilotingRollData(a.getId(), 0, "critical hit"));
}
break;
case Aero.CRIT_FUEL_TANK:
// fuel tank
r = new Report(9120);
r.subject = a.getId();
r.newlines = 0;
int boomTarget = 9;
if(a.isLargeCraft() && a.isClan() && game.getOptions().booleanOption("stratops_harjel")) {
boomTarget = 11;
}
// check for possible explosion
int fuelroll = Compute.d6(2);
if (fuelroll > boomTarget) {
r.choose(true);
vDesc.add(r);
vDesc.addAll(destroyEntity(a, "fuel explosion", false, false));
} else {
r.choose(false);
vDesc.add(r);
}
break;
case Aero.CRIT_CREW:
// pilot hit
r = new Report(6650);
if (en.crew.getOptions().booleanOption("dermal_armor")) {
r = new Report(6651);
r.subject = a.getId();
vDesc.add(r);
break;
}
if ((a instanceof SmallCraft) || (a instanceof Jumpship)) {
r = new Report(9197);
}
if(a.isLargeCraft() && a.isClan()
&& game.getOptions().booleanOption("stratops_harjel") && (a.getIgnoredCrewHits() < 2)) {
a.setIgnoredCrewHits(a.getIgnoredCrewHits() + 1);
r = new Report(9198);
r.subject = a.getId();
r.newlines = 1;
vDesc.add(r);
break;
}
r.subject = a.getId();
r.newlines = 1;
vDesc.add(r);
vDesc.addAll(damageCrew(a, 1));
// The pilot may have just expired.
if ((a.crew.isDead() || a.crew.isDoomed()) && !a.crew.isEjected()) {
vDesc.addAll(destroyEntity(a, "pilot death", true, true));
}
break;
case Aero.CRIT_GEAR:
// landing gear
r = new Report(9125);
r.subject = a.getId();
r.newlines = 0;
vDesc.add(r);
a.setGearHit(true);
break;
case Aero.CRIT_BOMB:
// bomb destroyed
// go through bomb list and choose one
ArrayList<Mounted> bombs = new ArrayList<Mounted>();
for (Mounted bomb : a.getBombs()) {
if (!bomb.isDestroyed() && bomb.getType().isHittable() && (bomb.getShotsLeft() > 0)) {
bombs.add(bomb);
}
}
if (bombs.size() > 0) {
Mounted hitbomb = bombs.get(Compute.randomInt(bombs.size()));
hitbomb.setShotsLeft(0);
hitbomb.setDestroyed(true);
r = new Report(9130);
r.subject = a.getId();
r.newlines = 0;
r.add(hitbomb.getDesc());
vDesc.add(r);
} else {
r = new Report(9131);
r.subject = a.getId();
r.newlines = 0;
vDesc.add(r);
}
break;
case Aero.CRIT_HEATSINK:
// heat sink hit
int sinksLost = 1;
if (isCapital) {
sinksLost = 10;
}
r = new Report(9135);
r.subject = a.getId();
r.newlines = 0;
r.add(sinksLost);
vDesc.add(r);
a.setHeatSinks(Math.max(0, a.getHeatSinks() - sinksLost));
break;
case Aero.CRIT_WEAPON_BROAD:
if(a instanceof Warship) {
if((loc == Warship.LOC_ALS) || (loc == Warship.LOC_FLS)) {
loc = Warship.LOC_LBS;
} else if((loc == Warship.LOC_ARS) || (loc == Warship.LOC_FRS)) {
loc = Warship.LOC_RBS;
}
}
case Aero.CRIT_WEAPON:
if(a.isCapitalFighter()) {
boolean destroyAll = false;
if((loc == Aero.LOC_NOSE) || (loc == Aero.LOC_AFT)) {
destroyAll = true;
}
if(loc == Aero.LOC_WINGS) {
if(a.areWingsHit()) {
destroyAll = true;
} else {
a.setWingsHit(true);
}
}
for (Mounted weap : a.getWeaponList()) {
if (weap.getLocation() == loc) {
if(destroyAll) {
weap.setHit(true);
weap.setDestroyed(true);
} else {
weap.setNWeapons(weap.getNWeapons() / 2);
}
}
}
//also destroy any ECM or BAP in this location
for(Mounted misc : a.getMisc()) {
if(misc.getType().hasFlag(MiscType.F_ECM) || misc.getType().hasFlag(MiscType.F_ANGEL_ECM)
|| misc.getType().hasFlag(MiscType.F_BAP)) {
misc.setHit(true);
misc.setDestroyed(true);
}
}
r = new Report(9152);
r.subject = a.getId();
r.newlines = 0;
r.add(a.getLocationName(loc));
vDesc.add(r);
break;
}
r = new Report(9150);
r.subject = a.getId();
r.newlines = 0;
ArrayList<Mounted> weapons = new ArrayList<Mounted>();
for (Mounted weap : a.getWeaponList()) {
if ((weap.getLocation() == loc) && !weap.isDestroyed() && weap.getType().isHittable()) {
weapons.add(weap);
}
}
//add in in hittable misc equipment
for(Mounted misc : a.getMisc()) {
if (misc.getType().isHittable() && (misc.getLocation() == loc) && !misc.isDestroyed()) {
weapons.add(misc);
}
}
if (weapons.size() > 0) {
Mounted weapon = weapons.get(Compute.randomInt(weapons.size()));
// possibly check for an ammo explosion
// don't allow ammo explosions on fighter squadrons
if (game.getOptions().booleanOption("ammo_explosions") && !(a instanceof FighterSquadron) && (weapon.getType() instanceof WeaponType)) {
// does it use Ammo?
WeaponType wtype = (WeaponType) weapon.getType();
if (wtype.getAmmoType() != AmmoType.T_NA) {
Mounted m = weapon.getLinked();
int ammoroll = Compute.d6(2);
if (ammoroll >= 10) {
r = new Report(9151);
r.subject = a.getId();
r.add(m.getName());
r.newlines = 0;
vDesc.add(r);
vDesc.addAll(explodeEquipment(a, loc, m));
break;
}
}
}
weapon.setHit(true);
r.add(weapon.getName());
vDesc.add(r);
// explosive weapons e.g. gauss now explode
vDesc.addAll(explodeEquipment(a, loc, weapon));
weapon.setDestroyed(true);
} else {
r = new Report(9155);
r.subject = a.getId();
r.newlines = 0;
vDesc.add(r);
}
break;
case Aero.CRIT_ENGINE:
// engine hit
r = new Report(9140);
r.subject = a.getId();
r.newlines = 0;
vDesc.add(r);
a.engineHitsThisRound++;
boolean engineExploded = checkEngineExplosion(a, vDesc, 1);
if (((a.getEngineHits() + 1) < a.getMaxEngineHits()) && !engineExploded) {
a.setEngineHits(a.getEngineHits() + 1);
if ((a instanceof SmallCraft) || (a instanceof Jumpship)) {
a.setOriginalWalkMP(Math.max(0, a.getOriginalWalkMP() - 1));
} else {
a.setOriginalWalkMP(Math.max(0, a.getOriginalWalkMP() - 2));
}
} else {
// this engine hit puts the ASF out of commission
vDesc.addAll(destroyEntity(a, "engine destruction", true, true));
}
break;
case Aero.CRIT_LEFT_THRUSTER:
// thruster hit
r = new Report(9160);
r.subject = a.getId();
r.newlines = 0;
vDesc.add(r);
a.setLeftThrustHits(a.getLeftThrustHits() + 1);
break;
case Aero.CRIT_RIGHT_THRUSTER:
// thruster hit
r = new Report(9160);
r.subject = a.getId();
r.newlines = 0;
vDesc.add(r);
a.setRightThrustHits(a.getRightThrustHits() + 1);
break;
case Aero.CRIT_CARGO:
// cargo hit
// First what percentage of the cargo did the hit destroy?
double percentDestroyed = 0.0;
double mult = 2.0;
if(a.isLargeCraft() && a.isClan() && game.getOptions().booleanOption("stratops_harjel")) {
mult = 4.0;
}
if (damageCaused > 0) {
percentDestroyed = Math.min(damageCaused / (mult * a.getSI()), 1.0);
}
// did it hit cargo or units
int roll = Compute.d6(1);
if (roll < 4) {
// cargo was hit
// just report; no game effect
r = new Report(9165);
r.subject = a.getId();
r.newlines = 0;
r.add((int) (percentDestroyed * 100));
vDesc.add(r);
} else {
// units were hit
// get a list of units
Vector<Entity> passengers = en.getBayLoadedUnits();
int unitsDestroyed = (int) Math.ceil(percentDestroyed * passengers.size());
r = new Report(9166);
r.subject = a.getId();
r.newlines = 0;
r.add(unitsDestroyed);
vDesc.add(r);
while (unitsDestroyed > 0) {
// redraw loaded units to make sure I don't get ones
// already destroyed
Vector<Entity> units = en.getLoadedUnits();
if (units.size() > 0) {
Entity target = units.get(Compute.randomInt(units.size()));
vDesc.addAll(destroyEntity(target, "cargo damage", false, false));
}
unitsDestroyed--;
}
}
break;
case Aero.CRIT_DOOR:
// door hit
// choose a random bay
String bayType = en.damageBayDoor();
if (!bayType.equals("none")) {
r = new Report(9170);
r.subject = a.getId();
r.add(bayType);
r.newlines = 0;
vDesc.add(r);
} else {
r = new Report(9171);
r.subject = a.getId();
r.newlines = 0;
vDesc.add(r);
}
break;
case Aero.CRIT_DOCK_COLLAR:
// docking collar hit
// different effect for dropships and jumpships
if (en instanceof Dropship) {
Dropship ds = (Dropship) en;
ds.setDamageDockCollar(true);
r = new Report(9175);
r.subject = a.getId();
r.newlines = 0;
vDesc.add(r);
}
if (en instanceof Jumpship) {
// damage the docking collar
if (en.damageDockCollar()) {
r = new Report(9176);
r.subject = a.getId();
r.newlines = 0;
vDesc.add(r);
} else {
r = new Report(9177);
r.subject = a.getId();
r.newlines = 0;
vDesc.add(r);
}
}
break;
case Aero.CRIT_KF_BOOM:
// KF boom hit
// no real effect yet
r = new Report(9180);
r.subject = a.getId();
r.newlines = 0;
vDesc.add(r);
break;
case Aero.CRIT_CIC:
if (js == null) {
break;
}
// CIC hit
r = new Report(9185);
r.subject = a.getId();
r.newlines = 0;
vDesc.add(r);
js.setCICHits(js.getCICHits() + 1);
break;
case Aero.CRIT_KF_DRIVE:
if (js == null) {
break;
}
// KF Drive hit
r = new Report(9190);
r.subject = a.getId();
r.newlines = 0;
vDesc.add(r);
js.setKFIntegrity(js.getKFIntegrity() - 1);
break;
case Aero.CRIT_GRAV_DECK:
if (js == null) {
break;
}
// Grave Deck hit
r = new Report(9195);
r.subject = a.getId();
r.newlines = 0;
vDesc.add(r);
break;
case Aero.CRIT_LIFE_SUPPORT:
// Life Support hit
a.setLifeSupport(false);
r = new Report(9196);
r.subject = a.getId();
r.newlines = 0;
vDesc.add(r);
break;
}
} else if (en instanceof BattleArmor) {
// We might as well handle this here.
// However, we're considering a crit against BA as a "crew kill".
BattleArmor ba = (BattleArmor) en;
r = new Report(6111);
int randomTrooper = ba.getRandomTrooper();
ba.destroyLocation(randomTrooper);
r.add(randomTrooper);
r.newlines = 1;
vDesc.add(r);
} else if (CriticalSlot.TYPE_SYSTEM == cs.getType()) {
// Handle critical hits on system slots.
cs.setHit(true);
if (en instanceof Protomech) {
int numHit = ((Protomech) en).getCritsHit(loc);
if ((cs.getIndex() != Protomech.SYSTEM_TORSO_WEAPON_A) && (cs.getIndex() != Protomech.SYSTEM_TORSO_WEAPON_B)) {
r = new Report(6225);
r.subject = en.getId();
r.indent(3);
r.newlines = 0;
r.add(Protomech.systemNames[cs.getIndex()]);
vDesc.addElement(r);
}
switch (cs.getIndex()) {
case Protomech.SYSTEM_HEADCRIT:
if (2 == numHit) {
r = new Report(6230);
r.subject = en.getId();
r.newlines = 0;
vDesc.addElement(r);
en.destroyLocation(loc);
}
break;
case Protomech.SYSTEM_ARMCRIT:
if (2 == numHit) {
r = new Report(6235);
r.subject = en.getId();
r.newlines = 0;
vDesc.addElement(r);
en.destroyLocation(loc);
}
break;
case Protomech.SYSTEM_LEGCRIT:
if (3 == numHit) {
r = new Report(6240);
r.subject = en.getId();
r.newlines = 0;
vDesc.addElement(r);
en.destroyLocation(loc);
}
break;
case Protomech.SYSTEM_TORSOCRIT:
if (3 == numHit) {
vDesc.addAll(destroyEntity(en, "torso destruction"));
}
// Torso weapon hits are secondary effects and
// do not occur when loading from a scenario.
else if (secondaryEffects) {
int tweapRoll = Compute.d6(1);
CriticalSlot newSlot = null;
switch (tweapRoll) {
case 1:
case 2:
newSlot = new CriticalSlot(CriticalSlot.TYPE_SYSTEM, Protomech.SYSTEM_TORSO_WEAPON_A);
vDesc.addAll(applyCriticalHit(en, Entity.NONE, newSlot, secondaryEffects));
break;
case 3:
case 4:
newSlot = new CriticalSlot(CriticalSlot.TYPE_SYSTEM, Protomech.SYSTEM_TORSO_WEAPON_B);
vDesc.addAll(applyCriticalHit(en, Entity.NONE, newSlot, secondaryEffects));
break;
case 5:
case 6:
// no effect
}
}
break;
case Protomech.SYSTEM_TORSO_WEAPON_A:
Mounted weaponA = ((Protomech) en).getTorsoWeapon(true);
if (null != weaponA) {
weaponA.setHit(true);
r = new Report(6245);
r.subject = en.getId();
r.newlines = 0;
vDesc.addElement(r);
}
break;
case Protomech.SYSTEM_TORSO_WEAPON_B:
Mounted weaponB = ((Protomech) en).getTorsoWeapon(false);
if (null != weaponB) {
weaponB.setHit(true);
r = new Report(6250);
r.subject = en.getId();
r.newlines = 0;
vDesc.addElement(r);
}
break;
} // End switch( cs.getType() )
// Shaded hits cause pilot damage.
if (((Protomech) en).shaded(loc, numHit)) {
// Destroyed Protomech sections have
// already damaged the pilot.
int pHits = Protomech.POSSIBLE_PILOT_DAMAGE[loc] - ((Protomech) en).getPilotDamageTaken(loc);
if (Math.min(1, pHits) > 0) {
Report.addNewline(vDesc);
vDesc.addAll(damageCrew(en, 1));
pHits = 1 + ((Protomech) en).getPilotDamageTaken(loc);
((Protomech) en).setPilotDamageTaken(loc, pHits);
}
} // End have-shaded-hit
} // End entity-is-protomech
else {
r = new Report(6225);
r.subject = en.getId();
r.indent(3);
r.add(((Mech) en).getSystemName(cs.getIndex()));
r.newlines = 0;
vDesc.addElement(r);
switch (cs.getIndex()) {
case Mech.SYSTEM_COCKPIT:
// Don't kill a pilot multiple times.
if (Pilot.DEATH > en.getCrew().getHits()) {
// boink!
en.getCrew().setDoomed(true);
Report.addNewline(vDesc);
vDesc.addAll(destroyEntity(en, "pilot death", true));
}
break;
case Mech.SYSTEM_ENGINE:
// if the slot is missing, the location was previously
// destroyedd and the enginehit was then counted already
if (!cs.isMissing()) {
en.engineHitsThisRound++;
}
boolean engineExploded = false;
int numEngineHits = 0;
numEngineHits += en.getHitCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_ENGINE, Mech.LOC_CT);
numEngineHits += en.getHitCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_ENGINE, Mech.LOC_RT);
numEngineHits += en.getHitCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_ENGINE, Mech.LOC_LT);
engineExploded = checkEngineExplosion(en, vDesc, numEngineHits);
if (!engineExploded && (numEngineHits > 2)) {
// third engine hit
vDesc.addAll(destroyEntity(en, "engine destruction"));
if (game.getOptions().booleanOption("auto_abandon_unit")) {
vDesc.addAll(abandonEntity(en));
}
}
break;
case Mech.SYSTEM_GYRO:
if (en.getGyroType() != Mech.GYRO_HEAVY_DUTY) {
int gyroHits = en.getHitCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_GYRO, loc);
// check for < 3 to make sure only one PSR gets
// scheduled,
// otherwise the avoid pilot damage to-hit is too high
// pilot value at 3 so we end up at a total of +6
if ((gyroHits > 1) && (gyroHits < 3)) {
// gyro destroyed
game.addPSR(new PilotingRollData(en.getId(), TargetRoll.AUTOMATIC_FAIL, 3, "gyro destroyed"));
} else {
// first gyro hit
game.addPSR(new PilotingRollData(en.getId(), 3, "gyro hit"));
}// No check against HD Gyros.
} else {
int gyroHits = en.getHitCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_GYRO, loc);
// check for < 4 to make sure only one PSR gets
// scheduled,
// otherwise the avoid pilot damage to-hit is too high
// pilot value at 3 so we end up at a total of +6
if ((gyroHits > 2) && (gyroHits < 4)) {
// gyro destroyed
game.addPSR(new PilotingRollData(en.getId(), TargetRoll.AUTOMATIC_FAIL, 1, "gyro destroyed"));
} else if (gyroHits == 1) {
// first gyro hit
game.addPSR(new PilotingRollData(en.getId(), 2, "gyro hit"));
} else {
// second gyro hit
game.addPSR(new PilotingRollData(en.getId(), 3, "gyro hit"));
}
}
break;
case Mech.ACTUATOR_UPPER_LEG:
case Mech.ACTUATOR_LOWER_LEG:
case Mech.ACTUATOR_FOOT:
// leg/foot actuator piloting roll
game.addPSR(new PilotingRollData(en.getId(), 1, "leg/foot actuator hit"));
break;
case Mech.ACTUATOR_HIP:
// hip piloting roll
game.addPSR(new PilotingRollData(en.getId(), 2, "hip actuator hit"));
break;
}
} // End entity-is-mek
} // End crit-on-system-slot
// Handle critical hits on equipment slots.
else if (CriticalSlot.TYPE_EQUIPMENT == cs.getType()) {
cs.setHit(true);
Mounted mounted = en.getEquipment(cs.getIndex());
EquipmentType eqType = mounted.getType();
boolean hitBefore = mounted.isHit();
r = new Report(6225);
r.subject = en.getId();
r.indent(3);
r.add(mounted.getDesc());
r.newlines = 0;
vDesc.addElement(r);
// Shield objects are not useless when they take one crit.
// Shields can be critted and still be usable.
if ((eqType instanceof MiscType) && ((MiscType) eqType).isShield()) {
mounted.setHit(false);
} else {
mounted.setHit(true);
}
if ((eqType instanceof MiscType) && eqType.hasFlag(MiscType.F_HARJEL)) {
r = new Report(6254);
r.subject = en.getId();
r.indent(2);
vDesc.add(r);
vDesc.addAll(breachLocation(en, loc, null, true));
}
// If the item is the ECM suite of a Mek Stealth system
// then it's destruction turns off the stealth.
if (!hitBefore && (eqType instanceof MiscType) && eqType.hasFlag(MiscType.F_ECM) && (mounted.getLinkedBy() != null)) {
Mounted stealth = mounted.getLinkedBy();
r = new Report(6255);
r.subject = en.getId();
r.indent(2);
r.add(stealth.getType().getName());
r.newlines = 0;
vDesc.addElement(r);
stealth.setMode("Off");
}
// Handle equipment explosions.
// Equipment explosions are secondary effects and
// do not occur when loading from a scenario.
if (((secondaryEffects && eqType.isExplosive()) || mounted.isHotLoaded() || mounted.hasChargedCapacitor()) && !hitBefore) {
vDesc.addAll(explodeEquipment(en, loc, mounted));
}
// Make sure that ammo in this slot is exhaused.
if (mounted.getShotsLeft() > 0) {
mounted.setShotsLeft(0);
}
} // End crit-on-equipment-slot
// mechs with TSM hit by anti-tsm missiles this round get another crit
if ((en instanceof Mech) && en.hitThisRoundByAntiTSM) {
Mech mech = (Mech) en;
if (mech.hasTSM()) {
r = new Report(6430);
r.subject = en.getId();
r.indent(2);
r.addDesc(en);
r.newlines = 0;
vDesc.addElement(r);
vDesc.addAll(oneCriticalEntity(en, Compute.d6(2)));
}
en.hitThisRoundByAntiTSM = false;
}
// if using buffered VDNI then a possible pilot hit
if (en.crew.getOptions().booleanOption("bvdni") && !en.crew.getOptions().booleanOption("pain_shunt")) {
Report.addNewline(vDesc);
int roll = Compute.d6(2);
r = new Report(3580);
r.subject = en.getId();
r.addDesc(en);
r.add(7);
r.add(roll);
r.choose(roll >= 8);
r.indent(2);
vDesc.add(r);
if (roll >= 7) {
vDesc.addAll(damageCrew(en, 1));
}
}
// Return the results of the damage.
return vDesc;
}
/**
* Rolls and resolves critical hits with a die roll modifier.
*/
public Vector<Report> criticalEntity(Entity en, int loc, int critMod) {
return criticalEntity(en, loc, critMod, true, false);
}
/**
* Rolls one critical hit
*/
private Vector<Report> oneCriticalEntity(Entity en, int loc) {
return criticalEntity(en, loc, 0, false, false);
}
/**
* Crash a VTOL
*
* @param en
* the <code>VTOL</code> to be crashed
* @return the <code>Vector<Report></code> containg phasereports
*/
private Vector<Report> crashVTOLorWiGE(Tank en) {
return crashVTOLorWiGE(en, false, 0, en.getPosition(), en.getElevation(), 0);
}
/**
* Crash a VTOL
*
* @param en
* The <code>VTOL</code> to crash
* @param sideSlipCrash
* A <code>boolean</code> value indicating wether this is a
* sideslip crash or not.
* @param hexesMoved
* The <code>int</code> number of hexes moved.
* @param crashPos
* The <code>Coords</code> of the crash
* @param crashElevation
* The <code>int</code> elevation of the VTOL
* @param impactSide
* The <code>int</code> describing the side on which the VTOL
* falls
* @return a <code>Vector<Report></code> of Reports.
*/
private Vector<Report> crashVTOLorWiGE(Tank en, boolean sideSlipCrash, int hexesMoved, Coords crashPos, int crashElevation, int impactSide) {
Vector<Report> vDesc = new Vector<Report>();
Report r;
// we might be off the board after a DFA, so return then
if (!game.getBoard().contains(crashPos)) {
return vDesc;
}
if (!sideSlipCrash) {
// report lost movement and crashing
r = new Report(6260);
r.subject = en.getId();
r.newlines = 0;
r.addDesc(en);
vDesc.addElement(r);
int newElevation = 0;
IHex fallHex = game.getBoard().getHex(crashPos);
// May land on roof of building or bridge
if (fallHex.containsTerrain(Terrains.BLDG_ELEV)) {
newElevation = fallHex.terrainLevel(Terrains.BLDG_ELEV);
} else if (fallHex.containsTerrain(Terrains.BRIDGE_ELEV)) {
newElevation = fallHex.terrainLevel(Terrains.BRIDGE_ELEV);
if (newElevation > crashElevation) {
newElevation = 0; // vtol was under bridge already
}
}
int fall = crashElevation - newElevation;
if (fall == 0) {
// already on ground, no harm done
r = new Report(6265);
r.subject = en.getId();
vDesc.addElement(r);
return vDesc;
}
// set elevation 1st to avoid multiple crashes
en.setElevation(newElevation);
// plummets to ground
r = new Report(6270);
r.subject = en.getId();
r.add(fall);
vDesc.addElement(r);
// facing after fall
String side;
int table;
int facing = Compute.d6();
switch (facing) {
case 1:
case 2:
side = "right side";
table = ToHitData.SIDE_RIGHT;
break;
case 3:
side = "rear";
table = ToHitData.SIDE_REAR;
break;
case 4:
case 5:
side = "left side";
table = ToHitData.SIDE_LEFT;
break;
case 0:
default:
side = "front";
table = ToHitData.SIDE_FRONT;
}
if (newElevation <= 0) {
boolean waterFall = fallHex.containsTerrain(Terrains.WATER);
if (waterFall && fallHex.containsTerrain(Terrains.ICE)) {
int roll = Compute.d6(1);
r = new Report(2119);
r.subject = en.getId();
r.addDesc(en);
r.add(roll);
r.subject = en.getId();
vDesc.add(r);
if (roll > 3) {
vDesc.addAll(resolveIceBroken(crashPos));
} else {
waterFall = false; // saved by ice
}
}
if (waterFall) {
// falls into water and is destroyed
r = new Report(6275);
r.subject = en.getId();
vDesc.addElement(r);
vDesc.addAll(destroyEntity(en, "Fell into water", false, false));
// not sure, is this salvagable?
}
}
// calculate damage for hitting the surface
int damage = (int) Math.round(en.getWeight() / 10.0) * (fall + 1);
// adjust damage for gravity
damage = Math.round(damage * game.getPlanetaryConditions().getGravity());
// report falling
r = new Report(6280);
r.subject = en.getId();
r.indent();
r.addDesc(en);
r.add(side);
r.add(damage);
r.newlines = 0;
vDesc.addElement(r);
en.setFacing((en.getFacing() + (facing - 1)) % 6);
boolean exploded = false;
// standard damage loop
while (damage > 0) {
int cluster = Math.min(5, damage);
HitData hit = en.rollHitLocation(ToHitData.HIT_NORMAL, table);
hit.setGeneralDamageType(HitData.DAMAGE_PHYSICAL);
int ISBefore[] = { en.getInternal(Tank.LOC_FRONT), en.getInternal(Tank.LOC_RIGHT), en.getInternal(Tank.LOC_LEFT), en.getInternal(Tank.LOC_REAR) };// hack?
vDesc.addAll(damageEntity(en, hit, cluster));
int ISAfter[] = { en.getInternal(Tank.LOC_FRONT), en.getInternal(Tank.LOC_RIGHT), en.getInternal(Tank.LOC_LEFT), en.getInternal(Tank.LOC_REAR) };
for (int x = 0; x <= 3; x++) {
if (ISBefore[x] != ISAfter[x]) {
exploded = true;
}
}
damage -= cluster;
}
if (exploded) {
r = new Report(6285);
r.subject = en.getId();
r.addDesc(en);
vDesc.addElement(r);
vDesc.addAll(explodeVTOLorWiGE(en));
}
// check for location exposure
vDesc.addAll(doSetLocationsExposure(en, fallHex, false, newElevation));
} else {
en.setElevation(0);// considered landed in the hex.
// crashes into ground thanks to sideslip
r = new Report(6290);
r.subject = en.getId();
r.addDesc(en);
vDesc.addElement(r);
int damage = (int) Math.round(en.getWeight() / 10.0) * (hexesMoved + 1);
boolean exploded = false;
// standard damage loop
while (damage > 0) {
int cluster = Math.min(5, damage);
HitData hit = en.rollHitLocation(ToHitData.HIT_NORMAL, impactSide);
hit.setGeneralDamageType(HitData.DAMAGE_PHYSICAL);
int ISBefore[] = { en.getInternal(Tank.LOC_FRONT), en.getInternal(Tank.LOC_RIGHT), en.getInternal(Tank.LOC_LEFT), en.getInternal(Tank.LOC_REAR) };// hack?
vDesc.addAll(damageEntity(en, hit, cluster));
int ISAfter[] = { en.getInternal(Tank.LOC_FRONT), en.getInternal(Tank.LOC_RIGHT), en.getInternal(Tank.LOC_LEFT), en.getInternal(Tank.LOC_REAR) };
for (int x = 0; x <= 3; x++) {
if (ISBefore[x] != ISAfter[x]) {
exploded = true;
}
}
damage -= cluster;
}
if (exploded) {
r = new Report(6295);
r.subject = en.getId();
r.addDesc(en);
vDesc.addElement(r);
vDesc.addAll(explodeVTOLorWiGE(en));
}
}
if(game.containsMinefield(crashPos)) {
//may set off any minefields in the hex
enterMinefield(en, crashPos, 0, true, vDesc, 7);
//it may also clear any minefields that it detonated
clearDetonatedMines(crashPos, 5);
resetMines();
}
return vDesc;
}
/**
* Explode a VTOL
*
* @param en
* The <code>VTOL</code> to explode.
* @return a <code>Vector</code> of reports
*/
private Vector<Report> explodeVTOLorWiGE(Tank en) {
Vector<Report> vDesc = new Vector<Report>();
Report r;
if (en.getEngine().isFusion()) {
// fusion engine, no effect
r = new Report(6300);
r.subject = en.getId();
vDesc.addElement(r);
} else {
Coords pos = en.getPosition();
IHex hex = game.getBoard().getHex(pos);
if (hex.containsTerrain(Terrains.WOODS) || hex.containsTerrain(Terrains.JUNGLE)) {
ignite(pos, false, vDesc);
} else {
ignite(pos, true, vDesc);
}
vDesc.addAll(destroyEntity(en, "crashed and burned", false, false));
}
return vDesc;
}
/**
* rolls and resolves one tank critical hit
*
* @param t
* the <code>Tank</code> to be critted
* @param loc
* the <code>int</code> location of the Tank to be critted
* @param critMod
* the <code>int</code> modifier to the critroll
* @return a <code>Vector<Report></code> containing the phasereports
*/
private Vector<Report> criticalTank(Tank t, int loc, int critMod) {
Vector<Report> vDesc = new Vector<Report>();
Report r;
// roll the critical
r = new Report(6305);
r.subject = t.getId();
r.indent(2);
r.add(t.getLocationAbbr(loc));
r.newlines = 0;
vDesc.add(r);
int roll = Compute.d6(2);
r = new Report(6310);
r.subject = t.getId();
String rollString = "";
if (critMod != 0) {
rollString = "(" + roll;
if (critMod > 0) {
rollString += "+";
}
rollString += critMod + ") = ";
roll += critMod;
}
rollString += roll;
r.add(rollString);
r.newlines = 0;
vDesc.add(r);
// now look up on vehicle crits table
int critType = t.getCriticalEffect(roll, loc);
vDesc.addAll(applyCriticalHit(t, loc, new CriticalSlot(0, critType), true));
return vDesc;
}
/**
* Checks for aero criticals
* @param a - the entity being critted
* @param hit - the hitdata for the attack
* @param damage_orig - the original damage of the attack
* @param critThresh - did the attack go over the damage threshold
* @param critSI - did the attack damage SI
* @param ammoExplosion - was the damage from an ammo explosion
* @param nukeS2S - was this a ship 2 ship nuke attack
* @return
*/
private void checkAeroCrits(Vector<Report> vDesc, Aero a, HitData hit, int damage_orig,
boolean critThresh, boolean critSI, boolean ammoExplosion, boolean nukeS2S) {
Report r;
boolean isCapital = hit.isCapital();
// get any capital missile critical mods
int CapitalMissile = hit.getCapMisCritMod();
//check for nuclear critical
if(nukeS2S) {
//add a control roll
PilotingRollData nukePSR = new PilotingRollData( a.getId(), 4, "Nuclear attack", false);
game.addControlRoll(nukePSR);
//need some kind of report
int nukeroll = Compute.d6(2);
r = new Report(9145);
r.subject = a.getId();
r.newlines = 0;
r.indent(3);
r.add(CapitalMissile);
r.add(nukeroll);
vDesc.add(r);
if(nukeroll >= CapitalMissile) {
int nukeDamage = damage_orig;
a.setSI(a.getSI()-nukeDamage);
a.damageThisPhase += nukeDamage;
r = new Report(9146);
r.subject = a.getId();
r.newlines = 0;
r.add(nukeDamage);
r.add(Math.max(a.getSI(),0));
vDesc.addElement(r);
if(a.getSI() <= 0) {
vDesc.addAll( destroyEntity(a,"structural integrity collapse"));
a.setSI(0);
} else if(!critSI) {
critSI = true;
}
} else {
r = new Report(9147);
r.subject = a.getId();
r.newlines = 0;
vDesc.addElement(r);
}
}
// apply crits
if (hit.rolledBoxCars()) {
Report.addNewline(vDesc);
vDesc.addAll(criticalAero(a, hit.getLocation(), hit.glancingMod(), "12 to hit", 8, damage_orig, isCapital));
}
// ammo explosions shouldn't affect threshold because they
// go right to SI
if (critThresh && !ammoExplosion) {
Report.addNewline(vDesc);
vDesc.addAll(criticalAero(a, hit.getLocation(), hit.glancingMod(), "Damage threshold exceeded", 8, damage_orig, isCapital));
}
if (critSI && !ammoExplosion) {
Report.addNewline(vDesc);
vDesc.addAll(criticalAero(a, hit.getLocation(), hit.glancingMod(), "SI damaged", 8, damage_orig, isCapital));
}
if ((CapitalMissile > 0) && !nukeS2S) {
Report.addNewline(vDesc);
vDesc.addAll(criticalAero(a, hit.getLocation(), hit.glancingMod(), "Capital Missile", CapitalMissile, damage_orig, isCapital));
}
}
private Vector<Report> criticalAero(Aero a, int loc, int critMod, String reason, int target, int damage, boolean isCapital) {
Vector<Report> vDesc = new Vector<Report>();
Report r;
// roll the critical
r = new Report(9100);
r.subject = a.getId();
r.add(reason);
r.indent(3);
r.newlines = 0;
vDesc.add(r);
int roll = Compute.d6(2);
r = new Report(9101);
r.subject = a.getId();
r.add(target);
String rollString = "";
if (critMod != 0) {
rollString = "(" + roll;
if (critMod > 0) {
rollString += "+";
}
rollString += critMod + ") = ";
roll += critMod;
}
rollString += roll;
r.add(rollString);
r.newlines = 0;
vDesc.add(r);
// now look up on vehicle crits table
int critType = a.getCriticalEffect(roll, target);
vDesc.addAll(applyCriticalHit(a, loc, new CriticalSlot(0, critType), true, damage, isCapital));
return vDesc;
}
/**
* Rolls and resolves critical hits on mechs or vehicles. if rollNumber is
* false, a single hit is applied - needed for MaxTech Heat Scale rule.
*/
public Vector<Report> criticalEntity(Entity en, int loc, int critMod, boolean rollNumber, boolean isCapital) {
if (en instanceof Tank) {
return criticalTank((Tank) en, loc, critMod);
}
if (en instanceof Aero) {
return criticalAero((Aero) en, loc, critMod, "unknown", 8, -1, isCapital);
}
CriticalSlot slot = null;
Vector<Report> vDesc = new Vector<Report>();
Report r;
Coords coords = en.getPosition();
IHex hex = null;
int hits;
if (rollNumber) {
if (null != coords) {
hex = game.getBoard().getHex(coords);
}
r = new Report(6305);
r.subject = en.getId();
r.indent(2);
r.add(en.getLocationAbbr(loc));
r.newlines = 0;
vDesc.addElement(r);
hits = 0;
int roll = Compute.d6(2);
r = new Report(6310);
r.subject = en.getId();
String rollString = "";
// industrials get a +2 bonus on the roll
if ((en instanceof Mech) && ((Mech)en).isIndustrial()) {
critMod += 2;
}
if (critMod != 0) {
rollString = "(" + roll;
if (critMod > 0) {
rollString += "+";
}
rollString += critMod + ") = ";
roll += critMod;
}
rollString += roll;
r.add(rollString);
r.newlines = 0;
vDesc.addElement(r);
if (roll <= 7) {
// no effect
r = new Report(6005);
r.subject = en.getId();
r.newlines = 0;
vDesc.addElement(r);
return vDesc;
} else if ((roll >= 8) && (roll <= 9)) {
hits = 1;
r = new Report(6315);
r.subject = en.getId();
r.newlines = 0;
vDesc.addElement(r);
} else if ((roll >= 10) && (roll <= 11)) {
hits = 2;
r = new Report(6320);
r.subject = en.getId();
r.newlines = 0;
vDesc.addElement(r);
} else if (roll >= 12) {
if (en instanceof Protomech) {
hits = 3;
r = new Report(6325);
r.subject = en.getId();
r.newlines = 0;
vDesc.addElement(r);
} else if (en.locationIsLeg(loc)) {
CriticalSlot cs = en.getCritical(loc, 0);
if ((cs != null) && cs.isArmored()) {
r = new Report(6700);
r.subject = en.getId();
r.add(en.getLocationName(loc));
r.newlines = 0;
vDesc.addElement(r);
cs.setArmored(false);
return vDesc;
}
// limb blown off
r = new Report(6120);
r.subject = en.getId();
r.add(en.getLocationName(loc));
r.newlines = 0;
vDesc.addElement(r);
if (en.getInternal(loc) > 0) {
en.destroyLocation(loc);
}
if (null != hex) {
if (!hex.containsTerrain(Terrains.LEGS)) {
hex.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.LEGS, 1));
} else {
hex.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.LEGS, hex.terrainLevel(Terrains.LEGS) + 1));
}
}
sendChangedHex(en.getPosition());
return vDesc;
} else if ((loc == Mech.LOC_RARM) || (loc == Mech.LOC_LARM)) {
CriticalSlot cs = en.getCritical(loc, 0);
if ((cs != null) && cs.isArmored()) {
r = new Report(6700);
r.subject = en.getId();
r.add(en.getLocationName(loc));
r.newlines = 0;
vDesc.addElement(r);
cs.setArmored(false);
return vDesc;
}
// limb blown off
r = new Report(6120);
r.subject = en.getId();
r.add(en.getLocationName(loc));
r.newlines = 0;
vDesc.addElement(r);
en.destroyLocation(loc);
if (null != hex) {
if (!hex.containsTerrain(Terrains.ARMS)) {
hex.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.ARMS, 1));
} else {
hex.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.ARMS, hex.terrainLevel(Terrains.ARMS) + 1));
}
}
sendChangedHex(en.getPosition());
return vDesc;
} else if (loc == Mech.LOC_HEAD) {
// head blown off
r = new Report(6330);
r.subject = en.getId();
r.add(en.getLocationName(loc));
r.newlines = 0;
vDesc.addElement(r);
en.destroyLocation(loc);
if (((Mech) en).getCockpitType() != Mech.COCKPIT_TORSO_MOUNTED) {
// Don't kill a pilot multiple times.
if (Pilot.DEATH > en.getCrew().getHits()) {
en.crew.setDoomed(true);
Report.addNewline(vDesc);
vDesc.addAll(destroyEntity(en, "pilot death", true));
}
}
return vDesc;
} else {
// torso hit
hits = 3;
// industrials get 4 crits on a modified result of 14
if ((roll >= 14) && (en instanceof Mech) && ((Mech)en).isIndustrial()) {
hits = 4;
}
r = new Report(6325);
r.subject = en.getId();
r.newlines = 0;
vDesc.addElement(r);
}
}
} else {
hits = 1;
}
// transfer criticals, if needed
while ((hits > 0) && en.canTransferCriticals(loc) && (en.getTransferLocation(loc) != Entity.LOC_DESTROYED) && (en.getTransferLocation(loc) != Entity.LOC_NONE)) {
loc = en.getTransferLocation(loc);
r = new Report(6335);
r.subject = en.getId();
r.indent(3);
r.add(en.getLocationAbbr(loc));
r.newlines = 0;
vDesc.addElement(r);
}
// Roll critical hits in this location.
while (hits > 0) {
// Have we hit all available slots in this location?
if (en.getHittableCriticals(loc) <= 0) {
r = new Report(6340);
r.subject = en.getId();
r.indent(3);
r.newlines = 0;
vDesc.addElement(r);
break;
}
// Randomly pick a slot to be hit.
int slotIndex = Compute.randomInt(en.getNumberOfCriticals(loc));
slot = en.getCritical(loc, slotIndex);
// Ignore empty or unhitable slots (this
// includes all previously hit slots).
if ((slot != null) && slot.isHittable()) {
if (slot.isArmored()) {
r = new Report(6710);
r.subject = en.getId();
r.add(en.getLocationName(loc));
r.newlines = 0;
vDesc.addElement(r);
slot.setArmored(false);
hits--;
continue;
}
// if explosive use edge
if ((en instanceof Mech) && (en.crew.hasEdgeRemaining() && en.crew.getOptions().booleanOption("edge_when_explosion")) && (slot.getType() == CriticalSlot.TYPE_EQUIPMENT) && en.getEquipment(slot.getIndex()).getType().isExplosive()) {
en.crew.decreaseEdge();
r = new Report(6530);
r.subject = en.getId();
r.indent(3);
r.newlines = 0;
r.add(en.crew.getOptions().intOption("edge"));
vDesc.addElement(r);
continue;
}
// check for reactive armor
if (en.getArmorType() == EquipmentType.T_ARMOR_REACTIVE) {
Mounted mount = en.getEquipment(slot.getIndex());
if ((mount != null) && (mount.getType() instanceof MiscType) && ((MiscType) mount.getType()).hasFlag(MiscType.F_REACTIVE)) {
int roll = Compute.d6(2);
r = new Report(6082);
r.subject = en.getId();
r.indent(3);
r.newlines = 0;
r.add(roll);
vDesc.addElement(r);
// big budda boom
if (roll == 2) {
r = new Report(6083);
r.subject = en.getId();
r.indent(3);
r.newlines = 0;
vDesc.addElement(r);
vDesc.addElement(r);
vDesc.addAll(damageEntity(en, new HitData(loc), en.getArmor(loc)));
if (en.hasRearArmor(loc)) {
vDesc.addAll(damageEntity(en, new HitData(loc, true), en.getArmor(loc, true)));
}
vDesc.addAll(damageEntity(en, new HitData(loc), 1));
} else {
continue;
}
}
}
vDesc.addAll(applyCriticalHit(en, loc, slot, true));
hits--;
}
} // Hit another slot in this location.
return vDesc;
}
/**
* Checks for location breach and returns phase logging. <p/> Please note
* that dependent locations ARE NOT considered breached!
*
* @param entity
* the <code>Entity</code> that needs to be checked.
* @param loc
* the <code>int</code> location on the entity that needs to be
* checked for a breach.
* @param target
* the <code>int</code> target number for the breach
* @param hex
* the <code>IHex</code> the enitity occupies when checking
* This value will be <code>null</code> if the check is the
* result of an attack, and non-null if it occurs during
* movement.
*/
private Vector<Report> breachCheck(Entity entity, int loc, IHex hex) {
return breachCheck(entity, loc, hex, false);
}
/**
* Checks for location breach and returns phase logging. <p/> Please note
* that dependent locations ARE NOT considered breached!
*
* @param entity
* the <code>Entity</code> that needs to be checked.
* @param loc
* the <code>int</code> location on the entity that needs to be
* checked for a breach.
* @param target
* the <code>int</code> target number for the breach
* @param hex
* the <code>IHex</code> the enitity occupies when checking
* This value will be <code>null</code> if the check is the
* result of an attack, and non-null if it occurs during
* movement.
* @param underWater
* Is the breach check a result of an underwater attack?
*/
private Vector<Report> breachCheck(Entity entity, int loc, IHex hex, boolean underWater) {
Vector<Report> vDesc = new Vector<Report>();
Report r;
// BattleArmor does not breach
if (entity instanceof Infantry) {
return vDesc;
}
if (entity instanceof VTOL) {
return vDesc;
}
boolean dumping = false;
for (Mounted m : entity.getAmmo()) {
if (m.isDumping()) {
// dumping ammo underwater is very stupid thing to do
dumping = true;
break;
}
}
// This handles both water and vacuum breaches.
// Also need to account for hull breaches on surface naval vessels which are technically not "wet"
if ((entity.getLocationStatus(loc) > ILocationExposureStatus.NORMAL) ||
(entity.isSurfaceNaval() && (loc != Tank.LOC_TURRET))) {
// Does the location have armor (check rear armor on Mek)
// and is the check due to damage?
int breachroll = 0;
//set the target roll for the breach
int target = 10;
//if this is a vacuum check and we are in trace atmosphere then adjust target
if((entity.getLocationStatus(loc) == ILocationExposureStatus.VACUUM) &&
(game.getPlanetaryConditions().getAtmosphere() == PlanetaryConditions.ATMO_TRACE)) {
target = 12;
}
//if this is a surface naval vessel and the attack is not from underwater
//then the breach should only occur on a roll of 12
if(entity.isSurfaceNaval() && !underWater) {
target = 12;
}
if ((entity.getArmor(loc) > 0) && (entity instanceof Mech ? entity.getArmor(loc, true) > 0 : true) && (null == hex)) {
// functional HarJel prevents breach
if ((entity instanceof Mech) && ((Mech) entity).hasHarJelIn(loc)) {
r = new Report(6342);
r.subject = entity.getId();
r.indent(3);
vDesc.addElement(r);
return vDesc;
}
breachroll = Compute.d6(2);
r = new Report(6345);
r.subject = entity.getId();
r.indent(3);
r.add(entity.getLocationAbbr(loc));
r.add(breachroll);
r.newlines = 0;
if (breachroll >= target) {
r.choose(false);
} else {
r.choose(true);
}
vDesc.addElement(r);
}
// Breach by damage or lack of armor.
if ((breachroll >= target) || !(entity.getArmor(loc) > 0) || (dumping && (!(entity instanceof Mech) || (loc == Mech.LOC_CT) || (loc == Mech.LOC_RT) || (loc == Mech.LOC_LT))) || !(entity instanceof Mech ? entity.getArmor(loc, true) > 0 : true)) {
// functional HarJel prevents breach
if ((entity instanceof Mech) && ((Mech) entity).hasHarJelIn(loc)) {
r = new Report(6342);
r.subject = entity.getId();
r.indent(3);
vDesc.addElement(r);
return vDesc;
}
vDesc.addAll(breachLocation(entity, loc, hex, false));
}
}
return vDesc;
}
/**
* Marks all equipment in a location on an entity as useless.
*
* @param entity
* the <code>Entity</code> that needs to be checked.
* @param loc
* the <code>int</code> location on the entity that needs to be
* checked for a breach.
* @param hex
* the <code>IHex</code> the enitity occupies when checking
* This value will be <code>null</code> if the check is the
* result of an attack, and non-null if it occurs during
* movement.
* @param harJel
* a <code>boolean</code> value indicating if the uselessness
* is the cause of a critically hit HarJel system
*/
private Vector<Report> breachLocation(Entity entity, int loc, IHex hex, boolean harJel) {
Vector<Report> vDesc = new Vector<Report>();
Report r;
if ((entity.getInternal(loc) < 0) || (entity.getLocationStatus(loc) < ILocationExposureStatus.NORMAL)) {
// already destroyed or breached? don't bother
return vDesc;
}
r = new Report(6350);
if (harJel) {
r.messageId = 6351;
}
r.subject = entity.getId();
r.add(entity.getShortName());
r.add(entity.getLocationAbbr(loc));
r.newlines = 0;
vDesc.addElement(r);
if (entity instanceof Tank) {
vDesc.addAll(destroyEntity(entity, "hull breach", true, true));
return vDesc;
}
// equipment and crits will be marked in applyDamage?
// equipment marked missing
for (Mounted mounted : entity.getEquipment()) {
if (mounted.getLocation() == loc) {
mounted.setBreached(true);
}
}
// all critical slots set as useless
for (int i = 0; i < entity.getNumberOfCriticals(loc); i++) {
final CriticalSlot cs = entity.getCritical(loc, i);
if (cs != null) {
// for every undamaged actuator destroyed by breaching,
// we make a PSR (see bug 1040858)
if (entity.locationIsLeg(loc)) {
if (cs.isHittable()) {
switch (cs.getIndex()) {
case Mech.ACTUATOR_UPPER_LEG:
case Mech.ACTUATOR_LOWER_LEG:
case Mech.ACTUATOR_FOOT:
// leg/foot actuator piloting roll
game.addPSR(new PilotingRollData(entity.getId(), 1, "leg/foot actuator hit"));
break;
case Mech.ACTUATOR_HIP:
// hip piloting roll (at +0, because we get the
// +2
// anyway because the location is breached
// phase report will look a bit weird, but the
// roll
// is correct
game.addPSR(new PilotingRollData(entity.getId(), 0, "hip actuator hit"));
break;
}
}
}
cs.setBreached(true);
}
}
// Check location for engine/cockpit breach and report accordingly
if (loc == Mech.LOC_CT) {
vDesc.addAll(destroyEntity(entity, "hull breach"));
if (game.getOptions().booleanOption("auto_abandon_unit")) {
vDesc.addAll(abandonEntity(entity));
}
}
if (loc == Mech.LOC_HEAD) {
entity.crew.setDoomed(true);
vDesc.addAll(destroyEntity(entity, "hull breach"));
if (entity.getLocationStatus(loc) == ILocationExposureStatus.WET) {
r = new Report(6355);
r.subject = entity.getId();
r.addDesc(entity);
vDesc.addElement(r);
} else {
r = new Report(6360);
r.subject = entity.getId();
r.addDesc(entity);
vDesc.addElement(r);
}
}
// Set the status of the location.
// N.B. if we set the status before rolling water PSRs, we get a
// "LEG DESTROYED" modifier; setting the status after gives a hip
// actuator modifier.
entity.setLocationStatus(loc, ILocationExposureStatus.BREACHED);
// Did the hull breach destroy the engine?
if (entity.getHitCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_ENGINE, Mech.LOC_LT) + entity.getHitCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_ENGINE, Mech.LOC_CT) + entity.getHitCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_ENGINE, Mech.LOC_RT) >= 3) {
vDesc.addAll(destroyEntity(entity, "engine destruction"));
if (game.getOptions().booleanOption("auto_abandon_unit")) {
vDesc.addAll(abandonEntity(entity));
}
}
return vDesc;
}
/**
* Mark the unit as destroyed! Units transported in the destroyed unit will
* get a chance to escape.
*
* @param entity -
* the <code>Entity</code> that has been destroyed.
* @param reason -
* a <code>String</code> detailing why the entity was
* destroyed.
* @return a <code>Vector</code> of <code>Report</code> objects that can
* be sent to the output log.
*/
private Vector<Report> destroyEntity(Entity entity, String reason) {
return destroyEntity(entity, reason, true);
}
/**
* Marks a unit as destroyed! Units transported inside the destroyed unit
* will get a chance to escape unless the destruction was not survivable.
*
* @param entity -
* the <code>Entity</code> that has been destroyed.
* @param reason -
* a <code>String</code> detailing why the entity was
* destroyed.
* @param survivable -
* a <code>boolean</code> that identifies the desctruction as
* unsurvivable for transported units.
* @return a <code>Vector</code> of <code>Report</code> objects that can
* be sent to the output log.
*/
private Vector<Report> destroyEntity(Entity entity, String reason, boolean survivable) {
// Generally, the entity can still be salvaged.
return destroyEntity(entity, reason, survivable, true);
}
/**
* Marks a unit as destroyed! Units transported inside the destroyed unit
* will get a chance to escape unless the destruction was not survivable.
*
* @param entity -
* the <code>Entity</code> that has been destroyed.
* @param reason -
* a <code>String</code> detailing why the entity was
* destroyed.
* @param survivable -
* a <code>boolean</code> that identifies the desctruction as
* unsurvivable for transported units.
* @param canSalvage -
* a <code>boolean</code> that indicates if the unit can be
* salvaged (or cannibalized for spare parts). If
* <code>true</code>, salvage operations are possible, if
* <code>false</code>, the unit is too badly damaged.
* @return a <code>Vector</code> of <code>Report</code> objects that can
* be sent to the output log.
*/
private Vector<Report> destroyEntity(Entity entity, String reason, boolean survivable, boolean canSalvage) {
Vector<Report> vDesc = new Vector<Report>();
Report r;
//regardless of what was passed in, units loaded onto aeros not on the ground are destroyed
if((entity instanceof Aero) && !game.getBoard().onGround()) {
survivable = false;
}
// The unit can suffer an ammo explosion after it has been destroyed.
int condition = IEntityRemovalConditions.REMOVE_SALVAGEABLE;
if (!canSalvage) {
entity.setSalvage(canSalvage);
condition = IEntityRemovalConditions.REMOVE_DEVASTATED;
}
// Destroy the entity, unless it's already destroyed.
if (!entity.isDoomed() && !entity.isDestroyed()) {
r = new Report(6365);
r.subject = entity.getId();
r.addDesc(entity);
r.add(reason);
r.newlines = 0;
vDesc.addElement(r);
entity.setDoomed(true);
// Kill any picked up MechWarriors
Enumeration<Integer> iter = entity.getPickedUpMechWarriors().elements();
while (iter.hasMoreElements()) {
int mechWarriorId = iter.nextElement();
Entity mw = game.getEntity(mechWarriorId);
mw.setDestroyed(true);
game.removeEntity(mw.getId(), condition);
entityUpdate(mw.getId());
send(createRemoveEntityPacket(mw.getId(), condition));
r = new Report(6370);
r.subject = mw.getId();
r.addDesc(mw);
vDesc.addElement(r);
}
// make any remaining tele-missiles operated by this entity
// out of contact
for (int missileId : entity.getTMTracker().getMissiles()) {
Entity tm = game.getEntity(missileId);
if ((null != tm) && !tm.isDestroyed() && (tm instanceof TeleMissile)) {
((TeleMissile) tm).setOutContact(true);
entityUpdate(tm.getId());
}
}
// Mechanized BA that could die on a 3+
ArrayList<Entity> externalUnits = entity.getExternalUnits();
// Handle escape of transported units.
Enumeration<Entity> transporter = entity.getLoadedUnits().elements();
if (transporter.hasMoreElements()) {
Entity other = null;
Coords curPos = entity.getPosition();
IHex entityHex = game.getBoard().getHex(curPos);
int curFacing = entity.getFacing();
while (transporter.hasMoreElements()) {
other = transporter.nextElement();
// Can the other unit survive?
boolean survived = false;
if (entity instanceof Tank) {
if ((entity.getMovementMode() == IEntityMovementMode.NAVAL)
|| (entity.getMovementMode() == IEntityMovementMode.HYDROFOIL)) {
if (other.getMovementMode() == IEntityMovementMode.INF_UMU) {
survived = Compute.d6() <= 3;
} else if (other.getMovementMode() == IEntityMovementMode.INF_JUMP) {
survived = Compute.d6() == 1;
} else if (other.getMovementMode() == IEntityMovementMode.VTOL) {
survived = Compute.d6() <= 2;
}
} else if (entity.getMovementMode() == IEntityMovementMode.SUBMARINE) {
if (other.getMovementMode() == IEntityMovementMode.INF_UMU) {
survived = Compute.d6() == 1;
}
} else {
survived = Compute.d6() <= 4;
}
}
if (!survivable || (externalUnits.contains(other) && !survived)) {
// Nope.
other.setDestroyed(true);
game.moveToGraveyard(other.getId());
entityUpdate(other.getId());
send(createRemoveEntityPacket(other.getId(), condition));
r = new Report(6370);
r.subject = other.getId();
r.addDesc(other);
vDesc.addElement(r);
}
// Can we unload the unit to the current hex?
// TODO : unloading into stacking violation is not
// explicitly prohibited in the BMRr.
else if ((null != Compute.stackingViolation(game, other.getId(), curPos)) || other.isHexProhibited(entityHex)) {
// Nope.
other.setDestroyed(true);
game.moveToGraveyard(other.getId());
entityUpdate(other.getId());
send(createRemoveEntityPacket(other.getId(), condition));
r = new Report(6375);
r.subject = other.getId();
r.addDesc(other);
vDesc.addElement(r);
} // End can-not-unload
else {
// The other unit survives.
unloadUnit(entity, other, curPos, curFacing, entity.getElevation());
}
} // Handle the next transported unit.
} // End has-transported-unit
// Handle transporting unit.
if (Entity.NONE != entity.getTransportId()) {
final Entity transport = game.getEntity(entity.getTransportId());
Coords curPos = transport.getPosition();
int curFacing = transport.getFacing();
unloadUnit(transport, entity, curPos, curFacing, transport.getElevation());
entityUpdate(transport.getId());
//if this is the last fighter in a fighter squadron then remove the squadron
if((transport instanceof FighterSquadron) && (((FighterSquadron)transport).getFighters().size() < 1)) {
transport.setDestroyed(true);
game.moveToGraveyard(transport.getId());
entityUpdate(transport.getId());
send(createRemoveEntityPacket(transport.getId(), condition));
r = new Report(6365);
r.subject = transport.getId();
r.addDesc(transport);
r.add("fighter destruction");
vDesc.addElement(r);
}
} // End unit-is-transported
// Is this unit being swarmed?
final int swarmerId = entity.getSwarmAttackerId();
if (Entity.NONE != swarmerId) {
final Entity swarmer = game.getEntity(swarmerId);
swarmer.setSwarmTargetId(Entity.NONE);
// a unit that stopped swarming due to the swarmed unit dieing
// should be able to move
swarmer.setUnloaded(false);
entity.setSwarmAttackerId(Entity.NONE);
Report.addNewline(vDesc);
r = new Report(6380);
r.subject = swarmerId;
r.addDesc(swarmer);
vDesc.addElement(r);
if ((entity instanceof Mech) && !entity.isProne()) {
// Swarming infantry take a 2d6 point hit.
// ASSUMPTION : damage should not be doubled.
r = new Report(2335);
r.subject = swarmer.getId();
r.addDesc(swarmer);
vDesc.add(r);
vDesc.addAll(damageEntity(swarmer, swarmer.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT), Compute.d6(2)));
Report.addNewline(vPhaseReport);
}
entityUpdate(swarmerId);
}
// Is this unit swarming somebody?
final int swarmedId = entity.getSwarmTargetId();
if (Entity.NONE != swarmedId) {
final Entity swarmed = game.getEntity(swarmedId);
swarmed.setSwarmAttackerId(Entity.NONE);
entity.setSwarmTargetId(Entity.NONE);
r = new Report(6385);
r.subject = swarmed.getId();
r.addDesc(swarmed);
vDesc.addElement(r);
entityUpdate(swarmedId);
}
// If in a grapple, release both mechs
if (entity.getGrappled() != Entity.NONE) {
int grappler = entity.getGrappled();
entity.setGrappled(Entity.NONE, false);
Entity e = game.getEntity(grappler);
if (e != null) {
e.setGrappled(Entity.NONE, false);
}
entityUpdate(grappler);
}
} // End entity-not-already-destroyed.
//if using battlefield wreckage rules, then the destruction of this unit
//might convert the hex to rough
Coords curPos = entity.getPosition();
IHex entityHex = game.getBoard().getHex(curPos);
if(game.getOptions().booleanOption("tacops_battle_wreck")
&& (entityHex != null) && game.getBoard().onGround()
&& !((entity instanceof Infantry) || (entity instanceof Protomech))) {
//large support vees will create ultra rough, otherwise rough
if(entity instanceof LargeSupportTank) {
if(entityHex.terrainLevel(Terrains.ROUGH) < 2) {
entityHex.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.ROUGH, 2));
sendChangedHex(curPos);
}
} else if ( (entity.getWeight() >= 40) && !entityHex.containsTerrain(Terrains.ROUGH)) {
entityHex.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.ROUGH, 1));
sendChangedHex(curPos);
}
}
// update our entity, so clients have correct data
// needed for MekWars stuff
entityUpdate(entity.getId());
return vDesc;
}
/**
* Makes a piece of equipment on a mech explode! POW! This expects either
* ammo, or an explosive weapon. Returns a vector of Report objects.
*/
private Vector<Report> explodeEquipment(Entity en, int loc, int slot) {
return explodeEquipment(en, loc, en.getEquipment(en.getCritical(loc, slot).getIndex()));
}
/**
* Makes a piece of equipment on a mech explode! POW! This expects either
* ammo, or an explosive weapon. Returns a vector of Report objects.
*/
private Vector<Report> explodeEquipment(Entity en, int loc, Mounted mounted) {
Vector<Report> vDesc = new Vector<Report>();
// is this already destroyed?
if (mounted.isDestroyed()) {
System.err.println("server: explodeEquipment called on destroyed" + " equipment (" + mounted.getName() + ')');
return vDesc;
}
// special-case. RACs only explode when jammed
if ((mounted.getType() instanceof WeaponType) && (((WeaponType) mounted.getType()).getAmmoType() == AmmoType.T_AC_ROTARY)) {
if (!mounted.isJammed()) {
return vDesc;
}
}
// special case. ACs only explode when firing incendiary ammo
if ((mounted.getType() instanceof WeaponType) && ((((WeaponType) mounted.getType()).getAmmoType() == AmmoType.T_AC) || (((WeaponType) mounted.getType()).getAmmoType() == AmmoType.T_LAC))) {
if (!mounted.isUsedThisRound()) {
return vDesc;
}
Mounted ammo = mounted.getLinked();
if ((ammo == null) || !(ammo.getType() instanceof AmmoType) || (((AmmoType) ammo.getType()).getMunitionType() != AmmoType.M_INCENDIARY_AC)) {
return vDesc;
}
WeaponType wtype = (WeaponType) mounted.getType();
if ((wtype.getAmmoType() == AmmoType.T_LRM) || (wtype.getAmmoType() == AmmoType.T_LRM_STREAK) || (wtype.getAmmoType() == AmmoType.T_LRM_TORPEDO) || (wtype.getAmmoType() == AmmoType.T_LRM_TORPEDO_COMBO)) {
return vDesc;
}
}
// Inferno ammo causes heat buildup as well as the damage
if ((mounted.getType() instanceof AmmoType) && ((((AmmoType) mounted.getType()).getAmmoType() == AmmoType.T_SRM) || (((AmmoType) mounted.getType()).getAmmoType() == AmmoType.T_MML)) && (((AmmoType) mounted.getType()).getMunitionType() == AmmoType.M_INFERNO) && (mounted.getShotsLeft() > 0)) {
en.heatBuildup += Math.min(mounted.getExplosionDamage(), 30);
}
// determine and deal damage
int damage = mounted.getExplosionDamage();
// divide damage by 10 for aeros, per TW rules on pg. 161
if (en instanceof Aero) {
int newdamage = (int) Math.floor(damage / 10.0);
if ((newdamage == 0) && (damage > 0)) {
damage = 1;
} else {
damage = newdamage;
}
}
if (damage <= 0) {
return vDesc;
}
Report r = new Report(6390);
r.subject = en.getId();
r.add(mounted.getName());
r.add(damage);
r.indent(3);
r.newlines = 0;
vDesc.addElement(r);
// Mounted is a weapon and has Hot-Loaded ammo in it and it exploded now
// we need to roll for chain reaction
if ((mounted.getType() instanceof WeaponType) && mounted.isHotLoaded()) {
int roll = Compute.d6(2);
int ammoExploded = 0;
r = new Report(6077);
r.subject = en.getId();
r.add(roll);
r.indent(2);
vDesc.addElement(r);
// roll of 2-5 means a chain reaction happened
if (roll < 6) {
for (Mounted ammo : en.getAmmo()) {
if ((ammo.getLocation() == loc) && (ammo.getExplosionDamage() > 0)
// Dead-Fire ammo bins are designed not to explode
// from the chain reaction
// Of Critted Launchers with DFM or HotLoaded ammo.
&& (((AmmoType) ammo.getType()).getMunitionType() != AmmoType.M_DEAD_FIRE)) {
ammoExploded++;
vDesc.addAll(this.explodeEquipment(en, loc, ammo));
break;
}
}
if (ammoExploded == 0) {
r = new Report(6078);
r.subject = en.getId();
r.indent(2);
vDesc.addElement(r);
}
} else {
r = new Report(6079);
r.subject = en.getId();
r.indent(2);
vDesc.addElement(r);
}
}
HitData hit = new HitData(loc);
// check to determine whether this is capital scale if we have a capital
// scale entity
if (mounted.getType() instanceof AmmoType) {
if (((AmmoType) mounted.getType()).isCapital()) {
hit.setCapital(true);
}
}
mounted.setShotsLeft(0);
vDesc.addAll(damageEntity(en, hit, damage, true));
Report.addNewline(vDesc);
int pilotDamage = 2;
if (en instanceof Aero) {
pilotDamage = 1;
}
if (en.getCrew().getOptions().booleanOption("pain_resistance")) {
pilotDamage = 1;
}
if (en.getCrew().getOptions().booleanOption("iron_man")) {
pilotDamage = 1;
}
// tanks only take pilot damage when using BVDNI or VDNI
if ((en instanceof Tank) && !(en.crew.getOptions().booleanOption("vdni") || en.crew.getOptions().booleanOption("bvdni"))) {
pilotDamage = 0;
}
if (!en.crew.getOptions().booleanOption("pain_shunt")) {
vDesc.addAll(damageCrew(en, pilotDamage));
}
if (en.crew.isDoomed() || en.crew.isDead()) {
vDesc.addAll(destroyEntity(en, "crew death", true));
} else {
Report.addNewline(vDesc);
}
return vDesc;
}
/**
* Makes one slot of ammo, determined by certain rules, explode on a mech.
*/
private Vector<Report> explodeAmmoFromHeat(Entity entity) {
int damage = 0;
int rack = 0;
int boomloc = -1;
int boomslot = -1;
Vector<Report> vDesc = new Vector<Report>();
for (int j = 0; j < entity.locations(); j++) {
for (int k = 0; k < entity.getNumberOfCriticals(j); k++) {
CriticalSlot cs = entity.getCritical(j, k);
if ((cs == null) || cs.isDestroyed() || cs.isHit() || (cs.getType() != CriticalSlot.TYPE_EQUIPMENT)) {
continue;
}
Mounted mounted = entity.getEquipment(entity.getCritical(j, k).getIndex());
if (!(mounted.getType() instanceof AmmoType)) {
continue;
}
AmmoType atype = (AmmoType) mounted.getType();
if (!atype.isExplosive()) {
continue;
}
// ignore empty bins
if (mounted.getShotsLeft() == 0) {
continue;
}
// TW page 160, compare one rack's
// damage. Ties go to most rounds.
int newRack = atype.getDamagePerShot() * atype.getRackSize();
int newDamage = mounted.getExplosionDamage();
if (!mounted.isHit() && ((rack < newRack) || ((rack == newRack) && (damage < newDamage)))) {
rack = newRack;
damage = newDamage;
boomloc = j;
boomslot = k;
}
}
}
if ((boomloc != -1) && (boomslot != -1)) {
CriticalSlot slot = entity.getCritical(boomloc, boomslot);
slot.setHit(true);
entity.getEquipment(slot.getIndex()).setHit(true);
vDesc.addAll(explodeEquipment(entity, boomloc, boomslot));
} else {
// Luckily, there is no ammo to explode.
Report r = new Report(5105);
r.subject = entity.getId();
r.indent();
vDesc.addElement(r);
}
return vDesc;
}
/**
* Makes a mech fall.
*/
private Vector<Report> doEntityFall(Entity entity, Coords fallPos, int height, int facing, PilotingRollData roll) {
Vector<Report> vPhaseReport = new Vector<Report>();
Report r;
IHex fallHex = game.getBoard().getHex(fallPos);
// we don't need to deal damage yet, if the entity is doing DFA
if (entity.isMakingDfa()) {
r = new Report(2305);
r.subject = entity.getId();
vPhaseReport.add(r);
entity.setProne(true);
return vPhaseReport;
}
// facing after fall
String side;
int table;
switch (facing) {
case 1:
case 2:
side = "right side";
table = ToHitData.SIDE_RIGHT;
break;
case 3:
side = "rear";
table = ToHitData.SIDE_REAR;
break;
case 4:
case 5:
side = "left side";
table = ToHitData.SIDE_LEFT;
break;
case 0:
default:
side = "front";
table = ToHitData.SIDE_FRONT;
}
int waterDepth = fallHex.terrainLevel(Terrains.WATER);
boolean fallOntoBridge = false;
// only fall onto the bridge if we were in the hex and on it,
// or we fell from a hex that the bridge exits to
if ((entity.climbMode() && (entity.getPosition() != fallPos) &&
fallHex.containsTerrain(Terrains.BRIDGE) &&
fallHex.containsTerrainExit(Terrains.BRIDGE,
fallPos.direction(entity.getPosition()))) ||
(entity.getElevation() == fallHex.terrainLevel(Terrains.BRIDGE_ELEV))) {
fallOntoBridge = true;
}
int bridgeHeight = fallHex.terrainLevel(Terrains.BRIDGE_ELEV) + fallHex.depth();
int buildingHeight = fallHex.terrainLevel(Terrains.BLDG_ELEV);
int damageHeight = height;
int newElevation;
if ((height >= buildingHeight) && (buildingHeight >= 0)) {
damageHeight -= buildingHeight;
newElevation = buildingHeight;
} else if (fallOntoBridge && (height >= bridgeHeight) && (bridgeHeight >= 0)) {
damageHeight -= bridgeHeight;
waterDepth = 0;
newElevation = fallHex.terrainLevel(Terrains.BRIDGE_ELEV);
} else if (fallHex.containsTerrain(Terrains.ICE) && (entity.getElevation() == 0)) {
waterDepth = 0;
newElevation = 0;
}
// HACK: if the dest hex is water, assume that the fall height given is
// to the floor of the hex, and modifiy it so that it's to the surface
else if (waterDepth > 0) {
damageHeight = height - waterDepth;
newElevation = -waterDepth;
} else if (fallHex.containsTerrain(Terrains.BLDG_ELEV)) {
Building bldg = game.getBoard().getBuildingAt(fallPos);
if (bldg.getType() == Building.WALL) {
newElevation = Math.max(fallHex.getElevation(), fallHex.terrainLevel(Terrains.BLDG_ELEV));
} else {
newElevation = 0;
}
waterDepth = 0;
} else {
waterDepth = 0; // because it will be used to set elevation
newElevation = 0;
}
// Falling into water instantly destroys most non-mechs
if ((waterDepth > 0) && !(entity instanceof Mech) && !(entity instanceof Protomech) && !((entity.getRunMP() > 0) && (entity.getMovementMode() == IEntityMovementMode.HOVER)) && (entity.getMovementMode() != IEntityMovementMode.HYDROFOIL) && (entity.getMovementMode() != IEntityMovementMode.NAVAL) && (entity.getMovementMode() != IEntityMovementMode.SUBMARINE) && (entity.getMovementMode() != IEntityMovementMode.INF_UMU)) {
vPhaseReport.addAll(destroyEntity(entity, "a watery grave", false));
return vPhaseReport;
}
// set how deep the mech has fallen
if (entity instanceof Mech) {
Mech mech = (Mech)entity;
mech.setLevelsFallen(damageHeight+waterDepth+1);
// an industrial mech now needs to check for a crit at the end of
// the turn
if (mech.isIndustrial()) {
mech.setCheckForCrit(true);
}
}
// calculate damage for hitting the surface
int damage = (int) Math.round(entity.getWeight() / 10.0) * (damageHeight + 1);
// calculate damage for hitting the ground, but only if we actually fell
// into water
// if we fell onto the water surface, that damage is halved.
int waterDamage = 0;
if (waterDepth > 0) {
damage /= 2;
waterDamage = (int) Math.round(entity.getWeight() / 10.0) * (waterDepth + 1) / 2;
}
// If the waterDepth is larger than the fall height,
// we fell underwater
if (waterDepth > height) {
damage = 0;
waterDamage = (int) Math.round(entity.getWeight() / 10.0) * (height + 1) / 2;
}
// adjust damage for gravity
damage = Math.round(damage * game.getPlanetaryConditions().getGravity());
waterDamage = Math.round(waterDamage * game.getPlanetaryConditions().getGravity());
// report falling
if (waterDamage == 0) {
r = new Report(2310);
r.subject = entity.getId();
r.indent();
r.newlines = 0;
r.addDesc(entity);
r.add(side); // international issue
r.add(damage);
} else if (damage > 0) {
r = new Report(2315);
r.subject = entity.getId();
r.indent();
r.newlines = 0;
r.addDesc(entity);
r.add(side); // international issue
r.add(damage);
r.add(waterDamage);
} else {
r = new Report(2310);
r.subject = entity.getId();
r.indent();
r.newlines = 0;
r.addDesc(entity);
r.add(side); // international issue
r.add(waterDamage);
}
vPhaseReport.add(r);
damage += waterDamage;
// Any swarming infantry will be dislodged, but we don't want to
// interrupt the fall's report. We have to get the ID now because
// the fall may kill the entity which will reset the attacker ID.
final int swarmerId = entity.getSwarmAttackerId();
// Positioning must be prior to damage for proper handling of breaches
// Only Mechs can fall prone.
if (entity instanceof Mech) {
entity.setProne(true);
}
entity.setPosition(fallPos);
entity.setFacing((entity.getFacing() + (facing - 1)) % 6);
entity.setSecondaryFacing(entity.getFacing());
entity.setElevation(newElevation);
if (waterDepth > 0) {
for (int loop = 0; loop < entity.locations(); loop++) {
entity.setLocationStatus(loop, ILocationExposureStatus.WET);
}
}
//if falling into a bog-down hex, the entity automatically gets stuck
if(fallHex.getBogDownModifier(entity.getMovementMode(), entity instanceof LargeSupportTank) != TargetRoll.AUTOMATIC_SUCCESS) {
entity.setStuck(true);
r = new Report(2081);
r.subject = entity.getId();
r.add(entity.getDisplayName(), true);
vPhaseReport.add(r);
//check for quicksand
vPhaseReport.addAll(checkQuickSand(fallPos));
}
// standard damage loop
while (damage > 0) {
int cluster = Math.min(5, damage);
HitData hit = entity.rollHitLocation(ToHitData.HIT_NORMAL, table);
hit.makeFallDamage(true);
vPhaseReport.addAll(damageEntity(entity, hit, cluster));
damage -= cluster;
}
// check for location exposure
vPhaseReport.addAll(doSetLocationsExposure(entity, fallHex, false, -waterDepth));
// only mechs should roll to avoid pilot damage
// vehicles may fall due to sideslips
if ((entity instanceof Mech) && !entity.crew.getOptions().booleanOption("dermal_armor")) {
// we want to be able to avoid pilot damage even when it was
// an automatic fall, only unconsciousness should cause auto-damage
roll.removeAutos();
if (height > 1) {
roll.addModifier(height - 1, "height of fall");
}
entity.addPilotingModifierForTerrain(roll, fallPos);
if (roll.getValue() == TargetRoll.IMPOSSIBLE) {
r = new Report(2320);
r.subject = entity.getId();
r.addDesc(entity);
r.add(entity.crew.getName());
r.indent();
vPhaseReport.add(r);
vPhaseReport.addAll(damageCrew(entity, 1));
Report.addNewline(vPhaseReport);
} else {
int diceRoll = Compute.d6(2);
r = new Report(2325);
r.subject = entity.getId();
r.addDesc(entity);
r.add(entity.crew.getName());
r.add(roll.getValueAsString());
r.add(diceRoll);
if (diceRoll >= roll.getValue()) {
r.choose(true);
vPhaseReport.add(r);
} else {
r.choose(false);
vPhaseReport.add(r);
vPhaseReport.addAll(damageCrew(entity, 1));
Report.addNewline(vPhaseReport);
}
}
}
// Now dislodge any swarming infantry.
if (Entity.NONE != swarmerId) {
final Entity swarmer = game.getEntity(swarmerId);
entity.setSwarmAttackerId(Entity.NONE);
swarmer.setSwarmTargetId(Entity.NONE);
// Did the infantry fall into water?
if ((waterDepth > 0) && (swarmer.getMovementMode() != IEntityMovementMode.INF_UMU)) {
// Swarming infantry die.
swarmer.setPosition(fallPos);
r = new Report(2330);
r.newlines = 0;
r.subject = swarmer.getId();
r.addDesc(swarmer);
vPhaseReport.add(r);
vPhaseReport.addAll(destroyEntity(swarmer, "a watery grave", false));
} else {
// Swarming infantry take a 2d6 point hit.
// ASSUMPTION : damage should not be doubled.
r = new Report(2335);
r.newlines = 0;
r.subject = swarmer.getId();
r.addDesc(swarmer);
vPhaseReport.add(r);
vPhaseReport.addAll(damageEntity(swarmer, swarmer.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT), Compute.d6(2)));
Report.addNewline(vPhaseReport);
}
swarmer.setPosition(fallPos);
entityUpdate(swarmerId);
if (!swarmer.isDone()) {
swarmer.setDone(true);
game.removeTurnFor(swarmer);
send(createTurnVectorPacket());
}
} // End dislodge-infantry
// clear all PSRs after a fall -- the Mek has already failed ONE and
// fallen, it'd be cruel to make it fail some more!
game.resetPSRs(entity);
//if there is a minefield in this hex, then the mech may set it off
if (game.containsMinefield(fallPos) && enterMinefield(entity, fallPos, newElevation, true, vPhaseReport, 12)) {
resetMines();
}
return vPhaseReport;
}
/**
* The mech falls into an unoccupied hex from the given height above
*/
private Vector<Report> doEntityFall(Entity entity, Coords fallPos, int height, PilotingRollData roll) {
return doEntityFall(entity, fallPos, height, Compute.d6(1), roll);
}
/**
* The mech falls down in place
*/
private Vector<Report> doEntityFall(Entity entity, PilotingRollData roll) {
return doEntityFall(entity, entity.getPosition(), entity.getElevation() + game.getBoard().getHex(entity.getPosition()).depth(), roll);
}
/**
* Report: - Any ammo dumps beginning the following round. - Any ammo dumps
* that have ended with the end of this round.
*/
private void resolveAmmoDumps() {
Report r;
for (Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
Entity entity = i.nextElement();
for (Mounted m : entity.getAmmo()) {
if (m.isPendingDump()) {
// report dumping next round
r = new Report(5110);
r.subject = entity.getId();
r.addDesc(entity);
r.add(m.getName());
addReport(r);
// update status
m.setPendingDump(false);
m.setDumping(true);
} else if (m.isDumping()) {
// report finished dumping
r = new Report(5115);
r.subject = entity.getId();
r.addDesc(entity);
r.add(m.getName());
addReport(r);
// update status
m.setDumping(false);
m.setShotsLeft(0);
}
}
entity.reloadEmptyWeapons();
}
}
/**
* Checks for fire ignition based on a given target roll.
* If successful, lights a fire
* also checks to see that fire is possible in the specified hex.
*
* @param hex - the <code>IHex</code> to be lit.
* @param roll - the <code>TargetRoll</code> for the ignition roll
* @param bInferno - <code>true</code> if the fire is an inferno fire. If this value is <code>false</code> the hex will be
* lit only if it contains Woods,jungle or a Building.
* @param entityId - the entityId responsible for the ignite attempt. If the
* value is Entity.NONE, then the roll attempt will not be
* included in the report.
*/
public boolean checkIgnition(Coords c, TargetRoll roll, boolean bInferno, int entityId, Vector<Report> vPhaseReport) {
IHex hex = game.getBoard().getHex(c);
// The hex might be null due to spreadFire translation
// goes outside of the board limit.
if (null == hex) {
return false;
}
// The hex may already be on fire.
if (hex.containsTerrain(Terrains.FIRE)) {
return false;
}
if (!bInferno && !hex.isIgnitable()) {
return false;
}
int fireRoll = Compute.d6(2);
Report r = null;
if (entityId != Entity.NONE) {
r = new Report(3430);
r.indent(2);
r.subject = entityId;
r.add(roll.getValueAsString());
r.add(roll.getDesc());
r.add(fireRoll);
vPhaseReport.add(r);
}
if (fireRoll >= roll.getValue()) {
ignite(c, bInferno, vPhaseReport);
return true;
}
return false;
}
/**
* Returns true if the hex is set on fire with the specified roll. Of
* course, also checks to see that fire is possible in the specified hex.
* This version of the method will not report the attempt roll.
*
* @param hex -
* the <code>IHex</code> to be lit.
* @param roll -
* the <code>int</code> target number for the ignition roll
* @param bAnyTerrain -
* <code>true</code> if the fire can be lit in any terrain. If
* this value is <code>false</code> the hex will be lit only if
* it contains Woods, jungle or a Building.
*/
public boolean checkIgnition(Coords c, TargetRoll roll, boolean bInferno) {
return checkIgnition(c, roll, bInferno, Entity.NONE, null);
}
/**
* Returns true if the hex is set on fire with the specified roll. Of
* course, also checks to see that fire is possible in the specified hex.
* This version of the method will not report the attempt roll.
*
* @param hex -
* the <code>IHex</code> to be lit.
* @param roll -
* the <code>int</code> target number for the ignition roll
*/
public boolean checkIgnition(Coords c, TargetRoll roll) {
// default signature, assuming only woods can burn
return checkIgnition(c, roll, false, Entity.NONE, null);
}
/**
* add fire to a hex
*
* @param c - the <code>Coords</code> of the hex to be set on fire
* @param bInferno - <code>true</code> if the fire to be set is an inferno
*/
public void ignite(Coords c, boolean bInferno, Vector<Report> vReport) {
//you can't start fires in some planetary conditions!
if(null != game.getPlanetaryConditions().cannotStartFire()) {
Report r = new Report(3007);
r.indent(2);
r.add(game.getPlanetaryConditions().cannotStartFire());
r.type = Report.PUBLIC;
vReport.add(r);
return;
}
if(!game.getOptions().booleanOption("tacops_start_fire")) {
Report r = new Report(3008);
r.indent(2);
r.type = Report.PUBLIC;
vReport.add(r);
return;
}
IHex hex = game.getBoard().getHex(c);
if(null == hex) {
return;
}
Report r = new Report(3005);
r.indent(2);
r.add(c.getBoardNum());
r.type = Report.PUBLIC;
//type of fire. We use level 2 for infernos
int type = 1;
if(bInferno) {
type = type + 1;
r.messageId = 3006;
}
//report it
if(null != vReport) {
vReport.add(r);
}
hex.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.FIRE, type));
sendChangedHex(c);
}
/**
* remove fire from a hex
*
* @param x
* @param y
* @param hex
*/
public void removeFire(Coords fireCoords, String reason) {
IHex hex = game.getBoard().getHex(fireCoords);
if(null == hex) {
return;
}
hex.removeTerrain(Terrains.FIRE);
hex.resetFireTurn();
sendChangedHex(fireCoords);
// fire goes out
Report r = new Report(5170, Report.PUBLIC);
r.add(fireCoords.getBoardNum());
r.add(reason);
addReport(r);
}
/**
* Called when a fire is burning. Called 3 times per fire hex.
*
* @param x
* The <code>int</code> x-coordinate of the hex
* @param y
* The <code>int</code> y-coordinate of the hex
*/
public void addSmoke(ArrayList<Coords> coords, int windDir, boolean bInferno) {
// if a tornado, then no smoke!
if (game.getPlanetaryConditions().getWindStrength() > PlanetaryConditions.WI_STORM) {
return;
}
int smokeLevel = 0;
for (Coords smokeCoords : coords) {
IHex smokeHex = game.getBoard().getHex(smokeCoords);
Report r;
if (smokeHex == null) {
continue;
}
// Have to check if it's inferno smoke or from a heavy/hardened
// building
// - heavy smoke from those
if (bInferno
|| (Building.MEDIUM < smokeHex.terrainLevel(Terrains.FUEL_TANK))
|| (Building.MEDIUM < smokeHex.terrainLevel(Terrains.BUILDING))) {
if (smokeHex.terrainLevel(Terrains.SMOKE) == 2) {
// heavy smoke fills hex
r = new Report(5180, Report.PUBLIC);
r.add(smokeCoords.getBoardNum());
addReport(r);
smokeLevel = 2;
} else {
smokeLevel = Math.max(smokeLevel, 2);;
r = new Report(5185, Report.PUBLIC);
r.add(smokeCoords.getBoardNum());
addReport(r);
smokeLevel = 2;
}
} else {
if (smokeHex.terrainLevel(Terrains.SMOKE) == 2) {
// heavy smoke overpowers light
r = new Report(5190, Report.PUBLIC);
r.add(smokeCoords.getBoardNum());
smokeLevel = Math.max(smokeLevel, 1);
addReport(r);
} else if (smokeHex.terrainLevel(Terrains.SMOKE) == 1) {
// light smoke continue to fill hex
r = new Report(5195, Report.PUBLIC);
r.add(smokeCoords.getBoardNum());
addReport(r);
smokeLevel = Math.max(smokeLevel, 1);
} else {
smokeLevel = Math.max(smokeLevel, 1);;
// light smoke fills hex
r = new Report(5200, Report.PUBLIC);
r.add(smokeCoords.getBoardNum());
addReport(r);
}
}
}
createSmoke(coords, smokeLevel, 0);
}
/**
* Scans the boards directory for map boards of the appropriate size and
* returns them.
*/
private ArrayList<String> scanForBoardsInDir(String addPath, String basePath, int w, int h) {
File dir = new File(addPath);
String fileList[] = dir.list();
ArrayList<String> tempList = new ArrayList<String>();
for (String element : fileList) {
File x = new File(addPath.concat("/").concat(element));
if (x.isDirectory()) {
tempList.addAll(scanForBoardsInDir(addPath.concat("/").concat(element), basePath.concat("/").concat(element), w, h));
continue;
}
if (element.indexOf(".svn") != -1) {
continue; // Ignore Subversion directories
}
if (element.indexOf(".board") == -1) {
continue;
}
if (Board.boardIsSize(basePath.concat("/").concat(element), w, h)) {
tempList.add(basePath.concat("/").concat(element.substring(0, element.lastIndexOf(".board"))));
}
}
return tempList;
}
private ArrayList<String> scanForBoards(int boardWidth, int boardHeight) {
ArrayList<String> boards = new ArrayList<String>();
File boardDir = new File("data/boards");
boards.add(MapSettings.BOARD_GENERATED);
// just a check...
if (!boardDir.isDirectory()) {
return boards;
}
// scan files
ArrayList<String> tempList = new ArrayList<String>();
Comparator<String> sortComp = StringUtil.stringComparator();
tempList = scanForBoardsInDir("data/boards", "", boardWidth, boardHeight);
// if there are any boards, add these:
if (tempList.size() > 0) {
boards.add(MapSettings.BOARD_RANDOM);
boards.add(MapSettings.BOARD_SURPRISE);
Collections.sort(tempList, sortComp);
for (int loop = 0; loop < tempList.size(); loop++) {
boards.add(tempList.get(loop));
}
}
// TODO: alphabetize files?
return boards;
}
/**
* @return wether this game is double blind or not and we should be blind in
* the current phase
*/
private boolean doBlind() {
return game.getOptions().booleanOption("double_blind") && game.getPhase().isDuringOrAfter(IGame.Phase.PHASE_DEPLOYMENT);
}
/**
* In a double-blind game, update only visible entities. Otherwise, update
* everyone
*/
public void entityUpdate(int nEntityID) {
entityUpdate(nEntityID, new Vector<UnitLocation>());
}
/**
* In a double-blind game, update only visible entities. Otherwise, update
* everyone
*/
public void entityUpdate(int nEntityID, Vector<UnitLocation> movePath) {
Entity eTarget = game.getEntity(nEntityID);
if (eTarget == null) {
if (game.getOutOfGameEntity(nEntityID) != null) {
System.err.print("S: attempted to send entity update for out of game entity, id was ");
System.err.println(nEntityID);
} else {
System.err.print("S: attempted to send entity update for null entity, id was ");
System.err.println(nEntityID);
}
return; // do not send the update it will crash the client
}
// If we're doing double blind, be careful who can see it...
if (doBlind()) {
Vector<Player> vPlayers = game.getPlayersVector();
Vector<Player> vCanSee = whoCanSee(eTarget);
// send an entity update to everyone who can see
Packet pack = createEntityPacket(nEntityID, movePath);
for (int x = 0; x < vCanSee.size(); x++) {
Player p = vCanSee.elementAt(x);
send(p.getId(), pack);
}
// send an entity delete to everyone else
pack = createRemoveEntityPacket(nEntityID, eTarget.getRemovalCondition());
for (int x = 0; x < vPlayers.size(); x++) {
if (!vCanSee.contains(vPlayers.elementAt(x))) {
Player p = vPlayers.elementAt(x);
send(p.getId(), pack);
}
}
} else {
// But if we're not, then everyone can see.
send(createEntityPacket(nEntityID, movePath));
}
}
/**
* Returns a vector of which players can see this entity.
*/
private Vector<Player> whoCanSee(Entity entity) {
// Some times Null entities are sent to this
if (entity == null) {
return new Vector<Player>();
}
boolean bTeamVision = game.getOptions().booleanOption("team_vision");
Vector<Entity> vEntities = game.getEntitiesVector();
Vector<Player> vCanSee = new Vector<Player>();
vCanSee.addElement(entity.getOwner());
if (bTeamVision) {
addTeammates(vCanSee, entity.getOwner());
}
// Deal with players who can see all.
for (Enumeration<Player> p = game.getPlayers(); p.hasMoreElements();) {
Player player = p.nextElement();
if (player.canSeeAll() && !vCanSee.contains(p)) {
vCanSee.addElement(player);
}
}
// If the entity is hidden, skip this; noone else will be able to see
// it.
if (!entity.isHidden()) {
for (int i = 0; i < vEntities.size(); i++) {
Entity e = vEntities.elementAt(i);
if (vCanSee.contains(e.getOwner()) || !e.isActive()) {
continue;
}
// Off board units should not spot on board units
if (e.isOffBoard()) {
continue;
}
if (Compute.canSee(game, e, entity)) {
vCanSee.addElement(e.getOwner());
if (bTeamVision) {
addTeammates(vCanSee, e.getOwner());
}
addObservers(vCanSee);
}
}
}
return vCanSee;
}
/**
* can the passed <code>Player</code> see the passed <code>Entity</code>?
*
* @param p
* <code>Player</code>
* @param e
* <code>Entity</code>
* @return if the player can see the entity
*/
private boolean canSee(Player p, Entity e) {
if (e.getOwner().getId() == p.getId()) {
// The owner of an entity should be able to see it, of course.
return true;
}
Vector<Player> playersWhoCanSee = whoCanSee(e);
for (int i = 0; i < playersWhoCanSee.size(); i++) {
Player currentPlayer = playersWhoCanSee.elementAt(i);
if (currentPlayer.equals(p)) {
return true;
}
}
return false;
}
/**
* Adds teammates of a player to the Vector. Utility function for whoCanSee.
*/
private void addTeammates(Vector<Player> vector, Player player) {
Vector<Player> vPlayers = game.getPlayersVector();
for (int j = 0; j < vPlayers.size(); j++) {
Player p = vPlayers.elementAt(j);
if (!player.isEnemyOf(p) && !vector.contains(p)) {
vector.addElement(p);
}
}
}
/**
* Adds observers to the Vector. Utility function for whoCanSee.
*/
private void addObservers(Vector<Player> vector) {
Vector<Player> vPlayers = game.getPlayersVector();
for (int j = 0; j < vPlayers.size(); j++) {
Player p = vPlayers.elementAt(j);
if (p.isObserver() && !vector.contains(p)) {
vector.addElement(p);
}
}
}
/**
* Send the complete list of entities to the players. If double_blind is in
* effect, enforce it by filtering the entities
*/
private void entityAllUpdate() {
// If double-blind is in effect, filter each players' list individually,
// and then quit out...
if (doBlind()) {
Vector<Player> vPlayers = game.getPlayersVector();
for (int x = 0; x < vPlayers.size(); x++) {
Player p = vPlayers.elementAt(x);
send(p.getId(), createFilteredEntitiesPacket(p));
}
return;
}
// Otherwise, send the full list.
send(createEntitiesPacket());
}
/**
* Filters an entity vector according to LOS
*/
private Vector<Entity> filterEntities(Player pViewer, Vector<Entity> vEntities) {
Vector<Entity> vCanSee = new Vector<Entity>();
Vector<Entity> vMyEntities = new Vector<Entity>();
Vector<Entity> vAllEntities = game.getEntitiesVector();
boolean bTeamVision = game.getOptions().booleanOption("team_vision");
// If they can see all, return the input list
if (pViewer.canSeeAll()) {
return vEntities;
}
// If they're an observer, they can see anything seen by any enemy.
if (pViewer.isObserver()) {
vMyEntities.addAll(vAllEntities);
for (Entity a : vMyEntities) {
for (Entity b : vMyEntities) {
if (a.isEnemyOf(b) && Compute.canSee(game, b, a)) {
vCanSee.add(a);
break;
}
}
}
return vCanSee;
}
// If they aren't an observer and can't see all, create the list of
// "friendly" units.
for (int x = 0; x < vAllEntities.size(); x++) {
Entity e = vAllEntities.elementAt(x);
if ((e.getOwner() == pViewer) || (bTeamVision && !e.getOwner().isEnemyOf(pViewer))) {
vMyEntities.addElement(e);
}
}
// Then, break down the list by whether they're friendly,
// or whether or not any friendly unit can see them.
for (int x = 0; x < vEntities.size(); x++) {
Entity e = vEntities.elementAt(x);
// If it's their own unit, obviously, they can see it.
if (vMyEntities.contains(e)) {
vCanSee.addElement(e);
continue;
} else if (e.isHidden()) {
// If it's NOT friendly and is hidden, they can't see it,
// period.
// LOS doesn't matter.
continue;
}
for (int y = 0; y < vMyEntities.size(); y++) {
Entity e2 = vMyEntities.elementAt(y);
// If they're off-board, skip it; they can't see anything.
if (e2.isOffBoard()) {
continue;
}
// Otherwise, if they can see the entity in question...
if (Compute.canSee(game, e2, e)) {
vCanSee.addElement(e);
break;
}
}
}
return vCanSee;
}
/**
* filter a reportvector for double blind
*
* @param originalReportVector
* the original <code>Vector<Report></code>
* @param p
* the <code>Player</code> who should see stuff only visible to
* him
* @return the <code>Vector<Report></code> with stuff only Player p can
* see
*/
private Vector<Report> filterReportVector(Vector<Report> originalReportVector, Player p) {
// FIXME
// Please optimize and document me.
// Only bother actually doing all this crap if double-blind is in
// effect.
if (!doBlind()) {
return new Vector<Report>(originalReportVector);
}
// But if it is, then filter everything properly.
Vector<Report> filteredReportVector = new Vector<Report>();
Report r;
for (int i = 0; i < originalReportVector.size(); i++) {
r = originalReportVector.elementAt(i);
filteredReportVector.addElement(filterReport(r, p, false));
}
return filteredReportVector;
}
/**
* Filter a single report so that the correct double-blind obscuration takes
* place. to mark a message as "thsi should be visible toanyone seeing this
* entity" set r.subject to the entity id to mark a message as "only visble
* to the player" set r.player to that player's id and set
* r.type=Report.PLAYER to mark a message as visible to all , set r.type to
* Report.PUBLIC
*
* @param r
* the Report to filter
* @param p
* the Player that we are going to send the filtered report to
* @param omitCheck
* boolean indicating that this report hapened in the past, so we
* no longer have access to the Player
* @return a new Report, which has possibly been obscured
*/
private Report filterReport(Report r, Player p, boolean omitCheck) {
if ((r.subject == Entity.NONE) && (r.type != Report.PLAYER) && (r.type != Report.PUBLIC)) {
// Reports that don't have a subject should be public.
System.err.println("Error: Attempting to filter a Report object that is not public yet has no subject.\n\t\tmessageId: " + r.messageId);
return r;
}
if ((r.type == Report.PUBLIC) || ((p == null) && !omitCheck)) {
return r;
}
Entity entity = game.getEntity(r.subject);
if (entity == null) {
entity = game.getOutOfGameEntity(r.subject);
}
Player owner = null;
if (entity != null) {
owner = entity.getOwner();
// off board (Artillery) units get treated as public messages
if (entity.isOffBoard()) {
return r;
}
}
if ((r.type != Report.PLAYER) && !omitCheck && ((entity == null) || (owner == null))) {
System.err.println("Error: Attempting to filter a Report object that is not public but has a subject (" + entity + ") with owner (" + owner + ").\n\tmessageId: " + r.messageId);
return r;
}
Report copy = new Report(r);
for (int j = 0; j < copy.dataCount(); j++) {
if (((r.type == Report.PLAYER) && (p.getId() != r.player)) || omitCheck || ((entity != null) && !canSee(p, entity))) {
if (r.isValueObscured(j)) {
copy.hideData(j);
// Mark the original report to indicate which players
// received an obscured version of it.
if (p != null) {
r.addObscuredRecipient(p.getName());
}
}
// simulate hiding the report for *true* double-blind play
// ***DEBUG*** TESTING ONLY
// copy.markForTesting();
}
}
return copy;
}
/**
* Returns a vector which has as it's keys the round number and as it's
* elements vectors that contain all the reports for the specified player
* that round. The reports returned this way are properly filtered for
* double blind.
*
* @param pastReports
* @param p
* @return
*/
private Vector<Vector<Report>> filterPastReports(Vector<Vector<Report>> pastReports, Player p) {
// This stuff really only needs to be printed for debug reasons. other
// wise the logs get
// filled to the brim when ever someone connects. --Torren.
// System.err.println("filterPastReports() begin");
// System.err.println(" player is " + p.getName());
// Only actually bother with the filtering if double-blind is in effect.
if (doBlind()) {
// System.err.println(" pastReports vector is\n" + pastReports);
Vector<Vector<Report>> filteredReports = new Vector<Vector<Report>>();
Vector<Report> filteredRoundReports;
Vector<Report> roundReports = new Vector<Report>();
Report r;
for (int i = 0; i < pastReports.size(); i++) {
filteredRoundReports = new Vector<Report>();
roundReports = pastReports.elementAt(i);
// System.err.println(" roundReports vector is\n" +
// roundReports);
for (int j = 0; j < roundReports.size(); j++) {
r = roundReports.elementAt(j);
if (r.isObscuredRecipient(p.getName())) {
// System.err.println(" report is " + r + "
// -obscuring-");
filteredRoundReports.addElement(filterReport(r, null, true));
} else {
// System.err.println(" report is " + r);
filteredRoundReports.addElement(r);
}
}
// System.err.println(" filteredRoundReport is\n" +
// filteredRoundReports);
filteredReports.addElement(filteredRoundReports);
}
// System.err.println("filterPastReports() end");
return filteredReports;
}
// System.err.println("filterPastReports() end");
return pastReports;
}
/**
* Updates entities graphical "visibility indications" which are used in
* double-blind games.
*/
private void updateVisibilityIndicator() {
Vector<Entity> vAllEntities = game.getEntitiesVector();
for (int x = 0; x < vAllEntities.size(); x++) {
Entity e = vAllEntities.elementAt(x);
boolean previousVisibleValue = e.isVisibleToEnemy();
boolean previousSeenValue = e.isSeenByEnemy();
e.setVisibleToEnemy(false);
Vector<Player> vCanSee = whoCanSee(e);
for (int y = 0; y < vCanSee.size(); y++) {
Player p = vCanSee.elementAt(y);
if (e.getOwner().isEnemyOf(p) && !p.isObserver()) {
e.setVisibleToEnemy(true);
e.setSeenByEnemy(true);
}
}
if ((previousVisibleValue != e.isVisibleToEnemy()) || (previousSeenValue != e.isSeenByEnemy())) {
sendVisibilityIndicator(e);
}
}
}
/**
* Checks if an entity added by the client is valid and if so, adds it to
* the list
*/
private void receiveEntityAdd(Packet c, int connIndex) {
final Entity entity = (Entity) c.getObject(0);
// Verify the entity's design
if (Server.entityVerifier == null) {
Server.entityVerifier = new EntityVerifier(new File(VERIFIER_CONFIG_FILENAME));
}
// we can only test meks and vehicles right now
if ((entity instanceof Mech) || (entity instanceof Tank)) {
TestEntity testEntity = null;
entity.restore();
if (entity instanceof Mech) {
testEntity = new TestMech((Mech) entity, Server.entityVerifier.mechOption, null);
}
if (entity instanceof VTOL) {
testEntity = new TestTank((Tank) entity, Server.entityVerifier.tankOption, null);// not
}
// implemented
// yet.
if (entity instanceof Tank) {
testEntity = new TestTank((Tank) entity, Server.entityVerifier.tankOption, null);
}
StringBuffer sb = new StringBuffer();
if (testEntity.correctEntity(sb, !game.getOptions().booleanOption("is_eq_limits"))) {
entity.setDesignValid(true);
} else {
System.err.println(sb);
if (game.getOptions().booleanOption("allow_illegal_units")) {
entity.setDesignValid(false);
} else {
Player cheater = game.getPlayer(connIndex);
sendServerChat("Player " + cheater.getName() + " attempted to add an illegal unit design (" + entity.getShortNameRaw() + "), the unit was rejected.");
return;
}
}
}
// If we're adding a Protomech, calculate it's unit number.
if (entity instanceof Protomech) {
// How many Protomechs does the player already have?
int numPlayerProtos = game.getSelectedEntityCount(new EntitySelector() {
private final int ownerId = entity.getOwnerId();
public boolean accept(Entity entity) {
if ((entity instanceof Protomech) && (ownerId == entity.getOwnerId())) {
return true;
}
return false;
}
});
// According to page 54 of the BMRr, Protomechs must be
// deployed in full Points of five, unless circumstances have
// reduced the number to less that that.
entity.setUnitNumber((char) (numPlayerProtos / 5));
} // End added-Protomech
// Only assign an entity ID when the client hasn't.
if (Entity.NONE == entity.getId()) {
entity.setId(getFreeEntityId());
}
game.addEntity(entity.getId(), entity);
send(createAddEntityPacket(entity.getId()));
}
/**
* adds a squadron to the game
* This is not working, don't use it
*/
private void receiveSquadronAdd(Packet c, int connIndex) {
/*
final FighterSquadron fs = new FighterSquadron();
Vector<Entity> fighters = (Vector<Entity>)c.getObject(0);
if(fighters.size() < 1) {
return;
}
fs.setOwner(fighters.firstElement().getOwner());
// Only assign an entity ID when the client hasn't.
if (Entity.NONE == fs.getId()) {
fs.setId(getFreeEntityId());
}
game.addEntity(fs.getId(), fs);
send(createAddEntityPacket(fs.getId()));
for(Entity fighter : fighters) {
fs.load(fighter);
entityUpdate(fighter.getId());
}
entityUpdate(fs.getId());
*/
}
/**
* Updates an entity with the info from the client. Only valid to do this
* during the lounge phase, except for heat sink changing.
*/
private void receiveEntityUpdate(Packet c, int connIndex) {
Entity entity = (Entity) c.getObject(0);
Entity oldEntity = game.getEntity(entity.getId());
if ((oldEntity != null) && (oldEntity.getOwner() == getPlayer(connIndex))) {
game.setEntity(entity.getId(), entity);
entityUpdate(entity.getId());
// In the chat lounge, notify players of customizing of unit
if (game.getPhase() == IGame.Phase.PHASE_LOUNGE) {
StringBuffer message = new StringBuffer();
message.append("Unit ");
if (game.getOptions().booleanOption("blind_drop") || game.getOptions().booleanOption("real_blind_drop")) {
if (Entity.NONE != entity.getExternalId()) {
message.append('[').append(entity.getExternalId()).append("] ");
}
message.append(entity.getId()).append('(').append(entity.getOwner().getName()).append(')');
} else {
message.append(entity.getDisplayName());
}
message.append(" has been customized.");
sendServerChat(message.toString());
}
} else {
// hey!
}
}
private void receiveCustomInit(Packet c, int connIndex) {
// In the chat lounge, notify players of customizing of unit
if (game.getPhase() == IGame.Phase.PHASE_LOUNGE) {
Player p = (Player) c.getObject(0);
sendServerChat("" + p.getName() + " has customized initiative.");
}
}
/**
* receive and process an entity mode change packet
*
* @param c
* @param connIndex
*/
private void receiveEntityModeChange(Packet c, int connIndex) {
int entityId = c.getIntValue(0);
int equipId = c.getIntValue(1);
int mode = c.getIntValue(2);
Entity e = game.getEntity(entityId);
if (e.getOwner() != getPlayer(connIndex)) {
return;
}
Mounted m = e.getEquipment(equipId);
if ( m == null ) {
return;
}
try {
// a mode change for ammo means dumping or hotloading
if ((m.getType() instanceof AmmoType) && !m.getType().hasInstantModeSwitch() && (mode <= 0)) {
m.setPendingDump(mode == -1);
} else {
if ( !m.setMode(mode) ){
System.err.println(e.getShortName()+ ": " + m.getName() + ": " + e.getLocationName(m.getLocation()) + " trying to compensate");
sendServerChat(e.getShortName()+ ": " + m.getName() + ": " + e.getLocationName(m.getLocation()) + " trying to compensate");
e.setGameOptions();
if ( !m.setMode(mode) ){
System.err.println(e.getShortName()+ ": " + m.getName() + ": " + e.getLocationName(m.getLocation()) + " unable to compensate");
sendServerChat(e.getShortName()+ ": " + m.getName() + ": " + e.getLocationName(m.getLocation()) + " unable to compensate");
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* receive and process an entity sytem mode change packet
*
* @param c
* @param connIndex
*/
private void receiveEntitySystemModeChange(Packet c, int connIndex) {
int entityId = c.getIntValue(0);
int equipId = c.getIntValue(1);
int mode = c.getIntValue(2);
Entity e = game.getEntity(entityId);
if (e.getOwner() != getPlayer(connIndex)) {
return;
}
if ((e instanceof Mech) && (equipId == Mech.SYSTEM_COCKPIT)) {
((Mech) e).setCockpitStatus(mode);
}
}
/**
* Receive a packet that contains an Entity ammo change
*
* @param c
* @param connIndex
*/
private void receiveEntityAmmoChange(Packet c, int connIndex) {
int entityId = c.getIntValue(0);
int weaponId = c.getIntValue(1);
int ammoId = c.getIntValue(2);
Entity e = game.getEntity(entityId);
// Did we receive a request for a valid Entity?
if (null == e) {
System.err.print("Server.receiveEntityAmmoChange: could not find entity #");
System.err.println(entityId);
return;
}
Player player = getPlayer(connIndex);
if ((null != player) && (e.getOwner() != player)) {
System.err.print("Server.receiveEntityAmmoChange: player ");
System.err.print(player.getName());
System.err.print(" does not own the entity ");
System.err.println(e.getDisplayName());
return;
}
// Make sure that the entity has the given equipment.
Mounted mWeap = e.getEquipment(weaponId);
Mounted mAmmo = e.getEquipment(ammoId);
if (null == mAmmo) {
System.err.print("Server.receiveEntityAmmoChange: entity ");
System.err.print(e.getDisplayName());
System.err.print(" does not have ammo #");
System.err.println(ammoId);
return;
}
if (!(mAmmo.getType() instanceof AmmoType)) {
System.err.print("Server.receiveEntityAmmoChange: item # ");
System.err.print(ammoId);
System.err.print(" of entity ");
System.err.print(e.getDisplayName());
System.err.print(" is a ");
System.err.print(mAmmo.getName());
System.err.println(" and not ammo.");
return;
}
if (null == mWeap) {
System.err.print("Server.receiveEntityAmmoChange: entity ");
System.err.print(e.getDisplayName());
System.err.print(" does not have weapon #");
System.err.println(weaponId);
return;
}
if (!(mWeap.getType() instanceof WeaponType)) {
System.err.print("Server.receiveEntityAmmoChange: item # ");
System.err.print(weaponId);
System.err.print(" of entity ");
System.err.print(e.getDisplayName());
System.err.print(" is a ");
System.err.print(mWeap.getName());
System.err.println(" and not a weapon.");
return;
}
if (((WeaponType) mWeap.getType()).getAmmoType() == AmmoType.T_NA) {
System.err.print("Server.receiveEntityAmmoChange: item # ");
System.err.print(weaponId);
System.err.print(" of entity ");
System.err.print(e.getDisplayName());
System.err.print(" is a ");
System.err.print(mWeap.getName());
System.err.println(" and does not use ammo.");
return;
}
if (((WeaponType) mWeap.getType()).hasFlag(WeaponType.F_ONESHOT)) {
System.err.print("Server.receiveEntityAmmoChange: item # ");
System.err.print(weaponId);
System.err.print(" of entity ");
System.err.print(e.getDisplayName());
System.err.print(" is a ");
System.err.print(mWeap.getName());
System.err.println(" and cannot use external ammo.");
return;
}
// Load the weapon.
e.loadWeapon(mWeap, mAmmo);
}
/**
* Deletes an entity owned by a certain player from the list
*/
private void receiveEntityDelete(Packet c, int connIndex) {
int entityId = c.getIntValue(0);
final Entity entity = game.getEntity(entityId);
// Only allow players to delete their *own* entities.
if ((entity != null) && (entity.getOwner() == getPlayer(connIndex))) {
// If we're deleting a Protomech, recalculate unit numbers.
if (entity instanceof Protomech) {
// How many Protomechs does the player have (include this one)?
int numPlayerProtos = game.getSelectedEntityCount(new EntitySelector() {
private final int ownerId = entity.getOwnerId();
public boolean accept(Entity entity) {
if ((entity instanceof Protomech) && (ownerId == entity.getOwnerId())) {
return true;
}
return false;
}
});
// According to page 54 of the BMRr, Protomechs must be
// deployed in full Points of five, unless "losses" have
// reduced the number to less that that.
final char oldMax = (char) (Math.ceil(numPlayerProtos / 5.0) - 1);
char newMax = (char) (Math.ceil((numPlayerProtos - 1) / 5.0) - 1);
char deletedUnitNum = entity.getUnitNumber();
// Do we have to update a Protomech from the last unit?
if ((oldMax != deletedUnitNum) && (oldMax != newMax)) {
// Yup. Find a Protomech from the last unit, and
// set it's unit number to the deleted entity.
Enumeration<Entity> lastUnit = game.getSelectedEntities(new EntitySelector() {
private final int ownerId = entity.getOwnerId();
private final char lastUnitNum = oldMax;
public boolean accept(Entity entity) {
if ((entity instanceof Protomech) && (ownerId == entity.getOwnerId()) && (lastUnitNum == entity.getUnitNumber())) {
return true;
}
return false;
}
});
Entity lastUnitMember = lastUnit.nextElement();
lastUnitMember.setUnitNumber(deletedUnitNum);
entityUpdate(lastUnitMember.getId());
} // End update-unit-numbetr
} // End added-Protomech
game.removeEntity(entityId, IEntityRemovalConditions.REMOVE_NEVER_JOINED);
send(createRemoveEntityPacket(entityId, IEntityRemovalConditions.REMOVE_NEVER_JOINED));
}
}
/**
* Sets a player's ready status
*/
private void receivePlayerDone(Packet pkt, int connIndex) {
boolean ready = pkt.getBooleanValue(0);
Player player = getPlayer(connIndex);
if (null != player) {
player.setDone(ready);
}
}
private void receiveInitiativeRerollRequest(Packet pkt, int connIndex) {
Player player = getPlayer(connIndex);
if (IGame.Phase.PHASE_INITIATIVE_REPORT != game.getPhase()) {
StringBuffer message = new StringBuffer();
if (null == player) {
message.append("Player #").append(connIndex);
} else {
message.append(player.getName());
}
message.append(" is not allowed to ask for a reroll at this time.");
System.err.println(message.toString());
sendServerChat(message.toString());
return;
}
if (game.hasTacticalGenius(player)) {
game.addInitiativeRerollRequest(game.getTeamForPlayer(player));
}
if (null != player) {
player.setDone(true);
}
checkReady();
}
/**
* Sets game options, providing that the player has specified the password
* correctly.
*
* @return true if any options have been successfully changed.
*/
private boolean receiveGameOptions(Packet packet, int connId) {
Player player = game.getPlayer(connId);
// Check player
if (null == player) {
System.err.print("Server does not recognize player at connection ");
System.err.println(connId);
return false;
}
// check password
if ((password != null) && (password.length() > 0) && !password.equals(packet.getObject(0))) {
sendServerChat(connId, "The password you specified to change game options is incorrect.");
return false;
}
if ( game.getPhase().isDuringOrAfter(Phase.PHASE_DEPLOYMENT) ){
return false;
}
int changed = 0;
for (Enumeration<?> i = ((Vector<?>) packet.getObject(1)).elements(); i.hasMoreElements();) {
IBasicOption option = (IBasicOption) i.nextElement();
IOption originalOption = game.getOptions().getOption(option.getName());
if (originalOption == null) {
continue;
}
StringBuffer message = new StringBuffer();
message.append("Player ").append(player.getName()).append(" changed option \"").append(originalOption.getDisplayableName()).append("\" to ").append(option.getValue().toString()).append('.');
sendServerChat(message.toString());
originalOption.setValue(option.getValue());
changed++;
}
// Set proper RNG
Compute.setRNG(game.getOptions().intOption("rng_type"));
if (changed > 0) {
for (Enumeration<Entity> entities = game.getEntities(); entities.hasMoreElements(); ){
Entity en = entities.nextElement();
en.setGameOptions();
}
entityAllUpdate();
return true;
}
return false;
}
/**
* Performs the additional processing of the received options after the the
* <code>receiveGameOptions<code> done its job; should be called after
* <code>receiveGameOptions<code> only if the <code>receiveGameOptions<code>
* returned <code>true</code>
* @param packet
* @param connId
*/
private void receiveGameOptionsAux(Packet packet, int connId) {
for (Enumeration<?> i = ((Vector<?>) packet.getObject(1)).elements(); i.hasMoreElements();) {
IBasicOption option = (IBasicOption) i.nextElement();
IOption originalOption = game.getOptions().getOption(option.getName());
if (originalOption != null) {
if ("maps_include_subdir".equals(originalOption.getName())) {
mapSettings.setBoardsAvailableVector(scanForBoards(mapSettings.getBoardWidth(), mapSettings.getBoardHeight()));
mapSettings.removeUnavailable();
mapSettings.setNullBoards(DEFAULT_BOARD);
send(createMapSettingsPacket());
}
}
}
}
/**
* Sends out all player info to the specified connection
*/
private void transmitAllPlayerConnects(int connId) {
for (Enumeration<Player> i = game.getPlayers(); i.hasMoreElements();) {
final Player player = i.nextElement();
send(connId, createPlayerConnectPacket(player.getId()));
}
}
/**
* Creates a packet informing that the player has connected
*/
private Packet createPlayerConnectPacket(int playerId) {
final Object[] data = new Object[2];
data[0] = new Integer(playerId);
data[1] = getPlayer(playerId);
return new Packet(Packet.COMMAND_PLAYER_ADD, data);
}
/**
* Creates a packet containing the player info, for update
*/
private Packet createPlayerUpdatePacket(int playerId) {
final Object[] data = new Object[2];
data[0] = new Integer(playerId);
data[1] = getPlayer(playerId);
return new Packet(Packet.COMMAND_PLAYER_UPDATE, data);
}
/**
* Sends out the player info updates for all players to all connections
*/
private void transmitAllPlayerUpdates() {
for (Enumeration<Player> i = game.getPlayers(); i.hasMoreElements();) {
final Player player = i.nextElement();
if (null != player) {
send(createPlayerUpdatePacket(player.getId()));
}
}
}
/**
* Sends out the player ready stats for all players to all connections
*/
private void transmitAllPlayerDones() {
for (Enumeration<Player> i = game.getPlayers(); i.hasMoreElements();) {
final Player player = i.nextElement();
send(createPlayerDonePacket(player.getId()));
}
}
/**
* Creates a packet containing the player ready status
*/
private Packet createPlayerDonePacket(int playerId) {
Object[] data = new Object[2];
data[0] = new Integer(playerId);
data[1] = Boolean.valueOf(getPlayer(playerId).isDone());
return new Packet(Packet.COMMAND_PLAYER_READY, data);
}
/** Creates a packet containing the current turn vector */
private Packet createTurnVectorPacket() {
return new Packet(Packet.COMMAND_SENDING_TURNS, game.getTurnVector());
}
/** Creates a packet containing the current turn index */
private Packet createTurnIndexPacket() {
return new Packet(Packet.COMMAND_TURN, new Integer(game.getTurnIndex()));
}
/**
* Creates a packet containing the map settings
*/
private Packet createMapSettingsPacket() {
return new Packet(Packet.COMMAND_SENDING_MAP_SETTINGS, mapSettings);
}
/**
* Creates a packet containing the planetary conditions
*/
private Packet createPlanetaryConditionsPacket() {
return new Packet(Packet.COMMAND_SENDING_PLANETARY_CONDITIONS, game.getPlanetaryConditions());
}
/**
* Creates a packet containing temporary map settings as a response to a
* client query
*/
private Packet createMapQueryPacket(MapSettings temp) {
return new Packet(Packet.COMMAND_QUERY_MAP_SETTINGS, temp);
}
/**
* Creates a packet containing the game settingss
*/
private Packet createGameSettingsPacket() {
return new Packet(Packet.COMMAND_SENDING_GAME_SETTINGS, game.getOptions());
}
/**
* Creates a packet containing the game board
*/
private Packet createBoardPacket() {
return new Packet(Packet.COMMAND_SENDING_BOARD, game.getBoard());
}
/**
* Creates a packet containing a single entity, for update
*/
private Packet createEntityPacket(int entityId, Vector<UnitLocation> movePath) {
final Entity entity = game.getEntity(entityId);
final Object[] data = new Object[3];
data[0] = new Integer(entityId);
data[1] = entity;
data[2] = movePath;
return new Packet(Packet.COMMAND_ENTITY_UPDATE, data);
}
/**
* Creates a packet containing a Vector of Reports
*/
private Packet createReportPacket(Player p) {
// When the final report is created, MM sends a null player to create
// the
// report. This will handle that issue.
if ((p == null) || !doBlind()) {
return new Packet(Packet.COMMAND_SENDING_REPORTS, vPhaseReport);
}
return new Packet(Packet.COMMAND_SENDING_REPORTS, p.getTurnReport());
}
/**
* Creates a packet containing a Vector of special Reports which needs to be
* sent during a phase that is not a report phase.
*/
private Packet createSpecialReportPacket() {
return new Packet(Packet.COMMAND_SENDING_REPORTS_SPECIAL, vPhaseReport.clone());
}
/**
* Creates a packet containing a Vector of Reports that represent a Tactical
* Genius re-roll request which needs to update a current phase's report.
*/
private Packet createTacticalGeniusReportPacket() {
return new Packet(Packet.COMMAND_SENDING_REPORTS_TACTICAL_GENIUS, vPhaseReport.clone());
}
/**
* Creates a packet containing all the round reports
*/
private Packet createAllReportsPacket(Player p) {
return new Packet(Packet.COMMAND_SENDING_REPORTS_ALL, filterPastReports(game.getAllReports(), p));
}
/**
* Creates a packet containing all current entities
*/
private Packet createEntitiesPacket() {
return new Packet(Packet.COMMAND_SENDING_ENTITIES, game.getEntitiesVector());
}
/**
* Creates a packet containing all current and out-of-game entities
*/
private Packet createFullEntitiesPacket() {
final Object[] data = new Object[2];
data[0] = game.getEntitiesVector();
data[1] = game.getOutOfGameEntitiesVector();
return new Packet(Packet.COMMAND_SENDING_ENTITIES, data);
}
/**
* Creates a packet containing all entities visible to the player in a blind
* game
*/
private Packet createFilteredEntitiesPacket(Player p) {
return new Packet(Packet.COMMAND_SENDING_ENTITIES, filterEntities(p, game.getEntitiesVector()));
}
/**
* Creates a packet containing all entities, including wrecks, visible to
* the player in a blind game
*/
private Packet createFilteredFullEntitiesPacket(Player p) {
final Object[] data = new Object[2];
data[0] = filterEntities(p, game.getEntitiesVector());
data[1] = game.getOutOfGameEntitiesVector();
return new Packet(Packet.COMMAND_SENDING_ENTITIES, data);
}
/**
* Creates a packet detailing the addition of an entity
*/
private Packet createAddEntityPacket(int entityId) {
final Entity entity = game.getEntity(entityId);
final Object[] data = new Object[2];
data[0] = new Integer(entityId);
data[1] = entity;
return new Packet(Packet.COMMAND_ENTITY_ADD, data);
}
/**
* Creates a packet detailing the removal of an entity. Maintained for
* backwards compatability.
*
* @param entityId -
* the <code>int</code> ID of the entity being removed.
* @return A <code>Packet</code> to be sent to clients.
*/
private Packet createRemoveEntityPacket(int entityId) {
return createRemoveEntityPacket(entityId, IEntityRemovalConditions.REMOVE_SALVAGEABLE);
}
/**
* Creates a packet detailing the removal of an entity.
*
* @param entityId -
* the <code>int</code> ID of the entity being removed.
* @param condition -
* the <code>int</code> condition the unit was in. This value
* must be one of constants in
* <code>IEntityRemovalConditions</code>, or an
* <code>IllegalArgumentException</code> will be thrown.
* @return A <code>Packet</code> to be sent to clients.
*/
private Packet createRemoveEntityPacket(int entityId, int condition) {
if ((condition != IEntityRemovalConditions.REMOVE_UNKNOWN) && (condition != IEntityRemovalConditions.REMOVE_IN_RETREAT) && (condition != IEntityRemovalConditions.REMOVE_PUSHED) && (condition != IEntityRemovalConditions.REMOVE_SALVAGEABLE) && (condition != IEntityRemovalConditions.REMOVE_EJECTED) && (condition != IEntityRemovalConditions.REMOVE_CAPTURED) && (condition != IEntityRemovalConditions.REMOVE_DEVASTATED) && (condition != IEntityRemovalConditions.REMOVE_NEVER_JOINED)) {
throw new IllegalArgumentException("Unknown unit condition: " + condition);
}
Object[] array = new Object[2];
array[0] = new Integer(entityId);
array[1] = new Integer(condition);
return new Packet(Packet.COMMAND_ENTITY_REMOVE, array);
}
/**
* Creates a packet indicating end of game, including detailed unit status
*/
private Packet createEndOfGamePacket() {
Object[] array = new Object[3];
array[0] = getDetailedVictoryReport();
array[1] = new Integer(game.getVictoryPlayerId());
array[2] = new Integer(game.getVictoryTeam());
return new Packet(Packet.COMMAND_END_OF_GAME, array);
}
/**
* Transmits a chat message to all players
*/
public void sendChat(int connId, String origin, String message) {
send(connId, new Packet(Packet.COMMAND_CHAT, origin + ": " + message));
}
/**
* Transmits a chat message to all players
*/
private void sendChat(String origin, String message) {
String chat = origin + ": " + message;
send(new Packet(Packet.COMMAND_CHAT, chat));
}
public void sendServerChat(int connId, String message) {
sendChat(connId, "***Server", message);
}
public void sendServerChat(String message) {
sendChat("***Server", message);
}
/**
* Creates a packet containing a hex, and the coordinates it goes at.
*/
private Packet createHexChangePacket(Coords coords, IHex hex) {
final Object[] data = new Object[2];
data[0] = coords;
data[1] = hex;
return new Packet(Packet.COMMAND_CHANGE_HEX, data);
}
/**
* Sends notification to clients that the specified hex has changed.
*/
public void sendChangedHex(Coords coords) {
send(createHexChangePacket(coords, game.getBoard().getHex(coords)));
}
/**
* Creates a packet containing a vector of mines.
*/
private Packet createMineChangePacket(Coords coords) {
return new Packet(Packet.COMMAND_UPDATE_MINEFIELDS, game.getMinefields(coords));
}
/**
* Sends notification to clients that the specified hex has changed.
*/
public void sendChangedMines(Coords coords) {
send(createMineChangePacket(coords));
}
public void sendVisibilityIndicator(Entity e) {
final Object[] data = new Object[3];
data[0] = new Integer(e.getId());
data[1] = Boolean.valueOf(e.isSeenByEnemy());
data[2] = Boolean.valueOf(e.isVisibleToEnemy());
send(new Packet(Packet.COMMAND_ENTITY_VISIBILITY_INDICATOR, data));
}
/**
* Creates a packet for an attack
*/
private Packet createAttackPacket(Vector<?> vector, int charges) {
final Object[] data = new Object[2];
data[0] = vector;
data[1] = new Integer(charges);
return new Packet(Packet.COMMAND_ENTITY_ATTACK, data);
}
/**
* Creates a packet for an attack
*/
private Packet createAttackPacket(EntityAction ea, int charge) {
Vector<EntityAction> vector = new Vector<EntityAction>(1);
vector.addElement(ea);
Object[] data = new Object[2];
data[0] = vector;
data[1] = new Integer(charge);
return new Packet(Packet.COMMAND_ENTITY_ATTACK, data);
}
/**
*
*/
private Packet createSpecialHexDisplayPacket(int toPlayer) {
Hashtable<Coords, Collection<SpecialHexDisplay>> shdTable = game.getBoard().getSpecialHexDisplayTable();
Hashtable<Coords, Collection<SpecialHexDisplay>> shdTable2 = new Hashtable<Coords, Collection<SpecialHexDisplay>>();
LinkedList<SpecialHexDisplay> tempList = null;
Player player = getPlayer(toPlayer);
if (player != null) {
final String playerName = getPlayer(toPlayer).getName();
for (Coords coord : shdTable.keySet()) {
tempList = new LinkedList<SpecialHexDisplay>();
for (SpecialHexDisplay shd : shdTable.get(coord)) {
if (!shd.isObscured() || shd.isOwner(playerName)) {
tempList.add(0, shd);
}
}
if (!tempList.isEmpty()) {
shdTable2.put(coord, tempList);
}
}
}
return new Packet(Packet.COMMAND_SENDING_SPECIAL_HEX_DISPLAY, shdTable2);
}
/**
* Creates a packet containing offboard artillery attacks
*/
private Packet createArtilleryPacket(Player p) {
Vector<ArtilleryAttackAction> v = new Vector<ArtilleryAttackAction>();
int team = p.getTeam();
for (Enumeration<AttackHandler> i = game.getAttacks(); i.hasMoreElements();) {
WeaponHandler wh = (WeaponHandler) i.nextElement();
if (wh.waa instanceof ArtilleryAttackAction) {
ArtilleryAttackAction aaa = (ArtilleryAttackAction) wh.waa;
if ((aaa.getPlayerId() == p.getId()) || ((team != Player.TEAM_NONE) && (team == game.getPlayer(aaa.getPlayerId()).getTeam())) || p.getSeeAll()) {
v.addElement(aaa);
}
}
}
return new Packet(Packet.COMMAND_SENDING_ARTILLERYATTACKS, v);
}
/**
* Creates a packet containing flares
*/
private Packet createFlarePacket() {
return new Packet(Packet.COMMAND_SENDING_FLARES, game.getFlares());
}
/**
* Send a packet to all connected clients.
*/
private void send(Packet packet) {
if (connections == null) {
return;
}
for (Enumeration<IConnection> connEnum = connections.elements();connEnum.hasMoreElements();) {
IConnection conn = connEnum.nextElement();
conn.send(packet);
}
}
private void sendReport() {
sendReport(false);
}
/**
* Send the round report to all connected clients.
*/
private void sendReport(boolean tacticalGeniusReport) {
if (connections == null) {
return;
}
for (Enumeration<IConnection> connEnum = connections.elements();connEnum.hasMoreElements();) {
IConnection conn = connEnum.nextElement();
Player p = game.getPlayer(conn.getId());
Packet packet;
if (tacticalGeniusReport) {
packet = createTacticalGeniusReportPacket();
} else {
packet = createReportPacket(p);
}
conn.send(packet);
}
}
/**
* Send a packet to a specific connection.
*/
private void send(int connId, Packet packet) {
if (getClient(connId) != null) {
getClient(connId).send(packet);
} else {
// What should we do if we've lost this client?
// For now, nothing.
}
}
/**
* Send a packet to a pending connection
*/
private void sendToPending(int connId, Packet packet) {
if (getPendingConnection(connId) != null) {
getPendingConnection(connId).send(packet);
} else {
// What should we do if we've lost this client?
// For now, nothing.
}
}
/**
* Process an in-game command
*/
private void processCommand(int connId, String commandString) {
String[] args;
String commandName;
// all tokens are read as strings; if they're numbers, string-ize 'em.
// replaced the tokenizer with the split function.
args = commandString.split("\\s+");
// figure out which command this is
commandName = args[0].substring(1);
// process it
ServerCommand command = getCommand(commandName);
if (command != null) {
command.run(connId, args);
} else {
sendServerChat(connId, "Command not recognized. Type /help for a list of commands.");
}
}
// Easter eggs. Happy April Fool's Day!!
private static final String DUNE_CALL = "They tried and failed?";
private static final String DUNE_RESPONSE = "They tried and died!";
private static final String STAR_WARS_CALL = "I'd just as soon kiss a Wookiee.";
private static final String STAR_WARS_RESPONSE = "I can arrange that!";
private static final String INVADER_ZIM_CALL = "What does the G stand for?";
private static final String INVADER_ZIM_RESPONSE = "I dont know.";
/**
* Process a packet from a connection.
*
* @param connId -
* the <code>int</code> ID the connection that received the
* packet.
* @param packet -
* the <code>Packet</code> to be processed.
*/
protected synchronized void handle(int connId, Packet packet) {
Player player = game.getPlayer(connId);
// Check player. Please note, the connection may be pending.
if ((null == player) && (null == getPendingConnection(connId))) {
System.err.print("Server does not recognize player at connection ");
System.err.println(connId);
return;
}
// System.out.println("s(" + cn + "): received command");
if (packet == null) {
System.out.println("server.connection.handle: got null packet");
return;
}
// act on it
switch (packet.getCommand()) {
case Packet.COMMAND_CLOSE_CONNECTION:
// We have a client going down!
IConnection c = getConnection(connId);
if (c != null) {
c.close();
}
break;
case Packet.COMMAND_CLIENT_NAME:
receivePlayerName(packet, connId);
break;
case Packet.COMMAND_PLAYER_UPDATE:
receivePlayerInfo(packet, connId);
validatePlayerInfo(connId);
send(createPlayerUpdatePacket(connId));
break;
case Packet.COMMAND_PLAYER_READY:
receivePlayerDone(packet, connId);
send(createPlayerDonePacket(connId));
checkReady();
break;
case Packet.COMMAND_REROLL_INITIATIVE:
receiveInitiativeRerollRequest(packet, connId);
send(createPlayerDonePacket(connId));
break;
case Packet.COMMAND_CHAT:
String chat = (String) packet.getObject(0);
if (chat.startsWith("/")) {
processCommand(connId, chat);
} else {
sendChat(player.getName(), chat);
}
// Easter eggs. Happy April Fool's Day!!
if (DUNE_CALL.equalsIgnoreCase(chat)) {
sendServerChat(DUNE_RESPONSE);
} else if (STAR_WARS_CALL.equalsIgnoreCase(chat)) {
sendServerChat(STAR_WARS_RESPONSE);
} else if ( INVADER_ZIM_CALL.equalsIgnoreCase(chat) ){
sendServerChat(INVADER_ZIM_RESPONSE);
}
break;
case Packet.COMMAND_ENTITY_MOVE:
receiveMovement(packet, connId);
break;
case Packet.COMMAND_ENTITY_DEPLOY:
receiveDeployment(packet, connId);
break;
case Packet.COMMAND_DEPLOY_MINEFIELDS:
receiveDeployMinefields(packet, connId);
break;
case Packet.COMMAND_ENTITY_ATTACK:
receiveAttack(packet, connId);
break;
case Packet.COMMAND_ENTITY_ADD:
receiveEntityAdd(packet, connId);
resetPlayersDone();
transmitAllPlayerDones();
break;
case Packet.COMMAND_ENTITY_UPDATE:
receiveEntityUpdate(packet, connId);
resetPlayersDone();
transmitAllPlayerDones();
break;
case Packet.COMMAND_ENTITY_MODECHANGE:
receiveEntityModeChange(packet, connId);
break;
case Packet.COMMAND_ENTITY_SYSTEMMODECHANGE:
receiveEntitySystemModeChange(packet, connId);
break;
case Packet.COMMAND_ENTITY_AMMOCHANGE:
receiveEntityAmmoChange(packet, connId);
break;
case Packet.COMMAND_ENTITY_REMOVE:
receiveEntityDelete(packet, connId);
resetPlayersDone();
transmitAllPlayerDones();
break;
case Packet.COMMAND_SENDING_GAME_SETTINGS:
if (receiveGameOptions(packet, connId)) {
resetPlayersDone();
transmitAllPlayerDones();
send(createGameSettingsPacket());
receiveGameOptionsAux(packet, connId);
}
break;
case Packet.COMMAND_SENDING_MAP_SETTINGS:
if ( game.getPhase().isBefore(Phase.PHASE_DEPLOYMENT)){
MapSettings newSettings = (MapSettings) packet.getObject(0);
if (!mapSettings.equalMapGenParameters(newSettings)) {
sendServerChat("Player " + player.getName() + " changed mapsettings");
}
mapSettings = newSettings;
newSettings = null;
mapSettings.replaceBoardWithRandom(MapSettings.BOARD_RANDOM);
resetPlayersDone();
transmitAllPlayerDones();
send(createMapSettingsPacket());
}
break;
case Packet.COMMAND_SENDING_PLANETARY_CONDITIONS:
// MapSettings newSettings = (MapSettings) packet.getObject(0);
if ( game.getPhase().isBefore(Phase.PHASE_DEPLOYMENT) ){
PlanetaryConditions conditions = (PlanetaryConditions) packet.getObject(0);
sendServerChat("Player " + player.getName() + " changed planetary conditions");
game.setPlanetaryConditions(conditions);
resetPlayersDone();
transmitAllPlayerDones();
send(createPlanetaryConditionsPacket());
}
break;
case Packet.COMMAND_QUERY_MAP_SETTINGS:
MapSettings temp = (MapSettings) packet.getObject(0);
temp.setBoardsAvailableVector(scanForBoards(temp.getBoardWidth(), temp.getBoardHeight()));
temp.removeUnavailable();
temp.setNullBoards(DEFAULT_BOARD);
temp.replaceBoardWithRandom(MapSettings.BOARD_RANDOM);
temp.removeUnavailable();
// if still only nulls left, use BOARD_GENERATED
if (temp.getBoardsSelected().next() == null) {
temp.setNullBoards((MapSettings.BOARD_GENERATED));
}
send(connId, createMapQueryPacket(temp));
break;
case Packet.COMMAND_UNLOAD_STRANDED:
receiveUnloadStranded(packet, connId);
break;
case Packet.COMMAND_SET_ARTYAUTOHITHEXES:
receiveArtyAutoHitHexes(packet, connId);
case Packet.COMMAND_CUSTOM_INITIATIVE:
receiveCustomInit(packet, connId);
resetPlayersDone();
transmitAllPlayerDones();
break;
case Packet.COMMAND_LOAD_GAME:
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream((File)packet.getObject(0)));
oos.writeObject(packet.getObject(1));
oos.flush();
oos.close();
if(loadGame((File) packet.getObject(0))) {
resetConnections();
}
} catch (Exception e) {
System.out.println("Error loading savegame sent from client");
}
case Packet.COMMAND_SQUADRON_ADD:
receiveSquadronAdd(packet, connId);
resetPlayersDone();
transmitAllPlayerDones();
break;
}
}
/**
* Listen for incoming clients.
*/
public void run() {
Thread currentThread = Thread.currentThread();
System.out.println("s: listening for clients...");
HashSet<IConnection> toUpdate = new HashSet<IConnection>();
while (connector == currentThread) {
try {
Socket s = serverSocket.accept();
int id = getFreeConnectionId();
System.out.println("s: accepting player connection #" + id + " ...");
IConnection c = ConnectionFactory.getInstance().createServerConnection(s, id);
c.addConnectionListener(connectionListener);
c.open();
connectionsPending.addElement(c);
greeting(id);
ConnectionWatchdog w = new ConnectionWatchdog(this, id);
timer.schedule(w, 1000, 500);
} catch (InterruptedIOException iioe) {
// ignore , just SOTimeout blowing..
} catch (IOException ex) {
}
/* update all connections */
toUpdate.clear();
toUpdate.addAll(connections);
toUpdate.addAll(connectionsPending);
/* process stuff */
Iterator<IConnection> it = toUpdate.iterator();
while (it.hasNext()) {
it.next().update();
}
/* then make sure stuff is sent away */
it = toUpdate.iterator();
while (it.hasNext()) {
it.next().flush();
}
}
}
/**
* Makes one slot of inferno ammo, determined by certain rules, explode on a
* mech.
*
* @param entity
* The <code>Entity</code> that should suffer an inferno ammo
* explosion.
*/
private Vector<Report> explodeInfernoAmmoFromHeat(Entity entity) {
int damage = 0;
int rack = 0;
int boomloc = -1;
int boomslot = -1;
Vector<Report> vDesc = new Vector<Report>();
Report r;
// Find the most destructive Inferno ammo.
for (int j = 0; j < entity.locations(); j++) {
for (int k = 0; k < entity.getNumberOfCriticals(j); k++) {
CriticalSlot cs = entity.getCritical(j, k);
// Ignore empty, destroyed, hit, and structure slots.
if ((cs == null) || cs.isDestroyed() || cs.isHit() || (cs.getType() != CriticalSlot.TYPE_EQUIPMENT)) {
continue;
}
// Ignore everything but weapons slots.
Mounted mounted = entity.getEquipment(entity.getCritical(j, k).getIndex());
if (!(mounted.getType() instanceof AmmoType)) {
continue;
}
// Ignore everything but Inferno ammo.
AmmoType atype = (AmmoType) mounted.getType();
if (!atype.isExplosive() || (atype.getMunitionType() != AmmoType.M_INFERNO)) {
continue;
}
// Find the most destructive undamaged ammo.
// BMRr, pg. 48, compare one rack's
// damage. Ties go to most rounds.
int newRack = atype.getDamagePerShot() * atype.getRackSize();
int newDamage = mounted.getExplosionDamage();
if (!mounted.isHit() && ((rack < newRack) || ((rack == newRack) && (damage < newDamage)))) {
rack = newRack;
damage = newDamage;
boomloc = j;
boomslot = k;
}
}
}
// Did we find anything to explode?
if ((boomloc != -1) && (boomslot != -1)) {
CriticalSlot slot = entity.getCritical(boomloc, boomslot);
slot.setHit(true);
Mounted equip = entity.getEquipment(slot.getIndex());
equip.setHit(true);
// We've allocated heatBuildup to heat in resolveHeat(),
// so need to add to the entity's heat instead.
entity.heat += Math.min(equip.getExplosionDamage(), 30);
vDesc.addAll(explodeEquipment(entity, boomloc, boomslot));
r = new Report(5155);
r.indent();
r.subject = entity.getId();
r.add(entity.heat);
vDesc.addElement(r);
entity.heatBuildup = 0;
} else { // no ammo to explode
r = new Report(5160);
r.indent();
r.subject = entity.getId();
vDesc.addElement(r);
}
return vDesc;
}
/**
* checks for unintended exposion of heavy industrial zone hex and applies damage
* to entities occupying the hex
*/
public void checkExplodeIndustrialZone(Coords c, Vector<Report> vDesc) {
Report r;
IHex hex = game.getBoard().getHex(c);
if(null == hex) {
return;
}
if(!hex.containsTerrain(Terrains.INDUSTRIAL)) {
return;
}
r = new Report(3590, Report.PUBLIC);
r.add(c.getBoardNum());
r.indent(2);
int effect = Compute.d6(2);
r.add(8);
r.add(effect);
if(effect > 7) {
r.choose(true);
r.newlines = 0;
vDesc.add(r);
boolean onFire = false;
boolean powerLine = false;
boolean minorExp = false;
boolean elecExp = false;
boolean majorExp = false;
if(effect == 8) {
onFire = true;
r = new Report(3600, Report.PUBLIC);
r.newlines = 0;
vDesc.add(r);
} else if(effect == 9) {
powerLine = true;
r = new Report(3605, Report.PUBLIC);
r.newlines = 0;
vDesc.add(r);
} else if (effect == 10) {
minorExp = true;
onFire = true;
r = new Report(3610, Report.PUBLIC);
r.newlines = 0;
vDesc.add(r);
} else if (effect == 11) {
elecExp = true;
r = new Report(3615, Report.PUBLIC);
r.newlines = 0;
vDesc.add(r);
} else {
onFire = true;
majorExp = true;
r = new Report(3620, Report.PUBLIC);
r.newlines = 0;
vDesc.add(r);
}
//apply damage here
if(powerLine || minorExp || elecExp || majorExp) {
//cycle through the entities in the hex and apply damage
for (Enumeration<Entity> e = game.getEntities(c); e.hasMoreElements();) {
Entity en = e.nextElement();
int damage = 3;
if(minorExp) {
damage = 5;
}
if(elecExp) {
damage = Compute.d6(1) + 3;
}
if(majorExp) {
damage = Compute.d6(2);
}
HitData hit = en.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT);
if(en instanceof BattleArmor) {
//ugly - I have to apply damage to each trooper separately
for(int loc = 0; loc < en.locations(); loc++) {
if((IArmorState.ARMOR_NA != en.getInternal(loc))
&& (IArmorState.ARMOR_DESTROYED != en.getInternal(loc))
&& (IArmorState.ARMOR_DOOMED != en.getInternal(loc))) {
vDesc.addAll( damageEntity(en, new HitData(loc), damage) );
}
}
} else {
vDesc.addAll( damageEntity(en, hit, damage) );
}
if(majorExp) {
//lets pretend that the infernos came from the entity itself (should give us side_front)
vDesc.addAll( deliverInfernoMissiles(en, en, Compute.d6(2)) );
}
}
}
Report.addNewline(vDesc);
if(onFire && !hex.containsTerrain(Terrains.FIRE)) {
ignite(c, false, vDesc);
}
} else {
//report no explosion
r.choose(false);
vDesc.add(r);
}
}
/**
* Determine the results of an entity moving through a wall of a building
* after having moved a certain distance. This gets called when a Mech or a
* Tank enters a building, leaves a building, or travels from one hex to
* another inside a multi-hex building.
*
* @param entity -
* the <code>Entity</code> that passed through a wall. Don't
* pass <code>Infantry</code> units to this method.
* @param bldg -
* the <code>Building</code> the entity is passing through.
* @param lastPos -
* the <code>Coords</code> of the hex the entity is exiting.
* @param curPos -
* the <code>Coords</code> of the hex the entity is entering
* @param distance -
* the <code>int</code> number of hexes the entity has moved
* already this phase.
* @param why -
* the <code>String</code> explanation for this action.
* @param backwards -
* the <code>boolean</code> indicating if the entity is
* entering the hex backwards
* @param entering -
* a <code>boolean</code> if the entity is entering or exiting a building
* @return <code>true</code> if the building collapses due to overloading.
*/
private boolean passBuildingWall(Entity entity, Building bldg, Coords lastPos, Coords curPos, int distance, String why, boolean backwards, int overallMoveType) {
Report r;
if (entity instanceof Protomech) {
Vector<Report> vBuildingReport = damageBuilding(bldg, 1, curPos);
for (Report report : vBuildingReport) {
report.subject = entity.getId();
}
addReport(vBuildingReport);
} else {
// Need to roll based on building type.
PilotingRollData psr = entity.rollMovementInBuilding(bldg, distance, why, overallMoveType);
// Did the entity make the roll?
if (0 < doSkillCheckWhileMoving(entity, lastPos, curPos, psr, false)) {
// Divide the building's current CF by 10, round up.
int damage = (int) Math.ceil(bldg.getCurrentCF(curPos) / 10.0);
// Infantry and Battle armor take different amounts of damage then Meks and vehicles.
if (entity instanceof Infantry) {
damage = bldg.getType() + 1;
}
// It is possible that the unit takes no damage.
if (damage == 0) {
r = new Report(6440);
r.add(entity.getDisplayName());
r.subject = entity.getId();
r.indent(2);
addReport(r);
} else {
// TW, pg. 268: if unit moves forward, damage from front,
// if backwards, damage from rear.
int side = ToHitData.SIDE_FRONT;
if (backwards) {
side = ToHitData.SIDE_REAR;
}
HitData hit = entity.rollHitLocation(ToHitData.HIT_NORMAL, side);
hit.setGeneralDamageType(HitData.DAMAGE_PHYSICAL);
addReport(damageEntity(entity, hit, damage));
}
}
// Infantry and BA are damaged by buildings but do not damage them
if (entity instanceof Infantry) {
return false;
}
// Damage the building. The CF can never drop below 0.
int toBldg = (int) Math.ceil(entity.getWeight() / 10.0);
int curCF = bldg.getCurrentCF(curPos);
curCF -= Math.min(curCF, toBldg);
bldg.setCurrentCF(curCF, curPos);
// Apply the correct amount of damage to infantry in the building.
// ASSUMPTION: We inflict toBldg damage to infantry and
// not the amount to bring building to 0 CF.
damageInfantryIn(bldg, toBldg, curPos);
}
return checkBuildingCollapseWhileMoving(bldg, entity, curPos);
}
/**
* check if a building collapes because of a moving entity
*
* @param bldg
* the <code>Building</code>
* @param entity
* the <code>Entity</code>
* @param curPos
* the <coode>Coords</code> of the position of the entity
* @return a <code>boolean</code> value indicating if the building collapses
*/
private boolean checkBuildingCollapseWhileMoving(Building bldg, Entity entity, Coords curPos) {
Coords oldPos = entity.getPosition();
// Count the moving entity in its current position, not
// its pre-move postition. Be sure to handle nulls.
entity.setPosition(curPos);
// Get the position map of all entities in the game.
Hashtable<Coords, Vector<Entity>> positionMap = game.getPositionMap();
// Check for collapse of this building due to overloading, and return.
boolean rv = checkForCollapse(bldg, positionMap, curPos, true);
// If the entity was not displaced and didnt fall, move it back where it
// was
if (curPos.equals(entity.getPosition()) && !entity.isProne()) {
entity.setPosition(oldPos);
}
return rv;
}
/**
* Apply the correct amount of damage that passes on to any infantry unit in
* the given building, based upon the amount of damage the building just
* sustained. This amount is a percentage dictated by pg. 52 of BMRr.
*
* @param bldg -
* the <code>Building</code> that sustained the damage.
* @param damage -
* the <code>int</code> amount of damage.
*/
public void damageInfantryIn(Building bldg, int damage, Coords hexCoords) {
if ( bldg == null ) {
return;
}
// Calculate the amount of damage the infantry will sustain.
float percent = 0.0f;
Report r;
switch (bldg.getType()) {
case Building.LIGHT:
percent = 0.75f;
break;
case Building.MEDIUM:
percent = 0.5f;
break;
case Building.HEAVY:
percent = 0.25f;
break;
}
// Round up at .5 points of damage.
int toInf = Math.round(damage * percent);
// Record if we find any infantry.
boolean foundInfantry = false;
// Walk through the entities in the game.
Enumeration<Entity> entities = game.getEntities();
while (entities.hasMoreElements()) {
Entity entity = entities.nextElement();
final Coords coords = entity.getPosition();
// If the entity is infantry in the affected hex?
if ((entity instanceof Infantry) && bldg.isIn(coords)
&& coords.equals(hexCoords)) {
// Is the entity is inside of the building
// (instead of just on top of it)?
if (Compute.isInBuilding(game, entity, coords)) {
// Report if the infantry receive no points of damage.
if (toInf == 0) {
r = new Report(6445);
r.subject = entity.getId();
addReport(r);
} else {
// Yup. Damage the entity.
// Battle Armor units use 5 point clusters.
r = new Report(6450);
r.indent(2);
r.subject = entity.getId();
r.add(entity.getDisplayName());
r.add(toInf);
addReport(r);
int remaining = toInf;
int cluster = toInf;
if (entity instanceof BattleArmor) {
cluster = 5;
}
while (remaining > 0) {
int next = Math.min(cluster, remaining);
HitData hit = entity.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT);
addReport(damageEntity(entity, hit, next));
remaining -= next;
}
addReport(new Report(1210, Report.PUBLIC));
}
} // End infantry-inside-building
} // End entity-is-infantry-in-building-hex
} // Handle the next entity
// If we found any infantry, add a line to the phase report.
if (foundInfantry) {
addReport(new Report(1210, Report.PUBLIC));
}
} // End private void damageInfantryIn( Building, int )
/**
* Determine if the given building should collapse. If so, inflict the
* appropriate amount of damage on each entity in the building and update
* the clients. If the building does not collapse, determine if any entities
* crash through its floor into its basement. Again, apply appropriate
* damage.
*
* @param bldg -
* the <code>Building</code> being checked. This value should
* not be <code>null</code>.
* @param positionMap -
* a <code>Hashtable</code> that maps the <code>Coords</code>
* positions or each unit in the game to a <code>Vector</code>
* of <code>Entity</code>s at that position. This value should
* not be <code>null</code>.
* @param coords -
* the <code>Coords</code> of the building hex to be checked
* @return <code>true</code> if the building collapsed.
*/
public boolean checkForCollapse(Building bldg, Hashtable<Coords, Vector<Entity>> positionMap, Coords coords, boolean checkBecauseOfDamage) {
// If the input is meaningless, do nothing and throw no exception.
if ((bldg == null) || (positionMap == null) || positionMap.isEmpty() || (coords == null) || !bldg.isIn(coords) || !bldg.hasCFIn(coords)) {
return false;
}
// Get the building's current CF.
final int currentCF = bldg.getCurrentCF(coords);
// Track all units that fall into the building's basement by Coords.
Hashtable<Coords, Vector<Entity>> basementMap = new Hashtable<Coords, Vector<Entity>>();
// look for a collapse.
boolean collapse = false;
if (checkBecauseOfDamage && (currentCF <= 0)) {
collapse = true;
}
// Get the Vector of Entities at these coordinates.
final Vector<Entity> vector = positionMap.get(coords);
// Are there any Entities at these coords?
if (vector != null) {
// How many levels does this building have in this hex?
final IHex curHex = game.getBoard().getHex(coords);
final int numFloors = Math.max(0, curHex.terrainLevel(Terrains.BLDG_ELEV));
final int bridgeEl = curHex.terrainLevel(Terrains.BRIDGE_ELEV);
int numLoads = numFloors;
if (bridgeEl != ITerrain.LEVEL_NONE) {
numLoads++;
}
if (numLoads < 1) {
System.err.println("Check for collapse: hex " + coords.toString() + " has no bridge or building");
return false;
}
// Track the load of each floor (and of the roof) separately.
// Track all units that fall into the basement in this hex.
// N.B. don't track the ground floor, the first floor is at
// index 0, the second is at index 1, etc., and the roof is
// at index (numFloors-1).
// if bridge is present, bridge will be numFloors
int[] loads = new int[numLoads];
Vector<Entity> basement = new Vector<Entity>();
for (int loop = 0; loop < numLoads; loop++) {
loads[loop] = 0;
}
// Walk through the entities in this position.
Enumeration<Entity> entities = vector.elements();
while (!collapse && entities.hasMoreElements()) {
final Entity entity = entities.nextElement();
final int entityElev = entity.getElevation();
if (entityElev != bridgeEl) {
// Ignore entities not *inside* the building
if (entityElev > numFloors) {
continue;
}
}
if ((entity.getMovementMode() == IEntityMovementMode.HYDROFOIL) || (entity.getMovementMode() == IEntityMovementMode.NAVAL) || (entity.getMovementMode() == IEntityMovementMode.SUBMARINE) || (entity.getMovementMode() == IEntityMovementMode.INF_UMU)) {
continue; // under the bridge even at same level
}
// Add the weight of a Mek or tank to the correct floor.
if ((entity instanceof Mech) || (entity instanceof Tank)) {
int load = (int) entity.getWeight();
int floor = entityElev;
if (floor == bridgeEl) {
floor = numLoads;
}
// Entities on the ground floor may fall into the
// basement, but they won't collapse the building.
if ((numFloors > 0) && (floor == 0) && (load > currentCF)) {
basement.addElement(entity);
} else if (floor > 0) {
// If the load on any floor but the ground floor
// exceeds the building's current CF it collapses.
floor--;
loads[floor] += load;
if (loads[floor] > currentCF) {
collapse = true;
}
} // End not-ground-floor
} // End increase-load
} // Handle the next entity.
// Track all entities that fell into the basement.
if (!basement.isEmpty()) {
basementMap.put(coords, basement);
}
} // End have-entities-here
// Collapse the building if the flag is set.
if (collapse) {
Report r = new Report(2375, Report.PUBLIC);
r.add(bldg.getName());
addReport(r);
collapseBuilding(bldg, positionMap, coords);
}
// Otherwise, did any entities fall into the basement?
else if (!basementMap.isEmpty()) {
// TODO: implement basements
}
// Return true if the building collapsed.
return collapse;
} // End private boolean checkForCollapse( Building, Hashtable )
public void collapseBuilding(Building bldg, Hashtable<Coords, Vector<Entity>> positionMap, Coords coords) {
collapseBuilding(bldg, positionMap, coords, true);
}
/**
* Collapse a building hex. Inflict the appropriate amount of damage on all
* entities in the building. Update all clients.
*
* @param bldg -
* the <code>Building</code> that has collapsed.
* @param positionMap -
* a <code>Hashtable</code> that maps the <code>Coords</code>
* positions or each unit in the game to a <code>Vector</code>
* of <code>Entity</code>s at that position. This value should
* not be <code>null</code>.
* @param coords -
* The <code>Coords></code> of the building hex that has collapsed
* @param collapseAll -
* A <code>boolean</code> indicating wether or not this collapse
* of a hex should be able to collapse the whole building
*/
public void collapseBuilding(Building bldg, Hashtable<Coords, Vector<Entity>> positionMap, Coords coords, boolean collapseAll) {
if (!bldg.hasCFIn(coords)) {
return;
}
// Loop through the hexes in the building, and apply
// damage to all entities inside or on top of the building.
Report r;
final int phaseCF = bldg.getPhaseCF(coords);
// Get the Vector of Entities at these coordinates.
final Vector<Entity> vector = positionMap.get(coords);
// Are there any Entities at these coords?
if (vector != null) {
// How many levels does this building have in this hex?
final IHex curHex = game.getBoard().getHex(coords);
final int bridgeEl = curHex.terrainLevel(Terrains.BRIDGE_ELEV);
final int numFloors = Math.max(bridgeEl, curHex.terrainLevel(Terrains.BLDG_ELEV));
// Now collapse the building in this hex, so entities fall to
// the ground
bldg.setCurrentCF(0, coords);
bldg.setPhaseCF(0, coords);
send(createCollapseBuildingPacket(coords));
game.getBoard().collapseBuilding(coords);
// Sort in elevation order
Collections.sort(vector, new Comparator<Entity>() {
public int compare(Entity a, Entity b) {
if (a.getElevation() > b.getElevation()) {
return -1;
} else if (a.getElevation() > b.getElevation()) {
return 1;
}
return 0;
}
});
// Walk through the entities in this position.
Enumeration<Entity> entities = vector.elements();
while (entities.hasMoreElements()) {
final Entity entity = entities.nextElement();
// final int entityElev = entity.elevationOccupied( curHex
// );
int floor = entity.getElevation();
// Ignore units above the building / bridge.
if (floor > numFloors) {
continue;
}
// Treat units on the roof like
// they were in the top floor.
if (floor == numFloors) {
floor--;
}
// Calculate collapse damage for this entity.
int damage = (int) Math.ceil(phaseCF * (numFloors - floor) / 10.0);
// Infantry suffer more damage.
if (entity instanceof Infantry) {
if ((entity instanceof BattleArmor) || ((Infantry)entity).isMechanized()) {
damage *= 2;
} else {
damage *= 3;
}
}
// Apply collapse damage the entity.
// ASSUMPTION: use 5 point clusters.
r = new Report(6455);
r.indent();
r.subject = entity.getId();
r.add(entity.getDisplayName());
r.add(damage);
addReport(r);
int remaining = damage;
int cluster = damage;
if ((entity instanceof BattleArmor) || (entity instanceof Mech) || (entity instanceof Tank)) {
cluster = 5;
}
while (remaining > 0) {
int next = Math.min(cluster, remaining);
// In
// www.classicbattletech.com/PDF/AskPMForumArchiveandFAQ.pdf,
// pg. 18, Randall Bills says that all damage from a
// collapsing building is applied to the front.
HitData hit = entity.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT);
hit.setGeneralDamageType(HitData.DAMAGE_PHYSICAL);
addReport(damageEntity(entity, hit, next));
remaining -= next;
}
addReport(new Report(1210, Report.PUBLIC));
// TODO: Why are dead entities showing up on firing phase?
// all entities should fall
// TODO: implement basements, then fall into it.
floor = entity.getElevation();
if ((floor > 0) || (floor == bridgeEl)) {
// ASSUMPTION: PSR to avoid pilot damage
// should use mods for entity damage and
// 20+ points of collapse damage (if any).
PilotingRollData psr = entity.getBasePilotingRoll();
entity.addPilotingModifierForTerrain(psr, coords);
if (damage >= 20) {
psr.addModifier(1, "20+ damage");
}
addReport(doEntityFallsInto(entity, coords, coords, psr));
}
// Update this entity.
// ASSUMPTION: this is the correct thing to do.
entityUpdate(entity.getId());
} // Handle the next entity.
} // End have-entities-here.
else {
// Update the building.
bldg.setCurrentCF(0, coords);
bldg.setPhaseCF(0, coords);
send(createCollapseBuildingPacket(coords));
game.getBoard().collapseBuilding(coords);
}
// if more than half of the hexes are gone, collapse all
if (bldg.getCollapsedHexCount() > bldg.getOriginalHexCount()/2) {
for (Enumeration<Coords> coordsEnum = bldg.getCoords();coordsEnum.hasMoreElements();) {
coords = coordsEnum.nextElement();
collapseBuilding(bldg, game.getPositionMap(), coords, false);
}
}
} // End private void collapseBuilding( Building )
/**
* Tell the clients to replace the given building with rubble hexes.
*
* @param bldg -
* the <code>Building</code> that has collapsed.
* @return a <code>Packet</code> for the command.
*/
private Packet createCollapseBuildingPacket(Coords coords) {
Vector<Coords> coordsV = new Vector<Coords>();
coordsV.addElement(coords);
return createCollapseBuildingPacket(coordsV);
}
/**
* Tell the clients to replace the given building hexes with rubble hexes.
*
* @param buildings -
* a <code>Vector</code> of <code>Building</code>s that has
* collapsed.
* @return a <code>Packet</code> for the command.
*/
private Packet createCollapseBuildingPacket(Vector<Coords> coords) {
return new Packet(Packet.COMMAND_BLDG_COLLAPSE, coords);
}
/**
* Tell the clients to update the CFs of the given buildings.
*
* @param buildings -
* a <code>Vector</code> of <code>Building</code>s that need
* to be updated.
* @return a <code>Packet</code> for the command.
*/
private Packet createUpdateBuildingCFPacket(Vector<Building> buildings) {
return new Packet(Packet.COMMAND_BLDG_UPDATE_CF, buildings);
}
/**
* Apply this phase's damage to all buildings. Buildings may collapse due to
* damage.
*/
private void applyBuildingDamage() {
// Walk through the buildings in the game.
// Build the collapse and update vectors as you go.
// N.B. never, NEVER, collapse buildings while you are walking through
// the Enumeration from megamek.common.Board#getBuildings.
Map<Building, Vector<Coords>> collapse = new HashMap<Building, Vector<Coords>>();
Map<Building, Vector<Coords>> update = new HashMap<Building, Vector<Coords>>();
Enumeration<Building> buildings = game.getBoard().getBuildings();
while (buildings.hasMoreElements()) {
Building bldg = buildings.nextElement();
Vector<Coords> collapseCoords = new Vector<Coords>();
Vector<Coords> updateCoords = new Vector<Coords>();
Enumeration<Coords> buildingCoords = bldg.getCoords();
while (buildingCoords.hasMoreElements()) {
Coords coords = buildingCoords.nextElement();
// If the CF is zero, the building should fall.
if (bldg.getCurrentCF(coords) == 0) {
collapseCoords.addElement(coords);
}
// If the building took damage this round, update it.
else if (bldg.getPhaseCF(coords) != bldg.getCurrentCF(coords)) {
bldg.setPhaseCF(bldg.getCurrentCF(coords), coords);
updateCoords.addElement(coords);
}
}
collapse.put(bldg, collapseCoords);
update.put(bldg, updateCoords);
} // Handle the next building
// If we have any buildings to collapse, collapse them now.
if (!collapse.isEmpty()) {
// Get the position map of all entities in the game.
Hashtable<Coords, Vector<Entity>> positionMap = game.getPositionMap();
// Walk through the hexes that have collapsed.
for (Building bldg : collapse.keySet()) {
Vector <Coords> coordsVector = collapse.get(bldg);
for (Coords coords : coordsVector) {
Report r = new Report(6460, Report.PUBLIC);
r.add(bldg.getName());
addReport(r);
collapseBuilding(bldg, positionMap, coords);
}
}
}
// check for buildings which should collapse due to being overloaded now
// CF is reduced
if (!update.isEmpty()) {
Hashtable<Coords, Vector<Entity>> positionMap = game.getPositionMap();
for (Building bldg : update.keySet()) {
Vector<Coords> updateCoords = update.get(bldg);
Vector<Coords> coordsToRemove = new Vector<Coords>();
for (Coords coords : updateCoords) {
if (checkForCollapse(bldg, positionMap, coords, false)) {
coordsToRemove.add(coords);
}
}
updateCoords.removeAll(coordsToRemove);
update.put(bldg, updateCoords);
}
}
// If we have any buildings to update, send the message.
if (!update.isEmpty()) {
sendChangedCFBuildings(new Vector<Building>(update.keySet()));
}
}
/**
* Apply the given amount of damage to the building. Please note, this
* method does <b>not</b> apply any damage to units inside the building,
* update the clients, or check for the building's collapse. <p/> A default
* message will be used to describe why the building took the damage.
*
* @param bldg -
* the <code>Building</code> that has been damaged. This value
* should not be <code>null</code>, but no exception will
* occur.
* @param damage -
* the <code>int</code> amount of damage.
* @param coords - the <code>Coords</code> of the building hex to be damaged
* @return a <code>Report</code> to be shown to the players.
*/
public Vector<Report> damageBuilding(Building bldg, int damage, Coords coords) {
final String defaultWhy = " absorbs ";
return damageBuilding(bldg, damage, defaultWhy, coords);
}
/**
* Apply the given amount of damage to the building. Please note, this
* method does <b>not</b> apply any damage to units inside the building,
* update the clients, or check for the building's collapse.
*
* @param bldg -
* the <code>Building</code> that has been damaged. This value
* should not be <code>null</code>, but no exception will
* occur.
* @param damage -
* the <code>int</code> amount of damage.
* @param why -
* the <code>String</code> message that describes why the
* building took the damage.
* @param coords - the <code>Coords</code> of the building hex to be damaged
* @return a <code>Report</code> to be shown to the players.
*/
public Vector<Report> damageBuilding(Building bldg, int damage, String why,
Coords coords) {
Vector<Report> vPhaseReport = new Vector<Report>();
Report r = new Report(1210, Report.PUBLIC);
r.newlines = 0;
// Do nothing if no building or no damage was passed.
if ((bldg != null) && (damage > 0)) {
int curCF = bldg.getCurrentCF(coords);
final int startingCF = curCF;
curCF -= Math.min(curCF, damage);
bldg.setCurrentCF(curCF, coords);
r.messageId = 3435;
r.add(bldg.getName());
r.add(why);
r.add(damage);
r.newlines = 1;
// If the CF is zero, the building should fall.
if ((curCF == 0) && (startingCF != 0)) {
if (bldg instanceof FuelTank) {
// If this is a fuel tank, we'll give it its own message.
r.messageId = 3441;
r.type = Report.PUBLIC;
vPhaseReport.add(r);
// ...But we ALSO need to blow up everything nearby.
// Bwahahahahaha...
r = new Report(3560);
r.type = Report.PUBLIC;
r.newlines = 1;
vPhaseReport.add(r);
Vector<Report> vRep = new Vector<Report>();
doExplosion(((FuelTank) bldg).getMagnitude(), 10, false, bldg.getCoords().nextElement(), true, vRep, null);
Report.indentAll(vRep, 2);
vPhaseReport.addAll(vRep);
return vPhaseReport;
}
if (bldg.getType() == Building.WALL) {
r.messageId = 3442;
} else {
r.messageId = 3440;
}
}
vPhaseReport.add(r);
}
Report.indentAll(vPhaseReport, 2);
return vPhaseReport;
}
public void sendChangedCFBuildings(Vector<Building> buildings) {
send(createUpdateBuildingCFPacket(buildings));
}
/**
* Receives an packet to unload entityis stranded on immobile transports,
* and queue all valid requests for execution. If all players that have
* stranded entities have answered, executes the pending requests and end
* the current turn.
*/
private void receiveUnloadStranded(Packet packet, int connId) {
GameTurn.UnloadStrandedTurn turn = null;
final Player player = game.getPlayer(connId);
int[] entityIds = (int[]) packet.getObject(0);
Vector<Player> declared = null;
Player other = null;
Enumeration<EntityAction> pending = null;
UnloadStrandedAction action = null;
Entity entity = null;
// Is this the right phase?
if (game.getPhase() != IGame.Phase.PHASE_MOVEMENT) {
System.err.println("error: server got unload stranded packet in wrong phase");
return;
}
// Are we in an "unload stranded entities" turn?
if (game.getTurn() instanceof GameTurn.UnloadStrandedTurn) {
turn = (GameTurn.UnloadStrandedTurn) game.getTurn();
} else {
System.err.println("error: server got unload stranded packet out of sequence");
StringBuffer message = new StringBuffer();
message.append(player.getName()).append(" should not be sending 'unload stranded entity' packets at this time.");
sendServerChat(message.toString());
return;
}
// Can this player act right now?
if (!turn.isValid(connId, game)) {
System.err.println("error: server got unload stranded packet from invalid player");
StringBuffer message = new StringBuffer();
message.append(player.getName()).append(" should not be sending 'unload stranded entity' packets.");
sendServerChat(message.toString());
return;
}
// Did the player already send an 'unload' request?
// N.B. we're also building the list of players who
// have declared their "unload stranded" actions.
declared = new Vector<Player>();
pending = game.getActions();
while (pending.hasMoreElements()) {
action = (UnloadStrandedAction) pending.nextElement();
if (action.getPlayerId() == connId) {
System.err.println("error: server got multiple unload stranded packets from player");
StringBuffer message = new StringBuffer();
message.append(player.getName()).append(" should not send multiple 'unload stranded entity' packets.");
sendServerChat(message.toString());
return;
}
// This player is not from the current connection.
// Record this player to determine if this turn is done.
other = game.getPlayer(action.getPlayerId());
if (!declared.contains(other)) {
declared.addElement(other);
}
} // Handle the next "unload stranded" action.
// Make sure the player selected at least *one* valid entity ID.
boolean foundValid = false;
for (int index = 0; (null != entityIds) && (index < entityIds.length); index++) {
entity = game.getEntity(entityIds[index]);
if (!game.getTurn().isValid(connId, entity, game)) {
System.err.println("error: server got unload stranded packet for invalid entity");
StringBuffer message = new StringBuffer();
message.append(player.getName()).append(" can not unload stranded entity ");
if (null == entity) {
message.append('#').append(entityIds[index]);
} else {
message.append(entity.getDisplayName());
}
message.append(" at this time.");
sendServerChat(message.toString());
} else {
foundValid = true;
game.addAction(new UnloadStrandedAction(connId, entityIds[index]));
}
}
// Did the player choose not to unload any valid stranded entity?
if (!foundValid) {
game.addAction(new UnloadStrandedAction(connId, Entity.NONE));
}
// Either way, the connection's player has now declared.
declared.addElement(player);
// Are all players who are unloading entities done? Walk
// through the turn's stranded entities, and look to see
// if their player has finished their turn.
entityIds = turn.getEntityIds();
for (int entityId : entityIds) {
entity = game.getEntity(entityId);
other = entity.getOwner();
if (!declared.contains(other)) {
// At least one player still needs to declare.
return;
}
}
// All players have declared whether they're unloading stranded units.
// Walk the list of pending actions and unload the entities.
pending = game.getActions();
while (pending.hasMoreElements()) {
action = (UnloadStrandedAction) pending.nextElement();
// Some players don't want to unload any stranded units.
if (Entity.NONE != action.getEntityId()) {
entity = game.getEntity(action.getEntityId());
if (null == entity) {
// After all this, we couldn't find the entity!!!
System.err.print("error: server could not find stranded entity #");
System.err.print(action.getEntityId());
System.err.println(" to unload!!!");
} else {
// Unload the entity. Get the unit's transporter.
Entity transporter = game.getEntity(entity.getTransportId());
unloadUnit(transporter, entity, transporter.getPosition(), transporter.getFacing(), transporter.getElevation());
}
}
} // Handle the next pending unload action
// Clear the list of pending units and move to the next turn.
game.resetActions();
changeToNextTurn();
}
/**
* For all current artillery attacks in the air from this entity with this
* weapon, clear the list of spotters. Needed because firing another round
* before first lands voids spotting.
*
* @param entityID
* the <code>int</code> id of the entity
* @param weaponID
* the <code>int</code> id of the weapon
*/
private void clearArtillerySpotters(int entityID, int weaponID) {
for (Enumeration<AttackHandler> i = game.getAttacks(); i.hasMoreElements();) {
WeaponHandler wh = (WeaponHandler) i.nextElement();
if ((wh.waa instanceof ArtilleryAttackAction) && (wh.waa.getEntityId() == entityID) && (wh.waa.getWeaponId() == weaponID)) {
ArtilleryAttackAction aaa = (ArtilleryAttackAction) wh.waa;
aaa.setSpotterIds(null);
}
}
}
/**
* Credits a Kill for an entity, if the target got killed.
*
* @param target
* The <code>Entity</code> that got killed.
* @param attacker
* The <code>Entity</code> that did the killing.
*/
public void creditKill(Entity target, Entity attacker) {
if ((target.isDoomed() || target.getCrew().isDoomed()) && !target.getGaveKillCredit()) {
attacker.addKill(target);
}
}
/**
* pre-treats a physical attack
*
* @param aaa
* The <code>AbstractAttackAction</code> of the physical attack
* to pre-treat
* @return The <code>PhysicalResult</code> of that action, including
* possible damage.
*/
private PhysicalResult preTreatPhysicalAttack(AbstractAttackAction aaa) {
final Entity ae = game.getEntity(aaa.getEntityId());
int damage = 0;
PhysicalResult pr = new PhysicalResult();
ToHitData toHit = new ToHitData();
pr.roll = Compute.d6(2);
pr.aaa = aaa;
if (aaa instanceof BrushOffAttackAction) {
BrushOffAttackAction baa = (BrushOffAttackAction) aaa;
int arm = baa.getArm();
baa.setArm(BrushOffAttackAction.LEFT);
toHit = BrushOffAttackAction.toHit(game, aaa.getEntityId(), aaa.getTarget(game), BrushOffAttackAction.LEFT);
baa.setArm(BrushOffAttackAction.RIGHT);
pr.toHitRight = BrushOffAttackAction.toHit(game, aaa.getEntityId(), aaa.getTarget(game), BrushOffAttackAction.RIGHT);
damage = BrushOffAttackAction.getDamageFor(ae, BrushOffAttackAction.LEFT);
pr.damageRight = BrushOffAttackAction.getDamageFor(ae, BrushOffAttackAction.RIGHT);
baa.setArm(arm);
pr.rollRight = Compute.d6(2);
} else if (aaa instanceof ChargeAttackAction) {
ChargeAttackAction caa = (ChargeAttackAction) aaa;
toHit = caa.toHit(game);
if (caa.getTarget(game) instanceof Entity) {
Entity target = (Entity) caa.getTarget(game);
damage = ChargeAttackAction.getDamageFor(ae, target, game.getOptions().booleanOption("tacops_charge_damage"), toHit.getMoS());
} else {
damage = ChargeAttackAction.getDamageFor(ae);
}
} else if (aaa instanceof ClubAttackAction) {
ClubAttackAction caa = (ClubAttackAction) aaa;
toHit = caa.toHit(game);
damage = ClubAttackAction.getDamageFor(ae, caa.getClub(), (caa.getTarget(game) instanceof Infantry) && !(caa.getTarget(game) instanceof BattleArmor));
} else if (aaa instanceof DfaAttackAction) {
DfaAttackAction daa = (DfaAttackAction) aaa;
toHit = daa.toHit(game);
damage = DfaAttackAction.getDamageFor(ae, (daa.getTarget(game) instanceof Infantry) && !(daa.getTarget(game) instanceof BattleArmor));
} else if (aaa instanceof KickAttackAction) {
KickAttackAction kaa = (KickAttackAction) aaa;
toHit = kaa.toHit(game);
damage = KickAttackAction.getDamageFor(ae, kaa.getLeg(), (kaa.getTarget(game) instanceof Infantry) && !(kaa.getTarget(game) instanceof BattleArmor));
} else if (aaa instanceof ProtomechPhysicalAttackAction) {
ProtomechPhysicalAttackAction paa = (ProtomechPhysicalAttackAction) aaa;
toHit = paa.toHit(game);
damage = ProtomechPhysicalAttackAction.getDamageFor(ae);
} else if (aaa instanceof PunchAttackAction) {
PunchAttackAction paa = (PunchAttackAction) aaa;
int arm = paa.getArm();
int damageRight = 0;
paa.setArm(PunchAttackAction.LEFT);
toHit = paa.toHit(game);
paa.setArm(PunchAttackAction.RIGHT);
ToHitData toHitRight = paa.toHit(game);
damage = PunchAttackAction.getDamageFor(ae, PunchAttackAction.LEFT, (paa.getTarget(game) instanceof Infantry) && !(paa.getTarget(game) instanceof BattleArmor));
damageRight = PunchAttackAction.getDamageFor(ae, PunchAttackAction.RIGHT, (paa.getTarget(game) instanceof Infantry) && !(paa.getTarget(game) instanceof BattleArmor));
paa.setArm(arm);
// If we're punching while prone (at a Tank,
// duh), then we can only use one arm.
if (ae.isProne()) {
double oddsLeft = Compute.oddsAbove(toHit.getValue());
double oddsRight = Compute.oddsAbove(toHitRight.getValue());
// Use the best attack.
if (oddsLeft * damage > oddsRight * damageRight) {
paa.setArm(PunchAttackAction.LEFT);
} else {
paa.setArm(PunchAttackAction.RIGHT);
}
}
pr.damageRight = damageRight;
pr.toHitRight = toHitRight;
pr.rollRight = Compute.d6(2);
} else if (aaa instanceof PushAttackAction) {
PushAttackAction paa = (PushAttackAction) aaa;
toHit = paa.toHit(game);
} else if (aaa instanceof TripAttackAction) {
TripAttackAction paa = (TripAttackAction) aaa;
toHit = paa.toHit(game);
} else if (aaa instanceof LayExplosivesAttackAction) {
LayExplosivesAttackAction leaa = (LayExplosivesAttackAction) aaa;
toHit = leaa.toHit(game);
damage = LayExplosivesAttackAction.getDamageFor(ae);
} else if (aaa instanceof ThrashAttackAction) {
ThrashAttackAction taa = (ThrashAttackAction) aaa;
toHit = taa.toHit(game);
damage = ThrashAttackAction.getDamageFor(ae);
} else if (aaa instanceof JumpJetAttackAction) {
JumpJetAttackAction jaa = (JumpJetAttackAction) aaa;
toHit = jaa.toHit(game);
if (jaa.getLeg() == JumpJetAttackAction.BOTH) {
damage = JumpJetAttackAction.getDamageFor(ae, JumpJetAttackAction.LEFT);
pr.damageRight = JumpJetAttackAction.getDamageFor(ae, JumpJetAttackAction.LEFT);
} else {
damage = JumpJetAttackAction.getDamageFor(ae, jaa.getLeg());
pr.damageRight = 0;
}
ae.heatBuildup += (damage + pr.damageRight) / 3;
} else if (aaa instanceof GrappleAttackAction) {
GrappleAttackAction taa = (GrappleAttackAction) aaa;
toHit = taa.toHit(game);
} else if (aaa instanceof BreakGrappleAttackAction) {
BreakGrappleAttackAction taa = (BreakGrappleAttackAction) aaa;
toHit = taa.toHit(game);
} else if (aaa instanceof RamAttackAction) {
RamAttackAction raa = (RamAttackAction) aaa;
toHit = raa.toHit(game);
damage = RamAttackAction.getDamageFor((Aero) ae, (Aero) aaa.getTarget(game));
} else if (aaa instanceof TeleMissileAttackAction) {
TeleMissileAttackAction taa = (TeleMissileAttackAction) aaa;
toHit = taa.toHit(game);
damage = TeleMissileAttackAction.getDamageFor(ae);
} else if (aaa instanceof BAVibroClawAttackAction) {
BAVibroClawAttackAction bvca = (BAVibroClawAttackAction) aaa;
toHit = bvca.toHit(game);
damage = BAVibroClawAttackAction.getDamageFor(ae);
}
pr.toHit = toHit;
pr.damage = damage;
return pr;
}
/**
* Resolve a Physical Attack
*
* @param pr
* The <code>PhysicalResult</code> of the physical attack
* @param cen
* The <code>int</code> Entity Id of the entit's whose physical
* attack was last resolved
*/
private void resolvePhysicalAttack(PhysicalResult pr, int cen) {
AbstractAttackAction aaa = pr.aaa;
if (aaa instanceof PunchAttackAction) {
PunchAttackAction paa = (PunchAttackAction) aaa;
if (paa.getArm() == PunchAttackAction.BOTH) {
paa.setArm(PunchAttackAction.LEFT);
pr.aaa = paa;
resolvePunchAttack(pr, cen);
cen = paa.getEntityId();
paa.setArm(PunchAttackAction.RIGHT);
pr.aaa = paa;
resolvePunchAttack(pr, cen);
} else {
resolvePunchAttack(pr, cen);
cen = paa.getEntityId();
}
} else if (aaa instanceof KickAttackAction) {
resolveKickAttack(pr, cen);
cen = aaa.getEntityId();
} else if (aaa instanceof BrushOffAttackAction) {
BrushOffAttackAction baa = (BrushOffAttackAction) aaa;
if (baa.getArm() == BrushOffAttackAction.BOTH) {
baa.setArm(BrushOffAttackAction.LEFT);
pr.aaa = baa;
resolveBrushOffAttack(pr, cen);
cen = baa.getEntityId();
baa.setArm(BrushOffAttackAction.RIGHT);
pr.aaa = baa;
resolveBrushOffAttack(pr, cen);
} else {
resolveBrushOffAttack(pr, cen);
cen = baa.getEntityId();
}
} else if (aaa instanceof ThrashAttackAction) {
resolveThrashAttack(pr, cen);
cen = aaa.getEntityId();
} else if (aaa instanceof ProtomechPhysicalAttackAction) {
resolveProtoAttack(pr, cen);
cen = aaa.getEntityId();
} else if (aaa instanceof ClubAttackAction) {
resolveClubAttack(pr, cen);
cen = aaa.getEntityId();
} else if (aaa instanceof PushAttackAction) {
resolvePushAttack(pr, cen);
cen = aaa.getEntityId();
} else if (aaa instanceof ChargeAttackAction) {
resolveChargeAttack(pr, cen);
cen = aaa.getEntityId();
} else if (aaa instanceof DfaAttackAction) {
resolveDfaAttack(pr, cen);
cen = aaa.getEntityId();
} else if (aaa instanceof LayExplosivesAttackAction) {
resolveLayExplosivesAttack(pr, cen);
cen = aaa.getEntityId();
} else if (aaa instanceof TripAttackAction) {
resolveTripAttack(pr, cen);
cen = aaa.getEntityId();
} else if (aaa instanceof JumpJetAttackAction) {
resolveJumpJetAttack(pr, cen);
cen = aaa.getEntityId();
} else if (aaa instanceof GrappleAttackAction) {
resolveGrappleAttack(pr, cen);
cen = aaa.getEntityId();
} else if (aaa instanceof BreakGrappleAttackAction) {
resolveBreakGrappleAttack(pr, cen);
cen = aaa.getEntityId();
} else if (aaa instanceof RamAttackAction) {
resolveRamAttack(pr, cen);
cen = aaa.getEntityId();
} else if (aaa instanceof TeleMissileAttackAction) {
resolveTeleMissileAttack(pr, cen);
cen = aaa.getEntityId();
} else if (aaa instanceof BAVibroClawAttackAction){
resolveBAVibroClawAttack(pr, cen);
cen = aaa.getEntityId();
} else {
// hmm, error.
}
// Not all targets are Entities.
Targetable target = game.getTarget(aaa.getTargetType(), aaa.getTargetId());
if (target instanceof Entity) {
creditKill((Entity) target, game.getEntity(cen));
}
}
/**
* Add any extreme gravity PSRs the entity gets due to its movement
*
* @param entity
* The <code>Entity</code> to check.
* @param step
* The last <code>MoveStep</code> of this entity
* @param curPos
* The current <code>Coords</code> of this entity
* @param cachedMaxMPExpenditure
* Server checks run/jump MP at start of move, as appropriate,
* caches to avoid mid-move change in MP causing erroneous grav
* check
*/
private void checkExtremeGravityMovement(Entity entity, MoveStep step, Coords curPos, int cachedMaxMPExpenditure) {
PilotingRollData rollTarget;
if (game.getPlanetaryConditions().getGravity() != 1) {
if (entity instanceof Mech) {
if ((step.getMovementType() == IEntityMovementType.MOVE_WALK) || (step.getMovementType() == IEntityMovementType.MOVE_VTOL_WALK) || (step.getMovementType() == IEntityMovementType.MOVE_RUN) || (step.getMovementType() == IEntityMovementType.MOVE_VTOL_RUN)) {
if (step.getMpUsed() > cachedMaxMPExpenditure) {
// We moved too fast, let's make PSR to see if we get
// damage
game.addExtremeGravityPSR(entity.checkMovedTooFast(step));
}
} else if (step.getMovementType() == IEntityMovementType.MOVE_JUMP) {
System.err.println("gravity move check jump: " + step.getMpUsed() + "/" + cachedMaxMPExpenditure);
System.err.flush();
if (step.getMpUsed() > cachedMaxMPExpenditure) {
// We jumped too far, let's make PSR to see if we get
// damage
game.addExtremeGravityPSR(entity.checkMovedTooFast(step));
} else if (game.getPlanetaryConditions().getGravity() > 1) {
// jumping in high g is bad for your legs
rollTarget = entity.getBasePilotingRoll(step.getParent().getLastStepMovementType());
entity.addPilotingModifierForTerrain(rollTarget, step);
rollTarget.append(new PilotingRollData(entity.getId(), 0, "jumped in high gravity"));
game.addExtremeGravityPSR(rollTarget);
}
}
} else if (entity instanceof Tank) {
if ((step.getMovementType() == IEntityMovementType.MOVE_WALK) || (step.getMovementType() == IEntityMovementType.MOVE_VTOL_WALK) || (step.getMovementType() == IEntityMovementType.MOVE_RUN) || (step.getMovementType() == IEntityMovementType.MOVE_VTOL_RUN)) {
// For Tanks, we need to check if the tank had
// more MPs because it was moving along a road.
if ((step.getMpUsed() > cachedMaxMPExpenditure) && !step.isOnlyPavement()) {
game.addExtremeGravityPSR(entity.checkMovedTooFast(step));
} else if (step.getMpUsed() > cachedMaxMPExpenditure + 1) {
// If the tank was moving on a road, he got a +1 bonus.
// N.B. The Ask Precentor Martial forum said that a 4/6
// tank on a road can move 5/7, **not** 5/8.
game.addExtremeGravityPSR(entity.checkMovedTooFast(step));
} // End tank-has-road-bonus
}
}
}
}
/**
* Damage the inner structure of a mech's leg / a tank's front. This only
* happens when the Entity fails an extreme Gravity PSR.
*
* @param entity
* The <code>Entity</code> to damage.
* @param damage
* The <code>int</code> amount of damage.
*/
private Vector<Report> doExtremeGravityDamage(Entity entity, int damage) {
Vector<Report> vPhaseReport = new Vector<Report>();
HitData hit;
if (entity instanceof BipedMech) {
for (int i = 6; i <= 7; i++) {
hit = new HitData(i);
vPhaseReport.addAll(damageEntity(entity, hit, damage, false, DamageType.NONE, true));
}
}
if (entity instanceof QuadMech) {
for (int i = 4; i <= 7; i++) {
hit = new HitData(i);
vPhaseReport.addAll(damageEntity(entity, hit, damage, false, DamageType.NONE, true));
}
} else if (entity instanceof Tank) {
hit = new HitData(Tank.LOC_FRONT);
vPhaseReport.addAll(damageEntity(entity, hit, damage, false, DamageType.NONE, true));
vPhaseReport.addAll(vehicleMotiveDamage((Tank) entity, 0));
}
return vPhaseReport;
}
/**
* Eject an Entity.
*
* @param entity
* The <code>Entity</code> to eject.
* @param autoEject
* The <code>boolean</code> state of the entity's auto-
* ejection system
* @return a <code>Vector</code> of report objects for the gamelog.
*/
public Vector<Report> ejectEntity(Entity entity, boolean autoEject) {
Vector<Report> vDesc = new Vector<Report>();
Report r;
// An entity can only eject it's crew once.
if (entity.getCrew().isEjected()) {
return vDesc;
}
// If the crew are already dead, don't bother
if (entity.isCarcass()) {
return vDesc;
}
// Mek pilots may get hurt during ejection,
// and run around the board afterwards.
if (entity instanceof Mech) {
PilotingRollData rollTarget = new PilotingRollData(entity.getId(), entity.getCrew().getPiloting(), "ejecting");
if (entity.isProne()) {
rollTarget.addModifier(5, "Mech is prone");
}
if (entity.getCrew().isUnconscious()) {
rollTarget.addModifier(3, "pilot unconscious");
}
if (autoEject) {
rollTarget.addModifier(1, "automatic ejection");
}
if (entity.getInternal(Mech.LOC_HEAD) < 3) {
rollTarget.addModifier(Math.min(3 - entity.getInternal(Mech.LOC_HEAD), 2), "Head Internal Structure Damage");
}
int facing = entity.getFacing();
Coords targetCoords = entity.getPosition().translated((facing + 3) % 6);
IHex targetHex = game.getBoard().getHex(targetCoords);
if (targetHex != null) {
if ((targetHex.terrainLevel(Terrains.WATER) > 0) && !targetHex.containsTerrain(Terrains.ICE)) {
rollTarget.addModifier(-1, "landing in water");
} else if (targetHex.containsTerrain(Terrains.ROUGH)) {
rollTarget.addModifier(0, "landing in rough");
} else if (targetHex.containsTerrain(Terrains.RUBBLE)) {
rollTarget.addModifier(0, "landing in rubble");
} else if (targetHex.terrainLevel(Terrains.WOODS) == 1) {
rollTarget.addModifier(2, "landing in light woods");
} else if (targetHex.terrainLevel(Terrains.WOODS) == 2) {
rollTarget.addModifier(3, "landing in heavy woods");
} else if (targetHex.terrainLevel(Terrains.WOODS) == 3) {
rollTarget.addModifier(4, "landing in ultra heavy woods");
} else if (targetHex.terrainLevel(Terrains.JUNGLE) == 1) {
rollTarget.addModifier(3, "landing in light jungle");
} else if (targetHex.terrainLevel(Terrains.JUNGLE) == 2) {
rollTarget.addModifier(5, "landing in heavy jungle");
} else if (targetHex.terrainLevel(Terrains.JUNGLE) == 3) {
rollTarget.addModifier(7, "landing in ultra heavy jungle");
} else if (targetHex.terrainLevel(Terrains.BLDG_ELEV) > 0) {
rollTarget.addModifier(targetHex.terrainLevel(Terrains.BLDG_ELEV), "landing in a building");
} else {
rollTarget.addModifier(-2, "landing in clear terrain");
}
} else {
rollTarget.addModifier(-2, "landing off the board");
}
if ( game.getPlanetaryConditions().getGravity() == 0 ){
rollTarget.addModifier(3,"Zero-G");
}else if ( game.getPlanetaryConditions().getGravity() < .8){
rollTarget.addModifier(2,"Low-G");
}else if ( game.getPlanetaryConditions().getGravity() > 1.2 ){
rollTarget.addModifier(2,"High-G");
}
if ( game.getPlanetaryConditions().getAtmosphere() == PlanetaryConditions.ATMO_VACUUM ){
rollTarget.addModifier(3,"Vacuum");
}else if ( game.getPlanetaryConditions().getAtmosphere() == PlanetaryConditions.ATMO_VHIGH){
rollTarget.addModifier(2,"Very High Atmosphere Pressure");
}else if ( game.getPlanetaryConditions().getAtmosphere() == PlanetaryConditions.ATMO_TRACE){
rollTarget.addModifier(2,"Trace atmosphere");
}
if ( (game.getPlanetaryConditions().getWeather() == PlanetaryConditions.WE_HEAVY_SNOW)
|| (game.getPlanetaryConditions().getWeather() == PlanetaryConditions.WE_ICE_STORM)
|| (game.getPlanetaryConditions().getWeather() == PlanetaryConditions.WE_DOWNPOUR)
|| (game.getPlanetaryConditions().getWindStrength() == PlanetaryConditions.WI_STRONG_GALE)){
rollTarget.addModifier(2,"Bad Weather");
}
if ( (game.getPlanetaryConditions().getWindStrength() >= PlanetaryConditions.WI_STORM)
|| ( (game.getPlanetaryConditions().getWeather() == PlanetaryConditions.WE_HEAVY_SNOW)
&& (game.getPlanetaryConditions().getWindStrength() == PlanetaryConditions.WI_STRONG_GALE))){
rollTarget.addModifier(3,"Really Bad Weather");
}
if (autoEject) {
r = new Report(6395);
r.subject = entity.getId();
r.addDesc(entity);
r.indent(2);
r.newlines = 0;
vDesc.addElement(r);
}
// okay, print the info
r = new Report(2180);
r.subject = entity.getId();
r.addDesc(entity);
r.add(rollTarget.getLastPlainDesc(), true);
r.indent(3);
vDesc.addElement(r);
// roll
final int diceRoll = Compute.d6(2);
r = new Report(2190);
r.subject = entity.getId();
r.add(rollTarget.getValueAsString());
r.add(rollTarget.getDesc());
r.add(diceRoll);
r.indent(4);
r.newlines = 0;
// create the MechWarrior in any case, for campaign tracking
MechWarrior pilot = new MechWarrior(entity);
pilot.setDeployed(true);
pilot.setId(getFreeEntityId());
pilot.setLanded(false);
game.addEntity(pilot.getId(), pilot);
send(createAddEntityPacket(pilot.getId()));
// make him not get a move this turn
pilot.setDone(true);
if (diceRoll < rollTarget.getValue()) {
r.choose(false);
vDesc.addElement(r);
Report.addNewline(vDesc);
if ( (rollTarget.getValue()-diceRoll) > 1){
vDesc.addAll(damageCrew(pilot, (rollTarget.getValue()-diceRoll)/2));
}
} else {
r.choose(true);
vDesc.addElement(r);
}
if (entity.getCrew().isDoomed()) {
vDesc.addAll(destroyEntity(pilot, "deadly ejection", false, false));
} else {
// Add the pilot as an infantry unit on the battlefield.
if (game.getBoard().contains(targetCoords)) {
pilot.setPosition(targetCoords);
/*
* Can pilots eject into water??? ASSUMPTION : They can
* (because they get a -1 mod to the PSR. // Did the pilot
* land in water? if ( game.getBoard().getHex(
* targetCoords).levelOf ( Terrain.WATER ) > 0 ) { //report
* missing desc.append("and the pilot ejects, but lands in
* water!!!\n"); //report missing desc.append(destroyEntity(
* pilot, "a watery grave", false )); } else { //report
* missing desc.append("and the pilot ejects safely!\n"); }
*/
// report safe ejection
r = new Report(6400);
r.subject = entity.getId();
r.indent(5);
vDesc.addElement(r);
//infantry have auto XTC gear so do pilots
/*if (game.getPlanetaryConditions().isVacuum()) {
// ended up in a vacuum
r = new Report(6405);
r.subject = entity.getId();
r.indent(3);
vDesc.addElement(r);
vDesc.addAll(destroyEntity(pilot, "explosive decompression", false, false));
}*/
// Update the entity
entityUpdate(pilot.getId());
// check if the pilot lands in a minefield
vDesc.addAll(doEntityDisplacementMinefieldCheck(pilot, entity.getPosition(), targetCoords, entity.getElevation()));
} else {
// ejects safely
r = new Report(6410);
r.subject = entity.getId();
r.indent(3);
vDesc.addElement(r);
/*
if (game.getPlanetaryConditions().isVacuum()) {
// landed in vacuum
r = new Report(6405);
r.subject = entity.getId();
r.indent(3);
vDesc.addElement(r);
vDesc.addAll(destroyEntity(pilot, "explosive decompression", false, false));
} else {*/
game.removeEntity(pilot.getId(), IEntityRemovalConditions.REMOVE_IN_RETREAT);
send(createRemoveEntityPacket(pilot.getId(), IEntityRemovalConditions.REMOVE_IN_RETREAT));
//}
}
if (game.getOptions().booleanOption("ejected_pilots_flee")) {
game.removeEntity(pilot.getId(), IEntityRemovalConditions.REMOVE_IN_RETREAT);
send(createRemoveEntityPacket(pilot.getId(), IEntityRemovalConditions.REMOVE_IN_RETREAT));
}
} // Pilot safely ejects.
} // End entity-is-Mek
else if (game.getBoard().contains(entity.getPosition()) && !game.getOptions().booleanOption("ejected_pilots_flee") && (entity instanceof Tank)) {
int crewSize = Math.max(1, (int) (14 + entity.getWeight()) / 15);
MechWarrior pilot = new MechWarrior(entity);
pilot.setChassis("Vehicle Crew");
pilot.setDeployed(true);
pilot.setId(getFreeEntityId());
pilot.initializeInternal(crewSize, Infantry.LOC_INFANTRY);
game.addEntity(pilot.getId(), pilot);
send(createAddEntityPacket(pilot.getId()));
// make him not get a move this turn
pilot.setDone(true);
// place on board
pilot.setPosition(entity.getPosition());
// Update the entity
entityUpdate(pilot.getId());
// check if the pilot lands in a minefield
vDesc.addAll(doEntityDisplacementMinefieldCheck(pilot, entity.getPosition(), entity.getPosition(), entity.getElevation()));
}
// Mark the entity's crew as "ejected".
entity.getCrew().setEjected(true);
if (entity instanceof VTOL) {
vDesc.addAll(crashVTOLorWiGE((VTOL) entity));
}
vDesc.addAll(destroyEntity(entity, "ejection", true, true));
// only remove the unit that ejected manually
if (!autoEject) {
game.removeEntity(entity.getId(), IEntityRemovalConditions.REMOVE_EJECTED);
send(createRemoveEntityPacket(entity.getId(), IEntityRemovalConditions.REMOVE_EJECTED));
}
return vDesc;
}
/**
* Abandon an Entity.
*
* @param entity
* The <code>Entity</code> to abandon.
* @return a <code>Vector</code> of report objects for the gamelog.
*/
public Vector<Report> abandonEntity(Entity entity) {
Vector<Report> vDesc = new Vector<Report>();
Report r;
// An entity can only eject it's crew once.
if (entity.getCrew().isEjected()) {
return vDesc;
}
if (entity.getCrew().isDoomed()) {
return vDesc;
}
// Don't make them abandon into vacuum
if (game.getPlanetaryConditions().isVacuum()) {
return vDesc;
}
Coords targetCoords = entity.getPosition();
if (entity instanceof Mech) {
// okay, print the info
r = new Report(2027);
r.subject = entity.getId();
r.add(entity.getCrew().getName());
r.addDesc(entity);
r.indent(3);
vDesc.addElement(r);
// create the MechWarrior in any case, for campaign tracking
MechWarrior pilot = new MechWarrior(entity);
pilot.getCrew().setUnconscious(entity.getCrew().isUnconscious());
pilot.setDeployed(true);
pilot.setId(getFreeEntityId());
game.addEntity(pilot.getId(), pilot);
send(createAddEntityPacket(pilot.getId()));
// make him not get a move this turn
pilot.setDone(true);
// Add the pilot as an infantry unit on the battlefield.
if (game.getBoard().contains(targetCoords)) {
pilot.setPosition(targetCoords);
}
// Update the entity
entityUpdate(pilot.getId());
// check if the pilot lands in a minefield
vDesc.addAll(doEntityDisplacementMinefieldCheck(pilot, entity.getPosition(), targetCoords, entity.getElevation()));
if (game.getOptions().booleanOption("ejected_pilots_flee")) {
game.removeEntity(pilot.getId(), IEntityRemovalConditions.REMOVE_IN_RETREAT);
send(createRemoveEntityPacket(pilot.getId(), IEntityRemovalConditions.REMOVE_IN_RETREAT));
}
} // End entity-is-Mek
else if (game.getBoard().contains(entity.getPosition()) && !game.getOptions().booleanOption("ejected_pilots_flee") && game.getOptions().booleanOption("vehicles_can_eject") && (entity instanceof Tank)) {
int crewSize = Math.max(1, (int) (14 + entity.getWeight()) / 15);
MechWarrior pilot = new MechWarrior(entity);
pilot.setChassis("Vehicle Crew");
pilot.setDeployed(true);
pilot.setId(getFreeEntityId());
pilot.initializeInternal(crewSize, Infantry.LOC_INFANTRY);
game.addEntity(pilot.getId(), pilot);
send(createAddEntityPacket(pilot.getId()));
// make him not get a move this turn
pilot.setDone(true);
// place on board
pilot.setPosition(entity.getPosition());
// Update the entity
entityUpdate(pilot.getId());
// check if the pilot lands in a minefield
vDesc.addAll(doEntityDisplacementMinefieldCheck(pilot, entity.getPosition(), entity.getPosition(), entity.getElevation()));
}
// Mark the entity's crew as "ejected".
entity.getCrew().setEjected(true);
return vDesc;
}
/**
* Checks if ejected Mechwarriors are eligible to be picked up, and if so,
* captures them or picks them up
*/
private void resolveMechWarriorPickUp() {
Report r;
// fetch all mechWarriors that are not picked up
Enumeration<Entity> mechWarriors = game.getSelectedEntities(new EntitySelector() {
public boolean accept(Entity entity) {
if (entity instanceof MechWarrior) {
MechWarrior mw = (MechWarrior) entity;
if ((mw.getPickedUpById() == Entity.NONE) && !mw.isDoomed() && (mw.getTransportId() == Entity.NONE)) {
return true;
}
}
return false;
}
});
// loop through them, check if they are in a hex occupied by another
// unit
while (mechWarriors.hasMoreElements()) {
boolean pickedUp = false;
MechWarrior e = (MechWarrior) mechWarriors.nextElement();
Enumeration<Entity> pickupEntities = game.getEntities(e.getPosition());
while (pickupEntities.hasMoreElements()) {
Entity pe = pickupEntities.nextElement();
if (pe.isDoomed() || pe.isShutDown() || pe.getCrew().isUnconscious()) {
continue;
}
if (!pickedUp && (pe.getOwnerId() == e.getOwnerId()) && (pe.getId() != e.getId())) {
if (pe instanceof MechWarrior) {
// MWs have a beer together
r = new Report(6415, Report.PUBLIC);
r.add(pe.getDisplayName());
addReport(r);
continue;
}
// Pick up the unit.
pe.pickUp(e);
// The picked unit is being carried by the loader.
e.setPickedUpById(pe.getId());
e.setPickedUpByExternalId(pe.getExternalId());
pickedUp = true;
r = new Report(6420, Report.PUBLIC);
r.add(e.getDisplayName());
r.addDesc(pe);
addReport(r);
break;
}
}
if (!pickedUp) {
Enumeration<Entity> pickupEnemyEntities = game.getEnemyEntities(e.getPosition(), e);
while (pickupEnemyEntities.hasMoreElements()) {
Entity pe = pickupEnemyEntities.nextElement();
if (pe.isDoomed() || pe.isShutDown() || pe.getCrew().isUnconscious()) {
continue;
}
if (pe instanceof MechWarrior) {
// MWs have a beer together
r = new Report(6415, Report.PUBLIC);
r.add(pe.getDisplayName());
addReport(r);
continue;
}
// Capture the unit.
pe.pickUp(e);
// The captured unit is being carried by the loader.
e.setCaptured(true);
e.setPickedUpById(pe.getId());
e.setPickedUpByExternalId(pe.getExternalId());
pickedUp = true;
r = new Report(6420, Report.PUBLIC);
r.add(e.getDisplayName());
r.addDesc(pe);
addReport(r);
break;
}
}
if (pickedUp) {
// Remove the picked-up unit from the screen.
e.setPosition(null);
// Update the loaded unit.
entityUpdate(e.getId());
}
}
}
/**
* destroy all wheeled and tracked Tanks that got displaced into water
*/
private void resolveSinkVees() {
Enumeration<Entity> sinkableTanks = game.getSelectedEntities(new EntitySelector() {
public boolean accept(Entity entity) {
if (entity.isOffBoard()) {
return false;
}
if ((entity instanceof Tank) && (entity.getPosition() != null) && ((entity.getMovementMode() == IEntityMovementMode.TRACKED) || (entity.getMovementMode() == IEntityMovementMode.WHEELED) || ((entity.getMovementMode() == IEntityMovementMode.HOVER) && entity.isImmobile())) && (game.getBoard().getHex(entity.getPosition()).terrainLevel(Terrains.WATER) > 0) && (entity.getElevation() < 0)) {
return true;
}
return false;
}
});
while (sinkableTanks.hasMoreElements()) {
Entity e = sinkableTanks.nextElement();
addReport(destroyEntity(e, "a watery grave", false));
}
}
/**
* let all Entities make their "break-free-of-swamp-stickyness" PSR
*/
private void doTryUnstuck() {
if (game.getPhase() != IGame.Phase.PHASE_MOVEMENT) {
return;
}
Report r;
Enumeration<Entity> stuckEntities = game.getSelectedEntities(new EntitySelector() {
public boolean accept(Entity entity) {
if (entity.isStuck()) {
return true;
}
return false;
}
});
PilotingRollData rollTarget;
while (stuckEntities.hasMoreElements()) {
Entity entity = stuckEntities.nextElement();
rollTarget = entity.getBasePilotingRoll();
entity.addPilotingModifierForTerrain(rollTarget);
// apart from swamp & liquid magma, -1 modifier
IHex hex = game.getBoard().getHex(entity.getPosition());
rollTarget.addModifier(hex.getUnstuckModifier(entity.getElevation()), "terrain");
// okay, print the info
r = new Report(2340);
r.addDesc(entity);
addReport(r);
// roll
final int diceRoll = Compute.d6(2);
r = new Report(2190);
r.subject = entity.getId();
r.add(rollTarget.getValueAsString());
r.add(rollTarget.getDesc());
r.add(diceRoll);
if (diceRoll < rollTarget.getValue()) {
r.choose(false);
} else {
r.choose(true);
entity.setStuck(false);
entity.setCanUnstickByJumping(false);
entity.setElevation(0);
entityUpdate(entity.getId());
}
addReport(r);
}
}
/**
* Remove all iNarc pods from all vehicles that did not move and shoot this
* round NOTE: this is not quite what the rules say, the player should be
* able to choose whether or not to remove all iNarc Pods that are attached.
*/
private void resolveVeeINarcPodRemoval() {
Enumeration<Entity> vees = game.getSelectedEntities(new EntitySelector() {
public boolean accept(Entity entity) {
if ((entity instanceof Tank) && (entity.mpUsed == 0)) {
return true;
}
return false;
}
});
boolean canSwipePods;
while (vees.hasMoreElements()) {
canSwipePods = true;
Entity entity = vees.nextElement();
for (int i = 0; i <= 5; i++) {
if (entity.weaponFiredFrom(i)) {
canSwipePods = false;
}
}
if (((Tank) entity).getStunnedTurns() > 0) {
canSwipePods = false;
}
if (canSwipePods && entity.hasINarcPodsAttached() && entity.getCrew().isActive()) {
entity.removeAllINarcPods();
Report r = new Report(2345);
r.addDesc(entity);
addReport(r);
}
}
}
/*
* //See note above where knownDeadEntities variable is declared private
* void deadEntitiesCleanup() { Entity en = null; for(Enumeration k =
* game.getGraveyardEntities(); k.hasMoreElements(); en = (Entity)
* k.nextElement()) { if (en != null) { if (!knownDeadEntities.contains(en)) {
* knownDeadEntities.add(en); } } } }
*/
/**
* remove Ice in the hex that's at the passed coords, and let entities fall
* into water below it, if there is water
*
* @param c
* the <code>Coords</code> of the hex where ice should be
* removed
* @return a <code>Vector<Report></code> for the phase report
*/
private Vector<Report> resolveIceBroken(Coords c) {
Vector<Report> vPhaseReport = new Vector<Report>();
IHex hex = game.getBoard().getHex(c);
hex.removeTerrain(Terrains.ICE);
sendChangedHex(c);
// if there is water below the ice
if (hex.terrainLevel(Terrains.WATER) > 0) {
// drop entities on the surface into the water
for (Enumeration<Entity> entities = game.getEntities(c); entities.hasMoreElements();) {
Entity e = entities.nextElement();
// If the unit is on the surface, and is no longer allowed in
// the hex
boolean isHoverOrWiGE = (e.getMovementMode() == IEntityMovementMode.HOVER) || (e.getMovementMode() == IEntityMovementMode.WIGE);
if ((e.getElevation() == 0) &&
!(hex.containsTerrain(Terrains.BLDG_ELEV, 0)) &&
!(isHoverOrWiGE && (e.getRunMP() >= 0)) &&
(e.getMovementMode() != IEntityMovementMode.INF_UMU) && !e.hasUMU()) {
vPhaseReport.addAll(doEntityFallsInto(e, c, c, new PilotingRollData(TargetRoll.AUTOMATIC_FAIL)));
}
}
}
return vPhaseReport;
}
/**
* melt any snow or ice in a hex, including checking for the effects of breaking through ice
*/
private Vector<Report> meltIceAndSnow(Coords c, int entityId) {
Vector<Report> vDesc = new Vector<Report>();
Report r;
IHex hex = game.getBoard().getHex(c);
r = new Report(3069);
r.indent(2);
r.subject = entityId;
vDesc.add(r);
if (hex.containsTerrain(Terrains.SNOW)) {
hex.removeTerrain(Terrains.SNOW);
sendChangedHex(c);
}
if (hex.containsTerrain(Terrains.ICE)) {
vDesc.addAll(resolveIceBroken(c));
}
//if we were not in water, then add mud
if (!hex.containsTerrain(Terrains.MUD) && !hex.containsTerrain(Terrains.WATER)) {
hex.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.MUD, 1));
sendChangedHex(c);
}
return vDesc;
}
/**
* check to see if a swamp hex becomes quicksand
*/
private Vector<Report> checkQuickSand(Coords c) {
Vector<Report> vDesc = new Vector<Report>();
Report r;
IHex hex = game.getBoard().getHex(c);
if(hex.terrainLevel(Terrains.SWAMP) == 1) {
if(Compute.d6(2) == 12) {
//better find a rope
hex.removeTerrain(Terrains.SWAMP);
hex.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.SWAMP,2));
sendChangedHex(c);
r = new Report(2440);
r.indent(1);
vDesc.add(r);
}
}
return vDesc;
}
/**
* check for vehicle fire, according to the MaxTech rules
*
* @param tank
* the <code>Tank</code> to be checked
* @param inferno
* a <code>boolean</code> parameter wether or not this check is
* because of inferno fire
*/
public Vector<Report> checkForVehicleFire(Tank tank, boolean inferno) {
Vector<Report> vPhaseReport = new Vector<Report>();
int boomroll = Compute.d6(2);
int penalty = 0;
switch (tank.getMovementMode()) {
case IEntityMovementMode.HOVER:
penalty = 4;
break;
case IEntityMovementMode.VTOL:
case IEntityMovementMode.WHEELED:
penalty = 2;
break;
}
if (inferno) {
boomroll = 12;
}
Report r = new Report(5250);
r.subject = tank.getId();
r.addDesc(tank);
r.add(8 - penalty);
r.add(boomroll);
if (boomroll + penalty < 8) {
// phew!
r.choose(true);
vPhaseReport.add(r);
} else {
// eek
if (!inferno) {
r.choose(false);
vPhaseReport.add(r);
}
if (boomroll + penalty < 10) {
addReport(vehicleMotiveDamage(tank, penalty - 1));
} else {
vPhaseReport.addAll(resolveVehicleFire(tank, false));
if (boomroll + penalty >= 12) {
r = new Report(5255);
r.subject = tank.getId();
r.indent(3);
vPhaseReport.add(r);
tank.setOnFire(inferno);
}
}
}
return vPhaseReport;
}
private Vector<Report> resolveVehicleFire(Tank tank, boolean existingStatus) {
Vector<Report> vPhaseReport = new Vector<Report>();
if (existingStatus && !tank.isOnFire()) {
return vPhaseReport;
}
for (int i = 0; i < tank.locations(); i++) {
if ((i == Tank.LOC_BODY) || ((tank instanceof VTOL) && (i == VTOL.LOC_ROTOR))) {
continue;
}
if (existingStatus && !tank.isLocationBurning(i)) {
continue;
}
HitData hit = new HitData(i);
int damage = Compute.d6(1);
vPhaseReport.addAll(damageEntity(tank, hit, damage));
if ((damage == 1) && existingStatus) {
tank.extinguishLocation(i);
}
}
return vPhaseReport;
}
private Vector<Report> vehicleMotiveDamage(Tank te, int modifier) {
Vector<Report> vDesc = new Vector<Report>();
Report r;
r = new Report(1210, Report.PUBLIC);
vDesc.add(r);
switch (te.getMovementMode()) {
case IEntityMovementMode.HOVER:
case IEntityMovementMode.HYDROFOIL:
modifier += 3;
break;
case IEntityMovementMode.WHEELED:
modifier += 2;
break;
case IEntityMovementMode.WIGE:
modifier += 4;
break;
case IEntityMovementMode.VTOL:
// VTOL don't roll, auto -1 MP
int nMP = te.getOriginalWalkMP();
if (nMP > 0) {
te.setOriginalWalkMP(nMP - 1);
}
if (nMP > 1) {
r = new Report(6660);
r.subject = te.getId();
vDesc.add(r);
} else {
r = new Report(6670);
r.subject = te.getId();
vDesc.add(r);
vDesc.addAll(crashVTOLorWiGE(te));
}
return vDesc;
}
if (game.getOptions().booleanOption("tacops_vehicle_effective")) {
modifier = Math.max(modifier - 1, 0);
}
int roll = Compute.d6(2) + modifier;
r = new Report(6305);
r.subject = te.getId();
r.add("movement system");
r.newlines = 0;
r.indent(3);
vDesc.add(r);
r = new Report(6310);
r.subject = te.getId();
r.add(roll);
r.newlines = 0;
vDesc.add(r);
r = new Report(3340);
r.add(modifier);
r.subject = te.getId();
vDesc.add(r);
if (roll <= 5) {
// no effect
r = new Report(6005);
r.subject = te.getId();
r.indent(3);
vDesc.add(r);
} else if (roll <= 7) {
// minor damage
r = new Report(6470);
r.subject = te.getId();
r.indent(3);
vDesc.add(r);
te.addMovementDamage(1);
} else if (roll <= 9) {
// moderate damage
r = new Report(6471);
r.subject = te.getId();
r.indent(3);
vDesc.add(r);
te.addMovementDamage(2);
int nMP = te.getOriginalWalkMP();
if (nMP > 0) {
te.setOriginalWalkMP(nMP - 1);
}
} else if (roll <= 11) {
// heavy damage
r = new Report(6472);
r.subject = te.getId();
r.indent(3);
vDesc.add(r);
te.addMovementDamage(3);
int nMP = te.getOriginalWalkMP();
if (nMP > 0) {
te.setOriginalWalkMP(nMP / 2);
}
} else {
r = new Report(6473);
r.subject = te.getId();
r.indent(3);
vDesc.add(r);
te.immobilize();
}
if ((te.getOriginalWalkMP() == 0) || te.isImmobile()) {
// Hovercraft reduced to 0MP over water sink
if (((te.getMovementMode() == IEntityMovementMode.HOVER) || ((te.getMovementMode() == IEntityMovementMode.WIGE) && (te.getElevation() == 0))) && (game.getBoard().getHex(te.getPosition()).terrainLevel(Terrains.WATER) > 0) && !game.getBoard().getHex(te.getPosition()).containsTerrain(Terrains.ICE)) {
vDesc.addAll(destroyEntity(te, "a watery grave", false));
}
if ((te instanceof VTOL) || ((te.getMovementMode() == IEntityMovementMode.WIGE) && (te.getElevation() > 0))) {
// report problem: add tab
vDesc.addAll(crashVTOLorWiGE(te));
}
}
return vDesc;
}
/**
* Add a whole lotta Reports to the players report queues as well as the
* Master report queue vPhaseReport.
*/
private void addReport(Vector<Report> reports) {
// Only bother with player reports if doing double blind.
if (doBlind()) {
for (IConnection conn : connections) {
Player p = game.getPlayer(conn.getId());
p.getTurnReport().addAll(filterReportVector(reports, p));
}
}
vPhaseReport.addAll(reports);
}
/**
* Add a single report to the report queue of all players and the master
* vPhaseReport queue
*/
private void addReport(Report report) {
// Only bother with player reports if doing double blind.
if (doBlind()) {
for (IConnection conn : connections) {
Player p = game.getPlayer(conn.getId());
p.getTurnReport().addElement(filterReport(report, p, false));
}
}
vPhaseReport.addElement(report);
}
/**
* New Round has started clear everyone's report queue
*/
private void clearReports() {
// Only bother with player reports if doing double blind.
if (doBlind()) {
for (IConnection conn : connections) {
Player p = game.getPlayer(conn.getId());
p.getTurnReport().removeAllElements();
}
}
vPhaseReport.removeAllElements();
}
/**
* make sure all the new lines that were added to the old vPhaseReport get
* added to all of the players filters
*/
private void addNewLines() {
// Only bother with player reports if doing double blind.
if (doBlind()) {
for (IConnection conn : connections) {
Player p = game.getPlayer(conn.getId());
Report.addNewline(p.getTurnReport());
}
}
Report.addNewline(vPhaseReport);
}
/**
* resolve an assault drop
*
* @param entity
* the <code>Entity</code> for which to resolve it
*/
public void doAssaultDrop(Entity entity) {
PilotingRollData psr;
if (entity instanceof Mech) {
psr = entity.getBasePilotingRoll();
} else {
psr = new PilotingRollData(entity.getId(), 4, "landing assault drop");
}
int roll = Compute.d6(2);
// check for a safe landing
Report r = new Report(2380);
r.subject = entity.getId();
r.add(entity.getDisplayName(), true);
r.add(psr.getValueAsString());
r.add(roll);
r.choose(roll >= psr.getValue());
addReport(r);
if (roll < psr.getValue()) {
int fallHeight = psr.getValue() - roll;
// determine where we really land
int distance = Compute.d6(fallHeight);
Coords c = Compute.scatterAssaultDrop(entity.getPosition(), fallHeight);
r = new Report(2385);
r.subject = entity.getId();
r.add(distance);
r.indent(3);
r.newlines = 0;
addReport(r);
if ((fallHeight >= 5) || !game.getBoard().contains(c)) {
r = new Report(2386);
addReport(r);
game.removeEntity(entity.getId(), IEntityRemovalConditions.REMOVE_NEVER_JOINED);
return;
}
entity.setPosition(c);
// do fall damage
if ((entity instanceof Mech) || (entity instanceof Protomech)) {
entity.setElevation(fallHeight);
addReport(doEntityFallsInto(entity, c, c, psr, true));
} else if (entity instanceof BattleArmor) {
for (int i = 1; i < entity.locations(); i++) {
HitData h = new HitData(i);
addReport(damageEntity(entity, h, Compute.d6(fallHeight)));
addNewLines();
}
} else if (entity instanceof Infantry) {
HitData h = new HitData(Infantry.LOC_INFANTRY);
addReport(damageEntity(entity, h, 1));
addNewLines();
}
}
// set entity to expected elevation
IHex hex = game.getBoard().getHex(entity.getPosition());
entity.setElevation(entity.elevationOccupied(hex) - hex.floor());
// finally, check for any stacking violations
Entity violated = Compute.stackingViolation(game, entity, entity.getPosition(), null);
if (violated != null) {
// handle this as accidental fall from above
entity.setElevation(violated.getElevation() + 2);
r = new Report(2390);
r.subject = entity.getId();
r.add(entity.getDisplayName(), true);
r.add(violated.getDisplayName(), true);
addReport(r);
addReport(doEntityFallsInto(entity, entity.getPosition(), entity.getPosition(), psr));
}
}
/**
* resolve assault drops for all entities
*/
void doAllAssaultDrops() {
for (Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
Entity e = i.nextElement();
if (e.isAssaultDropInProgress()) {
doAssaultDrop(e);
e.setLandedAssaultDrop();
}
}
}
/**
* do damage from magma
*
* @param en
* the affected <code>Entity</code>
* @param eruption
* <code>boolean</code> indicating wether or not this is
* because of an eruption
*/
void doMagmaDamage(Entity en, boolean eruption) {
if ((((en.getMovementMode() == IEntityMovementMode.VTOL) && (en.getElevation() > 0)) || (en.getMovementMode() == IEntityMovementMode.HOVER) || ((en.getMovementMode() == IEntityMovementMode.WIGE) && (en.getOriginalWalkMP() > 0) && !eruption)) && !en.isImmobile()) {
return;
}
Report r;
boolean isMech = en instanceof Mech;
if (isMech) {
r = new Report(2405);
} else {
r = new Report(2400);
}
r.addDesc(en);
r.subject = en.getId();
addReport(r);
if (isMech) {
HitData h;
for (int i = 0; i < en.locations(); i++) {
if (eruption || en.locationIsLeg(i) || en.isProne()) {
h = new HitData(i);
addReport(damageEntity(en, h, Compute.d6(2)));
}
}
} else {
addReport(destroyEntity(en, "fell into magma", false, false));
}
addNewLines();
}
/**
* sink any entities in quicksand in the current hex
*/
public void doSinkEntity(Entity en) {
Report r;
r = new Report(2445);
r.addDesc(en);
r.subject = en.getId();
addReport(r);
en.setElevation(en.getElevation() - 1);
//if this means the entity is below the ground, then bye-bye!
if(Math.abs(en.getElevation()) > en.getHeight()) {
addReport(destroyEntity(en, "quicksand"));
}
}
/**
* deal area saturation damage to an individual hex
*
* @param coords
* The hex being hit
* @param attackSource
* The location the attack came from. For hit table resolution
* @param damage
* Amount of damage to deal to each entity
* @param ammo
* The ammo type being used
* @param subjectId
* Subject for reports
* @param killer
* Who should be credited with kills
* @param exclude
* Entity that should take no damage (used for homing splash)
* @param flak
* Flak, hits flying units only, instead of flyers being immune
* @param altitude
* Absolute altitude for flak attack
*/
void artilleryDamageHex(Coords coords, Coords attackSource, int damage, AmmoType ammo, int subjectId, Entity killer, Entity exclude, boolean flak, int altitude, Vector<Report> vPhaseReport) {
IHex hex = game.getBoard().getHex(coords);
if (hex == null) {
return; // not on board.
}
int flakElevation = altitude - hex.surface();
Report r;
if (!flak) {
vPhaseReport.addAll(tryClearHex(coords, damage * 2, subjectId));
}
Building bldg = game.getBoard().getBuildingAt(coords);
int bldgAbsorbs = 0;
if ((bldg != null) && !(flak && (flakElevation > hex.terrainLevel(Terrains.BLDG_ELEV)))) {
bldgAbsorbs = bldg.getPhaseCF(coords) / 10;
if (!((ammo != null) && (ammo.getMunitionType() == AmmoType.M_FLECHETTE))) {
// damage the building
Vector<Report> buildingReport = damageBuilding(bldg, damage, coords);
for (Report report : buildingReport) {
report.subject = subjectId;
}
vPhaseReport.addAll(buildingReport);
addNewLines();
}
}
if (flak && ((flakElevation <= 0) || (flakElevation <= hex.terrainLevel(Terrains.BLDG_ELEV)) || (flakElevation == hex.terrainLevel(Terrains.BRIDGE_ELEV)))) {
// Flak in this hex would only hit landed units
return;
}
// get units in hex
for (Enumeration<Entity> impactHexHits = game.getEntities(coords); impactHexHits.hasMoreElements();) {
Entity entity = impactHexHits.nextElement();
int hits = damage;
ToHitData toHit = new ToHitData();
int cluster = 5;
// Check: is entity excluded?
if (entity == exclude) {
continue;
}
// Check: is entity inside building?
if ((bldg != null) && (bldgAbsorbs > 0) && (entity.getElevation() < hex.terrainLevel(Terrains.BLDG_ELEV))) {
cluster -= bldgAbsorbs;
if (entity instanceof Infantry) {
continue; // took its damage already from building damage
} else if (cluster <= 0) {
// entity takes no damage
r = new Report(6426);
r.subject = subjectId;
r.addDesc(entity);
vPhaseReport.add(r);
continue;
} else {
r = new Report(6425);
r.subject = subjectId;
r.add(bldgAbsorbs);
vPhaseReport.add(r);
}
}
if (flak) {
// Check: is entity not a VTOL in flight
if (!((entity instanceof VTOL) || (entity.getMovementMode() == IEntityMovementMode.VTOL))) {
continue;
}
// Check: is entity at correct elevation?
if (entity.getElevation() != flakElevation) {
continue;
}
} else {
// Check: is entity a VTOL in flight?
if ((entity instanceof VTOL) || (entity.getMovementMode() == IEntityMovementMode.VTOL)) {
// VTOLs take no damage from normal artillery unless landed
if ((entity.getElevation() != 0) && (entity.getElevation() != hex.terrainLevel(Terrains.BLDG_ELEV)) && (entity.getElevation() != hex.terrainLevel(Terrains.BRIDGE_ELEV))) {
continue;
}
}
}
// Work out hit table to use
if (attackSource != null) {
toHit.setSideTable(entity.sideTable(attackSource));
if ((ammo != null) && (ammo.getMunitionType() == AmmoType.M_CLUSTER) && attackSource.equals(coords)) {
if (entity instanceof Mech) {
toHit.setHitTable(ToHitData.HIT_ABOVE);
} else if (entity instanceof Tank) {
toHit.setSideTable(ToHitData.SIDE_FRONT);
toHit.addModifier(2, "cluster artillery hitting a Tank");
}
}
}
// convention infantry take x2 damage from AE weapons
if ((entity instanceof Infantry) && !(entity instanceof BattleArmor)) {
hits *= 2;
}
// Entity/ammo specific damage modifiers
if (ammo != null) {
if (ammo.getMunitionType() == AmmoType.M_CLUSTER) {
if (hex.containsTerrain(Terrains.FORTIFIED) && (entity instanceof Infantry) && !(entity instanceof BattleArmor)) {
hits *= 2;
}
} else if (ammo.getMunitionType() == AmmoType.M_FLECHETTE) {
// wheeled and hover tanks take movement critical
if ((entity instanceof Tank) && ((entity.getMovementMode() == IEntityMovementMode.WHEELED) || (entity.getMovementMode() == IEntityMovementMode.HOVER))) {
r = new Report(6480);
r.subject = entity.getId();
r.addDesc(entity);
r.add(toHit.getTableDesc());
r.add(0);
vPhaseReport.add(r);
vPhaseReport.addAll(vehicleMotiveDamage((Tank) entity, 0));
continue;
}
// only infantry and support vees with bar < 5 are affected
if ((entity instanceof BattleArmor) || (entity.getBARRating() > 4)) {
continue;
}
if (entity instanceof Infantry) {
hits = Compute.d6(damage);
hits *= 2;
}
if (entity.getBARRating() < 5) {
switch (ammo.getAmmoType()) {
case AmmoType.T_LONG_TOM:
// hack: check if damage is still at 4, so we're in the
// center hex. otherwise, do no damage
if (damage == 4) {
damage = (5 - entity.getBARRating()) * 5;
} else {
continue;
}
break;
case AmmoType.T_SNIPER:
// hack: check if damage is still at 2, so we're in the
// center hex. otherwise, do no damage
if (damage == 2) {
damage = (5 - entity.getBARRating()) * 3;
} else {
continue;
}
break;
case AmmoType.T_THUMPER:
// no need to check for damage, because falloff = damage for the thumper
damage = 5 - entity.getBARRating();
break;
}
}
}
}
// Do the damage
r = new Report(6480);
r.subject = entity.getId();
r.addDesc(entity);
r.add(toHit.getTableDesc());
r.add(hits);
vPhaseReport.add(r);
if (entity instanceof BattleArmor) {
// BA take full damage to each trooper, ouch!
for (int loc = 0; loc < entity.locations(); loc++) {
if (entity.getInternal(loc) > 0) {
HitData hit = new HitData(loc);
vPhaseReport.addAll(damageEntity(entity, hit, hits, false, DamageType.NONE, false, true, false));
}
}
} else {
while (hits > 0) {
HitData hit = entity.rollHitLocation(toHit.getHitTable(), toHit.getSideTable());
vPhaseReport.addAll(damageEntity(entity, hit, Math.min(cluster, hits), false, DamageType.NONE, false, true, false));
hits -= Math.min(5, hits);
}
}
if (killer != null) {
creditKill(entity, killer);
}
}
}
/**
* deal area saturation damage to the map, used for artillery
*
* @param centre
* The hex on which damage is centred
* @param attackSource
* The position the attack came from
* @param ammo
* The ammo type doing the damage
* @param subjectId
* Subject for reports
* @param killer
* Who should be credited with kills
* @param flak
* Flak, hits flying units only, instead of flyers being immune
* @param altitude
* Absolute altitude for flak attack
*/
public void artilleryDamageArea(Coords centre, Coords attackSource, AmmoType ammo, int subjectId, Entity killer, boolean flak, int altitude, boolean mineClear, Vector<Report> vPhaseReport) {
int damage = ammo.getRackSize();
int falloff = 10;
if (ammo.getAmmoType() == AmmoType.T_CRUISE_MISSILE) {
falloff = 25;
}
if (ammo.getMunitionType() == AmmoType.M_CLUSTER) {
// non-arrow-iv cluster does 5 less than standard
if (ammo.getAmmoType() != AmmoType.T_ARROW_IV) {
damage -= 5;
}
// thumper gets falloff 9 for 1 damage at 1 hex range
if (ammo.getAmmoType() == AmmoType.T_THUMPER) {
falloff = 9;
}
attackSource = centre;
} else if (ammo.getMunitionType() == AmmoType.M_FLECHETTE) {
switch (ammo.getAmmoType()) {
// for flechette, damage and fallof is number of d6, not absolut damage
case AmmoType.T_LONG_TOM:
damage = 4;
falloff = 2;
break;
case AmmoType.T_SNIPER:
damage = 2;
falloff = 1;
break;
case AmmoType.T_THUMPER:
damage = 1;
falloff = 1;
}
} else
//if this was a mine clearance, then it only affects the hex hit
if (mineClear) {
falloff = damage;
}
artilleryDamageArea(centre, attackSource, ammo, subjectId, killer, damage, falloff, flak, altitude, vPhaseReport);
}
/**
* Deals area-saturation damage to an area of the board. Used for artillery,
* bombs, or anything else with linear decreas in damage
*
* @param centre
* The hex on which damage is centred
* @param attackSource
* The position the attack came from
* @param ammo
* The ammo type doing the damage
* @param subjectId
* Subject for reports
* @param killer
* Who should be credited with kills
* @param damage
* Damage at ground zero
* @param falloff
* Reduction in damage for each hex of distance
* @param flak
* Flak, hits flying units only, instead of flyers being immune
* @param altitude
* Absolute altitude for flak attack
*/
public void artilleryDamageArea(Coords centre, Coords attackSource, AmmoType ammo, int subjectId, Entity killer, int damage, int falloff, boolean flak, int altitude, Vector<Report> vPhaseReport) {
for (int ring = 0; damage > 0; ring++, damage -= falloff) {
ArrayList<Coords> hexes = Compute.coordsAtRange(centre, ring);
for (Coords c : hexes) {
artilleryDamageHex(c, attackSource, damage, ammo, subjectId, killer, null, flak, altitude, vPhaseReport);
}
attackSource = centre; // all splash comes from ground zero
}
}
/**
* Resolve any Infantry units which are fortifying hexes
*/
void resolveFortify() {
Enumeration<Entity> entities = game.getEntities();
Report r;
while (entities.hasMoreElements()) {
Entity ent = entities.nextElement();
if (ent instanceof Infantry) {
Infantry inf = (Infantry) ent;
int dig = inf.getDugIn();
if (dig == Infantry.DUG_IN_WORKING) {
r = new Report(5300);
r.addDesc(inf);
r.subject = inf.getId();
addReport(r);
} else if (dig == Infantry.DUG_IN_FORTIFYING2) {
Coords c = inf.getPosition();
r = new Report(5305);
r.addDesc(inf);
r.add(c.getBoardNum());
r.subject = inf.getId();
addReport(r);
// fortification complete - add to map
IHex hex = game.getBoard().getHex(c);
hex.addTerrain(Terrains.getTerrainFactory().createTerrain(Terrains.FORTIFIED, 1));
sendChangedHex(c);
// Clear the dig in for any units in same hex, since they
// get it for free by fort
for (Enumeration<Entity> e = game.getEntities(c); e.hasMoreElements();) {
Entity ent2 = e.nextElement();
if (ent2 instanceof Infantry) {
Infantry inf2 = (Infantry) ent;
inf2.setDugIn(Infantry.DUG_IN_NONE);
}
}
}
}
}
}
/**
* check if spikes get breaken in the given location
*
* @param e
* the <code>Entity</code> to check
* @param loc
* the <code>int</code location
*/
private void checkBreakSpikes(Entity e, int loc) {
int roll = Compute.d6(2);
Report r;
if (roll < 9) {
r = new Report(4445);
r.newlines = 0;
r.add(roll);
r.subject = e.getId();
addReport(r);
return;
}
r = new Report(4440);
r.newlines = 0;
r.add(roll);
r.subject = e.getId();
addReport(r);
for (Mounted m : e.getMisc()) {
if (m.getType().hasFlag(MiscType.F_SPIKES) && (m.getLocation() == loc)) {
m.setHit(true);
}
}
}
class ConnectionWatchdog extends TimerTask {
private Server server;
private int id;
private int failCount;
public ConnectionWatchdog(Server server, int id) {
this.server = server;
this.id = id;
failCount = 0;
}
@Override
public void run() {
if (server.getPlayer(id) != null) {
// fully connected
cancel();
return;
}
if (server.getPendingConnection(id) == null) {
// dropped
cancel();
return;
}
System.err.println("Bark Bark");
if (failCount > 10) {
server.getPendingConnection(id).close();
cancel();
System.err.println("Growl");
System.err.println("\n\n\n\n\n");
return;
}
server.greeting(id);
failCount++;
}
}
/**
* @return a <code>String</code> representing the hostname
*/
public String getHost() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
e.printStackTrace();
return "";
}
}
/**
* @return the <code>int</code> this server is listening on
*/
public int getPort() {
return serverSocket.getLocalPort();
}
/**
* Loops through all the attacks the game has. Checks if they care about
* current phase, if so, runs them, and removes them if they don't want to
* stay. TODO: Refactor the new entity annoucement out of here.
*/
private void handleAttacks() {
Report r;
int lastAttackerId = -1;
Vector<AttackHandler> currentAttacks, keptAttacks;
currentAttacks = game.getAttacksVector();
keptAttacks = new Vector<AttackHandler>();
Vector<Report> handleAttackReports = new Vector<Report>();
// first, do any TAGs, so homing arty will have TAG
for (AttackHandler ah : currentAttacks) {
if (!(ah instanceof TAGHandler)) {
continue;
}
if (ah.cares(game.getPhase())) {
int aId = ah.getAttackerId();
if ((aId != lastAttackerId) && !ah.announcedEntityFiring()) {
// report who is firing
r = new Report(3100);
r.subject = aId;
Entity ae = game.getEntity(aId);
if (ae == null) {
System.err.println("ae null in handleattacks, entityId " + aId);
}
r.addDesc(game.getEntity(aId));
handleAttackReports.addElement(r);
ah.setAnnouncedEntityFiring(true);
}
lastAttackerId = aId;
boolean keep = ah.handle(game.getPhase(), handleAttackReports);
if (keep) {
keptAttacks.add(ah);
}
}
}
// now resolve everything but TAG
for (AttackHandler ah : currentAttacks) {
if (ah instanceof TAGHandler) {
continue;
}
if (ah.cares(game.getPhase())) {
int aId = ah.getAttackerId();
if ((aId != lastAttackerId) && !ah.announcedEntityFiring()) {
// if this is a new attacker then resolve any
// standard-to-cap damage
// from previous
handleAttackReports.addAll(checkFatalThresholds(aId));
// report who is firing
r = new Report(3100);
r.subject = aId;
Entity ae = game.getEntity(aId);
// for arty, attacker may be dead, or fled, so check out-of-
// game entities
if (ae == null) {
ae = game.getOutOfGameEntity(aId);
}
if (ae == null) {
System.err.println("ae null in handleattacks, entityId " + aId);
}
r.addDesc(game.getEntity(aId));
handleAttackReports.addElement(r);
ah.setAnnouncedEntityFiring(true);
}
lastAttackerId = aId;
boolean keep = ah.handle(game.getPhase(), handleAttackReports);
if (keep) {
keptAttacks.add(ah);
}
} else {
keptAttacks.add(ah);
}
}
// resolve standard to capital one more time
handleAttackReports.addAll(checkFatalThresholds(lastAttackerId));
addReport(handleAttackReports);
// HACK, but anything else seems to run into weird problems.
game.setAttacksVector(keptAttacks);
}
/**
* @return
*/
public static Server getServerInstance() {
return serverInstance;
}
/**
* create a <code>SmokeCloud object and add it to the server list</code>
* @param coords
*/
public void createSmoke(Coords coords){
createSmoke(coords,1,0);
}
/**
* create a <code>SmokeCloud object and add it to the server list</code>
* @param coords
* @param level 1=Light 2=Heavy Smoke
*/
public void createSmoke(Coords coords, int level){
createSmoke(coords,level,0);
}
/**
* create a <code>SmokeCloud object and add it to the server list</code>
* @param coords
* @param level 1=Light 2=Heavy Smoke
* @param duration How long the smoke will last.
*/
public void createSmoke(Coords coords, int level, int duration){
SmokeCloud cloud = new SmokeCloud(coords,level,duration);
game.addSmokeCloud(cloud);
}
/**
* create a <code>SmokeCloud object and add it to the server list</code>
* @param coords
* @param level 1=Light 2=Heavy Smoke
* @param duration duration How long the smoke will last.
*/
public void createSmoke(ArrayList<Coords> coords, int level, int duration){
SmokeCloud cloud = new SmokeCloud(coords,level,duration);
game.addSmokeCloud(cloud);
}
/**
* Update the map with a new set of coords.
* @param newCoords
*/
public void updateSmoke(SmokeCloud cloud, ArrayList<Coords> newCoords){
removeSmokeTerrain(cloud);
cloud.getCoordsList().clear();
cloud.getCoordsList().addAll(newCoords);
}
/**
* remove a cloud from the map
* @param cloud
*/
public void removeSmokeTerrain(SmokeCloud cloud){
for ( Coords coords : cloud.getCoordsList() ){
IHex nextHex = game.getBoard().getHex(coords);
if ((nextHex != null) && nextHex.containsTerrain(Terrains.SMOKE)) {
nextHex.removeTerrain(Terrains.SMOKE);
sendChangedHex(coords);
}
}
}
public List<SmokeCloud> getSmokeCloudList(){
return game.getSmokeCloudList();
}
/**
* Check to see if blowing sand caused damage to airborne VTOL/WIGEs
*/
private Vector<Report> resolveBlowingSandDamage() {
Vector<Report> vFullReport = new Vector<Report>();
vFullReport.add(new Report(5002, Report.PUBLIC));
int damage_bonus = Math.max(0, game.getPlanetaryConditions().getWindStrength() - PlanetaryConditions.WI_MOD_GALE);
//cycle through each team and damage 1d6 airborne VTOL/WIGE
for (Enumeration<Team> loop = game.getTeams(); loop.hasMoreElements();) {
Team team = loop.nextElement();
Vector<Integer> airborne = team.getAirborneVTOL();
if(airborne.size() > 0) {
//how many units are affected
int unitsAffected = Math.min(Compute.d6(), airborne.size());
while((unitsAffected > 0) && (airborne.size() > 0)) {
int loc = Compute.randomInt(airborne.size());
Entity en = game.getEntity(airborne.get(loc));
int damage = Math.max(1, Compute.d6()/2) + damage_bonus;
while(damage > 0) {
HitData hit = en.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_RANDOM);
vFullReport.addAll(damageEntity(en, hit, 1));
damage--;
}
unitsAffected--;
airborne.remove(loc);
}
}
}
Report.addNewline(vPhaseReport);
return vFullReport;
}
/**
* let an entity lay a mine
* @param entity the <code>Entity</code> that should lay a mine
* @param mineId an <code>int</code> pointing to the mine
*/
private void layMine(Entity entity, int mineId, Coords coords) {
Mounted mine = entity.getEquipment(mineId);
Report r;
if (!mine.isMissing()) {
switch (mine.getMineType()) {
case Mounted.MINE_CONVENTIONAL:
deliverThunderMinefield(coords, entity.getOwnerId(), 10, entity.getId());
mine.setMissing(true);
r = new Report(3500, Report.PLAYER);
r.subject = entity.getId();
r.addDesc(entity);
r.type = Report.PLAYER;
r.add(coords.getBoardNum());
addReport(r);
break;
case Mounted.MINE_VIBRABOMB:
deliverThunderVibraMinefield(coords, entity.getOwnerId(), 10, mine.getVibraSetting(), entity.getId());
mine.setMissing(true);
r = new Report(3505, Report.PLAYER);
r.subject = entity.getId();
r.addDesc(entity);
r.add(coords.getBoardNum());
addReport(r);
break;
case Mounted.MINE_ACTIVE:
deliverThunderActiveMinefield(coords, entity.getOwnerId(), 10, entity.getId());
mine.setMissing(true);
r = new Report(3510, Report.PLAYER);
r.subject = entity.getId();
r.addDesc(entity);
r.add(coords.getBoardNum());
addReport(r);
break;
case Mounted.MINE_INFERNO:
deliverThunderInfernoMinefield(coords, entity.getOwnerId(), 10, entity.getId());
mine.setMissing(true);
r = new Report(3515, Report.PLAYER);
r.subject = entity.getId();
r.addDesc(entity);
r.add(coords.getBoardNum());
addReport(r);
break;
// TODO: command-detonated mines
// case 2:
}
entity.setLayingMines(true);
}
}
}