mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-25 07:32:19 -05:00
Add tests and fix damage calculation of some wpns
This commit is contained in:
parent
5be4abf6cc
commit
6e43b6bbd3
|
|
@ -1,4 +1,5 @@
|
|||
import type { DamageType } from "./types";
|
||||
import type objectDamages from "./object-dmg.json";
|
||||
|
||||
export const MAX_LDE_INTENSITY = 21;
|
||||
export const MAX_AP = 57;
|
||||
|
|
@ -10,13 +11,13 @@ export const DAMAGE_RECEIVERS = [
|
|||
"GreatBarrier_Barrier", // Big Bubbler Shield
|
||||
"GreatBarrier_WeakPoint", // Big Bubbler Weak Point
|
||||
"Gachihoko_Barrier", // Rainmaker Shield
|
||||
"BulletUmbrellaCanopyCompact", // Undercover Brella Canopy
|
||||
"BulletUmbrellaCanopyNormal", // Splat Brella Canopy
|
||||
"BulletUmbrellaCanopyWide", // Tenta Brella Canopy
|
||||
"Wsb_Flag", // Squid Beakon
|
||||
"Wsb_Shield", // Splash Wall
|
||||
"Wsb_Sprinkler", // Sprinkler
|
||||
"Bomb_TorpedoBullet", // Torpedo
|
||||
"BulletUmbrellaCanopyCompact", // Undercover Brella Canopy
|
||||
"BulletUmbrellaCanopyNormal", // Splat Brella Canopy
|
||||
"BulletUmbrellaCanopyWide", // Tenta Brella Canopy
|
||||
] as const;
|
||||
|
||||
export const DAMAGE_TYPE = [
|
||||
|
|
@ -45,3 +46,107 @@ export const damageTypeToWeaponType: Record<
|
|||
BOMB_NORMAL: "SUB",
|
||||
BOMB_DIRECT: "SUB",
|
||||
};
|
||||
|
||||
export const objectDamageJsonKeyPriority: Record<
|
||||
keyof typeof objectDamages,
|
||||
Array<DamageType> | null
|
||||
> = {
|
||||
Blaster_BlasterMiddle: null,
|
||||
Blaster_BlasterShort: null,
|
||||
Blaster_KillOneShot: ["DIRECT"],
|
||||
Blaster: null,
|
||||
BlowerExhale_BombCore: null,
|
||||
BlowerInhale: null,
|
||||
BombFlower: null,
|
||||
Bomb_CurlingBullet: null,
|
||||
Bomb_DirectHit: ["BOMB_DIRECT"],
|
||||
Bomb_Fizzy: null,
|
||||
Bomb_Suction: null,
|
||||
Bomb_TorpedoBullet: null,
|
||||
Bomb_TorpedoSplashBurst: null,
|
||||
Bomb_Trap: null,
|
||||
Bomb: null,
|
||||
BrushCore: null,
|
||||
BrushSplash: null,
|
||||
CannonMissile: null,
|
||||
ChargerFull_Light: ["FULL_CHARGE"],
|
||||
ChargerFull_Long: ["FULL_CHARGE"],
|
||||
ChargerFull: ["FULL_CHARGE"],
|
||||
Charger_Light: ["MAX_CHARGE", "TAP_SHOT"],
|
||||
Charger_Long: ["MAX_CHARGE", "TAP_SHOT"],
|
||||
Charger: ["MAX_CHARGE", "TAP_SHOT"],
|
||||
Chariot_Body: null,
|
||||
Chariot_Cannon: null,
|
||||
Default: null,
|
||||
EnemyFlyingHohei_BombCore: null,
|
||||
GachihokoTimeUpBurst: null,
|
||||
Gachihoko_BombCore: null,
|
||||
Gachihoko_Bullet: null,
|
||||
Geyser: null,
|
||||
GoldenIkuraAttack: null,
|
||||
InkStormRain: null,
|
||||
InkStorm: null,
|
||||
Jetpack_BombCore: null,
|
||||
Jetpack_Bullet: null,
|
||||
Jetpack_Coop: null,
|
||||
Jetpack_Jet: null,
|
||||
Maneuver_Short: null,
|
||||
Maneuver: null,
|
||||
MicroLaser: null,
|
||||
MissionSalmonBuddy: null,
|
||||
MovePainter_Burst: null,
|
||||
MovePainter_Direct: null,
|
||||
MultiMissile_BombCore: null,
|
||||
MultiMissile_Bullet: null,
|
||||
NiceBall: null,
|
||||
ObjectEffect_Up: null,
|
||||
RollerCore: null,
|
||||
RollerSplash_Compact: null,
|
||||
RollerSplash_Heavy: null,
|
||||
RollerSplash_Hunter: null,
|
||||
RollerSplash: null,
|
||||
Saber_ChargeShot: null,
|
||||
Saber_ChargeSlash: null,
|
||||
Saber_Shot: null,
|
||||
Saber_Slash: null,
|
||||
SakerocketBullet: null,
|
||||
ShelterCanopy_Compact: null,
|
||||
ShelterCanopy_Wide: null,
|
||||
ShelterCanopy: null,
|
||||
ShelterShot_Compact: null,
|
||||
ShelterShot_Wide: null,
|
||||
ShelterShot: null,
|
||||
Shield: null,
|
||||
ShockSonar_Wave: null,
|
||||
Shooter_Blaze: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter_Expert: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter_First: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter_FlashRepeat: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter_Flash: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter_Gravity: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter_Heavy: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter_Long: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter_Precision: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter_Short: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter_TripleMiddle: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter_TripleQuick: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter: null,
|
||||
Skewer_Body: null,
|
||||
Skewer: null,
|
||||
Slosher_Bathtub: null,
|
||||
Slosher_Bear: null,
|
||||
Slosher_WashtubBombCore: null,
|
||||
Slosher_Washtub: null,
|
||||
Slosher: null,
|
||||
Spinner: null,
|
||||
Sprinkler: null,
|
||||
Stringer_Short: null,
|
||||
Stringer: null,
|
||||
SuperHook: null,
|
||||
SuperLanding: null,
|
||||
TripleTornado: null,
|
||||
UltraShot: null,
|
||||
UltraStamp_Swing: null,
|
||||
UltraStamp_Throw_BombCore: null,
|
||||
UltraStamp_Throw: null,
|
||||
};
|
||||
|
|
|
|||
114
app/modules/analyzer/objectDamage.test.ts
Normal file
114
app/modules/analyzer/objectDamage.test.ts
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
import { suite } from "uvu";
|
||||
import * as assert from "uvu/assert";
|
||||
import type { MainWeaponId } from "../in-game-lists";
|
||||
import { calculateDamage } from "./objectDamage";
|
||||
import { buildStats } from "./stats";
|
||||
import type { AbilityPoints, DamageType } from "./types";
|
||||
|
||||
function calculate({
|
||||
mainWeaponId = 10,
|
||||
abilityPoints = new Map(),
|
||||
damageType = "NORMAL_MAX",
|
||||
}: {
|
||||
mainWeaponId?: MainWeaponId;
|
||||
abilityPoints?: AbilityPoints;
|
||||
damageType?: DamageType;
|
||||
}) {
|
||||
const analyzed = buildStats({
|
||||
weaponSplId: mainWeaponId,
|
||||
});
|
||||
|
||||
return calculateDamage({
|
||||
abilityPoints,
|
||||
analyzed,
|
||||
mainWeaponId,
|
||||
damageType,
|
||||
});
|
||||
}
|
||||
|
||||
const CalculateDamage = suite("calculateDamage()");
|
||||
|
||||
CalculateDamage("BRU increases Splash Wall hitpoints", () => {
|
||||
const withoutBRU = calculate({});
|
||||
const withBRU = calculate({
|
||||
abilityPoints: new Map([["BRU", { ap: 10, apBeforeTacticooler: 10 }]]),
|
||||
});
|
||||
|
||||
const hpWithoutBRU = withoutBRU.find(
|
||||
(d) => d.receiver === "Wsb_Shield"
|
||||
)?.hitPoints;
|
||||
const hpWithBRU = withBRU.find((d) => d.receiver === "Wsb_Shield")?.hitPoints;
|
||||
|
||||
assert.ok(typeof hpWithoutBRU === "number");
|
||||
assert.ok(typeof hpWithBRU === "number");
|
||||
assert.ok(hpWithoutBRU < hpWithBRU);
|
||||
});
|
||||
|
||||
CalculateDamage("SPU increases Big Bubbler hitpoints", () => {
|
||||
const withoutSPU = calculate({});
|
||||
const withSPU = calculate({
|
||||
abilityPoints: new Map([["SPU", { ap: 10, apBeforeTacticooler: 10 }]]),
|
||||
});
|
||||
|
||||
const hpWithoutSPU = withoutSPU.find(
|
||||
(d) => d.receiver === "GreatBarrier_Barrier"
|
||||
)?.hitPoints;
|
||||
const hpWithSPU = withSPU.find(
|
||||
(d) => d.receiver === "GreatBarrier_Barrier"
|
||||
)?.hitPoints;
|
||||
|
||||
assert.ok(typeof hpWithoutSPU === "number");
|
||||
assert.ok(typeof hpWithSPU === "number");
|
||||
assert.ok(hpWithoutSPU < hpWithSPU);
|
||||
});
|
||||
|
||||
const shotsToPopRM: Array<
|
||||
[
|
||||
weaponId: MainWeaponId,
|
||||
damageType: DamageType,
|
||||
shotsToPop: number,
|
||||
shotsToPopOS: number
|
||||
]
|
||||
> = [
|
||||
// Splattershot
|
||||
[40, "NORMAL_MAX", 28, 26],
|
||||
// Range Blaster
|
||||
[220, "DIRECT", 5, 4],
|
||||
// .96 Gal
|
||||
[80, "NORMAL_MAX", 17, 15],
|
||||
// Splat Charger
|
||||
[2010, "FULL_CHARGE", 4, 3],
|
||||
// E-liter 4K
|
||||
[2030, "TAP_SHOT", 13, 12],
|
||||
];
|
||||
|
||||
CalculateDamage(
|
||||
"Calculates matching HTD Rainmaker shield to in-game tests",
|
||||
() => {
|
||||
for (const [
|
||||
mainWeaponId,
|
||||
damageType,
|
||||
shotsToPop,
|
||||
shotsToPopOS,
|
||||
] of shotsToPopRM) {
|
||||
const damages = calculate({ mainWeaponId, damageType });
|
||||
|
||||
const damageVsRM = damages.find(
|
||||
(d) => d.receiver === "Gachihoko_Barrier"
|
||||
)!;
|
||||
|
||||
assert.equal(
|
||||
damageVsRM.damages.find((d) => !d.objectShredder)!.hitsToDestroy,
|
||||
shotsToPop,
|
||||
`Shots to pop wrong for weapon id: ${mainWeaponId}`
|
||||
);
|
||||
assert.equal(
|
||||
damageVsRM.damages.find((d) => d.objectShredder)!.hitsToDestroy,
|
||||
shotsToPopOS,
|
||||
`Shots to pop wrong with OS for weapon id: ${mainWeaponId}`
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
CalculateDamage.run();
|
||||
|
|
@ -10,33 +10,14 @@ import type {
|
|||
SpecialWeaponId,
|
||||
SubWeaponId,
|
||||
} from "../in-game-lists";
|
||||
import { damageTypeToWeaponType, DAMAGE_RECEIVERS } from "./constants";
|
||||
import {
|
||||
damageTypeToWeaponType,
|
||||
DAMAGE_RECEIVERS,
|
||||
objectDamageJsonKeyPriority,
|
||||
} from "./constants";
|
||||
import { roundToNDecimalPlaces } from "~/utils/number";
|
||||
import { objectHitPoints } from "./objectHitPoints";
|
||||
|
||||
/** Keys to check in the json. Lower index takes priority over higher. If key is omitted means any key with valid weapon id is okay. One json key can only map to one DamageType. */
|
||||
const objectDamageJsonKeyPriority: Partial<
|
||||
Record<DamageType, Array<keyof typeof objectDamages>>
|
||||
> = {
|
||||
// NORMAL_MIN: [],
|
||||
// NORMAL_MAX: [],
|
||||
DIRECT: ["Blaster_KillOneShot"],
|
||||
FULL_CHARGE: [],
|
||||
MAX_CHARGE: [],
|
||||
TAP_SHOT: [],
|
||||
// DISTANCE: [],
|
||||
// BOMB_NORMAL: [],
|
||||
BOMB_DIRECT: ["Bomb_DirectHit"],
|
||||
};
|
||||
|
||||
const commonObjectDamageJsonKeys = () =>
|
||||
Object.keys(objectDamages).filter(
|
||||
(key) =>
|
||||
!Object.values(objectDamageJsonKeyPriority)
|
||||
.flat()
|
||||
.includes(key as any)
|
||||
) as Array<keyof typeof objectDamages>;
|
||||
|
||||
export function damageTypeToMultipliers({
|
||||
type,
|
||||
weapon,
|
||||
|
|
@ -56,10 +37,7 @@ export function damageTypeToMultipliers({
|
|||
id: SpecialWeaponId;
|
||||
};
|
||||
}) {
|
||||
const keysToCheck =
|
||||
objectDamageJsonKeyPriority[type] ?? commonObjectDamageJsonKeys();
|
||||
|
||||
for (const key of keysToCheck) {
|
||||
for (const key of jsonKeysToCeck(type)) {
|
||||
const objectDamagesObj = objectDamages[key];
|
||||
|
||||
let ok = false;
|
||||
|
|
@ -77,13 +55,38 @@ export function damageTypeToMultipliers({
|
|||
}
|
||||
|
||||
if (ok) {
|
||||
console.log(`for ${type} used ${key ?? "FALLBACK"}`);
|
||||
return objectDamagesObj.rates;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
const objectDamageJsonKeyPriorityEntries = Object.entries(
|
||||
objectDamageJsonKeyPriority
|
||||
);
|
||||
|
||||
// for example blaster belongs to both Blaster_KillOneShot
|
||||
// and Blaster categories so it needs to be specified
|
||||
// which damage type uses which
|
||||
function jsonKeysToCeck(type: DamageType) {
|
||||
const result: Array<keyof typeof objectDamages> = [];
|
||||
|
||||
for (const [key, value] of objectDamageJsonKeyPriorityEntries) {
|
||||
if (value?.includes(type)) {
|
||||
result.push(key as keyof typeof objectDamages);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.length) return result;
|
||||
|
||||
for (const [key, value] of objectDamageJsonKeyPriorityEntries) {
|
||||
if (!value) {
|
||||
result.push(key as keyof typeof objectDamages);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function multipliersToRecordWithFallbacks(
|
||||
multipliers: ReturnType<typeof damageTypeToMultipliers>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user