Merge branch 'main' into remove-imgur-references

This commit is contained in:
TrèsAbhi authored and GitHub committed 2026-10-18 14:03:19 -05:00
commit 73080fd60a
39 files changed
+550 -569

No files matched your search

@@ -434,6 +434,7 @@ export abstract class ServerBlitzKitAPI1 extends ServerBlitzKitAPI0 {
resistance_hard: terrainResistances[0],
resistance_medium: terrainResistances[1],
resistance_soft: terrainResistances[2],
repair_time: track.hysteresisHealth / track.healthRegenPerSec,
tier: track.level,
unlocks: resolveUnlocks(
this.blitzModuleTypeToBlitzkit,
@@ -4,7 +4,13 @@ import {
ChassisDefinitionsList,
Crew,
GunDefinitionsList,
I_HAT,
J_HAT,
K_HAT,
ModelDefinitions,
parseHierarchies,
Sc2ReadStream,
ScgReadStream,
TankParameters,
toUniqueId,
TurretDefinitionsList,
@@ -12,9 +18,10 @@ import {
VehicleDefinitionArmor,
VehicleDefinitionList,
VehicleDefinitions,
VertexAttribute,
} from "@blitzkit/core";
import { parse as parsePath } from "path";
import { Vector3Tuple } from "three";
import { MathUtils, Vector3 as THREEVector3, Vector3Tuple } from "three";
import { Cache } from "./0_base";
import { ServerBlitzKitAPI2 } from "./2_camouflageDefinitions";
@@ -23,6 +30,87 @@ export abstract class ServerBlitzKitAPI3 extends ServerBlitzKitAPI2 {
return { x: tuple[0], y: tuple[1], z: tuple[2] } satisfies Vector3;
}
private async collisionBoundingBoxes(
nation: string,
tankKey: string,
turretRotation?: { yaw: number; pitch: number; roll: number },
) {
const boundingBoxes: Record<
string,
{ min: Vector3Tuple; max: Vector3Tuple }
> = {};
const path = `Data/3d/Tanks/CollisionMeshes/${nation}-${tankKey}`;
if (!(await this.vfs.resolve(`${path}.sc2`))) {
throw new Error(`Missing collision mesh for ${nation}-${tankKey}`);
}
const sc2 = new Sc2ReadStream(
(await this.vfs.file(`${path}.sc2`)).buffer as ArrayBuffer,
).sc2();
const scg = new ScgReadStream(
(await this.vfs.file(`${path}.scg`)).buffer as ArrayBuffer,
).scg();
parseHierarchies(sc2["#hierarchy"], null, (hierarchy, components) => {
for (const component of components) {
if (component["comp.typename"] !== "RenderComponent") continue;
const polygonGroup = scg.get(
component["rc.renderObj"]["ro.batches"]["0000"]["rb.datasource"],
);
if (!polygonGroup) continue;
const min: Vector3Tuple = [Infinity, Infinity, Infinity];
const max: Vector3Tuple = [-Infinity, -Infinity, -Infinity];
const rotate =
turretRotation && hierarchy.name.toLowerCase().startsWith("turret");
polygonGroup.vertices.forEach((vertex) => {
vertex.forEach(({ attribute, value }) => {
if (attribute !== VertexAttribute.VERTEX) return;
const position = new THREEVector3(...value);
if (rotate) {
position
.applyAxisAngle(
I_HAT,
-MathUtils.degToRad(turretRotation.pitch),
)
.applyAxisAngle(
J_HAT,
-MathUtils.degToRad(turretRotation.roll),
)
.applyAxisAngle(
K_HAT,
-MathUtils.degToRad(turretRotation.yaw),
);
}
const coordinates = position.toArray();
for (let axis = 0; axis < 3; axis++) {
min[axis] = Math.min(min[axis], coordinates[axis]);
max[axis] = Math.max(max[axis], coordinates[axis]);
}
});
});
boundingBoxes[hierarchy.name.toLowerCase()] = { min, max };
}
return null;
});
if (!boundingBoxes.hull) {
throw new Error(`Missing hull collision mesh for ${nation}-${tankKey}`);
}
return boundingBoxes;
}
private assignArmor(
raw: VehicleDefinitionArmor[string],
id: number,
@@ -67,6 +155,12 @@ export abstract class ServerBlitzKitAPI3 extends ServerBlitzKitAPI2 {
const tankParameters = await this.vfs.yaml<TankParameters>(
`Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
);
const collisionBoundingBoxes = await this.collisionBoundingBoxes(
nation,
tankKey,
tankDefinition.root.hull.turretInitialRotation,
);
const hullBoundingBox = collisionBoundingBoxes.hull;
const turretOrigin = tankDefinition.root.hull.turretPositions.turret
.split(" ")
.map(Number) as Vector3Tuple;
@@ -141,12 +235,8 @@ export abstract class ServerBlitzKitAPI3 extends ServerBlitzKitAPI2 {
}
: undefined,
bounding_box: {
min: this.vector3TupleToBlitzkit(
tankParameters.collision.hull.bbox.min,
),
max: this.vector3TupleToBlitzkit(
tankParameters.collision.hull.bbox.max,
),
min: this.vector3TupleToBlitzkit(hullBoundingBox.min),
max: this.vector3TupleToBlitzkit(hullBoundingBox.max),
},
turrets: {},
tracks: {},
@@ -208,26 +298,35 @@ export abstract class ServerBlitzKitAPI3 extends ServerBlitzKitAPI2 {
turret.userString;
const turretCollisionName = parsePath(
turret.hitTester.collisionModel,
).name.toLowerCase();
const isFullYaw = -turretYaw[0] + turretYaw[1] === 360;
if (!collisionBoundingBoxes[turretCollisionName] && isFullYaw) {
throw new Error(
`Missing turret collision mesh for ${nation}-${tankKey} ${turretKey}`,
);
}
const turretBoundingBox = collisionBoundingBoxes[
turretCollisionName
] ?? {
min: [0, 0, 0] as Vector3Tuple,
max: [0, 0, 0] as Vector3Tuple,
};
modelDefinitions.models[tankId].turrets[turretId] = {
armor: turretArmor,
gun_origin: this.vector3TupleToBlitzkit(gunOrigin),
model_id: turretModel,
yaw:
-turretYaw[0] + turretYaw[1] === 360
? undefined
: { min: turretYaw[0], max: turretYaw[1] },
yaw: isFullYaw
? undefined
: { min: turretYaw[0], max: turretYaw[1] },
guns: {},
bounding_box: {
min: this.vector3TupleToBlitzkit(
tankParameters.collision[
parsePath(turret.hitTester.collisionModel).name.toLowerCase()
].bbox.min,
),
max: this.vector3TupleToBlitzkit(
tankParameters.collision[
parsePath(turret.hitTester.collisionModel).name.toLowerCase()
].bbox.max,
),
min: this.vector3TupleToBlitzkit(turretBoundingBox.min),
max: this.vector3TupleToBlitzkit(turretBoundingBox.max),
},
};
+1
View File
@@ -25,6 +25,7 @@ export * from "./sluggify";
export * from "./staticAsset";
export * from "./sum";
export * from "./tankDefinitions";
export * from "./tankBoundingBox";
export * from "./tankIcon";
export * from "./tankNames";
export * from "./unionBoundingBox";
@@ -0,0 +1,32 @@
import { ModelDefinition, TurretModelDefinition } from "../protos";
import { unionBoundingBox } from "./unionBoundingBox";
export function tankBoundingBox(
tankModelDefinition: ModelDefinition,
turretModelDefinition: TurretModelDefinition,
) {
const hull = tankModelDefinition.bounding_box!;
const turret = turretModelDefinition.bounding_box!;
const origin = tankModelDefinition.turret_origin!;
if (
turret.min!.x === turret.max!.x &&
turret.min!.y === turret.max!.y &&
turret.min!.z === turret.max!.z
) {
return unionBoundingBox(hull, hull);
}
return unionBoundingBox(hull, {
min: {
x: turret.min!.x + origin.x,
y: turret.min!.y + origin.z,
z: turret.min!.z + origin.y,
},
max: {
x: turret.max!.x + origin.x,
y: turret.max!.y + origin.z,
z: turret.max!.z + origin.y,
},
});
}
@@ -87,6 +87,7 @@ message TrackDefinition {
required float resistance_medium = 10;
required float resistance_soft = 11;
repeated Unlock unlocks = 12;
required float repair_time = 13;
}
message EngineDefinition {
@@ -338,6 +338,7 @@ export interface TrackDefinition {
resistance_medium: number;
resistance_soft: number;
unlocks: Unlock[];
repair_time: number;
}
export interface EngineDefinition {
@@ -1759,6 +1760,7 @@ function createBaseTrackDefinition(): TrackDefinition {
resistance_medium: 0,
resistance_soft: 0,
unlocks: [],
repair_time: 0,
};
}
@@ -1800,6 +1802,9 @@ export const TrackDefinition: MessageFns<TrackDefinition> = {
for (const v of message.unlocks) {
Unlock.encode(v!, writer.uint32(98).fork()).join();
}
if (message.repair_time !== 0) {
writer.uint32(109).float(message.repair_time);
}
return writer;
},
@@ -1906,6 +1911,14 @@ export const TrackDefinition: MessageFns<TrackDefinition> = {
message.unlocks.push(Unlock.decode(reader, reader.uint32()));
continue;
}
case 13: {
if (tag !== 109) {
break;
}
message.repair_time = reader.float();
continue;
}
}
if ((tag & 7) === 4 || tag === 0) {
break;
@@ -1929,6 +1942,7 @@ export const TrackDefinition: MessageFns<TrackDefinition> = {
resistance_medium: isSet(object.resistance_medium) ? globalThis.Number(object.resistance_medium) : 0,
resistance_soft: isSet(object.resistance_soft) ? globalThis.Number(object.resistance_soft) : 0,
unlocks: globalThis.Array.isArray(object?.unlocks) ? object.unlocks.map((e: any) => Unlock.fromJSON(e)) : [],
repair_time: isSet(object.repair_time) ? globalThis.Number(object.repair_time) : 0,
};
},
@@ -1970,6 +1984,9 @@ export const TrackDefinition: MessageFns<TrackDefinition> = {
if (message.unlocks?.length) {
obj.unlocks = message.unlocks.map((e) => Unlock.toJSON(e));
}
if (message.repair_time !== 0) {
obj.repair_time = message.repair_time;
}
return obj;
},
@@ -1994,6 +2011,7 @@ export const TrackDefinition: MessageFns<TrackDefinition> = {
message.resistance_medium = object.resistance_medium ?? 0;
message.resistance_soft = object.resistance_soft ?? 0;
message.unlocks = object.unlocks?.map((e) => Unlock.fromPartial(e)) || [];
message.repair_time = object.repair_time ?? 0;
return message;
},
};
+22
View File
@@ -90,6 +90,28 @@ export interface Hierarchy {
name: string;
}
export function parseHierarchies<Parent>(
hierarchies: Hierarchy[],
parent: Parent,
parseHierarchy: (
hierarchy: Hierarchy,
components: Component[],
parent: Parent,
) => Parent | undefined,
) {
for (const hierarchy of hierarchies) {
const components = Array.from(
{ length: hierarchy.components.count },
(_, index) => hierarchy.components[index.toString().padStart(4, "0")],
);
const child = parseHierarchy(hierarchy, components, parent);
if (child !== undefined && hierarchy["#hierarchy"]) {
parseHierarchies(hierarchy["#hierarchy"], child, parseHierarchy);
}
}
}
// 0001, 0002, 0003...: Component
type Components = {
count: number;
@@ -34,6 +34,8 @@ export interface VehicleDefinitions {
};
level: number;
hullPosition: string;
hysteresisHealth: number;
healthRegenPerSec: number;
armor: {
leftTrack:
| number