Hits implemented, now moving on to calculating wounds
This commit is contained in:
parent
c2a19499c9
commit
434b534012
5 changed files with 112 additions and 25 deletions
|
@ -97,18 +97,42 @@ public class AttackScenario {
|
||||||
int amountOfWeapons = weaponData._2;
|
int amountOfWeapons = weaponData._2;
|
||||||
|
|
||||||
// Attack probabilities
|
// Attack probabilities
|
||||||
int finalHitRollGoal = calculateHitRollGoal(weapon);
|
var passingHits = calculatePassingHits(weapon, amountOfWeapons);
|
||||||
int finalWoundRollGoal = calculateWoundRollGoal(weapon.getStrength(), defensiveProfile.getToughness(), weapon);
|
var passingWounds = calculatePassingWounds(weapon, amountOfWeapons, passingHits);
|
||||||
var passingHits = calculatePassingHits(weapon, amountOfWeapons, finalHitRollGoal);
|
|
||||||
|
|
||||||
|
|
||||||
int woundSuccessProbabilityNumerator = calculateProbabilityNumeratorFrom(finalWoundRollGoal);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private double calculatePassingHits(Weapon weapon, int amountOfWeapons, int hitRollGoal) {
|
private Tuple2<Double, Double> calculatePassingWounds(Weapon weapon, int amountOfWeapons, double passingHits) {
|
||||||
|
double lethalHitsCount = calculateLethalHits(weapon, amountOfWeapons);
|
||||||
|
var normalHits = passingHits - lethalHitsCount;
|
||||||
|
results.setWeaponStat(weapon, WeaponStat.NON_LETHAL_HITS_COUNT, normalHits);
|
||||||
|
|
||||||
|
int woundRollGoal = calculateWoundRollGoal(weapon);
|
||||||
|
int woundSuccessProbabilityNumerator = calculateProbabilityNumeratorFrom(woundRollGoal);
|
||||||
|
|
||||||
|
var passingWounds = normalHits * ( (double) woundSuccessProbabilityNumerator / PROBABILITY_DENOMINATOR_D6)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private double calculateLethalHits(Weapon weapon, int amountOfWeapons) {
|
||||||
|
var lethalHitsCount = 0d;
|
||||||
|
|
||||||
|
if(weapon.hasLethalHits()) {
|
||||||
|
double weaponAttacks = calculateBasicWeaponAttacks(weapon, amountOfWeapons);
|
||||||
|
var lethalHitsProbabilityNumerator = calculateProbabilityNumeratorFrom(criticalHitValue);
|
||||||
|
|
||||||
|
lethalHitsCount = weaponAttacks * ((double) lethalHitsProbabilityNumerator / PROBABILITY_DENOMINATOR_D6);
|
||||||
|
results.setWeaponStat(weapon, WeaponStat.LETHAL_HITS_COUNT, lethalHitsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lethalHitsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private double calculatePassingHits(Weapon weapon, int amountOfWeapons) {
|
||||||
|
int hitRollGoal = calculateHitRollGoal(weapon);
|
||||||
int hitSuccessProbabilityNumerator = calculateProbabilityNumeratorFrom(hitRollGoal);
|
int hitSuccessProbabilityNumerator = calculateProbabilityNumeratorFrom(hitRollGoal);
|
||||||
int hitFailureProbabilityNumerator = 6 - hitSuccessProbabilityNumerator;
|
|
||||||
double weaponAttacks = calculateWeaponAttacks(weapon, amountOfWeapons);
|
double weaponAttacks = calculateWeaponAttacks(weapon, amountOfWeapons);
|
||||||
|
|
||||||
// Base passing hits without rerolls
|
// Base passing hits without rerolls
|
||||||
|
@ -138,11 +162,7 @@ public class AttackScenario {
|
||||||
}
|
}
|
||||||
|
|
||||||
private double calculateWeaponAttacks(Weapon weapon, int amountOfWeapons) {
|
private double calculateWeaponAttacks(Weapon weapon, int amountOfWeapons) {
|
||||||
double weaponAttacks = weapon.getAttacks();
|
double weaponAttacks = calculateBasicWeaponAttacks(weapon, amountOfWeapons);
|
||||||
if(weapon.getRapidFire().isPresent() && attackerInRapidFireRange) {
|
|
||||||
weaponAttacks += weapon.getRapidFire().get();
|
|
||||||
}
|
|
||||||
weaponAttacks *= amountOfWeapons;
|
|
||||||
results.setWeaponStat(weapon, WeaponStat.POTENTIAL_HITS_COUNT, weaponAttacks);
|
results.setWeaponStat(weapon, WeaponStat.POTENTIAL_HITS_COUNT, weaponAttacks);
|
||||||
|
|
||||||
// Add sustained hits
|
// Add sustained hits
|
||||||
|
@ -162,6 +182,16 @@ public class AttackScenario {
|
||||||
return weaponAttacks;
|
return weaponAttacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private double calculateBasicWeaponAttacks(Weapon weapon, int amountOfWeapons) {
|
||||||
|
double weaponAttacks = weapon.getAttacks();
|
||||||
|
if(weapon.getRapidFire().isPresent() && attackerInRapidFireRange) {
|
||||||
|
weaponAttacks += weapon.getRapidFire().get();
|
||||||
|
}
|
||||||
|
weaponAttacks *= amountOfWeapons;
|
||||||
|
|
||||||
|
return weaponAttacks;
|
||||||
|
}
|
||||||
|
|
||||||
private int calculateHitRollGoal(Weapon weapon) {
|
private int calculateHitRollGoal(Weapon weapon) {
|
||||||
var finalHitRollGoal = weapon.getHitValue();
|
var finalHitRollGoal = weapon.getHitValue();
|
||||||
if(hasHitRollBonus) {
|
if(hasHitRollBonus) {
|
||||||
|
@ -191,9 +221,11 @@ public class AttackScenario {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int calculateWoundRollGoal(int strength, int toughness, Weapon weapon) {
|
private int calculateWoundRollGoal(Weapon weapon) {
|
||||||
var woundRollGoal = 4;
|
var woundRollGoal = 4;
|
||||||
var finalWoundPenalty = hasWoundRollPenalty ? 1 : 0;
|
var finalWoundPenalty = hasWoundRollPenalty ? 1 : 0;
|
||||||
|
var toughness = defensiveProfile.getToughness();
|
||||||
|
var strength = weapon.getStrength();
|
||||||
|
|
||||||
if(strength > toughness) {
|
if(strength > toughness) {
|
||||||
finalWoundPenalty += woundRollPenaltyIfStrengthIsHigher ? 1 : 0;
|
finalWoundPenalty += woundRollPenaltyIfStrengthIsHigher ? 1 : 0;
|
||||||
|
@ -213,6 +245,12 @@ public class AttackScenario {
|
||||||
// Having a -1 to wound rolls implies that the roll goal moves by 1
|
// Having a -1 to wound rolls implies that the roll goal moves by 1
|
||||||
woundRollGoal -= finalWoundRollModifier;
|
woundRollGoal -= finalWoundRollModifier;
|
||||||
|
|
||||||
|
// Now apply any anti-X if it is better than the current wound roll
|
||||||
|
var antiWoundRoll = weapon.getBestAntiFor(defensiveProfile);
|
||||||
|
if(antiWoundRoll.isPresent()) {
|
||||||
|
woundRollGoal = Math.min(woundRollGoal, antiWoundRoll.get());
|
||||||
|
}
|
||||||
|
|
||||||
return woundRollGoal;
|
return woundRollGoal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,9 @@ package ninja.thefirearchmage.games.fourtykcalculator;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public class DefensiveProfile {
|
public class DefensiveProfile {
|
||||||
|
@ -12,6 +14,7 @@ public class DefensiveProfile {
|
||||||
private final int bodies;
|
private final int bodies;
|
||||||
private final Optional<InvulnerableSave> invulnerableSave;
|
private final Optional<InvulnerableSave> invulnerableSave;
|
||||||
private final Optional<FeelNoPainEffect> feelNoPain;
|
private final Optional<FeelNoPainEffect> feelNoPain;
|
||||||
|
private final Set<UnitTypeKeyword> unitTypeKeywords;
|
||||||
|
|
||||||
public DefensiveProfile(int toughness, int wounds, int bodies, int normalSave) {
|
public DefensiveProfile(int toughness, int wounds, int bodies, int normalSave) {
|
||||||
this(toughness, wounds, bodies, normalSave, Optional.empty(), Optional.empty());
|
this(toughness, wounds, bodies, normalSave, Optional.empty(), Optional.empty());
|
||||||
|
@ -24,5 +27,11 @@ public class DefensiveProfile {
|
||||||
this.invulnerableSave = invulnerableSave;
|
this.invulnerableSave = invulnerableSave;
|
||||||
this.feelNoPain = feelNoPain;
|
this.feelNoPain = feelNoPain;
|
||||||
this.bodies = bodies;
|
this.bodies = bodies;
|
||||||
|
this.unitTypeKeywords = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefensiveProfile withUnitTypes(Set<UnitTypeKeyword> types) {
|
||||||
|
this.unitTypeKeywords.addAll(types);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package ninja.thefirearchmage.games.fourtykcalculator;
|
||||||
|
|
||||||
|
public enum UnitTypeKeyword {
|
||||||
|
INFANTRY,
|
||||||
|
VEHICLE,
|
||||||
|
PSYCHIC,
|
||||||
|
MONSTER;
|
||||||
|
}
|
|
@ -2,7 +2,10 @@ package ninja.thefirearchmage.games.fourtykcalculator;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class Weapon {
|
public class Weapon {
|
||||||
@Getter
|
@Getter
|
||||||
|
@ -14,7 +17,7 @@ public class Weapon {
|
||||||
@Getter
|
@Getter
|
||||||
private int attacks;
|
private int attacks;
|
||||||
@Getter
|
@Getter
|
||||||
private WeaponDamage averageDamage;
|
private WeaponDamage damage;
|
||||||
@Getter
|
@Getter
|
||||||
private WeaponRangeType rangeType;
|
private WeaponRangeType rangeType;
|
||||||
@Getter
|
@Getter
|
||||||
|
@ -22,16 +25,9 @@ public class Weapon {
|
||||||
private boolean hasLethalHits;
|
private boolean hasLethalHits;
|
||||||
@Getter
|
@Getter
|
||||||
private Optional<Integer> sustainedHits;
|
private Optional<Integer> sustainedHits;
|
||||||
private boolean hasMortalWounds;
|
private boolean hasDevastatingWounds;
|
||||||
private boolean hasHazardous;
|
private boolean hasHazardous;
|
||||||
@Getter
|
private Map<UnitTypeKeyword, Integer> antiStats;
|
||||||
private Optional<Integer> antiInfantery;
|
|
||||||
@Getter
|
|
||||||
private Optional<Integer> antiVehicle;
|
|
||||||
@Getter
|
|
||||||
private Optional<Integer> antiMonster;
|
|
||||||
@Getter
|
|
||||||
private Optional<Integer> antiPsychic;
|
|
||||||
private boolean hasHeavy;
|
private boolean hasHeavy;
|
||||||
private boolean hasBlast;
|
private boolean hasBlast;
|
||||||
private boolean hasLance;
|
private boolean hasLance;
|
||||||
|
@ -42,12 +38,19 @@ public class Weapon {
|
||||||
@Getter
|
@Getter
|
||||||
private Optional<Integer> rapidFire;
|
private Optional<Integer> rapidFire;
|
||||||
|
|
||||||
|
public Weapon() {
|
||||||
|
this.antiStats = new HashMap<>();
|
||||||
|
this.melta = Optional.empty();
|
||||||
|
this.rapidFire = Optional.empty();
|
||||||
|
this.sustainedHits = Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean hasLethalHits() {
|
public boolean hasLethalHits() {
|
||||||
return hasLethalHits;
|
return hasLethalHits;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasMortalWounds() {
|
public boolean hasDevastatingWounds() {
|
||||||
return hasMortalWounds;
|
return hasDevastatingWounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasHazardous() {
|
public boolean hasHazardous() {
|
||||||
|
@ -72,4 +75,32 @@ public class Weapon {
|
||||||
public boolean hasIndirectFire() {
|
public boolean hasIndirectFire() {
|
||||||
return hasIndirectFire;
|
return hasIndirectFire;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this weapon has Anti-X where X is the given unit type.
|
||||||
|
*/
|
||||||
|
public Optional<Integer> getAnti(UnitTypeKeyword unitType) {
|
||||||
|
return Optional.ofNullable(antiStats.get(unitType));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this weapon is anti for any of the given types
|
||||||
|
*/
|
||||||
|
public boolean isAntiFor(Set<UnitTypeKeyword> unitTypes) {
|
||||||
|
var antiUnitTypes = antiStats.keySet();
|
||||||
|
return unitTypes.stream().anyMatch(antiUnitTypes::contains);
|
||||||
|
}
|
||||||
|
public Optional<Integer> getBestAntiFor(DefensiveProfile target) {
|
||||||
|
var bestAnti = Optional.<Integer>empty();
|
||||||
|
if(isAntiFor(target.getUnitTypeKeywords())) {
|
||||||
|
var antiUnitTypes = antiStats.keySet();
|
||||||
|
var minAnti = target.getUnitTypeKeywords().stream()
|
||||||
|
.filter(antiUnitTypes::contains)
|
||||||
|
.mapToInt(type -> antiStats.get(type))
|
||||||
|
.min().getAsInt();
|
||||||
|
bestAnti = Optional.of(minAnti);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestAnti;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ public enum WeaponStat {
|
||||||
MORTAL_WOUNDS_COUNT(Double.class),
|
MORTAL_WOUNDS_COUNT(Double.class),
|
||||||
TOTAL_DAMAGE(Double.class),
|
TOTAL_DAMAGE(Double.class),
|
||||||
LETHAL_HITS_COUNT(Double.class),
|
LETHAL_HITS_COUNT(Double.class),
|
||||||
|
NON_LETHAL_HITS_COUNT(Double.class),
|
||||||
SUSTAINED_HITS_COUNT(Double.class);
|
SUSTAINED_HITS_COUNT(Double.class);
|
||||||
|
|
||||||
private final Class<?> statClass;
|
private final Class<?> statClass;
|
||||||
|
|
Loading…
Add table
Reference in a new issue