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..a1baf0cbf 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 @@ -24,6 +23,7 @@ export const DAMAGE_RECEIVERS = [ export const DAMAGE_TYPE = [ "NORMAL_MIN", "NORMAL_MAX", + "NORMAL_MAX_FULL_CHARGE", // Hydra Splatling goes from 32 to 40 dmg when fully charged "DIRECT", "FULL_CHARGE", "MAX_CHARGE", @@ -39,6 +39,7 @@ export const damageTypeToWeaponType: Record< > = { NORMAL_MIN: "MAIN", NORMAL_MAX: "MAIN", + NORMAL_MAX_FULL_CHARGE: "MAIN", DIRECT: "MAIN", FULL_CHARGE: "MAIN", MAX_CHARGE: "MAIN", @@ -47,3 +48,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 97810f643..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 diff --git a/app/modules/analyzer/stats.ts b/app/modules/analyzer/stats.ts index 5bd3badd0..5f67f7fda 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"], }; } @@ -331,6 +341,7 @@ const damageTypeToParamsKey: Record< > = { NORMAL_MIN: "DamageParam_ValueMin", NORMAL_MAX: "DamageParam_ValueMax", + NORMAL_MAX_FULL_CHARGE: "DamageParam_ValueFullChargeMax", DIRECT: "DamageParam_ValueDirect", DISTANCE: "BlastParam_DistanceDamage", FULL_CHARGE: "DamageParam_ValueFullCharge", @@ -388,7 +399,7 @@ function shotsToSplat({ type: DamageType; isTripleShooter: boolean; }) { - if (type !== "NORMAL_MAX") return; + if (type !== "NORMAL_MAX" && type !== "NORMAL_MAX_FULL_CHARGE") return; const multiplier = isTripleShooter ? 3 : 1; @@ -516,9 +527,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 +555,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 +575,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..bedf7fe6b 100644 --- a/app/modules/analyzer/types.ts +++ b/app/modules/analyzer/types.ts @@ -33,6 +33,7 @@ export interface MainWeaponParams { MoveSpeedFullCharge?: number; DamageParam_ValueMax?: number; DamageParam_ValueMin?: number; + DamageParam_ValueFullChargeMax?: number; DamageParam_ValueDirect?: number; Jump_DegSwerve?: number; Stand_DegSwerve?: number; @@ -185,8 +186,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 +203,7 @@ export interface AnalyzedBuild { damageTakenInEnemyInkPerSecond: Stat; enemyInkDamageLimit: Stat; quickRespawnTime: Stat; + quickRespawnTimeSplattedByRP: Stat; superJumpTimeGroundFrames: Stat; superJumpTimeTotal: Stat; shotSpreadAir?: Stat; diff --git a/app/modules/analyzer/useObjectDamage.ts b/app/modules/analyzer/useObjectDamage.ts index 8f44c8b0c..6b20383f9 100644 --- a/app/modules/analyzer/useObjectDamage.ts +++ b/app/modules/analyzer/useObjectDamage.ts @@ -1,5 +1,4 @@ import { useSearchParams } from "@remix-run/react"; -import invariant from "tiny-invariant"; import { type MainWeaponId } from "../in-game-lists"; import { calculateDamage } from "./objectDamage"; import { buildStats } from "./stats"; @@ -37,7 +36,7 @@ export function useObjectDamage() { { weapon: String(newMainWeaponId), [ABILITY_POINTS_SP_KEY]: String(newAbilityPoints), - [DAMAGE_TYPE_SP_KEY]: newDamageType, + [DAMAGE_TYPE_SP_KEY]: newDamageType ?? "", }, { replace: true, state: { scroll: false } } ); @@ -47,15 +46,17 @@ export function useObjectDamage() { mainWeaponId, subWeaponId: analyzed.weapon.subWeaponSplId, handleChange, - damagesToReceivers: calculateDamage({ - abilityPoints: new Map([ - ["BRU", { ap: abilityPoints, apBeforeTacticooler: abilityPoints }], - ["SPU", { ap: abilityPoints, apBeforeTacticooler: abilityPoints }], - ]), - analyzed, - mainWeaponId, - damageType, - }), + damagesToReceivers: damageType + ? calculateDamage({ + abilityPoints: new Map([ + ["BRU", { ap: abilityPoints, apBeforeTacticooler: abilityPoints }], + ["SPU", { ap: abilityPoints, apBeforeTacticooler: abilityPoints }], + ]), + analyzed, + mainWeaponId, + damageType, + }) + : null, abilityPoints: String(abilityPoints), damageType, allDamageTypes: Array.from( @@ -99,7 +100,6 @@ function validatedDamageTypeFromSearchParams({ const fallbackFound = damageTypePriorityList.find((type) => analyzed.stats.damages.some((d) => d.type === type) ); - invariant(fallbackFound); return fallbackFound; } diff --git a/app/modules/analyzer/weapon-params.json b/app/modules/analyzer/weapon-params.json index a01399359..172d0b5ba 100644 --- a/app/modules/analyzer/weapon-params.json +++ b/app/modules/analyzer/weapon-params.json @@ -375,7 +375,7 @@ "InkConsume_WeaponSwingParam": 0.18 }, "1030": { - "SpecialPoint": 200, + "SpecialPoint": 210, "subWeaponId": 10, "specialWeaponId": 4, "InkConsume_WeaponVerticalSwingParam": 0.12, @@ -513,7 +513,7 @@ "InkConsumeSlosher": 0.06 }, "3020": { - "SpecialPoint": 200, + "SpecialPoint": 210, "subWeaponId": 5, "specialWeaponId": 6, "MoveSpeed": 0.07, @@ -597,6 +597,7 @@ "MoveSpeed_Charge": 0.04, "DamageParam_ValueMax": 320, "DamageParam_ValueMin": 160, + "DamageParam_ValueFullChargeMax": 400, "Jump_DegSwerve": 6, "Stand_DegSwerve": 3, "InkRecoverStop": 40, @@ -748,7 +749,7 @@ "InkConsumeFullCharge_ChargeParam": 0.085 }, "7020": { - "SpecialPoint": 200, + "SpecialPoint": 210, "subWeaponId": 6, "specialWeaponId": 4, "WeaponSpeedType": "Fast", @@ -881,7 +882,7 @@ }, "SubInkSaveLv": 1, "InkConsume": 0.6, - "InkRecoverStop": 70, + "InkRecoverStop": 85, "DistanceDamage_BlastParamArray": [ [ { diff --git a/app/routes/analyzer.tsx b/app/routes/analyzer.tsx index 7e4148b32..11e6fc99d 100644 --- a/app/routes/analyzer.tsx +++ b/app/routes/analyzer.tsx @@ -1,4 +1,5 @@ import { type LinksFunction, type MetaFunction } from "@remix-run/node"; +import { Link } from "@remix-run/react"; import * as React from "react"; import { useTranslation } from "react-i18next"; import { AbilitiesSelector } from "~/components/AbilitiesSelector"; @@ -37,9 +38,14 @@ import styles from "~/styles/analyzer.css"; import { damageTypeTranslationString } from "~/utils/i18next"; import { type SendouRouteHandle } from "~/utils/remix"; import { makeTitle } from "~/utils/strings"; -import { specialWeaponImageUrl, subWeaponImageUrl } from "~/utils/urls"; +import { + navIconUrl, + objectDamageCalculatorPage, + specialWeaponImageUrl, + subWeaponImageUrl, +} from "~/utils/urls"; -export const CURRENT_PATCH = "1.1"; +export const CURRENT_PATCH = "1.2"; export const meta: MetaFunction = () => { return { @@ -69,6 +75,8 @@ export default function BuildAnalyzerPage() { effects, } = useAnalyzeBuild(); + const objectShredderSelected = build[2][0] === "OS"; + const mainWeaponCategoryItems = [ analyzed.stats.shotSpreadAir && ( + {analyzed.stats.specialDurationInSeconds && ( + + {objectShredderSelected && ( + + + {t("analyzer:objCalcAd")} + + )} @@ -834,7 +866,7 @@ function DamageTable({ return ( - {typeRowName} + {t(typeRowName)} {damage}{" "} {val.shotsToSplat && ( diff --git a/app/routes/index.tsx b/app/routes/index.tsx index 334a69d73..fb7c0b3cb 100644 --- a/app/routes/index.tsx +++ b/app/routes/index.tsx @@ -22,6 +22,7 @@ import { CALENDAR_PAGE, mapsPage, navIconUrl, + objectDamageCalculatorPage, 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 }]; }; export const handle: SendouRouteHandle = { i18n: ["weapons", "analyzer"], + navItemName: "object-damage-calculator", }; export default function ObjectDamagePage() { @@ -51,10 +54,6 @@ export default function ObjectDamagePage() { allDamageTypes, } = useObjectDamage(); - if (process.env.NODE_ENV !== "development") { - return
WIP :)
; - } - return (
@@ -74,7 +73,7 @@ export default function ObjectDamagePage() { clearsInputOnFocus />
-
+
- + {damagesToReceivers ? ( + + ) : ( +
{t("analyzer:noDmgData")}
+ )} +
+
+ {t("analyzer:dmgHtdExplanation")} +
+
+ {t("analyzer:patch")} {CURRENT_PATCH} +
+
); } @@ -126,6 +138,7 @@ function DamageTypesSelect({ return (