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

Implement RFE #5408: update Princess ammo conservation values to make… #5415

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
23 changes: 15 additions & 8 deletions megamek/src/megamek/client/bot/princess/Princess.java
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,7 @@ protected void calculateTargetingOffBoardTurn() {
sendDone(true);
}

private Map<WeaponMounted, Double> calcAmmoConservation(final Entity shooter) {
protected Map<WeaponMounted, Double> calcAmmoConservation(final Entity shooter) {
final double aggroFactor = getBehaviorSettings().getHyperAggressionIndex();
final StringBuilder msg = new StringBuilder("\nCalculating ammo conservation for ")
.append(shooter.getDisplayName());
Expand Down Expand Up @@ -763,9 +763,15 @@ private Map<WeaponMounted, Double> calcAmmoConservation(final Entity shooter) {
final WeaponType weaponType = weapon.getType();
msg.append("\n\t").append(weapon);
if (!(weaponType instanceof AmmoWeapon)) {
ammoConservation.put(weapon, 0.0);
// Just require a 12 or lower TN
ammoConservation.put(weapon, 0.01);
msg.append(" doesn't use ammo.");
continue;
} else if (weaponType.hasFlag(WeaponType.F_ONESHOT)) {
// Shoot OS weapons on a 10 / 9 / 8 for Aggro 10 / 5 / 0
ammoConservation.put(weapon, (35 - 2.0 * aggroFactor) / 100.0);
msg.append(" One Shot weapon.");
continue;
}

int ammoCount = 0;
Expand All @@ -776,13 +782,14 @@ private Map<WeaponMounted, Double> calcAmmoConservation(final Entity shooter) {
ammoCount += ammoCounts.get(ammoType);
}
msg.append(" has ").append(ammoCount).append(" shots left");
// Desired behavior:
// At min aggro (0 of 10), require ~50% chance to hit with > 3 shots left
// At normal aggro (5 of 10), require at least 10% to-hit chance with > 3 shots left
// At max aggro (10 of 10) require just over 0% chance to hit until at 1 round left.
// Desired behavior, with 7 / 3 / 1 rounds left:
// At min aggro (0 of 10), fire on TN 10, 9, 7
// At normal aggro (5 of 10), fire on 12, 11, 10
// At max aggro (10 of 10), fire on 12, 12, 10
final double toHitThreshold =
Math.max(0.0,
(0.8/((aggroFactor) + 2) + 1.0 / ((ammoCount*ammoCount)+1)));
Math.max(0.01,
(0.6/((8*aggroFactor) + 4)
+ 4.0 / (4 * (ammoCount*ammoCount) * (aggroFactor + 2) + (4 / (aggroFactor + 1)))));
msg.append("; To Hit Threshold = ").append(new DecimalFormat("0.000").format(toHitThreshold));
ammoConservation.put(weapon, toHitThreshold);
}
Expand Down
150 changes: 144 additions & 6 deletions megamek/unittests/megamek/client/bot/princess/PrincessTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import megamek.client.bot.princess.PathRanker.PathRankerType;
import megamek.common.*;
import megamek.common.enums.GamePhase;
import megamek.common.equipment.WeaponMounted;
import megamek.common.options.GameOptions;
import megamek.common.options.OptionsConstants;
import org.junit.jupiter.api.BeforeAll;
Expand All @@ -30,10 +31,9 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
Expand All @@ -45,6 +45,9 @@
*/
public class PrincessTest {

static WeaponType mockAC5 = (WeaponType) EquipmentType.get("ISAC5");
static AmmoType mockAC5AmmoType = (AmmoType) EquipmentType.get("ISAC5 Ammo");
static WeaponType mockRL20 = (WeaponType) EquipmentType.get("RL20");
private Princess mockPrincess;
private BasicPathRanker mockPathRanker;

Expand All @@ -63,6 +66,7 @@ public void beforeEach() {
when(mockPrincess.getPathRanker(PathRankerType.Basic)).thenReturn(mockPathRanker);
when(mockPrincess.getPathRanker(any(Entity.class))).thenReturn(mockPathRanker);
when(mockPrincess.getMoraleUtil()).thenReturn(mockMoralUtil);
when(mockPrincess.calcAmmoConservation(any(Entity.class))).thenCallRealMethod();
}

@Test
Expand Down Expand Up @@ -359,19 +363,19 @@ public void testIsFallingBack() {

when(mockPrincess.wantsToFallBack(any(Entity.class))).thenReturn(false);
when(mockPrincess.isFallingBack(any(Entity.class))).thenCallRealMethod();

BehaviorSettings mockBehavior = mock(BehaviorSettings.class);
when(mockBehavior.getDestinationEdge()).thenReturn(CardinalEdge.NONE);
when(mockBehavior.isForcedWithdrawal()).thenReturn(true);
when(mockPrincess.getBehaviorSettings()).thenReturn(mockBehavior);

// A normal undamaged mech.
assertFalse(mockPrincess.isFallingBack(mockMech));

// A mobile mech that wants to fall back (for any reason).
when(mockMech.isCrippled(anyBoolean())).thenReturn(true);
assertTrue(mockPrincess.isFallingBack(mockMech));

// A mech whose bot is set for a destination edge
when(mockBehavior.getDestinationEdge()).thenReturn(CardinalEdge.NEAREST);
assertTrue(mockPrincess.isFallingBack(mockMech));
Expand Down Expand Up @@ -537,4 +541,138 @@ public void testIsImmobilized() {
when(mockMech.isStuck()).thenReturn(true);
assertTrue(mockPrincess.isImmobilized(mockMech));
}

@Test
public void testCalcAmmoForDefaultAggressionLevel() throws megamek.common.LocationFullException {
// Expected toHitThresholds should equate to a TN of 12, 11, and 10 for ammo values
// of 7+, 3+, 1.

// Set aggression to default level
BehaviorSettings mockBehavior = mock(BehaviorSettings.class);
when(mockBehavior.getHyperAggressionIndex()).thenReturn(5);
when(mockPrincess.getBehaviorSettings()).thenReturn(mockBehavior);

// Set up unit
Mech mech1 = new BipedMech();
Mounted bin1 = mech1.addEquipment(mockAC5AmmoType, Mech.LOC_LT);
Mounted wpn1 = mech1.addEquipment(mockAC5, Mech.LOC_RT);

// Check default toHitThresholds
// Default toHitThreshold for 7+ rounds for this level should allow firing on 12s
double target = Compute.oddsAbove(12) / 100.0;
bin1.setShotsLeft(7);
Map<WeaponMounted, Double> conserveMap = mockPrincess.calcAmmoConservation(mech1);
assertTrue(conserveMap.get(wpn1) <= target);

// Default toHitThreshold for 3+ rounds for this level should allow firing on 11s
target = Compute.oddsAbove(11) / 100.0;
bin1.setShotsLeft(3);
conserveMap = mockPrincess.calcAmmoConservation(mech1);
assertTrue(conserveMap.get(wpn1) <= target);

// Default toHitThreshold for 1 rounds for this level should allow firing on 10s
target = Compute.oddsAbove(10) / 100.0;
bin1.setShotsLeft(1);
conserveMap = mockPrincess.calcAmmoConservation(mech1);
assertTrue(conserveMap.get(wpn1) <= target);
}

@Test
public void testCalcAmmoForMaxAggressionLevel() throws megamek.common.LocationFullException {
// Expected toHitThresholds should equate to a TN of 12, 12, and 10 for ammo values
// of 7+, 3+, 1.

// Set aggression to default level
BehaviorSettings mockBehavior = mock(BehaviorSettings.class);
when(mockBehavior.getHyperAggressionIndex()).thenReturn(10);
when(mockPrincess.getBehaviorSettings()).thenReturn(mockBehavior);

// Set up unit
Mech mech1 = new BipedMech();
Mounted bin1 = mech1.addEquipment(mockAC5AmmoType, Mech.LOC_LT);
Mounted wpn1 = mech1.addEquipment(mockAC5, Mech.LOC_RT);

// Check default toHitThresholds
// Default toHitThreshold for 7+ rounds for this level should allow firing on 12s
double target = Compute.oddsAbove(12) / 100.0;
bin1.setShotsLeft(7);
Map<WeaponMounted, Double> conserveMap = mockPrincess.calcAmmoConservation(mech1);
assertTrue(conserveMap.get(wpn1) <= target);

// Default toHitThreshold for 3+ rounds for this level should allow firing on 12s
bin1.setShotsLeft(3);
conserveMap = mockPrincess.calcAmmoConservation(mech1);
assertTrue(conserveMap.get(wpn1) <= target);

// Default toHitThreshold for 1 rounds for this level should allow firing on 10s
target = Compute.oddsAbove(10) / 100.0;
bin1.setShotsLeft(1);
conserveMap = mockPrincess.calcAmmoConservation(mech1);
assertTrue(conserveMap.get(wpn1) <= target);
}

@Test
public void testCalcAmmoForZeroAggressionLevel() throws megamek.common.LocationFullException {
// Expected toHitThresholds should equate to a TN of 10, 9, and 7 for ammo values
// of 7+, 3+, 1.

// Set aggression to default level
BehaviorSettings mockBehavior = mock(BehaviorSettings.class);
when(mockBehavior.getHyperAggressionIndex()).thenReturn(0);
when(mockPrincess.getBehaviorSettings()).thenReturn(mockBehavior);

// Set up unit
Mech mech1 = new BipedMech();
Mounted bin1 = mech1.addEquipment(mockAC5AmmoType, Mech.LOC_LT);
Mounted wpn1 = mech1.addEquipment(mockAC5, Mech.LOC_RT);

// Check default toHitThresholds
// Default toHitThreshold for 7+ rounds for this level should allow firing on 12s
double target = Compute.oddsAbove(10) / 100.0;
bin1.setShotsLeft(7);
Map<WeaponMounted, Double> conserveMap = mockPrincess.calcAmmoConservation(mech1);
assertTrue(conserveMap.get(wpn1) <= target);

// Default toHitThreshold for 3+ rounds for this level should allow firing on 11s
target = Compute.oddsAbove(9) / 100.0;
bin1.setShotsLeft(3);
conserveMap = mockPrincess.calcAmmoConservation(mech1);
assertTrue(conserveMap.get(wpn1) <= target);

// Default toHitThreshold for 1 rounds for this level should allow firing on 10s
target = Compute.oddsAbove(7) / 100.0;
bin1.setShotsLeft(1);
conserveMap = mockPrincess.calcAmmoConservation(mech1);
assertTrue(conserveMap.get(wpn1) <= target);
}

@Test
public void testCalcAmmoForOneShotWeapons() throws megamek.common.LocationFullException {
// Set aggression to the lowest level first
BehaviorSettings mockBehavior = mock(BehaviorSettings.class);
when(mockBehavior.getHyperAggressionIndex()).thenReturn(0);
when(mockPrincess.getBehaviorSettings()).thenReturn(mockBehavior);

// Set up unit
Mech mech1 = new BipedMech();
Mounted wpn1 = mech1.addEquipment(mockRL20, Mech.LOC_LT);

// Check default toHitThresholds
// For max aggro, shoot OS weapons at TN 10 or better
double target = Compute.oddsAbove(8) / 100.0;
Map<WeaponMounted, Double> conserveMap = mockPrincess.calcAmmoConservation(mech1);
assertTrue(conserveMap.get(wpn1) <= target);

// For default aggro, shoot OS weapons at TN 9 or better
when(mockBehavior.getHyperAggressionIndex()).thenReturn(5);
target = Compute.oddsAbove(9) / 100.0;
conserveMap = mockPrincess.calcAmmoConservation(mech1);
assertTrue(conserveMap.get(wpn1) <= target);

// For lowest aggro, shoot OS weapons at TN 8 or better
when(mockBehavior.getHyperAggressionIndex()).thenReturn(10);
target = Compute.oddsAbove(10) / 100.0;
conserveMap = mockPrincess.calcAmmoConservation(mech1);
assertTrue(conserveMap.get(wpn1) <= target);
}
}
Loading