Merge branch 'rewrite' of https://github.com/Sendouc/sendou.ink into FrederikFraJylland/rewrite

This commit is contained in:
Kalle 2022-10-26 09:11:56 +03:00
commit 2633b444af
21 changed files with 647 additions and 128 deletions

View File

@ -5,6 +5,7 @@
},
{ "name": "builds", "url": "builds" },
{ "name": "analyzer", "url": "analyzer" },
{ "name": "object-damage-calculator", "url": "object-damage-calculator" },
{ "name": "calendar", "url": "calendar" },
{ "name": "maps", "url": "maps" },
{ "name": "badges", "url": "badges" },

View File

@ -1,21 +1,20 @@
import type { DamageType } from "./types";
import type objectDamages from "./object-dmg.json";
export const MAX_LDE_INTENSITY = 21;
export const MAX_AP = 57;
export const DAMAGE_RECEIVERS = [
"Bomb_TorpedoBullet", // Torpedo
"Chariot", // Crab Tank
"Gachihoko_Barrier", // Rainmaker Shield
"GreatBarrier_Barrier", // Big Bubbler Shield
"GreatBarrier_WeakPoint", // Big Bubbler Weak Point
// "InkRail", // InkRail
"NiceBall_Armor", // Booyah Bomb Armor
"ShockSonar", // Wave Breaker
// "Sponge_Versus", // Sponge
"GreatBarrier_Barrier", // Big Bubbler Shield
"GreatBarrier_WeakPoint", // Big Bubbler Weak Point
"Gachihoko_Barrier", // Rainmaker Shield
"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
@ -47,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,
};

View 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();

View File

@ -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>
@ -96,6 +99,7 @@ export function multipliersToRecordWithFallbacks(
) as Record<DamageReceiver, number>;
}
const objectShredderMultipliers = objectDamages.ObjectEffect_Up.rates;
export function calculateDamage({
analyzed,
mainWeaponId,
@ -140,21 +144,34 @@ export function calculateDamage({
return {
receiver,
hitPoints: damageReceiverHp,
damages: filteredDamages.map((damage) => {
const multiplier = multipliers[damage.type]![receiver];
const damagePerHit = roundToNDecimalPlaces(damage.value * multiplier);
damages: filteredDamages
.flatMap((damage) => [
{ ...damage, objectShredder: false },
{ ...damage, objectShredder: true },
])
.map((damage) => {
const baseMultiplier = multipliers[damage.type]![receiver];
const objectShredderMultiplier =
objectShredderMultipliers.find((m) => m.target === receiver)
?.rate ?? 1;
const multiplier =
baseMultiplier *
(damage.objectShredder ? objectShredderMultiplier : 1);
const hitsToDestroy = Math.ceil(damageReceiverHp / damagePerHit);
const damagePerHit = roundToNDecimalPlaces(damage.value * multiplier);
return {
value: damagePerHit,
hitsToDestroy,
multiplier,
type: damage.type,
id: damage.id,
distance: damage.distance,
};
}),
const hitsToDestroy = Math.ceil(damageReceiverHp / damagePerHit);
return {
value: damagePerHit,
hitsToDestroy,
multiplier: roundToNDecimalPlaces(multiplier, 2),
type: damage.type,
id: `${damage.id}-${String(damage.objectShredder)}`,
distance: damage.distance,
objectShredder: damage.objectShredder,
};
}),
};
});
}

View File

@ -10,10 +10,13 @@ import type {
import { hpDivided } from "./utils";
import weaponParams from "./weapon-params.json";
const PLACEHOLDER_HP = 1000;
const WAVE_BREAKER_HP = 400;
const SPRINKER_HP = 100;
const RAINMAKER_HP = 1000;
const SPLAT_BRELLA_SHIELD_HP = 500;
const BOOYAH_BOMB_ARMOR_HP = 470;
const BEAKON_HP = 120;
const TORPEDO_HP = 20;
export const objectHitPoints = (abilityPoints: AbilityPoints): HitPoints => {
const Wsb_Shield = subStats({
@ -38,7 +41,7 @@ export const objectHitPoints = (abilityPoints: AbilityPoints): HitPoints => {
invariant(GreatBarrier_WeakPoint);
return {
BulletUmbrellaCanopyNormal: PLACEHOLDER_HP, // ?? needs defaults
BulletUmbrellaCanopyNormal: SPLAT_BRELLA_SHIELD_HP,
BulletUmbrellaCanopyWide: hpDivided(
weaponParams.mainWeapons[6010].CanopyHP
),
@ -46,14 +49,14 @@ export const objectHitPoints = (abilityPoints: AbilityPoints): HitPoints => {
weaponParams.mainWeapons[6020].CanopyHP
),
Wsb_Shield,
Bomb_TorpedoBullet: PLACEHOLDER_HP, // ??
Bomb_TorpedoBullet: TORPEDO_HP,
Chariot: hpDivided(weaponParams.specialWeapons[CRAB_TANK_ID].ArmorHP),
Gachihoko_Barrier: RAINMAKER_HP,
GreatBarrier_Barrier,
GreatBarrier_WeakPoint,
NiceBall_Armor: PLACEHOLDER_HP, // ??
NiceBall_Armor: BOOYAH_BOMB_ARMOR_HP, // ??
ShockSonar: WAVE_BREAKER_HP,
Wsb_Flag: PLACEHOLDER_HP, // ??
Wsb_Flag: BEAKON_HP,
Wsb_Sprinkler: SPRINKER_HP,
};
};

View File

@ -75,7 +75,8 @@ export function buildStats({
},
stats: {
specialPoint: specialPoint(input),
specialSavedAfterDeath: specialSavedAfterDeath(input),
specialLost: specialLost(input),
specialLostSplattedByRP: specialLost(input, true),
fullInkTankOptions: fullInkTankOptions(input),
damages: damages(input),
mainWeaponWhiteInkSeconds:
@ -97,7 +98,8 @@ export function buildStats({
enemyInkDamageLimit: enemyInkDamageLimit(input),
framesBeforeTakingDamageInEnemyInk:
framesBeforeTakingDamageInEnemyInk(input),
quickRespawnTime: quickRespawnTime(input),
quickRespawnTime: respawnTime(input),
quickRespawnTimeSplattedByRP: respawnTime(input, true),
superJumpTimeGroundFrames: superJumpTimeGroundFrames(input),
superJumpTimeTotal: superJumpTimeTotal(input),
shotSpreadAir: shotSpreadAir(input),
@ -165,11 +167,11 @@ function specialPoint({
}
const OWN_RESPAWN_PUNISHER_EXTRA_SPECIAL_LOST = 0.225;
function specialSavedAfterDeath({
abilityPoints,
mainWeaponParams,
mainOnlyAbilities,
}: StatFunctionInput): AnalyzedBuild["stats"]["specialPoint"] {
const ENEMY_RESPAWN_PUNISHER_EXTRA_SPECIAL_LOST = 0.15;
function specialLost(
{ abilityPoints, mainWeaponParams, mainOnlyAbilities }: StatFunctionInput,
splattedByRP = false
): AnalyzedBuild["stats"]["specialPoint"] {
const SPECIAL_SAVED_AFTER_DEATH_ABILITY = "SS";
const hasRespawnPunisher = mainOnlyAbilities.includes("RP");
const extraSpecialLost = hasRespawnPunisher
@ -189,9 +191,17 @@ function specialSavedAfterDeath({
weapon: mainWeaponParams,
});
const splattedByExtraPenalty = splattedByRP
? ENEMY_RESPAWN_PUNISHER_EXTRA_SPECIAL_LOST
: 0;
return {
baseValue: specialSavedAfterDeathForDisplay(baseEffect),
value: specialSavedAfterDeathForDisplay(effect - extraSpecialLost),
baseValue: specialSavedAfterDeathForDisplay(
baseEffect - splattedByExtraPenalty
),
value: specialSavedAfterDeathForDisplay(
effect - splattedByExtraPenalty - extraSpecialLost
),
modifiedBy: [SPECIAL_SAVED_AFTER_DEATH_ABILITY, "RP"],
};
}
@ -516,9 +526,11 @@ function swimSpeed(
const RESPAWN_CHASE_FRAME = 150;
const OWN_RESPAWN_PUNISHER_EXTRA_RESPAWN_FRAMES = 68;
const ENEMY_RESPAWN_PUNISHER_EXTRA_RESPAWN_FRAMES = 45;
const SPLATOON_3_FASTER_RESPAWN = 60;
function quickRespawnTime(
args: StatFunctionInput
function respawnTime(
args: StatFunctionInput,
splattedByRP = false
): AnalyzedBuild["stats"]["quickRespawnTime"] {
const QUICK_RESPAWN_TIME_ABILITY = "QR";
const hasRespawnPunisher = args.mainOnlyAbilities.includes("RP");
@ -542,14 +554,19 @@ function quickRespawnTime(
weapon: args.mainWeaponParams,
});
const extraFrames = hasRespawnPunisher
const ownRPExtraFrames = hasRespawnPunisher
? OWN_RESPAWN_PUNISHER_EXTRA_RESPAWN_FRAMES
: 0;
const splattedByExtraFrames = splattedByRP
? ENEMY_RESPAWN_PUNISHER_EXTRA_RESPAWN_FRAMES
: 0;
return {
baseValue: framesToSeconds(
RESPAWN_CHASE_FRAME +
chase.baseEffect +
splattedByExtraFrames +
around.baseEffect -
SPLATOON_3_FASTER_RESPAWN
),
@ -557,7 +574,8 @@ function quickRespawnTime(
RESPAWN_CHASE_FRAME +
chase.effect +
around.effect +
extraFrames -
splattedByExtraFrames +
ownRPExtraFrames -
SPLATOON_3_FASTER_RESPAWN
),
modifiedBy: [QUICK_RESPAWN_TIME_ABILITY, "RP"],

View File

@ -185,8 +185,8 @@ export interface AnalyzedBuild {
};
stats: {
specialPoint: Stat;
/** % of special charge saved when dying */
specialSavedAfterDeath: Stat;
specialLost: Stat;
specialLostSplattedByRP: Stat;
mainWeaponWhiteInkSeconds?: number;
subWeaponWhiteInkSeconds: number;
fullInkTankOptions: Array<FullInkTankOption & { id: string }>;
@ -202,6 +202,7 @@ export interface AnalyzedBuild {
damageTakenInEnemyInkPerSecond: Stat;
enemyInkDamageLimit: Stat;
quickRespawnTime: Stat;
quickRespawnTimeSplattedByRP: Stat;
superJumpTimeGroundFrames: Stat;
superJumpTimeTotal: Stat;
shotSpreadAir?: Stat;

View File

@ -245,10 +245,15 @@ export default function BuildAnalyzerPage() {
suffix={t("analyzer:suffix.specialPointsShort")}
/>
<StatCard
stat={analyzed.stats.specialSavedAfterDeath}
stat={analyzed.stats.specialLost}
title={t("analyzer:stat.specialLost")}
suffix="%"
/>
<StatCard
stat={analyzed.stats.specialLostSplattedByRP}
title={t("analyzer:stat.specialLostSplattedByRP")}
suffix="%"
/>
{analyzed.stats.specialDurationInSeconds && (
<StatCard
stat={analyzed.stats.specialDurationInSeconds}
@ -544,6 +549,11 @@ export default function BuildAnalyzerPage() {
title={t("analyzer:stat.quickRespawnTime")}
suffix={t("analyzer:suffix.seconds")}
/>
<StatCard
stat={analyzed.stats.quickRespawnTimeSplattedByRP}
title={t("analyzer:stat.quickRespawnTimeSplattedByRP")}
suffix={t("analyzer:suffix.seconds")}
/>
<StatCard
stat={analyzed.stats.superJumpTimeGroundFrames}
title={t("analyzer:stat.superJumpTimeGround")}
@ -834,7 +844,7 @@ function DamageTable({
return (
<tr key={val.id}>
<td>{typeRowName}</td>
<td>{t(typeRowName)}</td>
<td>
{damage}{" "}
{val.shotsToSplat && (

View File

@ -22,6 +22,7 @@ import {
CALENDAR_PAGE,
mapsPage,
navIconUrl,
OBJECT_DAMAGE_CALCULATOR,
plusSuggestionPage,
userPage,
} from "~/utils/urls";
@ -75,6 +76,12 @@ export default function Index() {
description={t("front:analyzer.description")}
to={analyzerPage()}
/>
<FeatureCard
navItem="object-damage-calculator"
title={t("common:pages.object-damage-calculator")}
description={t("front:object-damage-calculator.description")}
to={OBJECT_DAMAGE_CALCULATOR}
/>
<FeatureCard
navItem="plus"
title={t("common:pages.plus")}

View File

@ -29,6 +29,9 @@ import clsx from "clsx";
import { Label } from "~/components/Label";
import { Ability } from "~/components/Ability";
import { damageTypeTranslationString } from "~/utils/i18next";
import { useSetTitle } from "~/hooks/useSetTitle";
export const CURRENT_PATCH = "1.2";
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: styles }];
@ -50,15 +53,11 @@ export default function ObjectDamagePage() {
allDamageTypes,
} = useObjectDamage();
if (process.env.NODE_ENV !== "development") {
return <Main>WIP :)</Main>;
}
return (
<Main className="stack lg">
<div className="object-damage__controls">
<div>
<Label htmlFor="weapon">Weapon</Label>
<Label htmlFor="weapon">{t("analyzer:labels.weapon")}</Label>
<WeaponCombobox
id="weapon"
inputName="weapon"
@ -74,7 +73,7 @@ export default function ObjectDamagePage() {
/>
</div>
<div>
<Label htmlFor="damage">Damage type</Label>
<Label htmlFor="damage">{t("analyzer:labels.damageType")}</Label>
<DamageTypesSelect
handleChange={handleChange}
subWeaponId={subWeaponId}
@ -84,10 +83,12 @@ export default function ObjectDamagePage() {
</div>
<div>
<Label htmlFor="ap" labelClassName="object-damage__ap-label">
Amount of <Ability ability="BRU" size="TINY" />{" "}
{t("analyzer:labels.amountOf")}
<Ability ability="BRU" size="TINY" />
<Ability ability="SPU" size="TINY" />
</Label>
<select
className="object-damage__select"
id="ap"
value={abilityPoints}
onChange={(e) =>
@ -107,6 +108,14 @@ export default function ObjectDamagePage() {
subWeaponId={subWeaponId}
damagesToReceivers={damagesToReceivers}
/>
<div className="object-damage__bottom-container">
<div className="text-lighter text-xs">
{t("analyzer:dmgHtdExplanation")}
</div>
<div className="object-damage__patch">
{t("analyzer:patch")} {CURRENT_PATCH}
</div>
</div>
</Main>
);
}
@ -124,6 +133,7 @@ function DamageTypesSelect({
return (
<select
className="object-damage__select"
id="damage"
value={damageType}
onChange={(e) =>
@ -169,7 +179,8 @@ function DamageReceiversGrid({
ReturnType<typeof useObjectDamage>,
"damagesToReceivers" | "subWeaponId"
>) {
const { t } = useTranslation(["weapons", "analyzer"]);
const { t } = useTranslation(["weapons", "analyzer", "common"]);
useSetTitle(t("common:pages.object-damage-calculator"));
return (
<div
@ -184,18 +195,21 @@ function DamageReceiversGrid({
<div />
{damagesToReceivers[0]?.damages.map((damage) => (
<div key={damage.id} className="object-damage__table-header">
{t(
damageTypeTranslationString({
damageType: damage.type,
subWeaponId: subWeaponId,
})
)}
<div className="stack horizontal sm justify-center items-center">
{t(
damageTypeTranslationString({
damageType: damage.type,
subWeaponId: subWeaponId,
})
)}
{damage.objectShredder && <Ability ability="OS" size="TINY" />}
</div>
<div
className={clsx("object-damage__distance", {
invisible: !damage.distance,
})}
>
Distance: {damage.distance}
{t("analyzer:distanceInline", { value: damage.distance })}
</div>
</div>
))}
@ -210,21 +224,25 @@ function DamageReceiversGrid({
height={40}
/>
<div className="object-damage__hp">
{damageToReceiver.hitPoints}hp
{damageToReceiver.hitPoints}
{t("analyzer:suffix.hp")}
</div>
{damageToReceiver.damages.map((damage) => {
return (
<div key={damage.id} className="object-damage__table-card">
<div className="object-damage__table-card__results">
<abbr className="object-damage__abbr" title="Damage">
DMG
<abbr
className="object-damage__abbr"
title={t("analyzer:stat.category.damage")}
>
{t("analyzer:damageShort")}
</abbr>
<div>{damage.value}</div>
<abbr
className="object-damage__abbr"
title="Hits to destroy"
title={t("analyzer:hitsToDestroyLong")}
>
HTD
{t("analyzer:hitsToDestroyShort")}
</abbr>
<div>{damage.hitsToDestroy}</div>
</div>

View File

@ -1,6 +1,8 @@
.object-damage__controls {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: var(--s-4);
}
.object-damage__ap-label {
@ -13,6 +15,7 @@
display: grid;
width: 100%;
column-gap: var(--s-6);
overflow-x: auto;
place-items: center;
row-gap: var(--s-3);
}
@ -31,7 +34,9 @@
font-size: var(--fonts-xs);
font-weight: var(--semi-bold);
padding-block: var(--s-2);
padding-inline: var(--s-2);
text-align: center;
white-space: nowrap;
}
.object-damage__distance {
@ -56,6 +61,11 @@
display: grid;
column-gap: var(--s-2);
grid-template-columns: 1fr 1fr;
padding-inline: var(--s-2);
}
.object-damage__select {
width: initial;
}
.object-damage__abbr {
@ -71,3 +81,20 @@
letter-spacing: 0.5px;
margin-block-start: var(--s-2);
}
.object-damage__bottom-container {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
}
.object-damage__patch {
border-radius: var(--rounded);
background-color: var(--theme-transparent);
color: var(--text-lighter);
font-size: var(--fonts-xxxs);
font-weight: var(--bold);
padding-block: var(--s-0-5);
padding-inline: var(--s-1);
}

View File

@ -1,3 +1,7 @@
.text-lg {
font-size: var(--fonts-lg);
}
.text-sm {
font-size: var(--fonts-sm);
}
@ -6,8 +10,8 @@
font-size: var(--fonts-xs);
}
.text-lg {
font-size: var(--fonts-lg);
.text-xxs {
font-size: var(--fonts-xxs);
}
.text-center {

View File

@ -35,6 +35,7 @@ export const CONTRIBUTIONS_PAGE = "/contributions";
export const BADGES_PAGE = "/badges";
export const BUILDS_PAGE = "/builds";
export const CALENDAR_PAGE = "/calendar";
export const OBJECT_DAMAGE_CALCULATOR = "/object-damage-calculator";
export const STOP_IMPERSONATING_URL = "/auth/impersonate/stop";
export const SEED_URL = "/seed";

View File

@ -0,0 +1,13 @@
---
title: InkTV Announces Its Return
date: 2022-10-24
author: Riczi
---
In a [Twitter post](https://twitter.com/InkTVSplat/status/1584590544205930496) released on Monday, October 24th, 2022, InkTV announced that it would be returning to the Splatoon competitive scene. This platform is dedicated highlighting the players and teams involved in competitive Splatoon. They were heavily involved in developing tournaments, podcast feeds, and player interviews in an effort to grow the community.
InkTV was retired in early 2020, just as the Covid-19 pandemic began to escalate. Now with tournaments being held again and the incredibly successful launch of a new game in Splatoon 3, InkTV is ready to make its return.
The announcement video features some of Splatoon's most prominent community members, including caster NineWholeGrains and professional players ProChara and KyochaN. In the footage they talk about what InkTV did in the past and how it helped them grow and develop as individuals. Everyone was then surprised with the return announcement.
Be sure to follow InkTV on social media so you can be part of the ever-growing Splatoon community. Check out the video if you haven't already so you can get excited for what InkTV will have in store.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -16,9 +16,11 @@
"stat.maxChargeHoldSeconds": "Max charge hold time",
"stat.specialPoints": "Points to special",
"stat.specialLost": "Special lost when splatted",
"stat.specialLostSplattedByRP": "Special lost when splatted by RP user",
"stat.whiteInk": "No ink recovery time after usage",
"stat.squidFormInkRecoverySeconds": "Ink tank full recovery time (squid form)",
"stat.quickRespawnTime": "Quick respawn time",
"stat.quickRespawnTimeSplattedByRP": "Quick Respawn time (splatted by RP user)",
"stat.superJumpTimeGround": "Super jump vulnerable frames",
"stat.superJumpTimeTotal": "Super jump time (total)",
"stat.jumpShotSpread": "Shot spread in degrees (jumping)",
@ -92,5 +94,13 @@
"abilityPoints": "Ability points",
"abilityPoints.short": "AP",
"consumptionExplanation": "This chart shows the amount of actions left to perform with main weapon after using sub 0-{{maxSubsToUse}} times. Max amount of consecutive subs to use with full ink tank is {{maxSubsToUse}}.",
"trackingSubDefExplanation": "Point Sensor, Ink Mine and Angle Shooter tracking times are calculated against an opponent with 0AP of Sub Power Up."
"trackingSubDefExplanation": "Point Sensor, Ink Mine and Angle Shooter tracking times are calculated against an opponent with 0AP of Sub Power Up.",
"distanceInline": "Distance: {{value}}",
"damageShort": "DMG",
"hitsToDestroyLong": "Hits to destroy",
"hitsToDestroyShort": "HTD",
"labels.amountOf": "Amount of",
"labels.damageType": "Damage type",
"labels.weapon": "Weapon",
"dmgHtdExplanation": "DMG = Damage • HTD = Hits to destroy"
}

View File

@ -9,6 +9,7 @@
"pages.builds": "Builds",
"pages.analyzer": "Build Analyzer",
"pages.maps": "Map Lists",
"pages.object-damage-calculator": "Object DMG Calc",
"header.profile": "Profile",
"header.logout": "Log out",

View File

@ -7,6 +7,7 @@
"badges.description": "List of all the badges you can earn for your profile",
"analyzer.description": "Find out what your builds actually do",
"maps.description": "Turn pool of maps into a list to play on",
"object-damage-calculator.description": "Calculate damage dealt to different objects",
"recentWinners": "Recent winners",
"upcomingEvents": "Upcoming events",
"articleBy": "by {{author}}"

View File

@ -3,7 +3,7 @@ import "dotenv/config";
import { sql } from "~/db/sql";
sql
.prepare(`update "User" set "customUrl" = NULL where "customUrl" like '%/%'`)
.prepare(`update "User" set "customUrl" = NULL where "customUrl" like '%#%'`)
.run();
console.log("Done");

View File

@ -1,14 +1,26 @@
# Translation Progress
## /da (🟢 Done)
## /da (🟡 In progress)
---
### 🟡 analyzer.json
## /de (🟡 In progress)
**94/104**
### 🟢 analyzer.json
<details>
<summary>Missing</summary>
**94/94**
- stat.specialLostSplattedByRP
- stat.quickRespawnTimeSplattedByRP
- distanceInline
- damageShort
- hitsToDestroyLong
- hitsToDestroyShort
- labels.amountOf
- labels.damageType
- labels.weapon
- dmgHtdExplanation
</details>
### 🟢 badges.json
@ -24,11 +36,86 @@
### 🟡 common.json
**59/60**
**60/61**
<details>
<summary>Missing</summary>
- pages.object-damage-calculator
</details>
### 🟢 contributions.json
**6/6**
### 🟢 faq.json
**6/6**
### 🟡 front.json
**11/12**
<details>
<summary>Missing</summary>
- object-damage-calculator.description
</details>
### 🟢 game-misc.json
**17/17**
### 🟢 user.json
**25/25**
---
## /de (🟡 In progress)
### 🟡 analyzer.json
**94/104**
<details>
<summary>Missing</summary>
- stat.specialLostSplattedByRP
- stat.quickRespawnTimeSplattedByRP
- distanceInline
- damageShort
- hitsToDestroyLong
- hitsToDestroyShort
- labels.amountOf
- labels.damageType
- labels.weapon
- dmgHtdExplanation
</details>
### 🟢 badges.json
**7/7**
### 🟢 builds.json
**11/11**
### 🟢 calendar.json
**46/46**
### 🟡 common.json
**59/61**
<details>
<summary>Missing</summary>
- pages.object-damage-calculator
- actions.copyToClipboard
</details>
@ -41,9 +128,16 @@
**6/6**
### 🟢 front.json
### 🟡 front.json
**11/11**
**11/12**
<details>
<summary>Missing</summary>
- object-damage-calculator.description
</details>
### 🟢 game-misc.json
@ -59,14 +153,24 @@
### 🟡 analyzer.json
**91/94**
**91/104**
<details>
<summary>Missing</summary>
- stat.specialLostSplattedByRP
- stat.quickRespawnTimeSplattedByRP
- stat.shootingRunSpeed
- stat.shootingRunSpeedCharging
- stat.shootingRunSpeedFullCharge
- distanceInline
- damageShort
- hitsToDestroyLong
- hitsToDestroyShort
- labels.amountOf
- labels.damageType
- labels.weapon
- dmgHtdExplanation
</details>
@ -92,13 +196,14 @@
### 🟡 common.json
**48/60**
**48/61**
<details>
<summary>Missing</summary>
- pages.s2
- pages.maps
- pages.object-damage-calculator
- auth.errors.aborted
- auth.errors.failed
- auth.errors.discordPermissions
@ -122,12 +227,13 @@
### 🟡 front.json
**10/11**
**10/12**
<details>
<summary>Missing</summary>
- maps.description
- object-damage-calculator.description
</details>
@ -180,14 +286,24 @@
### 🟡 analyzer.json
**91/94**
**91/104**
<details>
<summary>Missing</summary>
- stat.specialLostSplattedByRP
- stat.quickRespawnTimeSplattedByRP
- stat.shootingRunSpeed
- stat.shootingRunSpeedCharging
- stat.shootingRunSpeedFullCharge
- distanceInline
- damageShort
- hitsToDestroyLong
- hitsToDestroyShort
- labels.amountOf
- labels.damageType
- labels.weapon
- dmgHtdExplanation
</details>
@ -213,7 +329,7 @@
### 🟡 common.json
**46/60**
**46/61**
<details>
<summary>Missing</summary>
@ -221,6 +337,7 @@
- pages.s2
- pages.analyzer
- pages.maps
- pages.object-damage-calculator
- auth.errors.aborted
- auth.errors.failed
- auth.errors.discordPermissions
@ -253,7 +370,7 @@
### 🟡 front.json
**8/11**
**8/12**
<details>
<summary>Missing</summary>
@ -261,6 +378,7 @@
- buildsGoTo
- analyzer.description
- maps.description
- object-damage-calculator.description
</details>
@ -313,7 +431,7 @@
### 🔴 analyzer.json
**0/94**
**0/104**
### 🔴 badges.json
@ -329,7 +447,7 @@
### 🔴 common.json
**0/60**
**0/61**
### 🔴 contributions.json
@ -341,7 +459,7 @@
### 🔴 front.json
**0/11**
**0/12**
### 🟡 game-misc.json
@ -368,7 +486,7 @@
### 🟡 analyzer.json
**12/94**
**12/104**
<details>
<summary>Missing</summary>
@ -378,9 +496,11 @@
- stat.maxChargeHoldSeconds
- stat.specialPoints
- stat.specialLost
- stat.specialLostSplattedByRP
- stat.whiteInk
- stat.squidFormInkRecoverySeconds
- stat.quickRespawnTime
- stat.quickRespawnTimeSplattedByRP
- stat.superJumpTimeGround
- stat.superJumpTimeTotal
- stat.jumpShotSpread
@ -455,6 +575,14 @@
- abilityPoints.short
- consumptionExplanation
- trackingSubDefExplanation
- distanceInline
- damageShort
- hitsToDestroyLong
- hitsToDestroyShort
- labels.amountOf
- labels.damageType
- labels.weapon
- dmgHtdExplanation
</details>
@ -480,7 +608,7 @@
### 🟡 common.json
**46/60**
**46/61**
<details>
<summary>Missing</summary>
@ -488,6 +616,7 @@
- pages.s2
- pages.analyzer
- pages.maps
- pages.object-damage-calculator
- auth.errors.aborted
- auth.errors.failed
- auth.errors.discordPermissions
@ -519,7 +648,7 @@
### 🟡 front.json
**8/11**
**8/12**
<details>
<summary>Missing</summary>
@ -527,6 +656,7 @@
- buildsGoTo
- analyzer.description
- maps.description
- object-damage-calculator.description
</details>
@ -579,7 +709,7 @@
### 🔴 analyzer.json
**0/94**
**0/104**
### 🟢 badges.json
@ -603,7 +733,7 @@
### 🟡 common.json
**35/60**
**35/61**
<details>
<summary>Missing</summary>
@ -611,6 +741,7 @@
- pages.s2
- pages.analyzer
- pages.maps
- pages.object-damage-calculator
- auth.errors.aborted
- auth.errors.failed
- auth.errors.discordPermissions
@ -653,7 +784,7 @@
### 🟡 front.json
**8/11**
**8/12**
<details>
<summary>Missing</summary>
@ -661,6 +792,7 @@
- buildsGoTo
- analyzer.description
- maps.description
- object-damage-calculator.description
</details>
@ -711,9 +843,25 @@
## /nl (🟡 In progress)
### 🟢 analyzer.json
### 🟡 analyzer.json
**94/94**
**94/104**
<details>
<summary>Missing</summary>
- stat.specialLostSplattedByRP
- stat.quickRespawnTimeSplattedByRP
- distanceInline
- damageShort
- hitsToDestroyLong
- hitsToDestroyShort
- labels.amountOf
- labels.damageType
- labels.weapon
- dmgHtdExplanation
</details>
### 🟢 badges.json
@ -737,13 +885,14 @@
### 🟡 common.json
**48/60**
**48/61**
<details>
<summary>Missing</summary>
- pages.analyzer
- pages.maps
- pages.object-damage-calculator
- auth.errors.aborted
- auth.errors.failed
- auth.errors.discordPermissions
@ -767,12 +916,13 @@
### 🟡 front.json
**10/11**
**10/12**
<details>
<summary>Missing</summary>
- maps.description
- object-damage-calculator.description
</details>
@ -813,7 +963,7 @@
### 🔴 analyzer.json
**0/94**
**0/104**
### 🟢 badges.json
@ -837,7 +987,7 @@
### 🟡 common.json
**35/60**
**35/61**
<details>
<summary>Missing</summary>
@ -845,6 +995,7 @@
- pages.s2
- pages.analyzer
- pages.maps
- pages.object-damage-calculator
- auth.errors.aborted
- auth.errors.failed
- auth.errors.discordPermissions
@ -887,7 +1038,7 @@
### 🟡 front.json
**8/11**
**8/12**
<details>
<summary>Missing</summary>
@ -895,6 +1046,7 @@
- buildsGoTo
- analyzer.description
- maps.description
- object-damage-calculator.description
</details>
@ -945,9 +1097,25 @@
## /zh (🟡 In progress)
### 🟢 analyzer.json
### 🟡 analyzer.json
**94/94**
**94/104**
<details>
<summary>Missing</summary>
- stat.specialLostSplattedByRP
- stat.quickRespawnTimeSplattedByRP
- distanceInline
- damageShort
- hitsToDestroyLong
- hitsToDestroyShort
- labels.amountOf
- labels.damageType
- labels.weapon
- dmgHtdExplanation
</details>
### 🟢 badges.json
@ -971,7 +1139,7 @@
### 🟡 common.json
**35/60**
**35/61**
<details>
<summary>Missing</summary>
@ -979,6 +1147,7 @@
- pages.s2
- pages.analyzer
- pages.maps
- pages.object-damage-calculator
- auth.errors.aborted
- auth.errors.failed
- auth.errors.discordPermissions
@ -1021,7 +1190,7 @@
### 🟡 front.json
**8/11**
**8/12**
<details>
<summary>Missing</summary>
@ -1029,6 +1198,7 @@
- buildsGoTo
- analyzer.description
- maps.description
- object-damage-calculator.description
</details>