Fix RP + new Tacticooler calculation

This commit is contained in:
Kalle 2023-01-21 14:53:55 +02:00
parent bbe57802b6
commit d8b5dbade6
11 changed files with 100 additions and 71 deletions

View File

@ -68,12 +68,14 @@ export function useAnalyzeBuild() {
effects,
ldeIntensity,
});
const hasTacticooler = effects.includes("TACTICOOLER");
const analyzed = buildStats({
abilityPoints,
weaponSplId: mainWeaponId,
mainOnlyAbilities: build
.map((row) => row[0])
.filter(filterMainOnlyAbilities),
hasTacticooler,
});
const buildAbilityPoints2 = buildToAbilityPoints(build2);
@ -88,6 +90,7 @@ export function useAnalyzeBuild() {
mainOnlyAbilities: build2
.map((row) => row[0])
.filter(filterMainOnlyAbilities),
hasTacticooler,
});
return {

View File

@ -134,10 +134,7 @@ export interface Stat<T = number> {
modifiedBy: Ability | Array<Ability>;
}
export type AbilityPoints = Map<
AbilityWithUnknown,
{ ap: number; apBeforeTacticooler: number }
>;
export type AbilityPoints = Map<AbilityWithUnknown, number>;
export type AbilityChunks = Map<AbilityWithUnknown, number>;
@ -148,6 +145,7 @@ export interface StatFunctionInput {
specialWeaponParams: SpecialWeaponParams;
abilityPoints: AbilityPoints;
mainOnlyAbilities: Array<Ability>;
hasTacticooler: boolean;
}
export type InkConsumeType = typeof INK_CONSUME_TYPES[number];

View File

@ -4,11 +4,6 @@ import { applySpecialEffects } from "./specialEffects";
const ApplySpecialEffects = suite("applySpecialEffects()");
const valueToAps = (value: number) => ({
ap: value,
apBeforeTacticooler: value,
});
ApplySpecialEffects("Adds an effect to empty build", () => {
const aps = applySpecialEffects({
effects: ["CB"],
@ -17,7 +12,7 @@ ApplySpecialEffects("Adds an effect to empty build", () => {
});
assert.equal(aps.size, 6);
assert.equal(aps.get("ISM")?.ap, 10);
assert.equal(aps.get("ISM"), 10);
});
ApplySpecialEffects(
@ -25,33 +20,33 @@ ApplySpecialEffects(
() => {
const aps = applySpecialEffects({
effects: ["CB"],
abilityPoints: new Map([["SPU", valueToAps(10)]]),
abilityPoints: new Map([["SPU", 10]]),
ldeIntensity: 0,
});
assert.equal(aps.size, 7);
assert.equal(aps.get("SPU")?.ap, 10);
assert.equal(aps.get("SPU"), 10);
}
);
ApplySpecialEffects("Does not boost ability beyond 57", () => {
const aps = applySpecialEffects({
effects: ["CB"],
abilityPoints: new Map([["ISM", valueToAps(57)]]),
abilityPoints: new Map([["ISM", 57]]),
ldeIntensity: 0,
});
assert.equal(aps.get("ISM")?.ap, 57);
assert.equal(aps.get("ISM"), 57);
});
ApplySpecialEffects("Tacticooler doesn't boost swim speed beyond 29", () => {
const aps = applySpecialEffects({
effects: ["TACTICOOLER"],
abilityPoints: new Map([["SSU", valueToAps(28)]]),
abilityPoints: new Map([["SSU", 28]]),
ldeIntensity: 0,
});
assert.equal(aps.get("SSU")?.ap, 29);
assert.equal(aps.get("SSU"), 29);
});
ApplySpecialEffects(
@ -59,53 +54,42 @@ ApplySpecialEffects(
() => {
const aps = applySpecialEffects({
effects: ["TACTICOOLER"],
abilityPoints: new Map([["SSU", valueToAps(30)]]),
abilityPoints: new Map([["SSU", 30]]),
ldeIntensity: 0,
});
assert.equal(aps.get("SSU")?.ap, 30);
assert.equal(aps.get("SSU"), 30);
}
);
ApplySpecialEffects("Tacticooler remembers AP before it was applied", () => {
const aps = applySpecialEffects({
effects: ["TACTICOOLER"],
abilityPoints: new Map([["QR", valueToAps(10)]]),
ldeIntensity: 0,
});
assert.equal(aps.get("QR")?.ap, 57);
assert.equal(aps.get("QR")?.apBeforeTacticooler, 10);
});
ApplySpecialEffects("Applies many effects", () => {
const aps = applySpecialEffects({
effects: ["DR", "CB"],
abilityPoints: new Map([["SSU", valueToAps(1)]]),
abilityPoints: new Map([["SSU", 1]]),
ldeIntensity: 0,
});
assert.equal(aps.get("SSU")?.ap, 21);
assert.equal(aps.get("SSU"), 21);
});
ApplySpecialEffects("Applies LDE", () => {
const aps = applySpecialEffects({
effects: ["LDE"],
abilityPoints: new Map([["ISM", valueToAps(1)]]),
abilityPoints: new Map([["ISM", 1]]),
ldeIntensity: 1,
});
assert.equal(aps.get("ISM")?.ap, 2);
assert.equal(aps.get("ISM"), 2);
});
ApplySpecialEffects("Applies LDE (intensity != aps given)", () => {
const aps = applySpecialEffects({
effects: ["LDE"],
abilityPoints: new Map([["ISM", valueToAps(1)]]),
abilityPoints: new Map([["ISM", 1]]),
ldeIntensity: 15,
});
assert.equal(aps.get("ISM")?.ap, 18);
assert.equal(aps.get("ISM"), 18);
});
ApplySpecialEffects.run();

View File

@ -147,17 +147,13 @@ export function applySpecialEffects({
for (const value of valuesArr) {
const boostsBeyond = "boostsBeyond" in value ? value.boostsBeyond : true;
const currentAP = result.get(value.type)?.ap ?? 0;
const currentAP = result.get(value.type) ?? 0;
const newAPUnlimited = boostsBeyond
? currentAP + value.ap
: Math.max(currentAP, value.ap);
const newAP = Math.min(newAPUnlimited, MAX_AP);
result.set(value.type, {
ap: newAP,
apBeforeTacticooler:
effectObj.type === "TACTICOOLER" ? currentAP : newAP,
});
result.set(value.type, newAP);
}
}

View File

@ -17,6 +17,7 @@ AnalyzeBuild("Every main weapon has damage", () => {
for (const weaponSplId of mainWeaponIds) {
const analyzed = buildStats({
weaponSplId,
hasTacticooler: false,
});
const hasDamage =
@ -38,11 +39,13 @@ AnalyzeBuild("Every main weapon has damage", () => {
AnalyzeBuild("Ninja Squid decreases swim speed", () => {
const analyzed = buildStats({
weaponSplId: 0,
hasTacticooler: false,
});
const analyzedWithNS = buildStats({
weaponSplId: 0,
mainOnlyAbilities: ["NS"],
hasTacticooler: false,
});
assert.ok(
@ -50,4 +53,34 @@ AnalyzeBuild("Ninja Squid decreases swim speed", () => {
);
});
AnalyzeBuild("Tacticooler / RP calculated correctly", () => {
const fullQR = buildStats({
weaponSplId: 0,
abilityPoints: new Map([["QR", 57]]),
hasTacticooler: false,
});
const tacticooler = buildStats({
weaponSplId: 0,
abilityPoints: new Map([["QR", 57]]),
hasTacticooler: true,
});
assert.ok(
fullQR.stats.quickRespawnTime.value ===
tacticooler.stats.quickRespawnTime.value,
"Base QR should be same whether 57AP of QR or Tacticooler"
);
assert.ok(
fullQR.stats.quickRespawnTimeSplattedByRP.value >
tacticooler.stats.quickRespawnTimeSplattedByRP.value,
"Tacticooler splatted by RP should respawn faster than 57AP of QR"
);
assert.ok(
tacticooler.stats.quickRespawnTime.value <
tacticooler.stats.quickRespawnTimeSplattedByRP.value,
"Tacticooler should respawn faster than Tacticooler splatted by RP"
);
});
AnalyzeBuild.run();

View File

@ -32,10 +32,12 @@ export function buildStats({
weaponSplId,
abilityPoints = new Map(),
mainOnlyAbilities = [],
hasTacticooler,
}: {
weaponSplId: MainWeaponId;
abilityPoints?: AbilityPoints;
mainOnlyAbilities?: Array<Ability>;
hasTacticooler: boolean;
}): AnalyzedBuild {
const mainWeaponParams = weaponParams().mainWeapons[weaponSplId];
invariant(mainWeaponParams, `Weapon with splId ${weaponSplId} not found`);
@ -61,6 +63,7 @@ export function buildStats({
specialWeaponParams,
abilityPoints,
mainOnlyAbilities,
hasTacticooler,
};
return {
@ -196,7 +199,6 @@ function specialLost(
abilityPoints: apFromMap({
abilityPoints: abilityPoints,
ability: SPECIAL_SAVED_AFTER_DEATH_ABILITY,
key: hasRespawnPunisher ? "apBeforeTacticooler" : "ap",
}),
key: "SpecialGaugeRt_Restart",
weapon: mainWeaponParams,
@ -593,6 +595,14 @@ function swimSpeedHoldingRainmaker(
};
}
const qrApAfterRespawnPunish = ({
ap,
hasTacticooler,
}: {
ap: number;
hasTacticooler: boolean;
}) => (hasTacticooler ? ap : Math.ceil(ap * 0.15));
const RESPAWN_CHASE_FRAME = 150;
const OWN_RESPAWN_PUNISHER_EXTRA_RESPAWN_FRAMES = 68;
const ENEMY_RESPAWN_PUNISHER_EXTRA_RESPAWN_FRAMES = 45;
@ -604,21 +614,25 @@ function respawnTime(
const QUICK_RESPAWN_TIME_ABILITY = "QR";
const hasRespawnPunisher = args.mainOnlyAbilities.includes("RP");
const ap = apFromMap({
abilityPoints: args.abilityPoints,
ability: QUICK_RESPAWN_TIME_ABILITY,
});
const abilityPoints =
splattedByRP || hasRespawnPunisher
? qrApAfterRespawnPunish({
ap,
hasTacticooler: args.hasTacticooler,
})
: ap;
const chase = abilityPointsToEffects({
abilityPoints: apFromMap({
abilityPoints: args.abilityPoints,
ability: QUICK_RESPAWN_TIME_ABILITY,
key: hasRespawnPunisher ? "apBeforeTacticooler" : "ap",
}),
abilityPoints,
key: "Dying_ChaseFrm",
weapon: args.mainWeaponParams,
});
const around = abilityPointsToEffects({
abilityPoints: apFromMap({
abilityPoints: args.abilityPoints,
ability: QUICK_RESPAWN_TIME_ABILITY,
key: hasRespawnPunisher ? "apBeforeTacticooler" : "ap",
}),
abilityPoints,
key: "Dying_AroundFrm",
weapon: args.mainWeaponParams,
});

View File

@ -19,9 +19,9 @@ BuildToAbilityPoints("Calculates ability points", () => {
EMPTY_ROW,
]);
assert.equal(aps.get("SS")?.ap, 13);
assert.equal(aps.get("RSU")?.ap, 6);
assert.equal(aps.get("UNKNOWN")?.ap, 38);
assert.equal(aps.get("SS"), 13);
assert.equal(aps.get("RSU"), 6);
assert.equal(aps.get("UNKNOWN"), 38);
});
BuildToAbilityPoints("Handles ability doubler", () => {
@ -31,7 +31,7 @@ BuildToAbilityPoints("Handles ability doubler", () => {
EMPTY_ROW,
]);
assert.equal(aps.get("SS")?.ap, 6);
assert.equal(aps.get("SS"), 6);
});
BuildToAbilityPoints("Does not calculate AP for main only abilities", () => {

View File

@ -37,9 +37,9 @@ export function buildToAbilityPoints(build: BuildAbilitiesTupleWithUnknown) {
const aps = i === 0 ? 10 : 3;
const apsDoubled = aps * (abilityDoublerActive ? 2 : 1);
const newAp = (result.get(ability)?.ap ?? 0) + apsDoubled;
const newAp = (result.get(ability) ?? 0) + apsDoubled;
result.set(ability, { ap: newAp, apBeforeTacticooler: newAp });
result.set(ability, newAp);
}
}
@ -57,13 +57,11 @@ function isStackableAbility(ability: AbilityWithUnknown): ability is Ability {
export function apFromMap({
abilityPoints,
ability,
key = "ap",
}: {
abilityPoints: AbilityPoints;
ability: Ability;
key?: "ap" | "apBeforeTacticooler";
}) {
return abilityPoints.get(ability)?.[key] ?? 0;
return abilityPoints.get(ability) ?? 0;
}
function abilityValues({

View File

@ -697,7 +697,7 @@ export default function BuildAnalyzerPage() {
>
{/** Hack the :has ;) */}
{(["ISM", "ISS"] as const).some(
(ability) => (abilityPoints.get(ability)?.ap ?? 0) > 0
(ability) => (abilityPoints.get(ability) ?? 0) > 0
) ? (
<div className="analyzer__stat-card-highlighted" />
) : null}
@ -855,6 +855,7 @@ export default function BuildAnalyzerPage() {
);
}
// xxx: no main abilities?
function APCompare({
abilityPoints,
abilityPoints2,
@ -867,13 +868,13 @@ function APCompare({
return (
<div className="analyzer__ap-compare">
{([...abilitiesShort, "UNKNOWN"] as const).map((ability) => {
const ap = abilityPoints.get(ability)?.ap ?? 0;
const ap2 = abilityPoints2.get(ability)?.ap ?? 0;
const ap = abilityPoints.get(ability) ?? 0;
const ap2 = abilityPoints2.get(ability) ?? 0;
if (!ap && !ap2) return null;
return (
<>
<React.Fragment key={ability}>
<div
className={clsx("justify-self-end", {
invisible: !ap,
@ -898,7 +899,7 @@ function APCompare({
{ap2}
{t("analyzer:abilityPoints.short")}
</div>
</>
</React.Fragment>
);
})}
</div>
@ -1076,7 +1077,7 @@ function StatCard({
// you have Ninja Squid and stack swim speed
// -> we still want to show the build value
return [stat[0].modifiedBy].flat().some((ability) => {
const hasStackable = (abilityPoints.get(ability)?.ap ?? 0) > 0;
const hasStackable = (abilityPoints.get(ability) ?? 0) > 0;
const hasEffect =
baseValue !== stat[0].value && baseValue !== stat[1].value;

View File

@ -23,6 +23,7 @@ export function useObjectDamage() {
const isMultiShot = validatedMultiShotFromSearchParams(searchParams);
const analyzed = buildStats({
weaponSplId: mainWeaponId,
hasTacticooler: false,
});
const damageType = validatedDamageTypeFromSearchParams({
@ -62,8 +63,8 @@ export function useObjectDamage() {
damagesToReceivers: damageType
? calculateDamage({
abilityPoints: new Map([
["BRU", { ap: abilityPoints, apBeforeTacticooler: abilityPoints }],
["SPU", { ap: abilityPoints, apBeforeTacticooler: abilityPoints }],
["BRU", abilityPoints],
["SPU", abilityPoints],
]),
analyzed,
mainWeaponId,

View File

@ -19,6 +19,7 @@ function calculate({
}) {
const analyzed = buildStats({
weaponSplId: mainWeaponId,
hasTacticooler: false,
});
return calculateDamage({
@ -35,7 +36,7 @@ const CalculateDamage = suite("calculateDamage()");
CalculateDamage("BRU increases Splash Wall hitpoints", () => {
const withoutBRU = calculate({});
const withBRU = calculate({
abilityPoints: new Map([["BRU", { ap: 10, apBeforeTacticooler: 10 }]]),
abilityPoints: new Map([["BRU", 10]]),
});
const hpWithoutBRU = withoutBRU.find(
@ -51,7 +52,7 @@ CalculateDamage("BRU increases Splash Wall hitpoints", () => {
CalculateDamage("SPU increases Big Bubbler hitpoints", () => {
const withoutSPU = calculate({});
const withSPU = calculate({
abilityPoints: new Map([["SPU", { ap: 10, apBeforeTacticooler: 10 }]]),
abilityPoints: new Map([["SPU", 10]]),
});
const hpWithoutSPU = withoutSPU.find(