/**
* MegaMek - Copyright (C) 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.
*/
/*
* Created on Sep 24, 2004
*
*/
package megamek.common.weapons;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Vector;
import megamek.common.AmmoType;
import megamek.common.BattleArmor;
import megamek.common.Compute;
import megamek.common.Coords;
import megamek.common.Entity;
import megamek.common.EntitySelector;
import megamek.common.IGame;
import megamek.common.INarcPod;
import megamek.common.Infantry;
import megamek.common.LosEffects;
import megamek.common.Minefield;
import megamek.common.Mounted;
import megamek.common.Report;
import megamek.common.SpecialHexDisplay;
import megamek.common.TargetRoll;
import megamek.common.Targetable;
import megamek.common.ToHitData;
import megamek.common.VTOL;
import megamek.common.actions.ArtilleryAttackAction;
import megamek.common.actions.WeaponAttackAction;
import megamek.server.Server;
/**
* @author Sebastian Brocks
*/
public class ArtilleryWeaponIndirectFireHandler extends AmmoWeaponHandler {
/**
*
*/
private static final long serialVersionUID = -1277649123562229298L;
boolean handledAmmoAndReport = false;
/**
* This consructor may only be used for deserialization.
*/
protected ArtilleryWeaponIndirectFireHandler() {
super();
}
/**
* @param t
* @param w
* @param g
*/
public ArtilleryWeaponIndirectFireHandler(ToHitData t,
WeaponAttackAction w, IGame g, Server s) {
super(t, w, g, s);
}
/*
* (non-Javadoc)
*
* @see megamek.common.weapons.AttackHandler#cares(int)
*/
@Override
public boolean cares(IGame.Phase phase) {
if ((phase == IGame.Phase.PHASE_OFFBOARD) || (phase == IGame.Phase.PHASE_TARGETING)) {
return true;
}
return false;
}
/*
* (non-Javadoc)
*
* @see megamek.common.weapons.AttackHandler#handle(int, java.util.Vector)
*/
@Override
public boolean handle(IGame.Phase phase, Vector<Report> vPhaseReport) {
if (!cares(phase)) {
return true;
}
ArtilleryAttackAction aaa = (ArtilleryAttackAction) waa;
if (phase == IGame.Phase.PHASE_TARGETING) {
if (!handledAmmoAndReport) {
addHeat();
// Report the firing itself
r = new Report(3121);
r.indent();
r.newlines = 0;
r.subject = subjectId;
r.add(wtype.getName());
r.add(aaa.turnsTilHit);
vPhaseReport.addElement(r);
Report.addNewline(vPhaseReport);
handledAmmoAndReport = true;
game.getBoard().addSpecialHexDisplay(
aaa.getTarget(game).getPosition(),
new SpecialHexDisplay(
SpecialHexDisplay.Type.ARTILLERY_INCOMING,
game.getRoundCount() + aaa.turnsTilHit,
game.getPlayer(aaa.getPlayerId()).getName(),
"Artilery Incoming. Better text later."));
}
// if this is the last targeting phase before we hit,
// make it so the firing entity is announced in the
// off-board attack phase that follows.
if (aaa.turnsTilHit == 0) {
announcedEntityFiring = false;
}
return true;
}
if (aaa.turnsTilHit > 0) {
aaa.turnsTilHit--;
return true;
}
final Vector<Integer> spottersBefore = aaa.getSpotterIds();
final Targetable target = aaa.getTarget(game);
final Coords targetPos = target.getPosition();
final int playerId = aaa.getPlayerId();
boolean isFlak = target instanceof VTOL;
Entity bestSpotter = null;
Entity ae = game.getEntity(aaa.getEntityId());
if (ae == null) {
ae = game.getOutOfGameEntity(aaa.getEntityId());
}
//TODO: Fix null pointer exception
if(ae == null) {
System.err.println("Artillery Entity is null!");
return true;
}
Mounted ammo = ae.getEquipment(aaa.getAmmoId());
final AmmoType atype = ammo == null ? null : (AmmoType) ammo.getType();
// Are there any valid spotters?
if ((null != spottersBefore) && !isFlak) {
// fetch possible spotters now
Enumeration<Entity> spottersAfter = game
.getSelectedEntities(new EntitySelector() {
public int player = playerId;
public Targetable targ = target;
public boolean accept(Entity entity) {
Integer id = new Integer(entity.getId());
if ((player == entity.getOwnerId())
&& spottersBefore.contains(id)
&& !(LosEffects.calculateLos(game, entity
.getId(), targ)).isBlocked()
&& entity.isActive()
&& !entity.isINarcedWith(INarcPod.HAYWIRE)) {
return true;
}
return false;
}
});
// Out of any valid spotters, pick the best.
while (spottersAfter.hasMoreElements()) {
Entity ent = spottersAfter.nextElement();
if ((bestSpotter == null)
|| (ent.crew.getGunnery() < bestSpotter.crew
.getGunnery())) {
bestSpotter = ent;
}
}
} // End have-valid-spotters
// If at least one valid spotter, then get the benefits thereof.
if (null != bestSpotter) {
int mod = (bestSpotter.crew.getGunnery() - 4) / 2;
toHit.addModifier(mod, "Spotting modifier");
}
// Is the attacker still alive and we're not shooting FLAK?
// then adjust the target
Entity artyAttacker = aaa.getEntity(game);
if ((null != artyAttacker) && !isFlak) {
// Get the arty weapon.
Mounted weapon = artyAttacker.getEquipment(aaa.getWeaponId());
// If the shot hit the target hex, then all subsequent
// fire will hit the hex automatically.
if (roll >= toHit.getValue()) {
artyAttacker.aTracker.setModifier(weapon,
TargetRoll.AUTOMATIC_SUCCESS, targetPos);
game.getBoard().addSpecialHexDisplay(targetPos,
new SpecialHexDisplay(
SpecialHexDisplay.Type.ARTILLERY_AUTOHIT,
game.getRoundCount(),
game.getPlayer(aaa.getPlayerId()).getName(),
"Artilery AutoHit. Better text later.",
false
)
);
}
// If the shot missed, but was adjusted by a
// spotter, future shots are more likely to hit.
else if (null != bestSpotter) {
artyAttacker.aTracker.setModifier(weapon, artyAttacker.aTracker
.getModifier(weapon, targetPos) - 1, targetPos);
game.getBoard().addSpecialHexDisplay(targetPos,
new SpecialHexDisplay(
SpecialHexDisplay.Type.ARTILLERY_ADJUSTED,
game.getRoundCount(),
game.getPlayer(aaa.getPlayerId()).getName(),
"Artilery toHit Adjusted. Better text later.",
false
)
);
}
} // End artyAttacker-alive
// Report weapon attack and its to-hit value.
r = new Report(3120);
r.indent();
r.newlines = 0;
r.subject = subjectId;
if (wtype != null) {
r.add(wtype.getName());
} else {
r.add("Error: From Nowhwere");
}
r.add(target.getDisplayName(), true);
vPhaseReport.addElement(r);
if (toHit.getValue() == TargetRoll.IMPOSSIBLE) {
r = new Report(3135);
r.subject = subjectId;
r.add(toHit.getDesc());
vPhaseReport.addElement(r);
return false;
} else if (toHit.getValue() == TargetRoll.AUTOMATIC_FAIL) {
r = new Report(3140);
r.newlines = 0;
r.subject = subjectId;
r.add(toHit.getDesc());
vPhaseReport.addElement(r);
} else if (toHit.getValue() == TargetRoll.AUTOMATIC_SUCCESS) {
r = new Report(3145);
r.newlines = 0;
r.subject = subjectId;
r.add(toHit.getDesc());
vPhaseReport.addElement(r);
} else {
// roll to hit
r = new Report(3150);
r.newlines = 0;
r.subject = subjectId;
r.add(toHit.getValue());
vPhaseReport.addElement(r);
}
// dice have been rolled, thanks
r = new Report(3155);
r.newlines = 0;
r.subject = subjectId;
r.add(roll);
vPhaseReport.addElement(r);
if (!isFlak) {
game.getBoard().addSpecialHexDisplay(
targetPos,
new SpecialHexDisplay(SpecialHexDisplay.Type.ARTILLERY_TARGET,
game.getRoundCount(),
game.getPlayer(aaa.getPlayerId()).getName(),
"Artilery Target. Better text later.",
false
)
);
}
// do we hit?
bMissed = roll < toHit.getValue();
// Set Margin of Success/Failure.
toHit.setMoS(roll-Math.max(2,toHit.getValue()));
// Do this stuff first, because some weapon's miss report reference the
// amount of shots fired and stuff.
if (!handledAmmoAndReport) {
addHeat();
}
Coords coords = target.getPosition();
if (!bMissed) {
if (!isFlak) {
r = new Report(3190);
} else {
r = new Report(3191);
}
r.subject = subjectId;
r.add(coords.getBoardNum());
vPhaseReport.addElement(r);
game.getBoard().addSpecialHexDisplay(targetPos,
new SpecialHexDisplay(SpecialHexDisplay.Type.ARTILLERY_HIT,
game.getRoundCount(),
"Artilery Hit. Better text later."));
} else {
// direct fire artillery only scatters by one d6
// we do this here to avoid duplicating handle()
// in the ArtilleryWeaponDirectFireHandler
if (phase == IGame.Phase.PHASE_FIRING) {
coords = Compute.scatterDirectArty(coords);
} else {
coords = Compute.scatter(coords, Math.abs(toHit.getMoS()));
}
if (game.getBoard().contains(coords)) {
// misses and scatters to another hex
if (!isFlak) {
r = new Report(3195);
game.getBoard().addSpecialHexDisplay(
coords,
new SpecialHexDisplay(
SpecialHexDisplay.Type.ARTILLERY_HIT,
game.getRoundCount(),
"Artilery Scatered Here. Better text later."
)
);
} else {
r = new Report(3192);
}
r.subject = subjectId;
r.add(coords.getBoardNum());
vPhaseReport.addElement(r);
} else {
// misses and scatters off-board
if (isFlak) {
r = new Report(3193);
} else {
r = new Report(3200);
}
r.subject = subjectId;
vPhaseReport.addElement(r);
return !bMissed;
}
}
if (atype.getMunitionType() == AmmoType.M_FLARE) {
int radius;
if (atype.getAmmoType() == AmmoType.T_ARROW_IV) {
radius = 4;
} else if (atype.getAmmoType() == AmmoType.T_LONG_TOM) {
radius = 3;
} else if (atype.getAmmoType() == AmmoType.T_SNIPER) {
radius = 2;
} else {
radius = 1;
}
server.deliverArtilleryFlare(coords, radius);
return false;
}
if (atype.getMunitionType() == AmmoType.M_DAVY_CROCKETT_M) {
// The appropriate term here is "Bwahahahahaha..."
server.doNuclearExplosion(coords, 1, vPhaseReport);
return false;
}
if (atype.getMunitionType() == AmmoType.M_FASCAM) {
server.deliverFASCAMMinefield(coords, ae.getOwner().getId(), atype.getRackSize(), ae.getId());
return false;
}
if (atype.getMunitionType() == AmmoType.M_INFERNO_IV) {
server.deliverArtilleryInferno(coords, artyAttacker, subjectId, vPhaseReport);
return false;
}
if (atype.getMunitionType() == AmmoType.M_VIBRABOMB_IV) {
server.deliverThunderVibraMinefield(coords, ae.getOwner().getId(),
atype.getRackSize(), waa.getOtherAttackInfo(), ae.getId());
return false;
}
if (atype.getMunitionType() == AmmoType.M_SMOKE) {
server.deliverArtillerySmoke(coords, vPhaseReport);
return false;
}
int altitude = 0;
if (isFlak) {
altitude = ((VTOL) target).getElevation();
}
//check to see if this is a mine clearing attack
//According to the RAW you have to hit the right hex to hit even if the scatter hex has minefields
boolean mineClear = target.getTargetType() == Targetable.TYPE_MINEFIELD_CLEAR;
if (mineClear && game.containsMinefield(coords)
&& !isFlak && !bMissed) {
r = new Report(3255);
r.indent(1);
r.subject = subjectId;
vPhaseReport.addElement(r);
Enumeration<Minefield> minefields = game.getMinefields(coords).elements();
ArrayList<Minefield> mfRemoved = new ArrayList<Minefield>();
while (minefields.hasMoreElements()) {
Minefield mf = minefields.nextElement();
if(server.clearMinefield(mf, ae, Minefield.CLEAR_NUMBER_WEAPON, vPhaseReport)) {
mfRemoved.add(mf);
}
}
//we have to do it this way to avoid a concurrent error problem
for(Minefield mf : mfRemoved) {
server.removeMinefield(mf);
}
}
server.artilleryDamageArea(coords, artyAttacker.getPosition(), atype,
subjectId, artyAttacker, isFlak, altitude, mineClear, vPhaseReport);
//artillery may unintentially clear minefields, but only if it wasn't trying to
if(!mineClear && game.containsMinefield(coords)) {
Enumeration<Minefield> minefields = game.getMinefields(coords).elements();
ArrayList<Minefield> mfRemoved = new ArrayList<Minefield>();
while (minefields.hasMoreElements()) {
Minefield mf = minefields.nextElement();
if(server.clearMinefield(mf, artyAttacker, 10, vPhaseReport)) {
mfRemoved.add(mf);
}
}
//we have to do it this way to avoid a concurrent error problem
for(Minefield mf : mfRemoved) {
server.removeMinefield(mf);
}
}
return false;
}
/*
* (non-Javadoc)
*
* @see megamek.common.weapons.WeaponHandler#calcDamagePerHit()
*/
@Override
protected int calcDamagePerHit() {
float toReturn = wtype.getDamage();
// area effect damage is double
if ((target instanceof Infantry) && !(target instanceof BattleArmor)) {
toReturn /= 0.5;
}
if (bGlancing) {
toReturn = (int) Math.floor(toReturn / 2.0);
}
//System.err.println("Attack is doing " + toReturn + " damage.");
return (int) Math.ceil(toReturn);
}
}