diff --git a/app/components/layout/nav-items.json b/app/components/layout/nav-items.json index a31567fa2..8f44c84c2 100644 --- a/app/components/layout/nav-items.json +++ b/app/components/layout/nav-items.json @@ -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" }, diff --git a/app/modules/analyzer/constants.ts b/app/modules/analyzer/constants.ts index 046c160c0..ea90740d4 100644 --- a/app/modules/analyzer/constants.ts +++ b/app/modules/analyzer/constants.ts @@ -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 | 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, +}; diff --git a/app/modules/analyzer/objectDamage.test.ts b/app/modules/analyzer/objectDamage.test.ts new file mode 100644 index 000000000..cae7e7eeb --- /dev/null +++ b/app/modules/analyzer/objectDamage.test.ts @@ -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(); diff --git a/app/modules/analyzer/objectDamage.ts b/app/modules/analyzer/objectDamage.ts index a253371ba..b8c357b33 100644 --- a/app/modules/analyzer/objectDamage.ts +++ b/app/modules/analyzer/objectDamage.ts @@ -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> -> = { - // 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; - 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 = []; + + 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 @@ -96,6 +99,7 @@ export function multipliersToRecordWithFallbacks( ) as Record; } +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, + }; + }), }; }); } diff --git a/app/modules/analyzer/objectHitPoints.ts b/app/modules/analyzer/objectHitPoints.ts index ced8e7121..ab83da5b6 100644 --- a/app/modules/analyzer/objectHitPoints.ts +++ b/app/modules/analyzer/objectHitPoints.ts @@ -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, }; }; diff --git a/app/modules/analyzer/stats.ts b/app/modules/analyzer/stats.ts index 5bd3badd0..dbad75bfa 100644 --- a/app/modules/analyzer/stats.ts +++ b/app/modules/analyzer/stats.ts @@ -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"], diff --git a/app/modules/analyzer/types.ts b/app/modules/analyzer/types.ts index 1803e4da2..7d6fe14f5 100644 --- a/app/modules/analyzer/types.ts +++ b/app/modules/analyzer/types.ts @@ -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; @@ -202,6 +202,7 @@ export interface AnalyzedBuild { damageTakenInEnemyInkPerSecond: Stat; enemyInkDamageLimit: Stat; quickRespawnTime: Stat; + quickRespawnTimeSplattedByRP: Stat; superJumpTimeGroundFrames: Stat; superJumpTimeTotal: Stat; shotSpreadAir?: Stat; diff --git a/app/routes/analyzer.tsx b/app/routes/analyzer.tsx index 7e4148b32..b9b89b763 100644 --- a/app/routes/analyzer.tsx +++ b/app/routes/analyzer.tsx @@ -245,10 +245,15 @@ export default function BuildAnalyzerPage() { suffix={t("analyzer:suffix.specialPointsShort")} /> + {analyzed.stats.specialDurationInSeconds && ( + - {typeRowName} + {t(typeRowName)} {damage}{" "} {val.shotsToSplat && ( diff --git a/app/routes/index.tsx b/app/routes/index.tsx index 334a69d73..e10b66453 100644 --- a/app/routes/index.tsx +++ b/app/routes/index.tsx @@ -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()} /> + { return [{ rel: "stylesheet", href: styles }]; @@ -50,15 +53,11 @@ export default function ObjectDamagePage() { allDamageTypes, } = useObjectDamage(); - if (process.env.NODE_ENV !== "development") { - return
WIP :)
; - } - return (
- +
- +
@@ -169,7 +179,8 @@ function DamageReceiversGrid({ ReturnType, "damagesToReceivers" | "subWeaponId" >) { - const { t } = useTranslation(["weapons", "analyzer"]); + const { t } = useTranslation(["weapons", "analyzer", "common"]); + useSetTitle(t("common:pages.object-damage-calculator")); return (
{damagesToReceivers[0]?.damages.map((damage) => (
- {t( - damageTypeTranslationString({ - damageType: damage.type, - subWeaponId: subWeaponId, - }) - )} +
+ {t( + damageTypeTranslationString({ + damageType: damage.type, + subWeaponId: subWeaponId, + }) + )} + {damage.objectShredder && } +
- Distance: {damage.distance} + {t("analyzer:distanceInline", { value: damage.distance })}
))} @@ -210,21 +224,25 @@ function DamageReceiversGrid({ height={40} />
- {damageToReceiver.hitPoints}hp + {damageToReceiver.hitPoints} + {t("analyzer:suffix.hp")}
{damageToReceiver.damages.map((damage) => { return (
- - DMG + + {t("analyzer:damageShort")}
{damage.value}
- HTD + {t("analyzer:hitsToDestroyShort")}
{damage.hitsToDestroy}
diff --git a/app/styles/object-damage.css b/app/styles/object-damage.css index 3320ea2d8..c781599f0 100644 --- a/app/styles/object-damage.css +++ b/app/styles/object-damage.css @@ -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); +} diff --git a/app/styles/utils.css b/app/styles/utils.css index 8a580823f..fef6c85f5 100644 --- a/app/styles/utils.css +++ b/app/styles/utils.css @@ -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 { diff --git a/app/utils/urls.ts b/app/utils/urls.ts index 8333bd9ea..14a038458 100644 --- a/app/utils/urls.ts +++ b/app/utils/urls.ts @@ -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"; diff --git a/content/articles/inktv-announces-return.md b/content/articles/inktv-announces-return.md new file mode 100644 index 000000000..93ce1d530 --- /dev/null +++ b/content/articles/inktv-announces-return.md @@ -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. diff --git a/public/img/layout/object-damage-calculator.avif b/public/img/layout/object-damage-calculator.avif new file mode 100644 index 000000000..29e8dc1e2 Binary files /dev/null and b/public/img/layout/object-damage-calculator.avif differ diff --git a/public/img/layout/object-damage-calculator.png b/public/img/layout/object-damage-calculator.png new file mode 100644 index 000000000..c441797b3 Binary files /dev/null and b/public/img/layout/object-damage-calculator.png differ diff --git a/public/locales/en/analyzer.json b/public/locales/en/analyzer.json index 3bf377dfd..bf14acb29 100644 --- a/public/locales/en/analyzer.json +++ b/public/locales/en/analyzer.json @@ -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" } diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 8734a1df7..db52a437d 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -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", diff --git a/public/locales/en/front.json b/public/locales/en/front.json index 9f403cce6..4e43e6a55 100644 --- a/public/locales/en/front.json +++ b/public/locales/en/front.json @@ -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}}" diff --git a/scripts/remove-bad-custom-urls.ts b/scripts/remove-bad-custom-urls.ts index 921572c71..4ef2b56cf 100644 --- a/scripts/remove-bad-custom-urls.ts +++ b/scripts/remove-bad-custom-urls.ts @@ -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"); diff --git a/translation-progress.md b/translation-progress.md index 106482a11..f412d1841 100644 --- a/translation-progress.md +++ b/translation-progress.md @@ -1,14 +1,26 @@ # Translation Progress -## /da (🟢 Done) +## /da (🟡 In progress) ---- +### 🟡 analyzer.json -## /de (🟡 In progress) +**94/104** -### 🟢 analyzer.json +
+Missing -**94/94** +- stat.specialLostSplattedByRP +- stat.quickRespawnTimeSplattedByRP +- distanceInline +- damageShort +- hitsToDestroyLong +- hitsToDestroyShort +- labels.amountOf +- labels.damageType +- labels.weapon +- dmgHtdExplanation + +
### 🟢 badges.json @@ -24,11 +36,86 @@ ### 🟡 common.json -**59/60** +**60/61**
Missing +- pages.object-damage-calculator + +
+ +### 🟢 contributions.json + +**6/6** + +### 🟢 faq.json + +**6/6** + +### 🟡 front.json + +**11/12** + +
+Missing + +- object-damage-calculator.description + +
+ +### 🟢 game-misc.json + +**17/17** + +### 🟢 user.json + +**25/25** + +--- + +## /de (🟡 In progress) + +### 🟡 analyzer.json + +**94/104** + +
+Missing + +- stat.specialLostSplattedByRP +- stat.quickRespawnTimeSplattedByRP +- distanceInline +- damageShort +- hitsToDestroyLong +- hitsToDestroyShort +- labels.amountOf +- labels.damageType +- labels.weapon +- dmgHtdExplanation + +
+ +### 🟢 badges.json + +**7/7** + +### 🟢 builds.json + +**11/11** + +### 🟢 calendar.json + +**46/46** + +### 🟡 common.json + +**59/61** + +
+Missing + +- pages.object-damage-calculator - actions.copyToClipboard
@@ -41,9 +128,16 @@ **6/6** -### 🟢 front.json +### 🟡 front.json -**11/11** +**11/12** + +
+Missing + +- object-damage-calculator.description + +
### 🟢 game-misc.json @@ -59,14 +153,24 @@ ### 🟡 analyzer.json -**91/94** +**91/104**
Missing +- stat.specialLostSplattedByRP +- stat.quickRespawnTimeSplattedByRP - stat.shootingRunSpeed - stat.shootingRunSpeedCharging - stat.shootingRunSpeedFullCharge +- distanceInline +- damageShort +- hitsToDestroyLong +- hitsToDestroyShort +- labels.amountOf +- labels.damageType +- labels.weapon +- dmgHtdExplanation
@@ -92,13 +196,14 @@ ### 🟡 common.json -**48/60** +**48/61**
Missing - 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**
Missing - maps.description +- object-damage-calculator.description
@@ -180,14 +286,24 @@ ### 🟡 analyzer.json -**91/94** +**91/104**
Missing +- stat.specialLostSplattedByRP +- stat.quickRespawnTimeSplattedByRP - stat.shootingRunSpeed - stat.shootingRunSpeedCharging - stat.shootingRunSpeedFullCharge +- distanceInline +- damageShort +- hitsToDestroyLong +- hitsToDestroyShort +- labels.amountOf +- labels.damageType +- labels.weapon +- dmgHtdExplanation
@@ -213,7 +329,7 @@ ### 🟡 common.json -**46/60** +**46/61**
Missing @@ -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**
Missing @@ -261,6 +378,7 @@ - buildsGoTo - analyzer.description - maps.description +- object-damage-calculator.description
@@ -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**
Missing @@ -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
@@ -480,7 +608,7 @@ ### 🟡 common.json -**46/60** +**46/61**
Missing @@ -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**
Missing @@ -527,6 +656,7 @@ - buildsGoTo - analyzer.description - maps.description +- object-damage-calculator.description
@@ -579,7 +709,7 @@ ### 🔴 analyzer.json -**0/94** +**0/104** ### 🟢 badges.json @@ -603,7 +733,7 @@ ### 🟡 common.json -**35/60** +**35/61**
Missing @@ -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**
Missing @@ -661,6 +792,7 @@ - buildsGoTo - analyzer.description - maps.description +- object-damage-calculator.description
@@ -711,9 +843,25 @@ ## /nl (🟡 In progress) -### 🟢 analyzer.json +### 🟡 analyzer.json -**94/94** +**94/104** + +
+Missing + +- stat.specialLostSplattedByRP +- stat.quickRespawnTimeSplattedByRP +- distanceInline +- damageShort +- hitsToDestroyLong +- hitsToDestroyShort +- labels.amountOf +- labels.damageType +- labels.weapon +- dmgHtdExplanation + +
### 🟢 badges.json @@ -737,13 +885,14 @@ ### 🟡 common.json -**48/60** +**48/61**
Missing - 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**
Missing - maps.description +- object-damage-calculator.description
@@ -813,7 +963,7 @@ ### 🔴 analyzer.json -**0/94** +**0/104** ### 🟢 badges.json @@ -837,7 +987,7 @@ ### 🟡 common.json -**35/60** +**35/61**
Missing @@ -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**
Missing @@ -895,6 +1046,7 @@ - buildsGoTo - analyzer.description - maps.description +- object-damage-calculator.description
@@ -945,9 +1097,25 @@ ## /zh (🟡 In progress) -### 🟢 analyzer.json +### 🟡 analyzer.json -**94/94** +**94/104** + +
+Missing + +- stat.specialLostSplattedByRP +- stat.quickRespawnTimeSplattedByRP +- distanceInline +- damageShort +- hitsToDestroyLong +- hitsToDestroyShort +- labels.amountOf +- labels.damageType +- labels.weapon +- dmgHtdExplanation + +
### 🟢 badges.json @@ -971,7 +1139,7 @@ ### 🟡 common.json -**35/60** +**35/61**
Missing @@ -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**
Missing @@ -1029,6 +1198,7 @@ - buildsGoTo - analyzer.description - maps.description +- object-damage-calculator.description