mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-22 03:07:04 +02:00
Merge branch 'main' into remove-imgur-references
This commit is contained in:
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),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user