Special weapon damages for analyzers + Trizooka

This commit is contained in:
Kalle
2023-06-18 12:32:46 +03:00
parent 06c04ceb0f
commit 58e60e746d
15 changed files with 215 additions and 16 deletions

View File

@@ -14,12 +14,17 @@ import {
subWeaponIds,
weaponCategories,
} from "~/modules/in-game-lists";
import { nonBombSubWeaponIds } from "~/modules/in-game-lists/weapon-ids";
import {
nonBombSubWeaponIds,
nonDamagingSpecialWeaponIds,
specialWeaponIds,
} from "~/modules/in-game-lists/weapon-ids";
import { type SerializedMapPoolEvent } from "~/routes/calendar/map-pool-events";
import type { Unpacked } from "~/utils/types";
import {
gearImageUrl,
mainWeaponImageUrl,
specialWeaponImageUrl,
subWeaponImageUrl,
} from "~/utils/urls";
import { Image } from "./Image";
@@ -307,6 +312,16 @@ export function AllWeaponCombobox({
});
}
for (const specialWeaponId of specialWeaponIds) {
if (nonDamagingSpecialWeaponIds.includes(specialWeaponId)) continue;
result.push({
value: `SPECIAL_${specialWeaponId}`,
label: t(`SPECIAL_${specialWeaponId}`),
imgPath: specialWeaponImageUrl(specialWeaponId),
});
}
return result;
};

View File

@@ -30,6 +30,8 @@ export const DAMAGE_TYPE = [
"ROLL_OVER",
] as const;
// a bit weird there is no SPECIAL here listed but it is used
// only to determine if the damage type is for main weapon or not
export const damageTypeToWeaponType: Record<
DamageType,
"MAIN" | "SUB" | "SPECIAL"

View File

@@ -206,6 +206,7 @@ export interface AnalyzedBuild {
subWeaponInkConsumptionPercentage: Stat;
fullInkTankOptions: Array<FullInkTankOption & { id: string }>;
damages: Array<Damage & { id: string }>;
specialWeaponDamages: Array<Damage & { id: string }>;
subWeaponDefenseDamages: Array<SubWeaponDamage & { id: string }>;
squidFormInkRecoverySeconds: Stat;
humanoidFormInkRecoverySeconds: Stat;
@@ -263,4 +264,5 @@ export type AbilityValuesKeys = keyof typeof abilityValues;
export type AnyWeapon =
| { type: "MAIN"; id: MainWeaponId }
| { type: "SUB"; id: SubWeaponId };
| { type: "SUB"; id: SubWeaponId }
| { type: "SPECIAL"; id: SpecialWeaponId };

View File

@@ -21,6 +21,7 @@ import type {
DamageType,
InkConsumeType,
MainWeaponParams,
SpecialWeaponParams,
StatFunctionInput,
SubWeaponParams,
} from "../analyzer-types";
@@ -107,6 +108,7 @@ export function buildStats({
specialLostSplattedByRP: specialLost(input, true),
fullInkTankOptions: fullInkTankOptions(input),
damages: damages(input),
specialWeaponDamages: specialWeaponDamages(input),
subWeaponDefenseDamages: subWeaponDefenseDamages(input),
mainWeaponWhiteInkSeconds:
typeof mainWeaponParams.InkRecoverStop === "number"
@@ -386,7 +388,9 @@ const damageTypeToParamsKey: Record<
DamageType,
| keyof MainWeaponParams
| keyof SubWeaponParams
| Array<keyof MainWeaponParams | keyof SubWeaponParams>
| Array<
keyof MainWeaponParams | keyof SubWeaponParams | keyof SpecialWeaponParams
>
> = {
NORMAL_MIN: "DamageParam_ValueMin",
NORMAL_MAX: "DamageParam_ValueMax",
@@ -458,6 +462,49 @@ function damages(args: StatFunctionInput): AnalyzedBuild["stats"]["damages"] {
return result;
}
// xxx: TODO: handle damage distance increasing e.g. inkzooka, small special power up icon next to the distance
function specialWeaponDamages(
args: StatFunctionInput
): AnalyzedBuild["stats"]["specialWeaponDamages"] {
const result: AnalyzedBuild["stats"]["specialWeaponDamages"] = [];
for (const type of DAMAGE_TYPE) {
for (const key of [damageTypeToParamsKey[type]].flat()) {
const value = args.specialWeaponParams[key as keyof SpecialWeaponParams];
if (Array.isArray(value)) {
for (const subValue of value.flat()) {
result.push({
type,
value: subValue.Damage / 10,
distance: subValue.Distance,
id: semiRandomId(),
multiShots: multiShot[args.weaponSplId],
});
}
continue;
}
if (typeof value !== "number") continue;
result.push({
id: semiRandomId(),
type,
value: value / 10,
shotsToSplat: shotsToSplat({
value,
type,
multiShots: multiShot[args.weaponSplId],
}),
multiShots: multiShot[args.weaponSplId],
});
}
}
return result;
}
function shotsToSplat({
value,
type,

View File

@@ -1,5 +1,14 @@
import type { AbilityType, SubWeaponId } from "~/modules/in-game-lists";
import { subWeaponIds } from "~/modules/in-game-lists";
import type {
AbilityType,
SpecialWeaponId,
SubWeaponId,
} from "~/modules/in-game-lists";
import {
subWeaponIds,
nonBombSubWeaponIds,
nonDamagingSpecialWeaponIds,
specialWeaponIds,
} from "~/modules/in-game-lists";
import {
abilities,
mainWeaponIds,
@@ -25,7 +34,6 @@ import invariant from "tiny-invariant";
import { EMPTY_BUILD } from "~/constants";
import { UNKNOWN_SHORT } from "../analyzer-constants";
import type { Unpacked } from "~/utils/types";
import { nonBombSubWeaponIds } from "~/modules/in-game-lists/weapon-ids";
export function weaponParams(): ParamsJson {
return weaponParamsJson as ParamsJson;
@@ -190,6 +198,20 @@ export function validatedAnyWeaponFromSearchParams(
return { type: "SUB", id: id as SubWeaponId };
}
if (rawWeapon?.startsWith("SPECIAL_")) {
const id = Number(rawWeapon.replace("SPECIAL_", ""));
if (
!specialWeaponIds
.filter((id) => !nonDamagingSpecialWeaponIds.includes(id))
.includes(id as any)
) {
return DEFAULT_ANY_WEAPON;
}
return { type: "SPECIAL", id: id as SpecialWeaponId };
}
if (rawWeapon?.startsWith("MAIN_")) {
const id = Number(rawWeapon.replace("MAIN_", ""));

View File

@@ -1688,7 +1688,14 @@
"Low": 360,
"Mid": 420
}
}
},
"DistanceDamage": [
{
"Damage": 400,
"Distance": 4
}
],
"DirectDamage": 2200
},
"2": {
"overwrites": {
@@ -1814,7 +1821,17 @@
"Low": 0.63,
"Mid": 0.665
}
}
},
"DistanceDamage": [
{
"Damage": 500,
"Distance": 3
},
{
"Damage": 300,
"Distance": 5
}
]
},
"11": {
"overwrites": {

View File

@@ -703,6 +703,19 @@ export default function BuildAnalyzerPage() {
</StatCategory>
)}
{analyzed.stats.specialWeaponDamages.length > 0 && (
<StatCategory
title={t("analyzer:stat.category.special.damage", {
specialWeapon: t(
`weapons:SPECIAL_${analyzed.weapon.specialWeaponSplId}`
),
})}
containerClassName="analyzer__table-container"
>
<DamageTable values={analyzed.stats.specialWeaponDamages} />
</StatCategory>
)}
{analyzed.stats.fullInkTankOptions.length > 0 && (
<StatCategory
title={t("analyzer:stat.category.actionsPerInkTank")}
@@ -1249,7 +1262,7 @@ function DamageTable({
values:
| AnalyzedBuild["stats"]["damages"]
| AnalyzedBuild["stats"]["subWeaponDefenseDamages"];
multiShots: AnalyzedBuild["weapon"]["multiShots"];
multiShots?: AnalyzedBuild["weapon"]["multiShots"];
}) {
const { t } = useTranslation(["weapons", "analyzer"]);

View File

@@ -157,7 +157,7 @@ export const objectDamageJsonKeyPriority: Record<
SuperHook: null,
SuperLanding: null,
TripleTornado: null,
UltraShot: null,
UltraShot: ["BOMB_NORMAL", "BOMB_DIRECT"],
UltraStamp_Swing: null,
UltraStamp_Throw_BombCore: null,
UltraStamp_Throw: null,

View File

@@ -13,6 +13,7 @@ import {
resolveAllUniqueDamageTypes,
} from "./core/objectDamage";
import type { AnyWeapon } from "../build-analyzer/analyzer-types";
import { exampleMainWeaponIdWithSpecialWeaponId } from "~/modules/in-game-lists";
const ABILITY_POINTS_SP_KEY = "ap";
const DAMAGE_TYPE_SP_KEY = "dmg";
@@ -25,13 +26,19 @@ export function useObjectDamage() {
const abilityPoints = validatedAbilityPointsFromSearchParams(searchParams);
const isMultiShot = validatedMultiShotFromSearchParams(searchParams);
const analyzed = buildStats({
weaponSplId: anyWeapon.type === "MAIN" ? anyWeapon.id : 0,
weaponSplId:
anyWeapon.type === "MAIN"
? anyWeapon.id
: anyWeapon.type === "SPECIAL"
? exampleMainWeaponIdWithSpecialWeaponId(anyWeapon.id)
: 0,
hasTacticooler: false,
});
const damageType = validatedDamageTypeFromSearchParams({
searchParams,
analyzed,
anyWeapon,
});
const handleChange = ({
@@ -126,18 +133,24 @@ assertType<
function validatedDamageTypeFromSearchParams({
searchParams,
analyzed,
anyWeapon,
}: {
searchParams: URLSearchParams;
analyzed: AnalyzedBuild;
anyWeapon: AnyWeapon;
}) {
const damages =
anyWeapon.type === "SPECIAL"
? analyzed.stats.specialWeaponDamages
: analyzed.stats.damages;
const damageType = searchParams.get(DAMAGE_TYPE_SP_KEY);
const found = analyzed.stats.damages.find((d) => d.type === damageType);
const found = damages.find((d) => d.type === damageType);
if (found) return found.type;
const fallbackFound = damageTypePriorityList.find((type) =>
analyzed.stats.damages.some((d) => d.type === type)
damages.some((d) => d.type === type)
);
return fallbackFound;

View File

@@ -114,6 +114,8 @@ export function resolveAllUniqueDamageTypes({
? analyzed.stats.subWeaponDefenseDamages
.filter((damage) => damage.subWeaponId === anyWeapon.id)
.map((d) => d.type)
: anyWeapon.type === "SPECIAL"
? analyzed.stats.specialWeaponDamages.map((d) => d.type)
: analyzed.stats.damages.map((d) => d.type);
return removeDuplicates(damageTypes);
@@ -138,7 +140,12 @@ function resolveFilteredDamages({
);
}
return analyzed.stats.damages
const damages =
anyWeapon.type === "SPECIAL"
? analyzed.stats.specialWeaponDamages
: analyzed.stats.damages;
return damages
.filter((d) => d.type === damageType || toCombine?.combineWith === d.type)
.map((damage) => {
if (!isMultiShot || !damage.multiShots) return damage;

View File

@@ -284,7 +284,7 @@ function DamageReceiversGrid({
variant="build"
className="object-damage__weapon-image"
/>
) : (
) : weapon.type === "SUB" ? (
<Image
alt=""
path={subWeaponImageUrl(weapon.id)}
@@ -292,6 +292,14 @@ function DamageReceiversGrid({
height={24}
className="object-damage__weapon-image"
/>
) : (
<Image
alt=""
path={specialWeaponImageUrl(weapon.id)}
width={24}
height={24}
className="object-damage__weapon-image"
/>
)}
{t(`weapons:${weapon.type}_${weapon.id}` as any)}
</div>

View File

@@ -6,6 +6,9 @@ export {
weaponIdIsNotAlt,
subWeaponIds,
specialWeaponIds,
exampleMainWeaponIdWithSpecialWeaponId,
nonBombSubWeaponIds,
nonDamagingSpecialWeaponIds,
SPLAT_BOMB_ID,
SUCTION_BOMB_ID,
BURST_BOMB_ID,

View File

@@ -1,4 +1,5 @@
import type { MainWeaponId, SubWeaponId } from "./types";
import { assertUnreachable } from "~/utils/types";
import type { MainWeaponId, SpecialWeaponId, SubWeaponId } from "./types";
export const weaponCategories = [
{
@@ -137,3 +138,49 @@ export const specialWeaponIds = [
SUPER_CHUMP_ID,
KRAKEN_ROYALE_ID,
] as const;
export const nonDamagingSpecialWeaponIds = [BIG_BUBBLER_ID, TACTICOOLER_ID];
export const exampleMainWeaponIdWithSpecialWeaponId = (
specialWeaponId: SpecialWeaponId
): MainWeaponId => {
switch (specialWeaponId) {
case TRIZOOKA_ID:
return 40;
case BIG_BUBBLER_ID:
return 10;
case ZIPCASTER_ID:
return 8000;
case TENTA_MISSILES_ID:
return 1030;
case INK_STORM_ID:
return 3040;
case BOOYAH_BOMB_ID:
return 3020;
case WAVE_BREAKER_ID:
return 220;
case INK_VAC_ID:
return 2010;
case KILLER_WAIL_ID:
return 50;
case INKJET_ID:
return 3010;
case ULTRA_STAMP_ID:
return 4000;
case CRAB_TANK_ID:
return 20;
case REEF_SLIDER_ID:
return 5040;
case TRIPLE_INKSTRIKE_ID:
return 41;
case TACTICOOLER_ID:
return 60;
case SUPER_CHUMP_ID:
return 61;
case KRAKEN_ROYALE_ID:
return 4011;
default: {
assertUnreachable(specialWeaponId);
}
}
};

View File

@@ -11,6 +11,7 @@
"stat.category.subDef": "Sub weapon effect defense",
"stat.category.actionsPerInkTank": "Actions per ink tank",
"stat.category.damage": "Main weapon damage",
"stat.category.special.damage": "{{specialWeapon}} damage",
"stat.category.subWeaponDefenseDamages": "Sub weapon damage defense",
"stat.category.movement": "Movement",
"stat.category.misc": "Miscellaneous",

View File

@@ -461,6 +461,8 @@ function parametersToSpecialWeaponResult(params: any) {
return {
ArmorHP: params["WeaponSpChariotParam"]?.["ArmorHP"],
overwrites: resultUnwrapped,
DistanceDamage: params["BlastParam"]?.["DistanceDamage"],
DirectDamage: params["DamageParam"]?.["DirectHitDamage"],
};
}