mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
feat: start work on dynamic armor
This commit is contained in:
1 parent
1d301bf2fb
commit
1ee232d104
26 files changed
+296
-583
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 706 KiB |
File renamed without changes.
Binary file not shown.
|
After Width: | Height: | Size: 280 KiB |
File renamed without changes.
@@ -22,7 +22,6 @@ import { useThree } from '@react-three/fiber';
|
||||
import { Fragment, useEffect, useRef } from 'react';
|
||||
import type { Mesh } from 'three';
|
||||
import {
|
||||
Box3,
|
||||
Color,
|
||||
DoubleSide,
|
||||
Group,
|
||||
@@ -109,6 +108,12 @@ export function DissectorSceneComponent({
|
||||
|
||||
const armorName = mesh.material.name;
|
||||
const armorPlate = groupArmor.armors[armorName];
|
||||
|
||||
if (armorPlate === undefined) {
|
||||
console.warn(`Missing armor: ${armorName}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const isModule =
|
||||
armorPlate.type !== 'Armor' && armorPlate.type !== 'ArmorScreen';
|
||||
const isTrack =
|
||||
@@ -149,8 +154,6 @@ export function DissectorSceneComponent({
|
||||
}),
|
||||
);
|
||||
const worldPosition = new Vector3();
|
||||
const boundsTop = new Vector3();
|
||||
const bounds = new Box3();
|
||||
|
||||
useEffect(() => {
|
||||
const offset = new Vector3();
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
import { memo, useRef } from 'react';
|
||||
import { Group, Plane, Scene, Vector3 } from 'three';
|
||||
import { correctZYTuple } from '../../../core/blitz/correctZYTuple';
|
||||
import { nameToArmorId } from '../../../core/blitzkit/nameToArmorId';
|
||||
import { resolveArmor } from '../../../core/blitzkit/resolveThickness';
|
||||
import { useArmor } from '../../../hooks/useArmor';
|
||||
import { useModel } from '../../../hooks/useModel';
|
||||
import { useTankModelDefinition } from '../../../hooks/useTankModelDefinition';
|
||||
import { useTankTransform } from '../../../hooks/useTankTransform';
|
||||
import { Scene } from 'three';
|
||||
import { arrayfy } from '../../../core/three/arrayfy';
|
||||
import { Duel } from '../../../stores/duel';
|
||||
import { ModelTankWrapper } from './ModelTankWrapper';
|
||||
import { SpacedArmorSceneComponent } from './SpacedArmorSceneComponent';
|
||||
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
|
||||
import { SpacedArmorSceneComponent } from './SpacedArmorSceneComponent/index';
|
||||
|
||||
export enum ArmorType {
|
||||
Primary,
|
||||
@@ -21,185 +14,25 @@ interface SpacedArmorSceneProps {
|
||||
scene: Scene;
|
||||
}
|
||||
|
||||
export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
|
||||
const wrapper = useRef<Group>(null);
|
||||
const turretContainer = useRef<Group>(null);
|
||||
const gunContainer = useRef<Group>(null);
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
const track = Duel.use((state) => state.protagonist.track);
|
||||
export function SpacedArmorScene({ scene }: SpacedArmorSceneProps) {
|
||||
const model = TankopediaEphemeral.use((state) => state.model);
|
||||
const turret = Duel.use((state) => state.protagonist.turret);
|
||||
const gun = Duel.use((state) => state.protagonist.gun);
|
||||
const { gltf: armorGltf } = useArmor(tank.id);
|
||||
const { gltf: modelGltf } = useModel(tank.id);
|
||||
const armorNodes = Object.values(armorGltf.nodes);
|
||||
const modelNodes = Object.values(modelGltf.nodes);
|
||||
const tankModelDefinition = useTankModelDefinition();
|
||||
const trackModelDefinition = tankModelDefinition.tracks[track.id];
|
||||
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
|
||||
const gunModelDefinition =
|
||||
turretModelDefinition.guns[gun.gun_type!.value.base.id];
|
||||
const hullOrigin = correctZYTuple(trackModelDefinition.origin);
|
||||
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin);
|
||||
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin);
|
||||
const maskOrigin =
|
||||
gunModelDefinition.mask === undefined
|
||||
? undefined
|
||||
: gunModelDefinition.mask + hullOrigin.y + turretOrigin.y + gunOrigin.y;
|
||||
const isDynamicArmorActive = Duel.use((state) =>
|
||||
state.protagonist.consumables.includes(73),
|
||||
);
|
||||
|
||||
useTankTransform(track, turret, turretContainer, gunContainer);
|
||||
const turretTranslation = arrayfy(model.modules[turret.id].translation);
|
||||
const gunTranslation = arrayfy(model.modules[gun.id].translation);
|
||||
|
||||
return (
|
||||
<ModelTankWrapper ref={wrapper}>
|
||||
<group position={hullOrigin}>
|
||||
{armorNodes.map((node) => {
|
||||
const isHull = node.name.startsWith('hull_');
|
||||
const isVisible = isHull;
|
||||
const armorId = nameToArmorId(node.name);
|
||||
const { spaced, thickness } = resolveArmor(
|
||||
tankModelDefinition.armor,
|
||||
armorId,
|
||||
);
|
||||
<group>
|
||||
<SpacedArmorSceneComponent group="hull" />
|
||||
<SpacedArmorSceneComponent group="chassis" />
|
||||
|
||||
if (
|
||||
!isVisible ||
|
||||
thickness === undefined ||
|
||||
(isDynamicArmorActive && node.name.includes('state_00')) ||
|
||||
(!isDynamicArmorActive && node.name.includes('state_01'))
|
||||
)
|
||||
return null;
|
||||
|
||||
return (
|
||||
<SpacedArmorSceneComponent
|
||||
scene={scene}
|
||||
key={node.uuid}
|
||||
type={spaced ? ArmorType.Spaced : ArmorType.Primary}
|
||||
thickness={thickness}
|
||||
node={node}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<group position={turretTranslation}>
|
||||
<SpacedArmorSceneComponent group={turret.id} />
|
||||
</group>
|
||||
|
||||
{modelNodes.map((node) => {
|
||||
const isWheel = node.name.startsWith('chassis_wheel_');
|
||||
const isTrack = node.name.startsWith('chassis_track_');
|
||||
const isVisible = isWheel || isTrack;
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
return (
|
||||
<SpacedArmorSceneComponent
|
||||
scene={scene}
|
||||
key={node.uuid}
|
||||
type={ArmorType.External}
|
||||
thickness={trackModelDefinition.thickness}
|
||||
variant="track"
|
||||
node={node}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
<group ref={turretContainer}>
|
||||
{armorNodes.map((node) => {
|
||||
const isCurrentTurret = node.name.startsWith(
|
||||
`turret_${turretModelDefinition.model_id.toString().padStart(2, '0')}`,
|
||||
);
|
||||
const isVisible = isCurrentTurret;
|
||||
const armorId = nameToArmorId(node.name);
|
||||
const { spaced, thickness } = resolveArmor(
|
||||
turretModelDefinition.armor,
|
||||
armorId,
|
||||
);
|
||||
|
||||
if (
|
||||
!isVisible ||
|
||||
thickness === undefined ||
|
||||
(isDynamicArmorActive && node.name.includes('state_00')) ||
|
||||
(!isDynamicArmorActive && node.name.includes('state_01'))
|
||||
)
|
||||
return null;
|
||||
|
||||
return (
|
||||
<group position={hullOrigin} key={node.uuid}>
|
||||
<group position={turretOrigin}>
|
||||
<SpacedArmorSceneComponent
|
||||
scene={scene}
|
||||
key={node.uuid}
|
||||
type={spaced ? ArmorType.Spaced : ArmorType.Primary}
|
||||
thickness={thickness}
|
||||
node={node}
|
||||
/>
|
||||
</group>
|
||||
</group>
|
||||
);
|
||||
})}
|
||||
|
||||
<group ref={gunContainer}>
|
||||
{armorNodes.map((node) => {
|
||||
const isCurrentGun = node.name.startsWith(
|
||||
`gun_${gunModelDefinition.model_id.toString().padStart(2, '0')}`,
|
||||
);
|
||||
const isVisible = isCurrentGun;
|
||||
const armorId = nameToArmorId(node.name);
|
||||
const { spaced, thickness } = resolveArmor(
|
||||
gunModelDefinition.armor,
|
||||
armorId,
|
||||
);
|
||||
|
||||
if (
|
||||
!isVisible ||
|
||||
thickness === undefined ||
|
||||
(isDynamicArmorActive && node.name.includes('state_00')) ||
|
||||
(!isDynamicArmorActive && node.name.includes('state_01'))
|
||||
)
|
||||
return null;
|
||||
|
||||
return (
|
||||
<group position={hullOrigin} key={node.uuid}>
|
||||
<group position={turretOrigin.clone().add(gunOrigin)}>
|
||||
<SpacedArmorSceneComponent
|
||||
scene={scene}
|
||||
type={spaced ? ArmorType.Spaced : ArmorType.Primary}
|
||||
thickness={thickness}
|
||||
node={node}
|
||||
/>
|
||||
</group>
|
||||
</group>
|
||||
);
|
||||
})}
|
||||
|
||||
{modelNodes.map((node) => {
|
||||
const gunString = `gun_${gunModelDefinition.model_id
|
||||
.toString()
|
||||
.padStart(2, '0')}`;
|
||||
const isCurrentGun = gunModelDefinition.mask
|
||||
? node.name.startsWith(gunString)
|
||||
: node.name === gunString;
|
||||
const isVisible = isCurrentGun;
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
return (
|
||||
<SpacedArmorSceneComponent
|
||||
scene={scene}
|
||||
key={node.uuid}
|
||||
type={ArmorType.External}
|
||||
thickness={gunModelDefinition.thickness}
|
||||
variant="gun"
|
||||
node={node}
|
||||
clip={
|
||||
maskOrigin === undefined
|
||||
? undefined
|
||||
: new Plane(new Vector3(0, 0, -1), -maskOrigin)
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</group>
|
||||
<group position={gunTranslation}>
|
||||
<SpacedArmorSceneComponent group={gun.id} />
|
||||
</group>
|
||||
</ModelTankWrapper>
|
||||
</group>
|
||||
);
|
||||
});
|
||||
}
|
||||
+1
-1
@@ -7,12 +7,12 @@ import {
|
||||
Plane,
|
||||
ShaderMaterial,
|
||||
} from 'three';
|
||||
import type { ArmorUserData, ExternalModuleVariant } from '../..';
|
||||
import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment';
|
||||
import { jsxTree } from '../../../../../../core/blitzkit/jsxTree';
|
||||
import { Duel, type EquipmentMatrix } from '../../../../../../stores/duel';
|
||||
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
|
||||
import { ArmorType } from '../../../SpacedArmorScene';
|
||||
import type { ArmorUserData, ExternalModuleVariant } from '../../_index';
|
||||
import fragmentShader from './shaders/fragment.glsl?raw';
|
||||
import vertexShader from './shaders/vertex.glsl?raw';
|
||||
|
||||
|
||||
+86
-114
@@ -1,24 +1,14 @@
|
||||
import { isExplosive, resolvePenetrationCoefficient } from '@blitzkit/core';
|
||||
import { useEffect } from 'react';
|
||||
import {
|
||||
AdditiveBlending,
|
||||
MeshBasicMaterial,
|
||||
ShaderMaterial,
|
||||
type Object3D,
|
||||
} from 'three';
|
||||
import { degToRad } from 'three/src/math/MathUtils.js';
|
||||
import type { ArmorUserData } from '../..';
|
||||
import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment';
|
||||
import { jsxTree } from '../../../../../../core/blitzkit/jsxTree';
|
||||
import { Duel, type EquipmentMatrix } from '../../../../../../stores/duel';
|
||||
import { ArmorPlate } from '@blitzkit/core';
|
||||
import type { MeshProps } from '@react-three/fiber';
|
||||
import { Fragment } from 'react';
|
||||
import { AdditiveBlending, MeshBasicMaterial, ShaderMaterial } from 'three';
|
||||
import { Duel } from '../../../../../../stores/duel';
|
||||
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
|
||||
import { ArmorType } from '../../../SpacedArmorScene';
|
||||
import fragmentShader from './shaders/fragment.glsl?raw';
|
||||
import vertexShader from './shaders/vertex.glsl?raw';
|
||||
|
||||
interface SpacedArmorSubSpacedProps {
|
||||
node: Object3D;
|
||||
thickness: number;
|
||||
interface SpacedArmorSubSpacedProps extends MeshProps {
|
||||
armorPlate: ArmorPlate;
|
||||
}
|
||||
|
||||
const depthWriteMaterial = new MeshBasicMaterial({
|
||||
@@ -27,8 +17,8 @@ const depthWriteMaterial = new MeshBasicMaterial({
|
||||
});
|
||||
|
||||
export function SpacedArmorSubSpaced({
|
||||
node,
|
||||
thickness,
|
||||
armorPlate,
|
||||
...props
|
||||
}: SpacedArmorSubSpacedProps) {
|
||||
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
|
||||
const duelStore = Duel.useStore();
|
||||
@@ -49,107 +39,89 @@ export function SpacedArmorSubSpaced({
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
function handleShellChange() {
|
||||
const duel = duelStore.getState();
|
||||
const tankopediaEphemeral = tankopediaEphemeralStore.getState();
|
||||
const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell;
|
||||
// useEffect(() => {
|
||||
// function handleShellChange() {
|
||||
// const duel = duelStore.getState();
|
||||
// const tankopediaEphemeral = tankopediaEphemeralStore.getState();
|
||||
// const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell;
|
||||
|
||||
material.uniforms.penetration.value = shell.penetration.near;
|
||||
material.uniforms.caliber.value = shell.caliber;
|
||||
material.uniforms.ricochet.value = degToRad(
|
||||
isExplosive(shell.type) ? 90 : shell.ricochet!,
|
||||
);
|
||||
material.uniforms.normalization.value = degToRad(
|
||||
shell.normalization ?? 0,
|
||||
);
|
||||
}
|
||||
async function handleProtagonistEquipmentChange(
|
||||
equipment: EquipmentMatrix,
|
||||
) {
|
||||
const duel = duelStore.getState();
|
||||
const hasEnhancedArmor = await hasEquipment(
|
||||
110,
|
||||
duel.protagonist.tank.equipment_preset,
|
||||
equipment,
|
||||
);
|
||||
material.uniforms.thickness.value = hasEnhancedArmor
|
||||
? thickness * 1.03
|
||||
: thickness;
|
||||
}
|
||||
async function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) {
|
||||
const duel = duelStore.getState();
|
||||
const tankopediaEphemeral = tankopediaEphemeralStore.getState();
|
||||
const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell;
|
||||
const penetration = shell.penetration.near;
|
||||
const hasCalibratedShells = await hasEquipment(
|
||||
103,
|
||||
duel.antagonist.tank.equipment_preset,
|
||||
equipment,
|
||||
);
|
||||
// material.uniforms.penetration.value = shell.penetration.near;
|
||||
// material.uniforms.caliber.value = shell.caliber;
|
||||
// material.uniforms.ricochet.value = degToRad(
|
||||
// isExplosive(shell.type) ? 90 : shell.ricochet!,
|
||||
// );
|
||||
// material.uniforms.normalization.value = degToRad(
|
||||
// shell.normalization ?? 0,
|
||||
// );
|
||||
// }
|
||||
// async function handleProtagonistEquipmentChange(
|
||||
// equipment: EquipmentMatrix,
|
||||
// ) {
|
||||
// const duel = duelStore.getState();
|
||||
// const hasEnhancedArmor = await hasEquipment(
|
||||
// 110,
|
||||
// duel.protagonist.tank.equipment_preset,
|
||||
// equipment,
|
||||
// );
|
||||
// material.uniforms.thickness.value = hasEnhancedArmor
|
||||
// ? thickness * 1.03
|
||||
// : thickness;
|
||||
// }
|
||||
// async function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) {
|
||||
// const duel = duelStore.getState();
|
||||
// const tankopediaEphemeral = tankopediaEphemeralStore.getState();
|
||||
// const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell;
|
||||
// const penetration = shell.penetration.near;
|
||||
// const hasCalibratedShells = await hasEquipment(
|
||||
// 103,
|
||||
// duel.antagonist.tank.equipment_preset,
|
||||
// equipment,
|
||||
// );
|
||||
|
||||
material.uniforms.penetration.value =
|
||||
penetration *
|
||||
resolvePenetrationCoefficient(hasCalibratedShells, shell.type);
|
||||
}
|
||||
// material.uniforms.penetration.value =
|
||||
// penetration *
|
||||
// resolvePenetrationCoefficient(hasCalibratedShells, shell.type);
|
||||
// }
|
||||
|
||||
handleShellChange();
|
||||
handleProtagonistEquipmentChange(
|
||||
duelStore.getState().protagonist.equipmentMatrix,
|
||||
);
|
||||
handleAntagonistEquipmentChange(
|
||||
duelStore.getState().antagonist.equipmentMatrix,
|
||||
);
|
||||
// handleShellChange();
|
||||
// handleProtagonistEquipmentChange(
|
||||
// duelStore.getState().protagonist.equipmentMatrix,
|
||||
// );
|
||||
// handleAntagonistEquipmentChange(
|
||||
// duelStore.getState().antagonist.equipmentMatrix,
|
||||
// );
|
||||
|
||||
const unsubscribes = [
|
||||
duelStore.subscribe((state) => state.antagonist.shell, handleShellChange),
|
||||
tankopediaEphemeralStore.subscribe(
|
||||
(state) => state.customShell,
|
||||
handleShellChange,
|
||||
),
|
||||
duelStore.subscribe(
|
||||
(state) => state.protagonist.equipmentMatrix,
|
||||
handleProtagonistEquipmentChange,
|
||||
),
|
||||
duelStore.subscribe(
|
||||
(state) => state.antagonist.equipmentMatrix,
|
||||
handleAntagonistEquipmentChange,
|
||||
),
|
||||
];
|
||||
// const unsubscribes = [
|
||||
// duelStore.subscribe((state) => state.antagonist.shell, handleShellChange),
|
||||
// tankopediaEphemeralStore.subscribe(
|
||||
// (state) => state.customShell,
|
||||
// handleShellChange,
|
||||
// ),
|
||||
// duelStore.subscribe(
|
||||
// (state) => state.protagonist.equipmentMatrix,
|
||||
// handleProtagonistEquipmentChange,
|
||||
// ),
|
||||
// duelStore.subscribe(
|
||||
// (state) => state.antagonist.equipmentMatrix,
|
||||
// handleAntagonistEquipmentChange,
|
||||
// ),
|
||||
// ];
|
||||
|
||||
return () => {
|
||||
unsubscribes.forEach((unsubscribe) => unsubscribe());
|
||||
};
|
||||
});
|
||||
// return () => {
|
||||
// unsubscribes.forEach((unsubscribe) => unsubscribe());
|
||||
// };
|
||||
// });
|
||||
|
||||
return (
|
||||
<>
|
||||
{jsxTree(
|
||||
node,
|
||||
{
|
||||
mesh: {
|
||||
renderOrder: 2,
|
||||
material,
|
||||
onClick() {},
|
||||
userData: {
|
||||
type: ArmorType.Spaced,
|
||||
thickness,
|
||||
} satisfies ArmorUserData,
|
||||
},
|
||||
},
|
||||
`${node.uuid}-exclude`,
|
||||
)}
|
||||
|
||||
{jsxTree(
|
||||
node,
|
||||
{
|
||||
mesh: {
|
||||
renderOrder: 5,
|
||||
material: depthWriteMaterial,
|
||||
},
|
||||
},
|
||||
`${node.uuid}-include`,
|
||||
)}
|
||||
</>
|
||||
<Fragment>
|
||||
<mesh
|
||||
{...props}
|
||||
renderOrder={2}
|
||||
material={material}
|
||||
onClick={() => {}}
|
||||
userData={armorPlate}
|
||||
/>
|
||||
<mesh {...props} renderOrder={5} material={depthWriteMaterial} />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
+72
-97
@@ -1,83 +1,42 @@
|
||||
import {
|
||||
ShellType,
|
||||
isExplosive,
|
||||
resolvePenetrationCoefficient,
|
||||
} from '@blitzkit/core';
|
||||
import { asset } from '@blitzkit/core';
|
||||
import { useGLTF } from '@react-three/drei';
|
||||
import { useThree } from '@react-three/fiber';
|
||||
import { useCallback } from 'react';
|
||||
import {
|
||||
type Intersection,
|
||||
MeshBasicMaterial,
|
||||
Object3D,
|
||||
Plane,
|
||||
Quaternion,
|
||||
Raycaster,
|
||||
Scene,
|
||||
Vector3,
|
||||
} from 'three';
|
||||
import { degToRad } from 'three/src/math/MathUtils.js';
|
||||
import { hasEquipment } from '../../../../core/blitzkit/hasEquipment';
|
||||
import { Material, MeshBasicMaterial } from 'three';
|
||||
import { jsxTree } from '../../../../core/blitzkit/jsxTree';
|
||||
import { discardClippingPlane } from '../../../../core/three/discardClippingPlane';
|
||||
import { Duel } from '../../../../stores/duel';
|
||||
import {
|
||||
type Shot,
|
||||
type ShotLayerBase,
|
||||
type ShotLayerNonExternal,
|
||||
TankopediaEphemeral,
|
||||
} from '../../../../stores/tankopediaEphemeral';
|
||||
import { ArmorType } from '../SpacedArmorScene';
|
||||
import { TankopediaEphemeral } from '../../../../stores/tankopediaEphemeral';
|
||||
import { TankopediaPersistent } from '../../../../stores/tankopediaPersistent';
|
||||
import { groupNameRegex } from '../StaticArmorSceneComponent';
|
||||
import { SpacedArmorSubExternal } from './components/SpacedArmorSubExternal';
|
||||
import { SpacedArmorSubSpaced } from './components/SpacedArmorSubSpaced';
|
||||
|
||||
type SpacedArmorSceneComponentProps = {
|
||||
node: Object3D;
|
||||
thickness: number;
|
||||
clip?: Plane;
|
||||
scene: Scene;
|
||||
} & (
|
||||
| {
|
||||
type: Exclude<ArmorType, ArmorType.External>;
|
||||
}
|
||||
| {
|
||||
type: ArmorType.External;
|
||||
variant: ExternalModuleVariant;
|
||||
}
|
||||
);
|
||||
|
||||
export type ExternalModuleVariant = 'gun' | 'track';
|
||||
export type ArmorUserData = {
|
||||
thickness: number;
|
||||
} & (
|
||||
| {
|
||||
type: Exclude<ArmorType, ArmorType.External>;
|
||||
}
|
||||
| {
|
||||
type: ArmorType.External;
|
||||
variant: ExternalModuleVariant;
|
||||
}
|
||||
);
|
||||
interface StaticArmorSceneComponentsProp {
|
||||
group: string;
|
||||
}
|
||||
|
||||
const omitMaterial = new MeshBasicMaterial({
|
||||
colorWrite: false,
|
||||
depthWrite: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* Render orders allocations:
|
||||
* - 0 : core omission
|
||||
* - 1-2: spaced without depth buffer write but do test to allow additive blending
|
||||
* - 3-4: external with depth buffer write in first pass and read in second to simplify geometry
|
||||
* - 5 : spaced depth write
|
||||
*/
|
||||
export function SpacedArmorSceneComponent({
|
||||
node,
|
||||
thickness,
|
||||
clip,
|
||||
...props
|
||||
}: SpacedArmorSceneComponentProps) {
|
||||
group,
|
||||
}: StaticArmorSceneComponentsProp) {
|
||||
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
|
||||
const duelStore = Duel.useStore();
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
const showModules = TankopediaPersistent.use((state) => state.showModules);
|
||||
const showArmorScreen = TankopediaPersistent.use(
|
||||
(state) => state.showArmorScreen,
|
||||
);
|
||||
const showArmor = TankopediaPersistent.use((state) => state.showArmor);
|
||||
const gltf = useGLTF(asset(`tanks/${tank.id}/collision/${group}.glb`));
|
||||
const armor = TankopediaEphemeral.use((state) => state.armor);
|
||||
const groupName = group.match(groupNameRegex)![1];
|
||||
const groupArmor = armor.groups[groupName];
|
||||
const thicknessRange = TankopediaEphemeral.use(
|
||||
(state) => state.thicknessRange,
|
||||
);
|
||||
const camera = useThree((state) => state.camera);
|
||||
|
||||
const shoot = useCallback(
|
||||
@@ -369,20 +328,30 @@ export function SpacedArmorSceneComponent({
|
||||
[camera],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{props.type === ArmorType.Primary &&
|
||||
jsxTree(
|
||||
node,
|
||||
{
|
||||
mesh: {
|
||||
renderOrder: 0,
|
||||
material: omitMaterial,
|
||||
userData: {
|
||||
type: ArmorType.Primary,
|
||||
thickness,
|
||||
} satisfies ArmorUserData,
|
||||
async onClick(event) {
|
||||
return jsxTree(gltf.scene, {
|
||||
mesh(mesh, props, key) {
|
||||
if (!(mesh.material instanceof Material)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const armorName = mesh.material.name;
|
||||
const armorPlate = groupArmor.armors[armorName];
|
||||
|
||||
if (armorPlate === undefined) {
|
||||
console.warn(`Missing armor: ${armorName}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (armorPlate.type) {
|
||||
case 'Armor':
|
||||
return (
|
||||
<mesh
|
||||
{...props}
|
||||
key={key}
|
||||
renderOrder={0}
|
||||
material={omitMaterial}
|
||||
userData={armorPlate}
|
||||
onClick={async (event) => {
|
||||
event.stopPropagation();
|
||||
const shot = (await shoot(
|
||||
event.point,
|
||||
@@ -390,24 +359,30 @@ export function SpacedArmorSceneComponent({
|
||||
true,
|
||||
))!;
|
||||
tankopediaEphemeralStore.setState({ shot });
|
||||
},
|
||||
},
|
||||
},
|
||||
node.uuid,
|
||||
)}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
{props.type === ArmorType.Spaced && (
|
||||
<SpacedArmorSubSpaced node={node} thickness={thickness} />
|
||||
)}
|
||||
case 'ArmorScreen':
|
||||
return (
|
||||
<SpacedArmorSubSpaced
|
||||
{...props}
|
||||
key={key}
|
||||
armorPlate={armorPlate}
|
||||
/>
|
||||
);
|
||||
|
||||
{props.type === ArmorType.External && (
|
||||
<SpacedArmorSubExternal
|
||||
node={node}
|
||||
thickness={thickness}
|
||||
variant={props.variant}
|
||||
clip={clip}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
return (
|
||||
<SpacedArmorSubExternal
|
||||
node={node}
|
||||
thickness={thickness}
|
||||
variant={props.variant}
|
||||
clip={clip}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { memo } from 'react';
|
||||
import { arrayfy } from '../../../core/three/arrayfy';
|
||||
import { Duel } from '../../../stores/duel';
|
||||
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
|
||||
@@ -8,8 +7,7 @@ export interface ThicknessRange {
|
||||
value: number;
|
||||
}
|
||||
|
||||
export const StaticArmor = memo(() => {
|
||||
const tank = Duel.use((state) => state.protagonist);
|
||||
export function StaticArmor() {
|
||||
const model = TankopediaEphemeral.use((state) => state.model);
|
||||
const turret = Duel.use((state) => state.protagonist.turret);
|
||||
const gun = Duel.use((state) => state.protagonist.gun);
|
||||
@@ -30,4 +28,4 @@ export const StaticArmor = memo(() => {
|
||||
</group>
|
||||
</group>
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { asset } from '@blitzkit/core';
|
||||
import { useGLTF } from '@react-three/drei';
|
||||
import { Fragment } from 'react/jsx-runtime';
|
||||
import {
|
||||
Color,
|
||||
LineBasicMaterial,
|
||||
@@ -37,7 +38,7 @@ export function StaticArmorSceneComponent({
|
||||
);
|
||||
|
||||
return jsxTree(gltf.scene, {
|
||||
mesh(mesh, props) {
|
||||
mesh(mesh, props, key) {
|
||||
if (!(mesh.material instanceof Material)) {
|
||||
return null;
|
||||
}
|
||||
@@ -109,13 +110,13 @@ export function StaticArmorSceneComponent({
|
||||
material.transparent = material.opacity < 1;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Fragment key={key}>
|
||||
<mesh {...props} material={material} />
|
||||
|
||||
<lineSegments material={outlineMaterial}>
|
||||
<edgesGeometry args={[props.geometry, 45]} />
|
||||
</lineSegments>
|
||||
</>
|
||||
</Fragment>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,6 +6,8 @@ import { spacedArmorRenderTarget } from './components/PrimaryArmorSceneComponent
|
||||
import { SpacedArmorScene } from './components/SpacedArmorScene';
|
||||
|
||||
export const Armor = memo(() => {
|
||||
return <SpacedArmorScene scene={new Scene()} />;
|
||||
|
||||
const [spacedArmorScene] = useState(() => new Scene());
|
||||
const [primaryArmorScene] = useState(() => new Scene());
|
||||
const spacedArmorPortal = createPortal(
|
||||
|
||||
@@ -403,18 +403,6 @@ export function Options({ canvas }: OptionsProps) {
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
</SegmentedControl.Item>
|
||||
<SegmentedControl.Item value={`${TankopediaDisplay.Dissector}`}>
|
||||
<Tooltip
|
||||
content={strings.website.tools.tanks.sandbox.dissector.name}
|
||||
>
|
||||
<Flex height="100%" align="center">
|
||||
<img
|
||||
src="/assets/images/icons/tankopedia-static-armor.png"
|
||||
style={{ height: '1.25em' }}
|
||||
/>
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
</SegmentedControl.Item>
|
||||
<SegmentedControl.Item value={`${TankopediaDisplay.Dissector}`}>
|
||||
<Tooltip
|
||||
content={strings.website.tools.tanks.sandbox.dissector.name}
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
import { asset } from '@blitzkit/core';
|
||||
import { Box, Flex, type FlexProps, Progress } from '@radix-ui/themes';
|
||||
import { useProgress } from '@react-three/drei';
|
||||
|
||||
@@ -15,7 +16,7 @@ export function TankSandboxLoader({ id, ...props }: TankSandboxLoaderProps) {
|
||||
width="100%"
|
||||
style={{
|
||||
filter: 'blur(1rem)',
|
||||
backgroundImage: `url(/api/tanks/${id}/icons/big.png)`,
|
||||
backgroundImage: `url(${asset(`tanks/${id}/icons/big.webp`)})`,
|
||||
backgroundPosition: 'center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}}
|
||||
|
||||
@@ -124,8 +124,7 @@ export const apiTool: Tool = {
|
||||
id: 'api',
|
||||
button: {
|
||||
color: 'indigo',
|
||||
},
|
||||
image: 'dIfvWnV',
|
||||
},
|
||||
};
|
||||
|
||||
export const TOOLS: Tool[] = [
|
||||
|
||||
@@ -10,14 +10,6 @@ export async function blobProxy(url: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export function apiBlobProxy(path: string) {
|
||||
return blobProxy(`http://localhost:5000${path}`);
|
||||
}
|
||||
|
||||
export function apiBlobProxyCurry(path: string) {
|
||||
return () => apiBlobProxy(path);
|
||||
}
|
||||
|
||||
export async function jsonMirror<Type>(
|
||||
path: string,
|
||||
message: MessageFns<Type>,
|
||||
|
||||
@@ -5,13 +5,16 @@ import { Mesh, Object3D } from 'three';
|
||||
export function jsxTree(
|
||||
node: Object3D,
|
||||
mergers?: {
|
||||
mesh?: (mesh: Mesh, props: MeshProps) => ReactNode;
|
||||
group?: (object3d: Object3D, props: Object3DProps) => ReactNode;
|
||||
mesh?: (mesh: Mesh, props: MeshProps, key: string) => ReactNode;
|
||||
group?: (
|
||||
object3d: Object3D,
|
||||
props: Object3DProps,
|
||||
key: string,
|
||||
) => ReactNode;
|
||||
},
|
||||
): ReactNode {
|
||||
if (node instanceof Mesh) {
|
||||
const props = {
|
||||
key: node.uuid,
|
||||
geometry: node.geometry,
|
||||
material: node.material,
|
||||
position: node.position,
|
||||
@@ -19,17 +22,24 @@ export function jsxTree(
|
||||
scale: node.scale,
|
||||
};
|
||||
|
||||
return mergers?.mesh ? mergers.mesh(node, props) : <mesh {...props} />;
|
||||
return mergers?.mesh ? (
|
||||
mergers.mesh(node, props, node.uuid)
|
||||
) : (
|
||||
<mesh key={node.uuid} {...props} />
|
||||
);
|
||||
} else if (node instanceof Object3D) {
|
||||
const props = {
|
||||
key: node.uuid,
|
||||
position: node.position,
|
||||
rotation: node.rotation,
|
||||
scale: node.scale,
|
||||
children: node.children.map((child) => jsxTree(child, mergers)),
|
||||
};
|
||||
|
||||
return mergers?.group ? mergers.group(node, props) : <group {...props} />;
|
||||
return mergers?.group ? (
|
||||
mergers.group(node, props, node.uuid)
|
||||
) : (
|
||||
<group key={node.uuid} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
import { fetchTanksFull } from '@blitzkit/core';
|
||||
import { asset, fetchTanksFull } from '@blitzkit/core';
|
||||
import { Box, Flex, Text } from '@radix-ui/themes';
|
||||
import { LinkI18n } from '../../../components/LinkI18n';
|
||||
import { PageWrapper } from '../../../components/PageWrapper';
|
||||
@@ -25,7 +25,7 @@ const tanksFull = await fetchTanksFull();
|
||||
width="8rem"
|
||||
height="6rem"
|
||||
style={{
|
||||
backgroundImage: `url(/api/tanks/${tank.id}/icons/big.png)`,
|
||||
backgroundImage: `url(${asset(`tanks/${tank.id}/icons/big.webp`)})`,
|
||||
backgroundSize: '150%',
|
||||
backgroundPosition: 'center',
|
||||
}}
|
||||
|
||||
@@ -2,9 +2,10 @@ import { TanksFull } from '@blitzkit/core';
|
||||
import { jsonMirrorCurry } from '../../../core/blitzkit/blobMirror';
|
||||
|
||||
/**
|
||||
* Identical to tanks/full.pb but in JSON.
|
||||
* Returns meta data similar to tanks/[id].json but for all tanks as an array. There is no guarantee of order.
|
||||
*
|
||||
* @warning This is a large response. Consider using tanks/[id]/meta.json for individual tanks.
|
||||
* @proto tanks_full
|
||||
* @returns JSON.
|
||||
*/
|
||||
export const GET = jsonMirrorCurry('/tanks/full.pb', TanksFull);
|
||||
@@ -1,8 +0,0 @@
|
||||
import { apiBlobProxyCurry } from '../../../core/blitzkit/blobMirror';
|
||||
|
||||
/**
|
||||
* Returns meta data similar to tanks/[id].json but for all tanks as an array. There is no guarantee of order.
|
||||
*
|
||||
* @proto tanks_full
|
||||
*/
|
||||
export const GET = apiBlobProxyCurry('/tanks/full.pb');
|
||||
@@ -8,7 +8,7 @@ import type { Vector3 } from 'three';
|
||||
import { create } from 'zustand';
|
||||
import { subscribeWithSelector } from 'zustand/middleware';
|
||||
import type { ArmorType } from '../components/Armor/components/SpacedArmorScene';
|
||||
import type { ExternalModuleVariant } from '../components/Armor/components/SpacedArmorSceneComponent';
|
||||
import type { ExternalModuleVariant } from '../components/Armor/components/SpacedArmorSceneComponent/_index';
|
||||
import type { XP_MULTIPLIERS } from '../components/Tankopedia/TechTreeSection';
|
||||
import { awaitableSkillDefinitions } from '../core/awaitables/skillDefinitions';
|
||||
import { createContextualStore } from '../core/zustand/createContextualStore';
|
||||
@@ -148,7 +148,7 @@ export const TankopediaEphemeral = createContextualStore(
|
||||
model,
|
||||
controlsEnabled: true,
|
||||
xpMultiplier: 1,
|
||||
display: TankopediaDisplay.StaticArmor,
|
||||
display: TankopediaDisplay.DynamicArmor,
|
||||
})),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using AssetRipper.TextureDecoder.Rgb.Channels;
|
||||
using Blitzkit;
|
||||
using Google.Protobuf;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace BlitzKit.CLI.Functions
|
||||
{
|
||||
public class Api(string[] args)
|
||||
{
|
||||
readonly WebApplication app = WebApplication.Create();
|
||||
readonly Mangler mangler = new(args);
|
||||
|
||||
void Map<T>(string path, Func<Task<T>> func)
|
||||
where T : IMessage<T> =>
|
||||
app.MapGet(path, async context => await Octet(context, await func()));
|
||||
|
||||
void Map<T>(string path, Func<T> func)
|
||||
where T : IMessage<T> => Map(path, () => Task.FromResult(func()));
|
||||
|
||||
static async Task Octet<T>(HttpContext context, T message)
|
||||
where T : IMessage<T>
|
||||
{
|
||||
var bytes = message.ToByteArray();
|
||||
|
||||
context.Response.ContentType = "application/octet-stream";
|
||||
await context.Response.Body.WriteAsync(bytes);
|
||||
}
|
||||
|
||||
public async Task Run()
|
||||
{
|
||||
await mangler.Initialize();
|
||||
|
||||
Map("/tanks/full.pb", mangler.TanksFull);
|
||||
Map("/tanks/list.pb", mangler.Tanks);
|
||||
|
||||
app.MapGet(
|
||||
"/tanks/{id}.pb",
|
||||
(HttpContext context, string id) => Octet(context, mangler.Tank(id))
|
||||
);
|
||||
|
||||
app.MapGet(
|
||||
"/tanks/{id}/icons/big.pb",
|
||||
(HttpContext context, string id) => Octet(context, mangler.TankIconBig(id))
|
||||
);
|
||||
|
||||
app.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,81 +49,15 @@ namespace BlitzKit.CLI.Functions
|
||||
readonly BlitzProvider provider = new(args.Contains("--depot"));
|
||||
Locales? locales;
|
||||
readonly Dictionary<string, Dictionary<string, string>> strings = [];
|
||||
TanksFull tanksFull = null!;
|
||||
readonly AssetUploader assetUploader = new("mangled ue assets") { enabled = true };
|
||||
|
||||
public async Task Initialize()
|
||||
public async Task Mangle()
|
||||
{
|
||||
await FetchStrings();
|
||||
tanksFull = TanksFull();
|
||||
await MangleNations();
|
||||
await assetUploader.Flush();
|
||||
}
|
||||
|
||||
public TanksFull TanksFull()
|
||||
{
|
||||
if (tanksFull != null)
|
||||
return tanksFull;
|
||||
|
||||
var dirs = provider.RootDirectory.GetDirectory("Blitz/Content/Tanks").Directories;
|
||||
tanksFull = new();
|
||||
|
||||
foreach (var dir in dirs)
|
||||
{
|
||||
if (dir.Key == "TankStub")
|
||||
continue;
|
||||
|
||||
foreach (var tankDir in dir.Value.Directories)
|
||||
{
|
||||
var tank = Tank(tankDir.Value);
|
||||
tanksFull.Tanks.Add(tank);
|
||||
}
|
||||
}
|
||||
|
||||
DeduplicateSlugs(tanksFull);
|
||||
|
||||
return tanksFull;
|
||||
}
|
||||
|
||||
public Tanks Tanks()
|
||||
{
|
||||
var tanksFull = TanksFull();
|
||||
Tanks tanks = new();
|
||||
|
||||
foreach (var tank in tanksFull.Tanks)
|
||||
{
|
||||
tanks.Tanks_.Add(tank.Id);
|
||||
}
|
||||
|
||||
return tanks;
|
||||
}
|
||||
|
||||
public Tank Tank(string id)
|
||||
{
|
||||
return tanksFull.Tanks.First(tank => tank.Id == id);
|
||||
}
|
||||
|
||||
Tank Tank(VFS tankDir)
|
||||
{
|
||||
var pdaName = $"PDA_{tankDir.Name}";
|
||||
var pda = provider.LoadObject($"{tankDir.Path}/{pdaName}.{pdaName}");
|
||||
|
||||
var id = pda.Get<FName>("TankId").Text;
|
||||
var name = GetString($"TankEntity__{id.ToLowerInvariant()}__Short_Name");
|
||||
var slug = Diacritics.Remove(name.Locales["en"]).ToLower();
|
||||
slug = NonAlphanumericRegex().Replace(slug, "-");
|
||||
slug = MultipleDashesRegex().Replace(slug, "-");
|
||||
slug = TrailingDashRegex().Replace(slug, "");
|
||||
|
||||
Tank tank = new()
|
||||
{
|
||||
Id = id,
|
||||
Name = name,
|
||||
Slug = slug,
|
||||
};
|
||||
|
||||
return tank;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
I18n GetString(string name)
|
||||
{
|
||||
I18n i18n = new();
|
||||
@@ -175,6 +109,42 @@ namespace BlitzKit.CLI.Functions
|
||||
}
|
||||
}
|
||||
|
||||
async Task MangleNations()
|
||||
{
|
||||
var dirs = provider.RootDirectory.GetDirectory("Blitz/Content/Tanks").Directories;
|
||||
TanksFull tanksFull = new();
|
||||
|
||||
foreach (var dir in dirs)
|
||||
{
|
||||
if (dir.Key == "TankStub")
|
||||
continue;
|
||||
|
||||
foreach (var tankDir in dir.Value.Directories)
|
||||
{
|
||||
// if (tankDir.Value.Name != "R90_IS_4")
|
||||
// continue;
|
||||
|
||||
// if (tankDir.Value.Name != "R81_IS_8")
|
||||
// continue;
|
||||
|
||||
var tank = await MangleTank(tankDir.Value);
|
||||
tanksFull.Tanks.Add(tank);
|
||||
}
|
||||
}
|
||||
|
||||
DeduplicateSlugs(tanksFull);
|
||||
Tanks tanks = new();
|
||||
|
||||
foreach (var tank in tanksFull.Tanks)
|
||||
{
|
||||
tanks.Tanks_.Add(tank.Id);
|
||||
await assetUploader.Add($"tanks/{tank.Id}/meta.pb", tank.ToByteArray());
|
||||
}
|
||||
|
||||
await assetUploader.Add("definitions/tanks.pb", tanks.ToByteArray());
|
||||
await assetUploader.Add("definitions/tanks-full.pb", tanksFull.ToByteArray());
|
||||
}
|
||||
|
||||
void DeduplicateSlugs(TanksFull tanksFull)
|
||||
{
|
||||
List<string> checkedSlugs = [];
|
||||
@@ -245,6 +215,33 @@ namespace BlitzKit.CLI.Functions
|
||||
}
|
||||
}
|
||||
|
||||
async Task<Tank> MangleTank(VFS tankDir)
|
||||
{
|
||||
var pdaName = $"PDA_{tankDir.Name}";
|
||||
var pda = provider.LoadObject($"{tankDir.Path}/{pdaName}.{pdaName}");
|
||||
|
||||
var id = pda.Get<FName>("TankId").Text;
|
||||
var name = GetString($"TankEntity__{id.ToLowerInvariant()}__Short_Name");
|
||||
var slug = Diacritics.Remove(name.Locales["en"]).ToLower();
|
||||
slug = NonAlphanumericRegex().Replace(slug, "-");
|
||||
slug = MultipleDashesRegex().Replace(slug, "-");
|
||||
slug = TrailingDashRegex().Replace(slug, "");
|
||||
|
||||
await MangleIcon(pda);
|
||||
await MangleArmor(pda);
|
||||
|
||||
Tank tank = new()
|
||||
{
|
||||
Id = id,
|
||||
Name = name,
|
||||
Slug = slug,
|
||||
};
|
||||
|
||||
await MangleModules(tank, pda);
|
||||
|
||||
return tank;
|
||||
}
|
||||
|
||||
async Task MangleArmor(UObject pda)
|
||||
{
|
||||
var id = pda.Get<FName>("TankId").Text;
|
||||
@@ -285,7 +282,7 @@ namespace BlitzKit.CLI.Functions
|
||||
PrettyLog.Warn($"Penetration groups missing for {id}; creating empty list");
|
||||
}
|
||||
|
||||
// await assetUploader.Add($"tanks/{id}/armor.pb", tankArmor.ToByteArray());
|
||||
await assetUploader.Add($"tanks/{id}/armor.pb", tankArmor.ToByteArray());
|
||||
}
|
||||
|
||||
async Task MangleIcon(UObject pda)
|
||||
@@ -295,7 +292,7 @@ namespace BlitzKit.CLI.Functions
|
||||
if (pda.TryGet<UTexture2D>("SmallIcon", out var smallIconTexture))
|
||||
{
|
||||
var smallIcon = BlitzKitExporter.Texture2D(smallIconTexture);
|
||||
// await assetUploader.Add($"tanks/{id}/icons/small.png", smallIcon);
|
||||
await assetUploader.Add($"tanks/{id}/icons/small.webp", smallIcon);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -305,7 +302,7 @@ namespace BlitzKit.CLI.Functions
|
||||
if (pda.TryGet<UTexture2D>("BigIcon", out var bigIconTexture))
|
||||
{
|
||||
var bigIcon = BlitzKitExporter.Texture2D(bigIconTexture);
|
||||
// await assetUploader.Add($"tanks/{id}/icons/big.png", bigIcon);
|
||||
await assetUploader.Add($"tanks/{id}/icons/big.webp", bigIcon);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -357,9 +354,9 @@ namespace BlitzKit.CLI.Functions
|
||||
{
|
||||
MonoGltf gltf = new(collision);
|
||||
|
||||
// await assetUploader.Add(
|
||||
// new($"tanks/{tank.Id}/collision/{moduleName}.glb", gltf.Write())
|
||||
// );
|
||||
await assetUploader.Add(
|
||||
new($"tanks/{tank.Id}/collision/{moduleName}.glb", gltf.Write())
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -368,7 +365,7 @@ namespace BlitzKit.CLI.Functions
|
||||
}
|
||||
}
|
||||
|
||||
// await assetUploader.Add($"tanks/{tank.Id}/model.pb", tankModel.ToByteArray());
|
||||
await assetUploader.Add($"tanks/{tank.Id}/model.pb", tankModel.ToByteArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,9 +87,9 @@ namespace BlitzKit.CLI.Models
|
||||
}
|
||||
else
|
||||
{
|
||||
PrettyLog.Background(
|
||||
$"🔵 {blobPath} ({change.Content.Count.ToString("N0", Program.Culture)}B)"
|
||||
);
|
||||
// PrettyLog.Background(
|
||||
// $"🔵 {blobPath} ({change.Content.Count.ToString("N0", Program.Culture)}B)"
|
||||
// );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +22,10 @@ namespace BlitzKit.CLI
|
||||
break;
|
||||
}
|
||||
|
||||
case "api":
|
||||
case "mangle":
|
||||
{
|
||||
Api api = new(args);
|
||||
await api.Run();
|
||||
Mangler mangler = new(args);
|
||||
await mangler.Mangle();
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace BlitzKit.CLI.Utils
|
||||
{
|
||||
return texture
|
||||
.Decode(ETexturePlatform.DesktopMobile)!
|
||||
.Encode(SKEncodedImageFormat.Webp, 100)
|
||||
.Encode(SKEncodedImageFormat.Webp, 80)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user