@s2script/sdk 0.10.0 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +320 -47
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4585,6 +4585,37 @@ import { readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from
|
|
|
4585
4585
|
import { join as join13 } from "node:path";
|
|
4586
4586
|
|
|
4587
4587
|
// src/schemagen/model.ts
|
|
4588
|
+
function enumMemberNames(enumName, members) {
|
|
4589
|
+
const raw = {};
|
|
4590
|
+
for (const m of members) raw[m] = m;
|
|
4591
|
+
if (members.length === 0) return raw;
|
|
4592
|
+
const stem = enumName.replace(/_t$/, "").replace(/[^A-Za-z0-9]/g, "").toUpperCase();
|
|
4593
|
+
if (stem === "") return raw;
|
|
4594
|
+
const stripped = {};
|
|
4595
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4596
|
+
for (const m of members) {
|
|
4597
|
+
const up = m.toUpperCase();
|
|
4598
|
+
let rest = null;
|
|
4599
|
+
for (const sep3 of ["_", ""]) {
|
|
4600
|
+
const pfx = stem + sep3;
|
|
4601
|
+
if (up.startsWith(pfx) && m.length > pfx.length) {
|
|
4602
|
+
rest = m.slice(pfx.length);
|
|
4603
|
+
break;
|
|
4604
|
+
}
|
|
4605
|
+
}
|
|
4606
|
+
if (rest === null && /^k[A-Z]/.test(m)) rest = m.slice(1);
|
|
4607
|
+
if (rest === null) return raw;
|
|
4608
|
+
const name = /^[0-9]/.test(rest) ? `_${rest}` : rest;
|
|
4609
|
+
if (seen.has(name)) return raw;
|
|
4610
|
+
seen.add(name);
|
|
4611
|
+
stripped[m] = name;
|
|
4612
|
+
}
|
|
4613
|
+
return stripped;
|
|
4614
|
+
}
|
|
4615
|
+
function enumTsName(schemaName) {
|
|
4616
|
+
const id = schemaName.replace(/[^A-Za-z0-9_$]/g, "_");
|
|
4617
|
+
return /^[0-9]/.test(id) ? `_${id}` : id;
|
|
4618
|
+
}
|
|
4588
4619
|
var READ = {
|
|
4589
4620
|
f32: "readFloat32",
|
|
4590
4621
|
bool: "readBool",
|
|
@@ -4640,9 +4671,15 @@ var ATOMIC = {
|
|
|
4640
4671
|
uint32: { k: "u32", w: true },
|
|
4641
4672
|
uint64: { k: "u64", w: false },
|
|
4642
4673
|
int64: { k: "i64", w: false },
|
|
4643
|
-
float64: { k: "f64", w: false }
|
|
4674
|
+
float64: { k: "f64", w: false },
|
|
4644
4675
|
// no narrow 64-bit writers
|
|
4676
|
+
// `Color` is a 4-byte RGBA struct, not a scalar, but it is laid out exactly as a little-endian
|
|
4677
|
+
// uint32 (R in the low byte) and every engine consumer treats it that way. Mapping it here rather
|
|
4678
|
+
// than adding a value class keeps the surface small and matches how packed colours are already
|
|
4679
|
+
// passed elsewhere in the SDK (e.g. the fade user-message).
|
|
4680
|
+
Color: { k: "u32", w: true }
|
|
4645
4681
|
};
|
|
4682
|
+
var ENUM_WIDTH = { 1: "u8", 2: "u16", 4: "u32", 8: "u64" };
|
|
4646
4683
|
var VEC = { Vector: "vector", VectorWS: "vector", QAngle: "qangle" };
|
|
4647
4684
|
var VEC_INFO = {
|
|
4648
4685
|
vector: { cls: "Vector", count: 3 },
|
|
@@ -4655,7 +4692,13 @@ function idiomaticName(raw) {
|
|
|
4655
4692
|
const core = m && KNOWN_TAGS.has(m[1]) ? m[2] : s;
|
|
4656
4693
|
return core.charAt(0).toLowerCase() + core.slice(1);
|
|
4657
4694
|
}
|
|
4658
|
-
function
|
|
4695
|
+
function transparentValue(catalog, className) {
|
|
4696
|
+
const e = catalog[className];
|
|
4697
|
+
if (!e || e.parent || e.fields.length !== 1) return null;
|
|
4698
|
+
const f = e.fields[0];
|
|
4699
|
+
return f.name === "m_Value" && f.type.kind === "atomic" ? f : null;
|
|
4700
|
+
}
|
|
4701
|
+
function classifyField(type, embeddable = /* @__PURE__ */ new Set()) {
|
|
4659
4702
|
if (type.kind === "handle") return { accessorKind: "handle", writable: false };
|
|
4660
4703
|
if (type.kind === "atomic") {
|
|
4661
4704
|
const vk = VEC[type.name ?? ""];
|
|
@@ -4669,8 +4712,16 @@ function classifyField(type) {
|
|
|
4669
4712
|
if (cm) return { accessorKind: "str", writable: false, strLen: parseInt(cm[1], 10) };
|
|
4670
4713
|
return { skip: `unmapped 'unknown' type '${type.name ?? ""}'` };
|
|
4671
4714
|
}
|
|
4672
|
-
if (type.kind === "enum")
|
|
4673
|
-
|
|
4715
|
+
if (type.kind === "enum") {
|
|
4716
|
+
const w = ENUM_WIDTH[type.size ?? 0];
|
|
4717
|
+
if (w) return { accessorKind: w, writable: WRITE[w] !== void 0, enumType: type.name };
|
|
4718
|
+
return { skip: `enum '${type.name ?? ""}' byte width not stated by the schema` };
|
|
4719
|
+
}
|
|
4720
|
+
if (type.kind === "class") {
|
|
4721
|
+
const n3 = type.name ?? "";
|
|
4722
|
+
if (embeddable.has(n3)) return { embedded: n3 };
|
|
4723
|
+
return { skip: `embedded class '${n3}' deferred` };
|
|
4724
|
+
}
|
|
4674
4725
|
if (type.kind === "ptr") return { skip: "raw pointer" };
|
|
4675
4726
|
return { skip: `unmapped kind '${type.kind}'` };
|
|
4676
4727
|
}
|
|
@@ -4685,7 +4736,11 @@ function flattenedFields(model, className) {
|
|
|
4685
4736
|
}
|
|
4686
4737
|
return chain.flatMap((c2) => c2.ownFields);
|
|
4687
4738
|
}
|
|
4688
|
-
function buildModel(catalog, requested) {
|
|
4739
|
+
function buildModel(catalog, requested, embeddable = [], enumCatalog = {}) {
|
|
4740
|
+
for (const n3 of embeddable) if (!catalog[n3]) throw new Error(`gen-schema: embedded class '${n3}' is not in the catalog`);
|
|
4741
|
+
const restrict = embeddable.length > 0 ? new Set(embeddable) : null;
|
|
4742
|
+
const canEmbed = (n3) => !!catalog[n3] && transparentValue(catalog, n3) === null && (restrict === null || restrict.has(n3));
|
|
4743
|
+
const embedSet = new Set(Object.keys(catalog).filter(canEmbed));
|
|
4689
4744
|
const inClosure = /* @__PURE__ */ new Set();
|
|
4690
4745
|
for (const start of requested) {
|
|
4691
4746
|
if (!catalog[start]) throw new Error(`gen-schema: requested class '${start}' is not in the catalog`);
|
|
@@ -4704,39 +4759,133 @@ function buildModel(catalog, requested) {
|
|
|
4704
4759
|
return d;
|
|
4705
4760
|
};
|
|
4706
4761
|
const ordered = [...inClosure].sort((a2, b) => depth(a2) - depth(b) || (a2 < b ? -1 : a2 > b ? 1 : 0));
|
|
4707
|
-
const
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4762
|
+
const usedEmbeds = /* @__PURE__ */ new Set();
|
|
4763
|
+
const classify = (f) => {
|
|
4764
|
+
if (f.type.kind === "class") {
|
|
4765
|
+
const inner = transparentValue(catalog, f.type.name ?? "");
|
|
4766
|
+
if (inner) {
|
|
4767
|
+
const c2 = classifyField(inner.type);
|
|
4768
|
+
if (!("skip" in c2) && !("embedded" in c2)) return { ...c2, addOffset: inner.offset };
|
|
4769
|
+
}
|
|
4770
|
+
}
|
|
4771
|
+
return classifyField(f.type, embedSet);
|
|
4772
|
+
};
|
|
4773
|
+
const fieldsOf = (owner, out, emb, skipped) => {
|
|
4774
|
+
for (const f of catalog[owner].fields) {
|
|
4775
|
+
const c2 = classify(f);
|
|
4713
4776
|
if ("skip" in c2) {
|
|
4714
|
-
skipped.push({ className, rawName: f.name, reason: c2.skip });
|
|
4777
|
+
skipped.push({ className: owner, rawName: f.name, reason: c2.skip });
|
|
4778
|
+
continue;
|
|
4779
|
+
}
|
|
4780
|
+
if ("embedded" in c2) {
|
|
4781
|
+
usedEmbeds.add(c2.embedded);
|
|
4782
|
+
emb.push({ propName: idiomaticName(f.name), rawName: f.name, declaringClass: owner, embeddedClass: c2.embedded });
|
|
4715
4783
|
continue;
|
|
4716
4784
|
}
|
|
4717
|
-
|
|
4785
|
+
out.push({ propName: idiomaticName(f.name), rawName: f.name, declaringClass: owner, accessorKind: c2.accessorKind, writable: c2.writable, strLen: c2.strLen, addOffset: c2.addOffset, enumType: c2.enumType });
|
|
4718
4786
|
}
|
|
4719
|
-
|
|
4787
|
+
};
|
|
4788
|
+
const classes = ordered.map((className) => {
|
|
4789
|
+
const parent = catalog[className].parent;
|
|
4790
|
+
const ownFields = [];
|
|
4791
|
+
const embeddedFields = [];
|
|
4792
|
+
const skipped = [];
|
|
4793
|
+
fieldsOf(className, ownFields, embeddedFields, skipped);
|
|
4794
|
+
return { className, parent: parent && inClosure.has(parent) ? parent : null, ownFields, embeddedFields, skipped };
|
|
4720
4795
|
});
|
|
4796
|
+
const embedded = [];
|
|
4797
|
+
const done = /* @__PURE__ */ new Set();
|
|
4798
|
+
for (; ; ) {
|
|
4799
|
+
const next = [...usedEmbeds].filter((n3) => !done.has(n3)).sort();
|
|
4800
|
+
if (next.length === 0) break;
|
|
4801
|
+
for (const className of next) {
|
|
4802
|
+
done.add(className);
|
|
4803
|
+
const fields = [];
|
|
4804
|
+
const embeddedFields = [];
|
|
4805
|
+
const skipped = [];
|
|
4806
|
+
const chain = [];
|
|
4807
|
+
for (let cur = className; cur && catalog[cur]; cur = catalog[cur].parent) chain.unshift(cur);
|
|
4808
|
+
for (const owner of chain) fieldsOf(owner, fields, embeddedFields, skipped);
|
|
4809
|
+
embedded.push({ className, fields, embeddedFields, skipped });
|
|
4810
|
+
}
|
|
4811
|
+
}
|
|
4812
|
+
embedded.sort((a2, b) => a2.className < b.className ? -1 : a2.className > b.className ? 1 : 0);
|
|
4813
|
+
const ancestors = (cls) => {
|
|
4814
|
+
const out = /* @__PURE__ */ new Set();
|
|
4815
|
+
for (let cur = catalog[cls]?.parent ?? null; cur && catalog[cur]; cur = catalog[cur].parent) out.add(cur);
|
|
4816
|
+
return out;
|
|
4817
|
+
};
|
|
4721
4818
|
const byProp = /* @__PURE__ */ new Map();
|
|
4722
|
-
for (const c2 of classes) for (const f of c2.ownFields) {
|
|
4819
|
+
for (const c2 of classes) for (const f of [...c2.ownFields, ...c2.embeddedFields]) {
|
|
4723
4820
|
(byProp.get(f.propName) ?? byProp.set(f.propName, []).get(f.propName)).push(f);
|
|
4724
4821
|
}
|
|
4725
4822
|
const collisions = [];
|
|
4726
4823
|
for (const [prop, fields] of byProp) {
|
|
4727
|
-
const
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4824
|
+
const decls = /* @__PURE__ */ new Map();
|
|
4825
|
+
for (const f of fields) (decls.get(`${f.declaringClass}.${f.rawName}`) ?? decls.set(`${f.declaringClass}.${f.rawName}`, []).get(`${f.declaringClass}.${f.rawName}`)).push(f);
|
|
4826
|
+
if (decls.size < 2) continue;
|
|
4827
|
+
const keys = [...decls.keys()].sort();
|
|
4828
|
+
const clsOf = (k) => decls.get(k)[0].declaringClass;
|
|
4829
|
+
const conflicting = /* @__PURE__ */ new Set();
|
|
4830
|
+
for (let i2 = 0; i2 < keys.length; i2++) {
|
|
4831
|
+
for (let j = i2 + 1; j < keys.length; j++) {
|
|
4832
|
+
const a2 = clsOf(keys[i2]), b = clsOf(keys[j]);
|
|
4833
|
+
if (a2 === b || ancestors(a2).has(b) || ancestors(b).has(a2)) {
|
|
4834
|
+
conflicting.add(keys[i2]);
|
|
4835
|
+
conflicting.add(keys[j]);
|
|
4836
|
+
}
|
|
4837
|
+
}
|
|
4731
4838
|
}
|
|
4839
|
+
if (conflicting.size === 0) continue;
|
|
4840
|
+
const depthOf = (k) => ancestors(clsOf(k)).size;
|
|
4841
|
+
const inConflict = [...conflicting].sort((x, y) => depthOf(x) - depthOf(y) || (x < y ? -1 : 1));
|
|
4842
|
+
const winnerCls = clsOf(inConflict[0]);
|
|
4843
|
+
const soleWinner = inConflict.filter((k) => clsOf(k) === winnerCls).length === 1;
|
|
4844
|
+
const renamed = [];
|
|
4845
|
+
for (const k of inConflict) {
|
|
4846
|
+
if (soleWinner && k === inConflict[0]) continue;
|
|
4847
|
+
for (const f of decls.get(k)) f.propName = f.rawName;
|
|
4848
|
+
renamed.push(k);
|
|
4849
|
+
}
|
|
4850
|
+
collisions.push(`${prop} <- ${renamed.sort().join(", ")}${soleWinner ? ` (kept by ${winnerCls})` : ""}`);
|
|
4732
4851
|
}
|
|
4733
4852
|
collisions.sort();
|
|
4734
|
-
|
|
4853
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
4854
|
+
const noteEnum = (f) => {
|
|
4855
|
+
if (f.type.kind === "enum" && f.type.name && enumCatalog[f.type.name]) referenced.add(f.type.name);
|
|
4856
|
+
};
|
|
4857
|
+
for (const cls of inClosure) for (const f of catalog[cls].fields) noteEnum(f);
|
|
4858
|
+
for (const e of embedded) for (const owner of new Set(e.fields.map((f) => f.declaringClass))) {
|
|
4859
|
+
for (const f of catalog[owner]?.fields ?? []) noteEnum(f);
|
|
4860
|
+
}
|
|
4861
|
+
const byTsName = /* @__PURE__ */ new Map();
|
|
4862
|
+
for (const n3 of referenced) {
|
|
4863
|
+
const id = enumTsName(n3);
|
|
4864
|
+
(byTsName.get(id) ?? byTsName.set(id, []).get(id)).push(n3);
|
|
4865
|
+
}
|
|
4866
|
+
const ambiguous = /* @__PURE__ */ new Set();
|
|
4867
|
+
for (const [, group] of byTsName) if (group.length > 1) for (const n3 of group) ambiguous.add(n3);
|
|
4868
|
+
const enums = [...referenced].filter((n3) => !ambiguous.has(n3)).sort().map((className) => {
|
|
4869
|
+
const values = enumCatalog[className].values;
|
|
4870
|
+
const names = enumMemberNames(className, Object.keys(values));
|
|
4871
|
+
return {
|
|
4872
|
+
className,
|
|
4873
|
+
tsName: enumTsName(className),
|
|
4874
|
+
members: Object.keys(values).sort().map((raw) => ({ name: names[raw], raw, value: values[raw] }))
|
|
4875
|
+
};
|
|
4876
|
+
});
|
|
4877
|
+
for (const n3 of [...ambiguous].sort()) collisions.push(`enum ${n3} -> ${enumTsName(n3)} (ambiguous; emitted as number)`);
|
|
4878
|
+
return { classes, embedded, enums, collisions };
|
|
4735
4879
|
}
|
|
4736
4880
|
|
|
4737
4881
|
// src/schemagen/emit-dts.ts
|
|
4738
4882
|
var HEADER = "// GENERATED by `s2script gen-schema` from schema-catalog.json \u2014 DO NOT EDIT.";
|
|
4739
4883
|
function emitDts(model) {
|
|
4884
|
+
const enumTs = new Map(model.enums.map((e) => [e.className, e.tsName]));
|
|
4885
|
+
const tsType = (f) => {
|
|
4886
|
+
const id = f.enumType ? enumTs.get(f.enumType) : void 0;
|
|
4887
|
+
return id ? `${id} | null` : TSTYPE[f.accessorKind];
|
|
4888
|
+
};
|
|
4740
4889
|
const names = new Set(model.classes.map((c2) => c2.className));
|
|
4741
4890
|
const out = [HEADER, 'import type { EntityRef } from "@s2script/sdk/entity";'];
|
|
4742
4891
|
const vecClasses = /* @__PURE__ */ new Set();
|
|
@@ -4744,27 +4893,98 @@ function emitDts(model) {
|
|
|
4744
4893
|
const vi = VEC_INFO[f.accessorKind];
|
|
4745
4894
|
if (vi) vecClasses.add(vi.cls);
|
|
4746
4895
|
}
|
|
4896
|
+
for (const e of model.embedded) for (const f of e.fields) {
|
|
4897
|
+
const vi = VEC_INFO[f.accessorKind];
|
|
4898
|
+
if (vi) vecClasses.add(vi.cls);
|
|
4899
|
+
}
|
|
4747
4900
|
if (vecClasses.size) out.push(`import type { ${[...vecClasses].sort().join(", ")} } from "@s2script/sdk/math";`);
|
|
4748
4901
|
out.push("");
|
|
4902
|
+
for (const e of model.enums) {
|
|
4903
|
+
out.push(`export declare const ${e.tsName}: {`);
|
|
4904
|
+
for (const m of e.members) {
|
|
4905
|
+
const doc = m.name === m.raw ? "" : ` /** \`${m.raw}\` */
|
|
4906
|
+
`;
|
|
4907
|
+
out.push(`${doc} readonly ${m.name}: ${m.value};`);
|
|
4908
|
+
}
|
|
4909
|
+
out.push("};");
|
|
4910
|
+
if (e.tsName !== e.className) out.push(`/** Schema enum \`${e.className}\`. */`);
|
|
4911
|
+
out.push(`export type ${e.tsName} = (typeof ${e.tsName})[keyof typeof ${e.tsName}];`, "");
|
|
4912
|
+
}
|
|
4913
|
+
for (const e of model.embedded) {
|
|
4914
|
+
out.push(`export interface ${e.className} {`);
|
|
4915
|
+
for (const f of e.fields) {
|
|
4916
|
+
out.push(` ${f.writable ? "" : "readonly "}${f.propName}: ${tsType(f)};`);
|
|
4917
|
+
}
|
|
4918
|
+
for (const f of e.embeddedFields) out.push(` readonly ${f.propName}: ${f.embeddedClass};`);
|
|
4919
|
+
out.push("}", "");
|
|
4920
|
+
}
|
|
4749
4921
|
for (const c2 of model.classes) {
|
|
4750
4922
|
const ext = c2.parent && names.has(c2.parent) ? ` extends ${c2.parent}` : "";
|
|
4751
4923
|
out.push(`export interface ${c2.className}${ext} {`);
|
|
4752
4924
|
for (const f of c2.ownFields) {
|
|
4753
4925
|
const ro = f.writable ? "" : "readonly ";
|
|
4754
|
-
out.push(` ${ro}${f.propName}: ${
|
|
4926
|
+
out.push(` ${ro}${f.propName}: ${tsType(f)};`);
|
|
4755
4927
|
}
|
|
4928
|
+
for (const f of c2.embeddedFields) out.push(` readonly ${f.propName}: ${f.embeddedClass};`);
|
|
4756
4929
|
out.push("}", "");
|
|
4757
4930
|
}
|
|
4931
|
+
out.push("/** Every wrappable entity class, by schema name. */", "export interface SchemaClasses {");
|
|
4932
|
+
for (const c2 of model.classes) out.push(` ${c2.className}: ${c2.className};`);
|
|
4933
|
+
out.push("}", "");
|
|
4934
|
+
out.push(
|
|
4935
|
+
"/**",
|
|
4936
|
+
" * Apply this schema class's field accessors to an entity reference.",
|
|
4937
|
+
" *",
|
|
4938
|
+
" * `createEntity` returns a bare `EntityRef` with no schema fields on it \u2014 this is how you get",
|
|
4939
|
+
" * them. `Player.pawn` is already wrapped; this is for entities you create or resolve yourself.",
|
|
4940
|
+
" *",
|
|
4941
|
+
" * The wrapper is a view, not a copy: it holds the ref and reads through it on every access, so",
|
|
4942
|
+
" * it stays correct as the entity changes and costs nothing to keep. Field reads return null",
|
|
4943
|
+
" * once the ref goes stale, exactly as they do on a pawn.",
|
|
4944
|
+
" *",
|
|
4945
|
+
" * @example",
|
|
4946
|
+
' * const prop = createEntity("prop_dynamic");',
|
|
4947
|
+
" * prop.spawn();",
|
|
4948
|
+
' * const fields = wrapEntity("CBaseModelEntity", prop);',
|
|
4949
|
+
" * fields.glow.glowing = true;",
|
|
4950
|
+
" */",
|
|
4951
|
+
"export declare function wrapEntity<K extends keyof SchemaClasses>(className: K, ref: EntityRef): SchemaClasses[K];",
|
|
4952
|
+
""
|
|
4953
|
+
);
|
|
4758
4954
|
return out.join("\n");
|
|
4759
4955
|
}
|
|
4760
4956
|
|
|
4761
4957
|
// src/schemagen/emit-js.ts
|
|
4762
4958
|
var HEADER2 = "// GENERATED by `s2script gen-schema` from schema-catalog.json \u2014 DO NOT EDIT.";
|
|
4763
4959
|
var S = JSON.stringify;
|
|
4960
|
+
function resolveExpr(f) {
|
|
4961
|
+
const base = `off(${S(f.declaringClass)},${S(f.rawName)})`;
|
|
4962
|
+
return f.addOffset ? `${base} + ${f.addOffset}` : base;
|
|
4963
|
+
}
|
|
4964
|
+
function accessorEntry(f, resolve13, guard2) {
|
|
4965
|
+
const g = guard2 === "" ? "" : `${guard2} `;
|
|
4966
|
+
const gs = guard2 === "" ? "" : `${guard2.replace("return null;", "return;")} `;
|
|
4967
|
+
let entry;
|
|
4968
|
+
if (f.accessorKind === "str") {
|
|
4969
|
+
entry = `get: function () { ${g}return this.ref.readString(${resolve13}, ${f.strLen}); }`;
|
|
4970
|
+
} else if (f.accessorKind === "u64" || f.accessorKind === "i64") {
|
|
4971
|
+
entry = `get: function () { ${g}var v = this.ref.${READ[f.accessorKind]}(${resolve13}); return v === null ? null : v.toString(); }`;
|
|
4972
|
+
} else if (VEC_INFO[f.accessorKind]) {
|
|
4973
|
+
const vi = VEC_INFO[f.accessorKind];
|
|
4974
|
+
entry = `get: function () { ${g}var a = this.ref.readFloats(${resolve13}, ${vi.count}); return a === null ? null : new ${vi.cls}(a[0], a[1], a[2]); }`;
|
|
4975
|
+
} else {
|
|
4976
|
+
entry = `get: function () { ${g}return this.ref.${READ[f.accessorKind]}(${resolve13}); }`;
|
|
4977
|
+
}
|
|
4978
|
+
if (f.writable) {
|
|
4979
|
+
const wm = WRITE[f.accessorKind];
|
|
4980
|
+
entry += `, set: function (v) { ${gs}var o = ${resolve13}; if (this.ref.${wm}(o, v)) this.ref.notifyStateChanged(o); }`;
|
|
4981
|
+
}
|
|
4982
|
+
return entry;
|
|
4983
|
+
}
|
|
4764
4984
|
function emitJs(model) {
|
|
4765
|
-
const out = [HEADER2, "(function () {", " var off = __s2_schema_offset;", " var A = {};"];
|
|
4985
|
+
const out = [HEADER2, "(function () {", " var off = __s2_schema_offset;", " var A = {};", " var E = {};"];
|
|
4766
4986
|
const vecClasses = /* @__PURE__ */ new Set();
|
|
4767
|
-
for (const c2 of model.classes) for (const f of
|
|
4987
|
+
for (const c2 of model.classes) for (const f of c2.ownFields) {
|
|
4768
4988
|
const vi = VEC_INFO[f.accessorKind];
|
|
4769
4989
|
if (vi) vecClasses.add(vi.cls);
|
|
4770
4990
|
}
|
|
@@ -4773,36 +4993,65 @@ function emitJs(model) {
|
|
|
4773
4993
|
}
|
|
4774
4994
|
for (const c2 of model.classes) {
|
|
4775
4995
|
out.push(` A[${S(c2.className)}] = {`);
|
|
4776
|
-
for (const f of
|
|
4777
|
-
const
|
|
4778
|
-
let entry;
|
|
4779
|
-
if (f.accessorKind === "str") {
|
|
4780
|
-
entry = `get: function () { return this.ref.readString(${resolve13}, ${f.strLen}); }`;
|
|
4781
|
-
} else if (f.accessorKind === "u64" || f.accessorKind === "i64") {
|
|
4782
|
-
entry = `get: function () { var v = this.ref.${READ[f.accessorKind]}(${resolve13}); return v === null ? null : v.toString(); }`;
|
|
4783
|
-
} else if (VEC_INFO[f.accessorKind]) {
|
|
4784
|
-
const vi = VEC_INFO[f.accessorKind];
|
|
4785
|
-
entry = `get: function () { var a = this.ref.readFloats(${resolve13}, ${vi.count}); return a === null ? null : new ${vi.cls}(a[0], a[1], a[2]); }`;
|
|
4786
|
-
} else {
|
|
4787
|
-
entry = `get: function () { return this.ref.${READ[f.accessorKind]}(${resolve13}); }`;
|
|
4788
|
-
}
|
|
4789
|
-
if (f.writable) {
|
|
4790
|
-
const wm = WRITE[f.accessorKind];
|
|
4791
|
-
entry += `, set: function (v) { var o = ${resolve13}; if (this.ref.${wm}(o, v)) this.ref.notifyStateChanged(o); }`;
|
|
4792
|
-
}
|
|
4996
|
+
for (const f of c2.ownFields) {
|
|
4997
|
+
const entry = accessorEntry(f, resolveExpr(f), "");
|
|
4793
4998
|
out.push(` ${S(f.propName)}: { ${entry} },`);
|
|
4794
4999
|
}
|
|
5000
|
+
for (const f of c2.embeddedFields) {
|
|
5001
|
+
out.push(` ${S(f.propName)}: { get: function () { return embed(${S(f.embeddedClass)}, this.ref, off(${S(f.declaringClass)},${S(f.rawName)})); } },`);
|
|
5002
|
+
}
|
|
5003
|
+
out.push(" };");
|
|
5004
|
+
}
|
|
5005
|
+
for (const e of model.embedded) {
|
|
5006
|
+
out.push(` E[${S(e.className)}] = {`);
|
|
5007
|
+
for (const f of e.fields) {
|
|
5008
|
+
const sub = `off(${S(f.declaringClass)},${S(f.rawName)})`;
|
|
5009
|
+
const resolve13 = f.addOffset ? `this.base + ${sub} + ${f.addOffset}` : `this.base + ${sub}`;
|
|
5010
|
+
const guard2 = `if (this.base < 0 || ${sub} < 0) return null;`;
|
|
5011
|
+
out.push(` ${S(f.propName)}: { ${accessorEntry(f, resolve13, guard2)} },`);
|
|
5012
|
+
}
|
|
5013
|
+
for (const f of e.embeddedFields) {
|
|
5014
|
+
const sub = `off(${S(f.declaringClass)},${S(f.rawName)})`;
|
|
5015
|
+
out.push(` ${S(f.propName)}: { get: function () { var s = ${sub}; return embed(${S(f.embeddedClass)}, this.ref, (this.base < 0 || s < 0) ? -1 : this.base + s); } },`);
|
|
5016
|
+
}
|
|
4795
5017
|
out.push(" };");
|
|
4796
5018
|
}
|
|
5019
|
+
out.push(" var P = {");
|
|
5020
|
+
for (const c2 of model.classes) if (c2.parent) out.push(` ${S(c2.className)}: ${S(c2.parent)},`);
|
|
5021
|
+
out.push(" };");
|
|
5022
|
+
if (model.enums.length > 0) {
|
|
5023
|
+
out.push(" var E_CONST = {};");
|
|
5024
|
+
for (const e of model.enums) {
|
|
5025
|
+
const body = e.members.map((m) => `${S(m.name)}: ${m.value}`).join(", ");
|
|
5026
|
+
out.push(` E_CONST[${S(e.tsName)}] = Object.freeze({ ${body} });`);
|
|
5027
|
+
}
|
|
5028
|
+
}
|
|
4797
5029
|
out.push(
|
|
5030
|
+
" // Walk the ancestor chain rather than storing every inherited accessor per class. Flattening",
|
|
5031
|
+
" // into each table duplicated the whole chain 3.77x on the CS2 class set and grew as",
|
|
5032
|
+
" // O(classes * depth), so every class added inflated every other one; this is O(total fields).",
|
|
5033
|
+
" // Ancestors are applied FIRST so a derived class's own definition wins if a name repeats.",
|
|
4798
5034
|
" function applyAccessors(proto, className) {",
|
|
4799
|
-
" var
|
|
4800
|
-
" for (
|
|
4801
|
-
"
|
|
5035
|
+
" var chain = [], c = className;",
|
|
5036
|
+
" for (; c; c = P[c]) chain.unshift(c);",
|
|
5037
|
+
" for (var i = 0; i < chain.length; i++) {",
|
|
5038
|
+
" var defs = A[chain[i]]; if (!defs) continue;",
|
|
5039
|
+
" for (var name in defs) {",
|
|
5040
|
+
" Object.defineProperty(proto, name, { get: defs[name].get, set: defs[name].set, enumerable: true, configurable: true });",
|
|
5041
|
+
" }",
|
|
4802
5042
|
" }",
|
|
4803
5043
|
" }",
|
|
4804
5044
|
" function wrap(className, ref) { var o = { ref: ref }; applyAccessors(o, className); return o; }",
|
|
5045
|
+
" function embed(className, ref, base) {",
|
|
5046
|
+
" var o = { ref: ref, base: base }, defs = E[className];",
|
|
5047
|
+
" if (defs) for (var name in defs) {",
|
|
5048
|
+
" Object.defineProperty(o, name, { get: defs[name].get, set: defs[name].set, enumerable: true, configurable: true });",
|
|
5049
|
+
" }",
|
|
5050
|
+
" return o;",
|
|
5051
|
+
" }",
|
|
4805
5052
|
" globalThis.__s2pkg_cs2_schema = { applyAccessors: applyAccessors, wrap: wrap };",
|
|
5053
|
+
" // Merge (not overwrite): this prelude runs FIRST, and pawn.js merges onto whatever it finds.",
|
|
5054
|
+
" globalThis.__s2pkg_cs2 = Object.assign({}, globalThis.__s2pkg_cs2, { wrapEntity: wrap }" + (model.enums.length > 0 ? ", E_CONST);" : ");"),
|
|
4806
5055
|
"})();"
|
|
4807
5056
|
);
|
|
4808
5057
|
return out.join("\n");
|
|
@@ -4810,13 +5059,25 @@ function emitJs(model) {
|
|
|
4810
5059
|
|
|
4811
5060
|
// src/schemagen/gen.ts
|
|
4812
5061
|
var CATALOG_PATH = "games/cs2/gamedata/schema-catalog.json";
|
|
5062
|
+
var ENUMS_PATH = "games/cs2/gamedata/schema-enums.json";
|
|
4813
5063
|
var LIST_PATH = "games/cs2/codegen-classes.json";
|
|
5064
|
+
var EMBED_PATH = "games/cs2/codegen-embedded.json";
|
|
4814
5065
|
var JS_OUT = "games/cs2/js/schema.generated.js";
|
|
4815
5066
|
var DTS_OUT = "packages/cs2/schema.generated.d.ts";
|
|
4816
5067
|
function runGenSchema(repoRoot2, opts) {
|
|
4817
5068
|
const catalog = JSON.parse(readFileSync13(join13(repoRoot2, CATALOG_PATH), "utf8"));
|
|
4818
5069
|
const list = JSON.parse(readFileSync13(join13(repoRoot2, LIST_PATH), "utf8"));
|
|
4819
|
-
|
|
5070
|
+
let embed = [];
|
|
5071
|
+
try {
|
|
5072
|
+
embed = JSON.parse(readFileSync13(join13(repoRoot2, EMBED_PATH), "utf8"));
|
|
5073
|
+
} catch {
|
|
5074
|
+
}
|
|
5075
|
+
let enumCatalog = {};
|
|
5076
|
+
try {
|
|
5077
|
+
enumCatalog = JSON.parse(readFileSync13(join13(repoRoot2, ENUMS_PATH), "utf8"));
|
|
5078
|
+
} catch {
|
|
5079
|
+
}
|
|
5080
|
+
const model = buildModel(catalog, list, embed, enumCatalog);
|
|
4820
5081
|
const dts = emitDts(model);
|
|
4821
5082
|
const js = emitJs(model);
|
|
4822
5083
|
const fields = model.classes.reduce((n3, c2) => n3 + c2.ownFields.length, 0);
|
|
@@ -5004,6 +5265,7 @@ function buildNavModel(config, catalog) {
|
|
|
5004
5265
|
source: entry.source,
|
|
5005
5266
|
target: entry.target,
|
|
5006
5267
|
path: entry.path,
|
|
5268
|
+
base: entry.base ?? [],
|
|
5007
5269
|
fields,
|
|
5008
5270
|
skippedKinds
|
|
5009
5271
|
};
|
|
@@ -5042,10 +5304,12 @@ function emitNavJs(model) {
|
|
|
5042
5304
|
out.push(` var ${cls} = __s2require("@s2script/sdk/math").${cls};`);
|
|
5043
5305
|
}
|
|
5044
5306
|
for (const w of model.wrappers) {
|
|
5045
|
-
out.push(` function ${w.wrapper}(root, path) { this.root = root; this.path = path; }`);
|
|
5307
|
+
out.push(` function ${w.wrapper}(root, path, base) { this.root = root; this.path = path; this.base = base; }`);
|
|
5046
5308
|
out.push(` Object.defineProperties(${w.wrapper}.prototype, {`);
|
|
5047
5309
|
for (const f of w.fields) {
|
|
5048
|
-
const
|
|
5310
|
+
const own = `off(${S2(f.declaringClass)},${S2(f.rawName)})`;
|
|
5311
|
+
const withDelta = f.addOffset ? `${own} + ${f.addOffset}` : own;
|
|
5312
|
+
const resolve13 = `${withDelta} + this.base`;
|
|
5049
5313
|
let getter;
|
|
5050
5314
|
const kind = f.accessorKind;
|
|
5051
5315
|
if (kind === "u64") {
|
|
@@ -5084,7 +5348,16 @@ function emitNavJs(model) {
|
|
|
5084
5348
|
const h2 = w.path[i2];
|
|
5085
5349
|
out.push(` var o${i2} = off(${S2(h2.cls)},${S2(h2.field)}); if (o${i2} < 0) return null; _p.push(o${i2});`);
|
|
5086
5350
|
}
|
|
5087
|
-
|
|
5351
|
+
if (w.base.length > 0) {
|
|
5352
|
+
out.push(` var _b = 0;`);
|
|
5353
|
+
for (let i2 = 0; i2 < w.base.length; i2++) {
|
|
5354
|
+
const h2 = w.base[i2];
|
|
5355
|
+
out.push(` var b${i2} = off(${S2(h2.cls)},${S2(h2.field)}); if (b${i2} < 0) return null; _b += b${i2};`);
|
|
5356
|
+
}
|
|
5357
|
+
out.push(` return new ${w.wrapper}(this.ref, _p, _b);`);
|
|
5358
|
+
} else {
|
|
5359
|
+
out.push(` return new ${w.wrapper}(this.ref, _p, 0);`);
|
|
5360
|
+
}
|
|
5088
5361
|
out.push(` }, enumerable: true, configurable: true,`);
|
|
5089
5362
|
out.push(` });`);
|
|
5090
5363
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@s2script/sdk",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"license": "MIT OR Apache-2.0",
|
|
5
5
|
"description": "s2script SDK — the builtin capability types (imported as @s2script/sdk/entity, …) plus the `s2s` CLI for building Source 2 / CS2 plugins.",
|
|
6
6
|
"type": "module",
|