mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 02:37:05 +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
+62
-3
No files matched your search
@@ -128,20 +128,31 @@ export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
|
||||
? shotStatusColors[shot.out.status]
|
||||
: undefined;
|
||||
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 (
|
||||
<Card variant="classic" {...props}>
|
||||
<Flex direction="column" gap="2">
|
||||
<Flex direction="column" gap="1">
|
||||
<Flex justify="between" align="center" gap="4">
|
||||
<Text color={inTitleColor} weight="bold">
|
||||
{literals(
|
||||
strings.website.tools.tankopedia.sandbox.dynamic.shot_card.status[
|
||||
shot.in.status
|
||||
],
|
||||
strings.website.tools.tankopedia.sandbox.dynamic.shot_card
|
||||
.status[shot.in.status],
|
||||
{ damage: Math.round(shot.damage).toLocaleString(locale) }
|
||||
)}
|
||||
</Text>
|
||||
|
||||
{!hasOutLayers && penetrationChance}
|
||||
</Flex>
|
||||
|
||||
<table
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
@@ -204,6 +215,7 @@ export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
|
||||
</Flex>
|
||||
</Inset>
|
||||
|
||||
<Flex justify="between" align="center" gap="4">
|
||||
<Text color={outTitleColor} weight="bold">
|
||||
{literals(
|
||||
strings.website.tools.tankopedia.sandbox.dynamic.shot_card
|
||||
@@ -212,6 +224,9 @@ export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
|
||||
)}
|
||||
</Text>
|
||||
|
||||
{penetrationChance}
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column">
|
||||
{shot.out.layers.map((layer, index) => {
|
||||
const layerName =
|
||||
|
||||
@@ -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({
|
||||
colorWrite: false,
|
||||
depthWrite: true,
|
||||
@@ -89,6 +119,7 @@ export function SpacedArmorSceneComponent({
|
||||
intersections: Intersection[],
|
||||
allowRicochet: boolean,
|
||||
remainingPenetrationInput?: number,
|
||||
penetrationScale = 1,
|
||||
) => {
|
||||
const { customShell } = Tankopedia.state;
|
||||
const shell = customShell ?? Duel.state.antagonist.shell;
|
||||
@@ -125,6 +156,7 @@ export function SpacedArmorSceneComponent({
|
||||
Duel.state.protagonist.equipmentMatrix,
|
||||
);
|
||||
const penetration =
|
||||
penetrationScale *
|
||||
shell.penetration!.near *
|
||||
resolvePenetrationCoefficient(
|
||||
hasCalibratedShells,
|
||||
@@ -367,6 +399,7 @@ export function SpacedArmorSceneComponent({
|
||||
ricochetIntersections,
|
||||
false,
|
||||
remainingPenetration,
|
||||
penetrationScale,
|
||||
);
|
||||
|
||||
shot.in.status = "ricochet";
|
||||
@@ -422,6 +455,16 @@ export function SpacedArmorSceneComponent({
|
||||
|
||||
const shot = shoot(event.point, event.intersections, true)!;
|
||||
|
||||
shot.penetrationChance = resolvePenetrationChance((scale) =>
|
||||
shoot(
|
||||
event.point,
|
||||
event.intersections,
|
||||
true,
|
||||
undefined,
|
||||
scale,
|
||||
),
|
||||
);
|
||||
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.shot = shot;
|
||||
});
|
||||
|
||||
@@ -43,6 +43,7 @@ export type Shot = {
|
||||
containsGaps: boolean;
|
||||
damage: number;
|
||||
splashRadius?: number;
|
||||
penetrationChance?: number;
|
||||
|
||||
in: {
|
||||
surfaceNormal: Vector3;
|
||||
|
||||
Reference in new issue
Block a user