Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix 5590: princess firing on 13s due to intervening trees, etc. #6477

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 29 additions & 10 deletions megamek/src/megamek/client/bot/princess/FireControl.java
Original file line number Diff line number Diff line change
Expand Up @@ -354,16 +354,22 @@ ToHitData guessToHitModifierHelperForAnyAttack(final Entity shooter,
}

// terrain modifiers, since "compute" won't let me do these remotely
LosEffects los = LosEffects.calculateLOS(game, shooter, target);

// We want to check the target hex _and_ the intervening hexes for woods, smoke, etc.
final Hex targetHex = game.getBoard().getHex(targetState.getPosition());
int woodsLevel = targetHex.terrainLevel(Terrains.WOODS);
int woodsLevel = targetHex.terrainLevel(Terrains.WOODS) +
((los.thruWoods()) ? los.getLightWoods() + los.getHeavyWoods() + los.getUltraWoods() : 0);
if (targetHex.terrainLevel(Terrains.JUNGLE) > woodsLevel) {
woodsLevel = targetHex.terrainLevel(Terrains.JUNGLE);
}
if (1 <= woodsLevel) {
toHitData.addModifier(woodsLevel, TH_WOODS);
}

final int smokeLevel = targetHex.terrainLevel(Terrains.SMOKE);
// final int smokeLevel = targetHex.terrainLevel(Terrains.SMOKE);
final int smokeLevel = targetHex.terrainLevel(Terrains.SMOKE) +
los.getLightSmoke() + los.getHeavySmoke();
if (1 <= smokeLevel) {
// Smoke level doesn't necessarily correspond to the to-hit modifier
// even levels are light smoke, odd are heavy smoke
Expand Down Expand Up @@ -2837,22 +2843,35 @@ void loadAmmo(final Entity shooter,

final AmmoMounted suggestedAmmo = info.getAmmo();
final AmmoMounted mountedAmmo = getPreferredAmmo(shooter, info.getTarget(), currentWeapon, suggestedAmmo);

if (mountedAmmo == null) {
continue;
}

// If the selected ammo would cause the shot to miss, skip loading it.
final WeaponAttackAction cloneWAA = new WeaponAttackAction(info.getAction());
cloneWAA.setAmmoId(shooter.getEquipmentNum(mountedAmmo));
cloneWAA.setAmmoMunitionType(((AmmoType) mountedAmmo.getType()).getMunitionType());
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show resolved Hide resolved
cloneWAA.setAmmoCarrier(mountedAmmo.getEntity().getId());
if (cloneWAA.toHit(owner.getGame(), owner.getPrecognition().getECMInfo()).getValue() > 12) {
logger.warn(shooter.getDisplayName() + " tried to load "
+ currentWeapon.getName() + " with ammo " +
mountedAmmo.getDesc() + " but this would have caused it to miss; skipping.");
Comment on lines +2857 to +2859
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warn should support varargs with %s.

continue;
}

// if we found preferred ammo but can't apply it to the weapon, log it and
// continue.
if ((null != mountedAmmo) && !shooter.loadWeapon(currentWeapon, mountedAmmo)) {
if (!shooter.loadWeapon(currentWeapon, mountedAmmo)) {
logger.warn(shooter.getDisplayName() + " tried to load "
+ currentWeapon.getName() + " with ammo " +
mountedAmmo.getDesc() + " but failed somehow.");
continue;
// if we didn't find preferred ammo after all, continue
} else if (mountedAmmo == null) {
continue;
}
final WeaponAttackAction action = info.getAction();
action.setAmmoId(shooter.getEquipmentNum(mountedAmmo));
action.setAmmoMunitionType(((AmmoType) mountedAmmo.getType()).getMunitionType());
action.setAmmoCarrier(mountedAmmo.getEntity().getId());
info.setAction(action);

// If everything looks okay, replace the old WAA with the updated copy
info.setAction(cloneWAA);
owner.sendAmmoChange(info.getShooter().getId(), shooter.getEquipmentNum(currentWeapon),
shooter.getEquipmentNum(mountedAmmo), mountedAmmo.getSwitchedReason());
}
Expand Down
24 changes: 24 additions & 0 deletions megamek/src/megamek/client/bot/princess/WeaponFireInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,30 @@ private WeaponAttackAction buildBombAttackAction(final HashMap<String, int[]> bo
) {
return 0D;
}

// Handle woods blocking cluster shots
if (game.getOptions().booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_WOODS_COVER)) {
// SRMs, LB-X, Flak AC, AC-2 derivatives,
// and Silver Bullet Gauss are among the weapons
// that lose all effectiveness if this rule is
// on and the target is in woods/jungle.
if (
game.getBoard().contains(target.getPosition())
&& game.getBoard().getHex(target.getPosition()).containsAnyTerrainOf(
Terrains.WOODS, Terrains.JUNGLE
)
) {
boolean blockedByWoods = (
weapon.getType().getDamage() == WeaponType.DAMAGE_BY_CLUSTERTABLE
);
blockedByWoods |= preferredAmmo.getType().getMunitionType().contains(
AmmoType.Munitions.M_CLUSTER
);
if (blockedByWoods) {
return 0D;
}
}
}
}

// bay weapons require special consideration, by looping through all weapons and
Expand Down
34 changes: 28 additions & 6 deletions megamek/src/megamek/common/actions/WeaponAttackAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,7 @@
*/
package megamek.common.actions;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;
import java.util.*;

import megamek.MMConstants;
import megamek.client.Client;
Expand Down Expand Up @@ -144,6 +139,33 @@ public WeaponAttackAction(int entityId, int targetType, int targetId, int weapon
this.bombPayloads.put("external", new int[BombType.B_NUM]);
}

// Copy constructor, hopefully with enough info to generate same to-hit data.
public WeaponAttackAction(final WeaponAttackAction other) {
this(other.getEntityId(), other.getTargetType(), other.getTargetId(), other.getWeaponId());

aimedLocation = other.aimedLocation;
aimMode = other.aimMode;
ammoCarrier = other.ammoCarrier;
ammoId = other.ammoId;
ammoMunitionType = other.ammoMunitionType;
isHomingShot = other.isHomingShot;
isPointblankShot = other.isPointblankShot;
isStrafing = other.isStrafing;
isStrafingFirstShot = other.isStrafingFirstShot;
launchVelocity = other.launchVelocity;
nemesisConfused = other.nemesisConfused;
oldTargetId = other.oldTargetId;
oldTargetType = other.oldTargetType;
originalTargetId = other.originalTargetId;
originalTargetType = other.originalTargetType;
otherAttackInfo = other.otherAttackInfo;
swarmingMissiles = other.swarmingMissiles;
swarmMissiles = other.swarmMissiles;
weaponId = other.weaponId;
this.bombPayloads.put("internal", Arrays.copyOf(other.bombPayloads.get("internal"), BombType.B_NUM));
this.bombPayloads.put("external", Arrays.copyOf(other.bombPayloads.get("external"), BombType.B_NUM));
}

public int getWeaponId() {
return weaponId;
}
Expand Down
59 changes: 55 additions & 4 deletions megamek/unittests/megamek/client/bot/princess/FireControlTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ void beforeEach() {
when(mockShooter.getHeat()).thenReturn(0);
mockShooterState = mock(EntityState.class);
mockShooterCoords = new Coords(0, 0);
when(mockShooter.getPosition()).thenReturn(mockShooterCoords);
// internal height values are 0-indexed, so meks are 1, not 2, here
when(mockShooter.getHeight()).thenReturn(1);
when(mockShooter.relHeight()).thenReturn(1);
when(mockShooterState.getPosition()).thenReturn(mockShooterCoords);
mockShooterMoveMod = new ToHitData();

Expand Down Expand Up @@ -242,6 +246,10 @@ void beforeEach() {
when(mockGame.getPlanetaryConditions()).thenReturn(planetaryConditions);

mockTarget = mock(BipedMek.class);
when(mockTarget.getPosition()).thenReturn(mockTargetCoords);
// internal height values are 0-indexed, so meks are 1, not 2, here
when(mockTarget.getHeight()).thenReturn(1);
when(mockTarget.relHeight()).thenReturn(1);
when(mockTarget.getDisplayName()).thenReturn("mock target");
when(mockTarget.getId()).thenReturn(MOCK_TARGET_ID);
when(mockTarget.isMilitary()).thenReturn(true);
Expand Down Expand Up @@ -1071,6 +1079,7 @@ void testGuessToHitModifierHelperForAnyAttack() {
.thenReturn(false);
when(mockHex.terrainLevel(Terrains.WOODS)).thenReturn(Terrain.LEVEL_NONE);
when(mockHex.terrainLevel(Terrains.JUNGLE)).thenReturn(Terrain.LEVEL_NONE);
when(mockHex.terrainLevel(Terrains.SMOKE)).thenReturn(Terrain.LEVEL_NONE);
when(mockPrincess.getMaxWeaponRange(any(Entity.class), anyBoolean())).thenReturn(21);
ToHitData expected = new ToHitData();
assertToHitDataEquals(expected, testFireControl.guessToHitModifierHelperForAnyAttack(
Expand Down Expand Up @@ -1235,17 +1244,47 @@ void testGuessToHitModifierHelperForAnyAttack() {
when(mockTargetState.getMovementType()).thenReturn(EntityMovementType.MOVE_NONE);

// Stand the target in light woods.
// 1. change coords to adjacent
mockShooterCoords = new Coords(0, 0);
when(mockShooter.getPosition()).thenReturn(mockShooterCoords);
mockTargetCoords = new Coords(1, 0);
when(mockTarget.getPosition()).thenReturn(mockTargetCoords);

// 2. change terrain info
when(mockHex.terrainLevel(Terrains.WOODS)).thenReturn(1);
when(mockHex.terrainLevel(Terrains.FOLIAGE_ELEV)).thenReturn(2);

expected = new ToHitData();
expected.addModifier(1, FireControl.TH_WOODS);
assertToHitDataEquals(expected, testFireControl.guessToHitModifierHelperForAnyAttack(mockShooter,
mockShooterState,
mockTarget,
mockTargetState,
10,
1,
mockGame));

// Stand the target farther away in light woods
mockShooterCoords = new Coords(0, 0);
when(mockShooter.getPosition()).thenReturn(mockShooterCoords);
mockTargetCoords = new Coords(0, 2);
when(mockTarget.getPosition()).thenReturn(mockTargetCoords);

expected = new ToHitData();
expected.addModifier(2, FireControl.TH_WOODS);
assertToHitDataEquals(expected, testFireControl.guessToHitModifierHelperForAnyAttack(mockShooter,
mockShooterState,
mockTarget,
mockTargetState,
2,
mockGame));
when(mockHex.terrainLevel(Terrains.WOODS)).thenReturn(Terrain.LEVEL_NONE);

// Revert positions and foliage
mockShooterCoords = new Coords(0, 0);
when(mockShooter.getPosition()).thenReturn(mockShooterCoords);
mockTargetCoords = new Coords(1, 0);
when(mockTarget.getPosition()).thenReturn(mockTargetCoords);

// Stand the target in heavy woods.
when(mockHex.terrainLevel(Terrains.WOODS)).thenReturn(2);
expected = new ToHitData();
Expand All @@ -1254,33 +1293,45 @@ void testGuessToHitModifierHelperForAnyAttack() {
mockShooterState,
mockTarget,
mockTargetState,
10,
1,
mockGame));
when(mockHex.terrainLevel(Terrains.WOODS)).thenReturn(Terrain.LEVEL_NONE);

// Stand the target in super heavy woods.
when(mockHex.terrainLevel(Terrains.WOODS)).thenReturn(3);
when(mockHex.terrainLevel(Terrains.FOLIAGE_ELEV)).thenReturn(3);
expected = new ToHitData();
expected.addModifier(3, FireControl.TH_WOODS);
assertToHitDataEquals(expected, testFireControl.guessToHitModifierHelperForAnyAttack(mockShooter,
mockShooterState,
mockTarget,
mockTargetState,
10,
1,
mockGame));
when(mockHex.terrainLevel(Terrains.WOODS)).thenReturn(Terrain.LEVEL_NONE);

// Stand the target in jungle.
when(mockHex.terrainLevel(Terrains.JUNGLE)).thenReturn(2);
when(mockHex.terrainLevel(Terrains.FOLIAGE_ELEV)).thenReturn(2);
expected = new ToHitData();
expected.addModifier(2, FireControl.TH_WOODS);
assertToHitDataEquals(expected, testFireControl.guessToHitModifierHelperForAnyAttack(mockShooter,
mockShooterState,
mockTarget,
mockTargetState,
10,
1,
mockGame));

// 3. reset coords and terrain
mockShooterCoords = new Coords(0, 0);
when(mockShooter.getPosition()).thenReturn(mockShooterCoords);
when(mockShooterState.getPosition()).thenReturn(mockShooterCoords);
mockTargetCoords = new Coords(10, 0);
when(mockTarget.getPosition()).thenReturn(mockTargetCoords);
when(mockTargetState.getPosition()).thenReturn(mockTargetCoords);

when(mockHex.terrainLevel(Terrains.JUNGLE)).thenReturn(Terrain.LEVEL_NONE);
when(mockHex.terrainLevel(Terrains.FOLIAGE_ELEV)).thenReturn(Terrain.LEVEL_NONE);

// Give the shooter the anti-air quirk but fire on a ground target.
when(mockShooter.hasQuirk(eq(OptionsConstants.QUIRK_POS_ANTI_AIR))).thenReturn(true);
Expand Down