first commit
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"version": 1,
|
||||
"source": "OpenSAGE src/OpenSage.Game/Data/StreamFS/AssetType.cs",
|
||||
"count": 79,
|
||||
"types": {
|
||||
"299416263": "AudioLod",
|
||||
"315614191": "LocalBuildListMonitor",
|
||||
"354222972": "GameLodPreset",
|
||||
"376113229": "AudioFile",
|
||||
"400896388": "CrowdResponse",
|
||||
"439207783": "TheaterOfWarTemplate",
|
||||
"504350110": "InGameUIPlayerPowerCommandSlots",
|
||||
"530081230": "IntelDB",
|
||||
"531798225": "StaticGameLod",
|
||||
"534008255": "LargeGroupAudioMap",
|
||||
"565855655": "ImageSequence",
|
||||
"568797146": "Texture",
|
||||
"607123156": "AIStrategicStateDefinition",
|
||||
"608742960": "W3dAnimation",
|
||||
"610186489": "InGameUIVoiceChatCommandSlots",
|
||||
"680780553": "Environment",
|
||||
"686292351": "FXParticleSystemTemplate",
|
||||
"726253425": "Achievement",
|
||||
"741706624": "MpGameRules",
|
||||
"819131716": "RadiusCursorLibrary",
|
||||
"866546168": "InGameUISettings",
|
||||
"926814458": "AITargetingHeuristic",
|
||||
"962203606": "InGameUILookAtCommandSlots",
|
||||
"980180622": "ArmorTemplate",
|
||||
"1345252658": "OnlineChatColors",
|
||||
"1350608344": "MappableKey",
|
||||
"1443425905": "AudioSettings",
|
||||
"1447728787": "VideoEventList",
|
||||
"1449288224": "PackedTextureImage",
|
||||
"1482556238": "CampaignTemplate",
|
||||
"1628705662": "InGameUIGroupSelectionCommandSlots",
|
||||
"1641540160": "W3dHierarchy",
|
||||
"1713477273": "PlayerPowerButtonTemplateStore",
|
||||
"1874610847": "ExperienceLevelTemplate",
|
||||
"1883691512": "MusicTrack",
|
||||
"2007776008": "TargetingInTurretArcCompare",
|
||||
"2070603733": "MiscAudio",
|
||||
"2101756272": "LogicCommand",
|
||||
"2178440954": "SpecialPowerTemplate",
|
||||
"2219670431": "AudioEvent",
|
||||
"2254974584": "FXList",
|
||||
"2359666647": "TargetingDistanceCompare",
|
||||
"2384988189": "MultiplayerColor",
|
||||
"2421413379": "UnitOverlayIconSettings",
|
||||
"2430161325": "Weather",
|
||||
"2458866148": "InGameUIFixedElementHotKeySlotMap",
|
||||
"2467477932": "OnDemandTexture",
|
||||
"2486173485": "GameObject",
|
||||
"2496977262": "WeaponTemplate",
|
||||
"2525284163": "StanceTemplate",
|
||||
"2525492603": "Mouse",
|
||||
"2565744451": "InGameUISideBarCommandSlots",
|
||||
"2745675575": "Multisound",
|
||||
"2800139175": "HotKeySlot",
|
||||
"2811124014": "InGameUIUnitAbilityCommandSlots",
|
||||
"2812698028": "InGameUITacticalCommandSlots",
|
||||
"2893598307": "AIBudgetStateDefinition",
|
||||
"2901356964": "DynamicGameLod",
|
||||
"2905958645": "DamageFX",
|
||||
"3188107749": "TargetingCompareList",
|
||||
"3266421346": "W3dMesh",
|
||||
"3319822471": "AttributeModifier",
|
||||
"3477794083": "MissionTemplate",
|
||||
"3558134211": "DialogEvent",
|
||||
"3587539190": "ArmyDefinition",
|
||||
"3604279694": "AIPersonalityDefinition",
|
||||
"3614134471": "UnitTypeIcon",
|
||||
"3650896041": "PhaseEffect",
|
||||
"3741098742": "DefaultHotKeys",
|
||||
"3786401627": "UpgradeTemplate",
|
||||
"3810008068": "W3dCollisionBox",
|
||||
"3899542881": "ObjectCreationList",
|
||||
"3928762264": "AmbientStream",
|
||||
"3959844197": "LogicCommandSet",
|
||||
"3971904488": "SkirmishOpeningMove",
|
||||
"3972178387": "LocomotorTemplate",
|
||||
"4042295058": "W3dContainer",
|
||||
"4157475773": "ShadowMap",
|
||||
"4262364347": "OnDemandTextureImage"
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,214 @@
|
||||
import schemaModel from "./schema-model.json";
|
||||
import assetTypes from "./asset-types.json";
|
||||
|
||||
export interface ChildInfo {
|
||||
name: string;
|
||||
type: string | null;
|
||||
min: number;
|
||||
max: number; // -1 = unbounded
|
||||
doc: string;
|
||||
}
|
||||
|
||||
export interface AttributeInfo {
|
||||
name: string;
|
||||
required: boolean;
|
||||
default: string | null;
|
||||
doc: string;
|
||||
kind: string;
|
||||
type: string | null;
|
||||
refType: string | null;
|
||||
/** True for reference-typed attributes whose simple type has no refType. */
|
||||
isRef: boolean;
|
||||
enumValues: string[];
|
||||
allowsDefine: boolean;
|
||||
isBoolean: boolean;
|
||||
base: string | null;
|
||||
}
|
||||
|
||||
export interface ComplexTypeInfo {
|
||||
kind: "complex";
|
||||
children: ChildInfo[];
|
||||
attributes: AttributeInfo[];
|
||||
base: string | null;
|
||||
doc: string;
|
||||
}
|
||||
|
||||
export interface SimpleTypeInfo {
|
||||
kind: "simple";
|
||||
base: string | null;
|
||||
refType: string | null;
|
||||
isRef: boolean;
|
||||
enumValues: string[];
|
||||
allowsDefine: boolean;
|
||||
doc: string;
|
||||
}
|
||||
|
||||
export type TypeInfo = ComplexTypeInfo | SimpleTypeInfo;
|
||||
|
||||
interface RawModel {
|
||||
version: number;
|
||||
rootXsd: string;
|
||||
topLevelElements: string[];
|
||||
elements: Record<string, { type: string | null; doc: string }>;
|
||||
types: Record<string, TypeInfo>;
|
||||
subTypesOf: Record<string, string[]>;
|
||||
}
|
||||
|
||||
const model = schemaModel as unknown as RawModel;
|
||||
|
||||
/** Lowercase type name -> canonical (XSD) type name. */
|
||||
const typeNameIndex = new Map<string, string>();
|
||||
for (const name of Object.keys(model.types)) {
|
||||
const lower = name.toLowerCase();
|
||||
if (!typeNameIndex.has(lower)) typeNameIndex.set(lower, name);
|
||||
}
|
||||
|
||||
/** Resolves a possibly-mis-cased type name to its canonical XSD spelling. */
|
||||
export function canonicalTypeName(name: string | null): string | null {
|
||||
if (!name) return null;
|
||||
return typeNameIndex.get(name.toLowerCase()) ?? name;
|
||||
}
|
||||
|
||||
/** element name -> type name, collected from every complex type's children. */
|
||||
const elementToType = new Map<string, string>();
|
||||
for (const type of Object.values(model.types)) {
|
||||
if (type.kind !== "complex") continue;
|
||||
for (const child of type.children) {
|
||||
if (!elementToType.has(child.name)) {
|
||||
elementToType.set(child.name, child.type ?? "");
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const [name, info] of Object.entries(model.elements)) {
|
||||
elementToType.set(name, info.type ?? "");
|
||||
}
|
||||
|
||||
export const modelMeta = {
|
||||
rootXsd: model.rootXsd,
|
||||
topLevelElementCount: model.topLevelElements.length,
|
||||
typeCount: Object.keys(model.types).length,
|
||||
};
|
||||
|
||||
export function topLevelElements(): string[] {
|
||||
return model.topLevelElements;
|
||||
}
|
||||
|
||||
export function isTopLevelElement(name: string): boolean {
|
||||
return model.topLevelElements.includes(name);
|
||||
}
|
||||
|
||||
export function typeInfo(name: string): TypeInfo | undefined {
|
||||
return model.types[name];
|
||||
}
|
||||
|
||||
export function elementTypeName(name: string): string | null {
|
||||
const t = elementToType.get(name);
|
||||
return t ? t : null;
|
||||
}
|
||||
|
||||
export function childrenOfElement(name: string): ChildInfo[] {
|
||||
const type = elementTypeName(name);
|
||||
if (!type) return [];
|
||||
return childrenOfType(type);
|
||||
}
|
||||
|
||||
export function childrenOfType(typeName: string | null): ChildInfo[] {
|
||||
if (!typeName) return [];
|
||||
const info = model.types[canonicalTypeName(typeName) ?? typeName];
|
||||
return info && info.kind === "complex" ? info.children : [];
|
||||
}
|
||||
|
||||
export function attributesOfElement(name: string): AttributeInfo[] {
|
||||
const type = elementTypeName(name);
|
||||
if (!type) return [];
|
||||
return attributesOfType(type);
|
||||
}
|
||||
|
||||
export function attributesOfType(typeName: string | null): AttributeInfo[] {
|
||||
if (!typeName) return [];
|
||||
const info = model.types[canonicalTypeName(typeName) ?? typeName];
|
||||
return info && info.kind === "complex" ? info.attributes : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of a child element inside a KNOWN parent type, or null
|
||||
* when the parent type is unknown or the child is not declared there.
|
||||
*/
|
||||
export function childTypeOf(
|
||||
parentTypeName: string | null,
|
||||
childName: string,
|
||||
): string | null {
|
||||
if (!parentTypeName) return null;
|
||||
const info = model.types[canonicalTypeName(parentTypeName) ?? parentTypeName];
|
||||
if (info?.kind !== "complex") return null;
|
||||
return info.children.find((c) => c.name === childName)?.type ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Context-aware element type resolution: prefers the child declaration inside
|
||||
* the parent element's type, falling back to the global element map. Same
|
||||
* element names used in different parents (e.g. <Weapon> under a weapon slot
|
||||
* vs. a plain reference) therefore resolve to their contextually correct type.
|
||||
*/
|
||||
export function elementTypeIn(
|
||||
parentElementName: string | null,
|
||||
childName: string,
|
||||
): string | null {
|
||||
if (parentElementName) {
|
||||
const parentType = elementTypeName(parentElementName);
|
||||
const typed = childTypeOf(parentType, childName);
|
||||
if (typed) return typed;
|
||||
}
|
||||
return elementTypeName(childName);
|
||||
}
|
||||
|
||||
export function typeDoc(name: string): string {
|
||||
const info = model.types[name];
|
||||
return info?.doc ?? "";
|
||||
}
|
||||
|
||||
/** The type itself plus all ancestors (nearest first). */
|
||||
export function typeChain(name: string): string[] {
|
||||
const out: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
let cur: string | null = canonicalTypeName(name);
|
||||
while (cur && !seen.has(cur)) {
|
||||
seen.add(cur);
|
||||
out.push(cur);
|
||||
const info: TypeInfo | undefined = model.types[cur];
|
||||
cur = info && "base" in info && info.base ? canonicalTypeName(info.base) : null;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* True when an asset of type `actualType` satisfies a reference to `refType`.
|
||||
* Falls back to exact name matching; unknown types only match exactly.
|
||||
*/
|
||||
export function isAssignableTo(actualType: string, refType: string | null): boolean {
|
||||
if (!refType) return true;
|
||||
const actual = canonicalTypeName(actualType) ?? actualType;
|
||||
const ref = canonicalTypeName(refType) ?? refType;
|
||||
if (actual === ref) return true;
|
||||
return typeChain(actual).includes(ref);
|
||||
}
|
||||
|
||||
/** Maps a manifest TypeId hash to a type name, when known. */
|
||||
export function assetTypeNameFromHash(hash: number): string | undefined {
|
||||
return (assetTypes as { types: Record<string, string> }).types[hash];
|
||||
}
|
||||
|
||||
export function assetTypeHashCount(): number {
|
||||
return (assetTypes as { count: number }).count ?? 0;
|
||||
}
|
||||
|
||||
/** Elements that are structurally relevant everywhere. */
|
||||
export const STRUCTURAL_ELEMENTS = [
|
||||
"AssetDeclaration",
|
||||
"Includes",
|
||||
"Include",
|
||||
"Tags",
|
||||
"Tag",
|
||||
"Defines",
|
||||
"Define",
|
||||
];
|
||||
Reference in New Issue
Block a user