mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-22 11:05:09 -05:00
Calculate own use of RP
This commit is contained in:
@@ -4,6 +4,11 @@ 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"],
|
||||
@@ -12,7 +17,7 @@ ApplySpecialEffects("Adds an effect to empty build", () => {
|
||||
});
|
||||
|
||||
assert.equal(aps.size, 6);
|
||||
assert.equal(aps.get("ISM"), 10);
|
||||
assert.equal(aps.get("ISM")?.ap, 10);
|
||||
});
|
||||
|
||||
ApplySpecialEffects(
|
||||
@@ -20,33 +25,33 @@ ApplySpecialEffects(
|
||||
() => {
|
||||
const aps = applySpecialEffects({
|
||||
effects: ["CB"],
|
||||
abilityPoints: new Map([["SPU", 10]]),
|
||||
abilityPoints: new Map([["SPU", valueToAps(10)]]),
|
||||
ldeIntensity: 0,
|
||||
});
|
||||
|
||||
assert.equal(aps.size, 7);
|
||||
assert.equal(aps.get("SPU"), 10);
|
||||
assert.equal(aps.get("SPU")?.ap, 10);
|
||||
}
|
||||
);
|
||||
|
||||
ApplySpecialEffects("Does not boost ability beyond 57", () => {
|
||||
const aps = applySpecialEffects({
|
||||
effects: ["CB"],
|
||||
abilityPoints: new Map([["ISM", 57]]),
|
||||
abilityPoints: new Map([["ISM", valueToAps(57)]]),
|
||||
ldeIntensity: 0,
|
||||
});
|
||||
|
||||
assert.equal(aps.get("ISM"), 57);
|
||||
assert.equal(aps.get("ISM")?.ap, 57);
|
||||
});
|
||||
|
||||
ApplySpecialEffects("Tacticooler doesn't boost swim speed beyond 29", () => {
|
||||
const aps = applySpecialEffects({
|
||||
effects: ["TACTICOOLER"],
|
||||
abilityPoints: new Map([["SSU", 28]]),
|
||||
abilityPoints: new Map([["SSU", valueToAps(28)]]),
|
||||
ldeIntensity: 0,
|
||||
});
|
||||
|
||||
assert.equal(aps.get("SSU"), 29);
|
||||
assert.equal(aps.get("SSU")?.ap, 29);
|
||||
});
|
||||
|
||||
ApplySpecialEffects(
|
||||
@@ -54,42 +59,53 @@ ApplySpecialEffects(
|
||||
() => {
|
||||
const aps = applySpecialEffects({
|
||||
effects: ["TACTICOOLER"],
|
||||
abilityPoints: new Map([["SSU", 30]]),
|
||||
abilityPoints: new Map([["SSU", valueToAps(30)]]),
|
||||
ldeIntensity: 0,
|
||||
});
|
||||
|
||||
assert.equal(aps.get("SSU"), 30);
|
||||
assert.equal(aps.get("SSU")?.ap, 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", 1]]),
|
||||
abilityPoints: new Map([["SSU", valueToAps(1)]]),
|
||||
ldeIntensity: 0,
|
||||
});
|
||||
|
||||
assert.equal(aps.get("SSU"), 21);
|
||||
assert.equal(aps.get("SSU")?.ap, 21);
|
||||
});
|
||||
|
||||
ApplySpecialEffects("Applies LDE", () => {
|
||||
const aps = applySpecialEffects({
|
||||
effects: ["LDE"],
|
||||
abilityPoints: new Map([["ISM", 1]]),
|
||||
abilityPoints: new Map([["ISM", valueToAps(1)]]),
|
||||
ldeIntensity: 1,
|
||||
});
|
||||
|
||||
assert.equal(aps.get("ISM"), 2);
|
||||
assert.equal(aps.get("ISM")?.ap, 2);
|
||||
});
|
||||
|
||||
ApplySpecialEffects("Applies LDE (intensity != aps given)", () => {
|
||||
const aps = applySpecialEffects({
|
||||
effects: ["LDE"],
|
||||
abilityPoints: new Map([["ISM", 1]]),
|
||||
abilityPoints: new Map([["ISM", valueToAps(1)]]),
|
||||
ldeIntensity: 15,
|
||||
});
|
||||
|
||||
assert.equal(aps.get("ISM"), 18);
|
||||
assert.equal(aps.get("ISM")?.ap, 18);
|
||||
});
|
||||
|
||||
ApplySpecialEffects.run();
|
||||
|
||||
@@ -147,12 +147,17 @@ export function applySpecialEffects({
|
||||
|
||||
for (const value of valuesArr) {
|
||||
const boostsBeyond = "boostsBeyond" in value ? value.boostsBeyond : true;
|
||||
const currentAP = result.get(value.type) ?? 0;
|
||||
const newAP = boostsBeyond
|
||||
const currentAP = result.get(value.type)?.ap ?? 0;
|
||||
const newAPUnlimited = boostsBeyond
|
||||
? currentAP + value.ap
|
||||
: Math.max(currentAP, value.ap);
|
||||
const newAP = Math.min(newAPUnlimited, MAX_AP);
|
||||
|
||||
result.set(value.type, Math.min(newAP, MAX_AP));
|
||||
result.set(value.type, {
|
||||
ap: newAP,
|
||||
apBeforeTacticooler:
|
||||
effectObj.type === "TACTICOOLER" ? currentAP : newAP,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -121,11 +121,18 @@ function specialPoint({
|
||||
};
|
||||
}
|
||||
|
||||
const OWN_RESPAWN_PUNISHER_EXTRA_SPECIAL_LOST = 0.225;
|
||||
function specialSavedAfterDeath({
|
||||
abilityPoints,
|
||||
mainWeaponParams,
|
||||
mainOnlyAbilities,
|
||||
}: StatFunctionInput): AnalyzedBuild["stats"]["specialPoint"] {
|
||||
const SPECIAL_SAVED_AFTER_DEATH_ABILITY = "SS";
|
||||
const hasRespawnPunisher = mainOnlyAbilities.includes("RP");
|
||||
const extraSpecialLost = hasRespawnPunisher
|
||||
? OWN_RESPAWN_PUNISHER_EXTRA_SPECIAL_LOST
|
||||
: 0;
|
||||
|
||||
const specialSavedAfterDeathForDisplay = (effect: number) =>
|
||||
Number(((1.0 - effect) * 100).toFixed(2));
|
||||
|
||||
@@ -133,6 +140,7 @@ function specialSavedAfterDeath({
|
||||
abilityPoints: apFromMap({
|
||||
abilityPoints: abilityPoints,
|
||||
ability: SPECIAL_SAVED_AFTER_DEATH_ABILITY,
|
||||
key: hasRespawnPunisher ? "apBeforeTacticooler" : "ap",
|
||||
}),
|
||||
key: "SpecialGaugeRt_Restart",
|
||||
weapon: mainWeaponParams,
|
||||
@@ -140,7 +148,7 @@ function specialSavedAfterDeath({
|
||||
|
||||
return {
|
||||
baseValue: specialSavedAfterDeathForDisplay(baseEffect),
|
||||
value: specialSavedAfterDeathForDisplay(effect),
|
||||
value: specialSavedAfterDeathForDisplay(effect - extraSpecialLost),
|
||||
modifiedBy: SPECIAL_SAVED_AFTER_DEATH_ABILITY,
|
||||
};
|
||||
}
|
||||
@@ -436,15 +444,18 @@ function swimSpeed(
|
||||
}
|
||||
|
||||
const RESPAWN_CHASE_FRAME = 150;
|
||||
const OWN_RESPAWN_PUNISHER_EXTRA_RESPAWN_FRAMES = 68;
|
||||
function quickRespawnTime(
|
||||
args: StatFunctionInput
|
||||
): AnalyzedBuild["stats"]["quickRespawnTime"] {
|
||||
const QUICK_RESPAWN_TIME_ABILITY = "QR";
|
||||
const hasRespawnPunisher = args.mainOnlyAbilities.includes("RP");
|
||||
|
||||
const chase = abilityPointsToEffects({
|
||||
abilityPoints: apFromMap({
|
||||
abilityPoints: args.abilityPoints,
|
||||
ability: QUICK_RESPAWN_TIME_ABILITY,
|
||||
key: hasRespawnPunisher ? "apBeforeTacticooler" : "ap",
|
||||
}),
|
||||
key: "Dying_ChaseFrm",
|
||||
weapon: args.mainWeaponParams,
|
||||
@@ -453,16 +464,23 @@ function quickRespawnTime(
|
||||
abilityPoints: apFromMap({
|
||||
abilityPoints: args.abilityPoints,
|
||||
ability: QUICK_RESPAWN_TIME_ABILITY,
|
||||
key: hasRespawnPunisher ? "apBeforeTacticooler" : "ap",
|
||||
}),
|
||||
key: "Dying_AroundFrm",
|
||||
weapon: args.mainWeaponParams,
|
||||
});
|
||||
|
||||
const extraFrames = hasRespawnPunisher
|
||||
? OWN_RESPAWN_PUNISHER_EXTRA_RESPAWN_FRAMES
|
||||
: 0;
|
||||
|
||||
return {
|
||||
baseValue: framesToSeconds(
|
||||
RESPAWN_CHASE_FRAME + chase.baseEffect + around.baseEffect
|
||||
),
|
||||
value: framesToSeconds(RESPAWN_CHASE_FRAME + chase.effect + around.effect),
|
||||
value: framesToSeconds(
|
||||
RESPAWN_CHASE_FRAME + chase.effect + around.effect + extraFrames
|
||||
),
|
||||
modifiedBy: QUICK_RESPAWN_TIME_ABILITY,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -107,7 +107,10 @@ export interface Stat {
|
||||
modifiedBy: Ability;
|
||||
}
|
||||
|
||||
export type AbilityPoints = Map<Ability, number>;
|
||||
export type AbilityPoints = Map<
|
||||
Ability,
|
||||
{ ap: number; apBeforeTacticooler: number }
|
||||
>;
|
||||
|
||||
export interface StatFunctionInput {
|
||||
mainWeaponParams: MainWeaponParams;
|
||||
|
||||
@@ -25,8 +25,8 @@ BuildToAbilityPoints("Calculates ability points", () => {
|
||||
EMPTY_ROW,
|
||||
]);
|
||||
|
||||
assert.equal(aps.get("SS"), 13);
|
||||
assert.equal(aps.get("RSU"), 6);
|
||||
assert.equal(aps.get("SS")?.ap, 13);
|
||||
assert.equal(aps.get("RSU")?.ap, 6);
|
||||
});
|
||||
|
||||
BuildToAbilityPoints("Handles ability doubler", () => {
|
||||
@@ -36,7 +36,7 @@ BuildToAbilityPoints("Handles ability doubler", () => {
|
||||
EMPTY_ROW,
|
||||
]);
|
||||
|
||||
assert.equal(aps.get("SS"), 6);
|
||||
assert.equal(aps.get("SS")?.ap, 6);
|
||||
});
|
||||
|
||||
BuildToAbilityPoints("Does not calculate AP for main only abilities", () => {
|
||||
|
||||
@@ -31,8 +31,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;
|
||||
|
||||
result.set(ability, (result.get(ability) ?? 0) + apsDoubled);
|
||||
result.set(ability, { ap: newAp, apBeforeTacticooler: newAp });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,11 +51,13 @@ 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) ?? 0;
|
||||
return abilityPoints.get(ability)?.[key] ?? 0;
|
||||
}
|
||||
|
||||
function abilityValues({
|
||||
|
||||
@@ -487,15 +487,17 @@ function AbilityPointsDetails({
|
||||
<summary className="analyzer__ap-summary">{t("abilityPoints")}</summary>
|
||||
<div className="stack sm horizontal flex-wrap mt-4">
|
||||
{abilities
|
||||
.filter((a) => (abilityPoints.get(a.name) ?? 0) > 0)
|
||||
.filter((a) => (abilityPoints.get(a.name)?.ap ?? 0) > 0)
|
||||
.sort((a, b) => {
|
||||
return abilityPoints.get(b.name)! - abilityPoints.get(a.name)!;
|
||||
return (
|
||||
abilityPoints.get(b.name)!.ap - abilityPoints.get(a.name)!.ap
|
||||
);
|
||||
})
|
||||
.map((a) => (
|
||||
<div key={a.name} className="stack items-center">
|
||||
<Ability ability={a.name} size="TINY" />
|
||||
<div className="analyzer__ap-text">
|
||||
{abilityPoints.get(a.name)}
|
||||
{abilityPoints.get(a.name)?.ap}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user