mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
Feature: Penetration chance in the armor shot card
Shows the chance a shot penetrates in the dynamic armor shot card, right-aligned on the shot's result line. - Penetration varies by ±5% (the same randomisation the armor highlighter shader uses), so the chance is the share of that range where the shot's final outcome is a penetration - Re-runs the existing shot calculation at different penetration values, so spaced armor, HEAT gap losses and ricochets are taken into account - For ricochets the chance is shown on the ricocheted shot's line; HE splashes don't count as penetrations - Adds the `shot_card.stats.penetration_chance` string Fixes #342
This commit is contained in:
1 parent
7234c39ad8
commit
fbdfa2c063
3 files changed
+74
-15
No files matched your search
@@ -128,19 +128,30 @@ export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
|
|||||||
? shotStatusColors[shot.out.status]
|
? shotStatusColors[shot.out.status]
|
||||||
: undefined;
|
: undefined;
|
||||||
const inTitleColor = shotStatusColors[shot.in.status];
|
const inTitleColor = shotStatusColors[shot.in.status];
|
||||||
|
const hasOutLayers = shot.out !== undefined && shot.out.layers.length > 0;
|
||||||
|
const penetrationChance = shot.penetrationChance !== undefined && (
|
||||||
|
<Text color="gray" weight="bold">
|
||||||
|
{literals(strings.common.units.percentage, {
|
||||||
|
value: (shot.penetrationChance * 100).toFixed(0),
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card variant="classic" {...props}>
|
<Card variant="classic" {...props}>
|
||||||
<Flex direction="column" gap="2">
|
<Flex direction="column" gap="2">
|
||||||
<Flex direction="column" gap="1">
|
<Flex direction="column" gap="1">
|
||||||
<Text color={inTitleColor} weight="bold">
|
<Flex justify="between" align="center" gap="4">
|
||||||
{literals(
|
<Text color={inTitleColor} weight="bold">
|
||||||
strings.website.tools.tankopedia.sandbox.dynamic.shot_card.status[
|
{literals(
|
||||||
shot.in.status
|
strings.website.tools.tankopedia.sandbox.dynamic.shot_card
|
||||||
],
|
.status[shot.in.status],
|
||||||
{ damage: Math.round(shot.damage).toLocaleString(locale) }
|
{ damage: Math.round(shot.damage).toLocaleString(locale) }
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
{!hasOutLayers && penetrationChance}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
<table
|
<table
|
||||||
style={{
|
style={{
|
||||||
@@ -204,13 +215,17 @@ export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Inset>
|
</Inset>
|
||||||
|
|
||||||
<Text color={outTitleColor} weight="bold">
|
<Flex justify="between" align="center" gap="4">
|
||||||
{literals(
|
<Text color={outTitleColor} weight="bold">
|
||||||
strings.website.tools.tankopedia.sandbox.dynamic.shot_card
|
{literals(
|
||||||
.status[shot.out.status],
|
strings.website.tools.tankopedia.sandbox.dynamic.shot_card
|
||||||
{ damage: Math.round(shot.damage).toLocaleString(locale) }
|
.status[shot.out.status],
|
||||||
)}
|
{ damage: Math.round(shot.damage).toLocaleString(locale) }
|
||||||
</Text>
|
)}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{penetrationChance}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
<Flex direction="column">
|
<Flex direction="column">
|
||||||
{shot.out.layers.map((layer, index) => {
|
{shot.out.layers.map((layer, index) => {
|
||||||
|
|||||||
@@ -63,6 +63,36 @@ export type ArmorUserData = {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const PENETRATION_RANDOMIZATION = 0.05;
|
||||||
|
|
||||||
|
function resolvePenetrationChance(shoot: (scale: number) => Shot | null) {
|
||||||
|
function penetrates(scale: number) {
|
||||||
|
const shot = shoot(scale);
|
||||||
|
|
||||||
|
return (shot?.out ?? shot?.in)?.status === "penetration";
|
||||||
|
}
|
||||||
|
|
||||||
|
let low = 1 - PENETRATION_RANDOMIZATION;
|
||||||
|
let high = 1 + PENETRATION_RANDOMIZATION;
|
||||||
|
|
||||||
|
if (penetrates(low)) return 1;
|
||||||
|
if (!penetrates(high)) return 0;
|
||||||
|
|
||||||
|
for (let iteration = 0; iteration < 16; iteration++) {
|
||||||
|
const middle = (low + high) / 2;
|
||||||
|
|
||||||
|
if (penetrates(middle)) {
|
||||||
|
high = middle;
|
||||||
|
} else {
|
||||||
|
low = middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
(1 + PENETRATION_RANDOMIZATION - high) / (2 * PENETRATION_RANDOMIZATION)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const omitMaterial = new MeshBasicMaterial({
|
const omitMaterial = new MeshBasicMaterial({
|
||||||
colorWrite: false,
|
colorWrite: false,
|
||||||
depthWrite: true,
|
depthWrite: true,
|
||||||
@@ -89,6 +119,7 @@ export function SpacedArmorSceneComponent({
|
|||||||
intersections: Intersection[],
|
intersections: Intersection[],
|
||||||
allowRicochet: boolean,
|
allowRicochet: boolean,
|
||||||
remainingPenetrationInput?: number,
|
remainingPenetrationInput?: number,
|
||||||
|
penetrationScale = 1,
|
||||||
) => {
|
) => {
|
||||||
const { customShell } = Tankopedia.state;
|
const { customShell } = Tankopedia.state;
|
||||||
const shell = customShell ?? Duel.state.antagonist.shell;
|
const shell = customShell ?? Duel.state.antagonist.shell;
|
||||||
@@ -125,6 +156,7 @@ export function SpacedArmorSceneComponent({
|
|||||||
Duel.state.protagonist.equipmentMatrix,
|
Duel.state.protagonist.equipmentMatrix,
|
||||||
);
|
);
|
||||||
const penetration =
|
const penetration =
|
||||||
|
penetrationScale *
|
||||||
shell.penetration!.near *
|
shell.penetration!.near *
|
||||||
resolvePenetrationCoefficient(
|
resolvePenetrationCoefficient(
|
||||||
hasCalibratedShells,
|
hasCalibratedShells,
|
||||||
@@ -367,6 +399,7 @@ export function SpacedArmorSceneComponent({
|
|||||||
ricochetIntersections,
|
ricochetIntersections,
|
||||||
false,
|
false,
|
||||||
remainingPenetration,
|
remainingPenetration,
|
||||||
|
penetrationScale,
|
||||||
);
|
);
|
||||||
|
|
||||||
shot.in.status = "ricochet";
|
shot.in.status = "ricochet";
|
||||||
@@ -422,6 +455,16 @@ export function SpacedArmorSceneComponent({
|
|||||||
|
|
||||||
const shot = shoot(event.point, event.intersections, true)!;
|
const shot = shoot(event.point, event.intersections, true)!;
|
||||||
|
|
||||||
|
shot.penetrationChance = resolvePenetrationChance((scale) =>
|
||||||
|
shoot(
|
||||||
|
event.point,
|
||||||
|
event.intersections,
|
||||||
|
true,
|
||||||
|
undefined,
|
||||||
|
scale,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
Tankopedia.mutate((draft) => {
|
Tankopedia.mutate((draft) => {
|
||||||
draft.shot = shot;
|
draft.shot = shot;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export type Shot = {
|
|||||||
containsGaps: boolean;
|
containsGaps: boolean;
|
||||||
damage: number;
|
damage: number;
|
||||||
splashRadius?: number;
|
splashRadius?: number;
|
||||||
|
penetrationChance?: number;
|
||||||
|
|
||||||
in: {
|
in: {
|
||||||
surfaceNormal: Vector3;
|
surfaceNormal: Vector3;
|
||||||
|
|||||||
Reference in new issue
Block a user