feat: armor display

This commit is contained in:
TrèsAbhi committed 2024-01-09 18:35:42 -06:00
1 parent 6aa4686c30
commit a7d8f08522
8 files changed
+186 -18

No files matched your search

+77 -1
View File
@@ -6,7 +6,10 @@ import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL';
import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL';
import { toUniqueId } from '../../src/core/blitz/toUniqueId';
import commitMultipleFiles from '../../src/core/blitzkrieg/commitMultipleFiles';
import { ModelDefinitions } from '../../src/core/blitzkrieg/modelDefinitions';
import {
ModelArmor,
ModelDefinitions,
} from '../../src/core/blitzkrieg/modelDefinitions';
import {
GunDefinition,
ShellType,
@@ -40,14 +43,20 @@ type ShellDefinitionsList = Record<
> & {
icons: Record<string, string>;
};
type VehicleDefinitionArmor = Record<
string,
number | { vehicleDamageFactor: 0; '#text': number }
>;
interface VehicleDefinitions {
invisibility: { moving: number; still: number; firePenalty: number };
hull: {
armor: VehicleDefinitionArmor;
turretPositions: { turret: string };
turretInitialRotation?: { yaw: 0; pitch: 6.5; roll: 0 };
};
turrets0: {
[key: string]: {
armor: VehicleDefinitionArmor;
userString: number;
level: number;
yawLimits: string | string[];
@@ -55,6 +64,7 @@ interface VehicleDefinitions {
models: { undamaged: string };
guns: {
[key: string]: {
armor: VehicleDefinitionArmor;
reloadTime: number;
maxAmmo: number;
extraPitchLimits?: {
@@ -174,6 +184,28 @@ export async function buildDefinitions() {
.map(Number) as Vector3Tuple;
const tankId = toUniqueId(nation, tank.id);
const tankTags = tank.tags.split(' ');
const hullArmor: ModelArmor = { thickness: {} };
Object.keys(tankDefinition.root.hull.armor)
.filter((name) => name.startsWith('armor_'))
.forEach((name) => {
const armorIdString = name.match(/armor_(\d+)/)?.[1];
if (armorIdString === undefined) {
throw new SyntaxError(`Invalid armor id: ${name}`);
}
const armorId = parseInt(armorIdString);
const armorRaw = tankDefinition.root.hull.armor[name];
if (typeof armorRaw === 'number') {
hullArmor.thickness[armorId] = armorRaw;
} else {
if (!hullArmor.spaced) hullArmor.spaced = [];
hullArmor.thickness[armorId] = armorRaw['#text'];
hullArmor.spaced.push(armorId);
}
});
tankDefinitions[tankId] = {
id: tankId,
@@ -202,6 +234,7 @@ export async function buildDefinitions() {
};
modelDefinitions[tankId] = {
armor: hullArmor,
turretOrigin,
turretRotation: tankDefinition.root.hull.turretInitialRotation
? {
@@ -234,6 +267,28 @@ export async function buildDefinitions() {
)
.split(' ')
.map(Number) as Vector3Tuple;
const turretArmor: ModelArmor = { thickness: {} };
Object.keys(turret.armor)
.filter((name) => name.startsWith('armor_'))
.forEach((name) => {
const armorIdString = name.match(/armor_(\d+)/)?.[1];
if (armorIdString === undefined) {
throw new SyntaxError(`Invalid armor id: ${name}`);
}
const armorId = parseInt(armorIdString);
const armorRaw = turret.armor[name];
if (typeof armorRaw === 'number') {
turretArmor.thickness[armorId] = armorRaw;
} else {
if (!turretArmor.spaced) turretArmor.spaced = [];
turretArmor.thickness[armorId] = armorRaw['#text'];
turretArmor.spaced.push(armorId);
}
});
tankDefinitions[tankId].turrets.push({
id: turretId,
@@ -247,6 +302,7 @@ export async function buildDefinitions() {
});
modelDefinitions[tankId].turrets[turretId] = {
armor: turretArmor,
gunOrigin,
model: turretModel,
yaw:
@@ -301,6 +357,25 @@ export async function buildDefinitions() {
? turretGunEntry.extraPitchLimits.transition
: turretGunEntry.extraPitchLimits.transition.at(-1)!
: undefined;
const gunArmor: ModelArmor = { thickness: {} };
Object.keys(turretGunEntry.armor)
.filter((name) => name.startsWith('armor_'))
.forEach((name) => {
const armorIdString = name.match(/armor_(\d+)/)?.[1];
if (armorIdString === undefined) {
throw new SyntaxError(`Invalid armor id: ${name}`);
}
const armorId = parseInt(armorIdString);
const armorRaw = turretGunEntry.armor[name];
if (typeof armorRaw === 'number') {
gunArmor.thickness[armorId] = armorRaw;
} else {
if (!gunArmor.spaced) gunArmor.spaced = [];
gunArmor.thickness[armorId] = armorRaw['#text'];
gunArmor.spaced.push(armorId);
}
});
tankDefinitions[tankId].turrets[turretIndex].guns.push({
id: gunId,
@@ -314,6 +389,7 @@ export async function buildDefinitions() {
} as GunDefinition);
modelDefinitions[tankId].turrets[turretId].guns[gunId] = {
armor: gunArmor,
model: gunModel,
pitch: {
min: gunPitch[0],
@@ -19,6 +19,7 @@ import { Loader } from '../../../../components/Loader';
import { Controls } from '../Control';
import { RotationInputs } from '../RotationInputs';
import { SceneProps } from '../SceneProps';
import { TankArmor } from './components/TankArmor';
import { TankModel } from './components/TankModel';
export function TankDisplay() {
@@ -131,6 +132,7 @@ export function TankDisplay() {
}
>
<TankModel ref={hullContainer} />
{mode === 'armor' && <TankArmor />}
</Suspense>
</Canvas>
</div>
@@ -0,0 +1,81 @@
import { useLoader } from '@react-three/fiber';
import {
forwardRef,
useEffect,
useImperativeHandle,
useRef,
useState,
} from 'react';
import { Group, Mesh, Vector3 } from 'three';
import { GLTFLoader } from 'three-stdlib';
import { X_AXIS } from '../../../../../../../constants/axis';
import { asset } from '../../../../../../../core/blitzkrieg/asset';
import {
ModelDefinitions,
modelDefinitions,
} from '../../../../../../../core/blitzkrieg/modelDefinitions';
import { useTankopedia } from '../../../../../../../stores/tankopedia';
export const TankArmor = forwardRef<Group>((_props, ref) => {
const hullContainer = useRef<Group>(null);
const protagonist = useTankopedia((state) => {
if (!state.areTanksAssigned) return;
return state.protagonist;
});
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
ModelDefinitions | undefined
>(undefined);
useEffect(() => {
(async () => {
setAwaitedModelDefinitions(await modelDefinitions);
})();
}, []);
useImperativeHandle(ref, () => hullContainer.current!);
if (!awaitedModelDefinitions) return null;
if (!protagonist) return null;
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
const turretModelDefinition =
tankModelDefinition.turrets[protagonist.turret.id];
const turretOrigin = new Vector3(
tankModelDefinition.turretOrigin[0],
tankModelDefinition.turretOrigin[1],
-tankModelDefinition.turretOrigin[2],
).applyAxisAngle(X_AXIS, Math.PI / 2);
const gunOrigin = new Vector3(
turretModelDefinition.gunOrigin[0],
turretModelDefinition.gunOrigin[1],
-turretModelDefinition.gunOrigin[2],
).applyAxisAngle(X_AXIS, Math.PI / 2);
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
const gltf = useLoader(
GLTFLoader,
asset(`3d/tanks/armor/${protagonist.tank.id}.glb`),
);
return (
<group ref={hullContainer} rotation={[-Math.PI / 2, 0, 0]}>
{Object.values(gltf.nodes).map((node) => {
const armor = Number(node.name.match(/.+_armor_(\d+)/)?.[1]);
const position = new Vector3();
if (node.name.startsWith('turret_')) position.add(turretOrigin);
if (node.name.startsWith('gun_'))
position.add(turretOrigin).add(gunOrigin);
return (
<mesh
key={node.uuid}
geometry={(node as Mesh).geometry}
position={position}
>
<meshBasicMaterial color="red" transparent opacity={0.5} />
</mesh>
);
})}
</group>
);
});
@@ -1,14 +1,14 @@
import { ThreeEvent, useThree } from '@react-three/fiber';
import { RefObject, forwardRef, useImperativeHandle, useRef } from 'react';
import { Group, Mesh, Object3D, Vector3 } from 'three';
import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits';
import { applyPitchYawLimits } from '../../../../../../../../../core/blitz/applyPitchYawLimits';
import {
InitialTurretRotation,
PitchLimits,
YawLimits,
} from '../../../../../core/blitzkrieg/modelDefinitions';
import { resolveJsxTree } from '../../../../../core/blitzkrieg/resolveJsxTree';
import { normalizeAnglePI } from '../../../../../core/math/normalizeAngle180';
} from '../../../../../../../../../core/blitzkrieg/modelDefinitions';
import { resolveJsxTree } from '../../../../../../../../../core/blitzkrieg/resolveJsxTree';
import { normalizeAnglePI } from '../../../../../../../../../core/math/normalizeAngle180';
interface GunContainerProps {
turretOrigin: Vector3;
@@ -1,8 +1,8 @@
import { ThreeEvent, useThree } from '@react-three/fiber';
import { ReactNode, forwardRef, useImperativeHandle, useRef } from 'react';
import { Group, Mesh, MeshStandardMaterial, Object3D, Vector2 } from 'three';
import { resolveJsxTree } from '../../../../../core/blitzkrieg/resolveJsxTree';
import { normalizeAnglePI } from '../../../../../core/math/normalizeAngle180';
import { resolveJsxTree } from '../../../../../../../../../core/blitzkrieg/resolveJsxTree';
import { normalizeAnglePI } from '../../../../../../../../../core/math/normalizeAngle180';
interface HullContainerProps {
children: ReactNode;
@@ -7,14 +7,14 @@ import {
useRef,
} from 'react';
import { Euler, Group, Mesh, Object3D, Vector3 } from 'three';
import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits';
import { applyPitchYawLimits } from '../../../../../../../../../core/blitz/applyPitchYawLimits';
import {
InitialTurretRotation,
PitchLimits,
YawLimits,
} from '../../../../../core/blitzkrieg/modelDefinitions';
import { resolveJsxTree } from '../../../../../core/blitzkrieg/resolveJsxTree';
import { normalizeAnglePI } from '../../../../../core/math/normalizeAngle180';
} from '../../../../../../../../../core/blitzkrieg/modelDefinitions';
import { resolveJsxTree } from '../../../../../../../../../core/blitzkrieg/resolveJsxTree';
import { normalizeAnglePI } from '../../../../../../../../../core/math/normalizeAngle180';
interface TurretContainerProps {
turretOrigin: Vector3;
@@ -8,18 +8,18 @@ import {
} from 'react';
import { Group, Vector3 } from 'three';
import { GLTFLoader } from 'three-stdlib';
import { X_AXIS } from '../../../../../../../constants/axis';
import { asset } from '../../../../../../../core/blitzkrieg/asset';
import { X_AXIS } from '../../../../../../../../constants/axis';
import { asset } from '../../../../../../../../core/blitzkrieg/asset';
import {
ModelDefinitions,
modelDefinitions,
} from '../../../../../../../core/blitzkrieg/modelDefinitions';
} from '../../../../../../../../core/blitzkrieg/modelDefinitions';
import mutateTankopedia, {
useTankopedia,
} from '../../../../../../../stores/tankopedia';
import { GunContainer } from '../../GunContainer';
import { HullContainer } from '../../HullContainer';
import { TurretContainer } from '../../TurretContainer';
} from '../../../../../../../../stores/tankopedia';
import { GunContainer } from './components/GunContainer';
import { HullContainer } from './components/HullContainer';
import { TurretContainer } from './components/TurretContainer';
export const TankModel = forwardRef<Group>((_props, ref) => {
// "TypeError: dispatcher.use is not a function"
+9
View File
@@ -2,7 +2,14 @@ import { Vector3Tuple } from 'three';
import { asset } from './asset';
export type ModelDefinitions = Record<number, ModelDefinition>;
export interface ModelArmor {
thickness: Record<number, number>;
spaced?: number[];
}
interface ModelDefinition {
armor: ModelArmor;
turretOrigin: Vector3Tuple;
turretRotation?: InitialTurretRotation;
turrets: Record<number, TurretModelDefinition>;
@@ -13,6 +20,7 @@ export interface InitialTurretRotation {
roll: number;
}
interface TurretModelDefinition {
armor: ModelArmor;
model: number;
gunOrigin: Vector3Tuple;
guns: Record<number, GunModelDefinition>;
@@ -23,6 +31,7 @@ export interface YawLimits {
max: number;
}
interface GunModelDefinition {
armor: ModelArmor;
model: number;
pitch: PitchLimits;
}