@s2script/sdk 0.10.0 → 0.10.1

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.
Files changed (2) hide show
  1. package/dist/cli.js +234 -46
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -4640,9 +4640,15 @@ var ATOMIC = {
4640
4640
  uint32: { k: "u32", w: true },
4641
4641
  uint64: { k: "u64", w: false },
4642
4642
  int64: { k: "i64", w: false },
4643
- float64: { k: "f64", w: false }
4643
+ float64: { k: "f64", w: false },
4644
4644
  // no narrow 64-bit writers
4645
+ // `Color` is a 4-byte RGBA struct, not a scalar, but it is laid out exactly as a little-endian
4646
+ // uint32 (R in the low byte) and every engine consumer treats it that way. Mapping it here rather
4647
+ // than adding a value class keeps the surface small and matches how packed colours are already
4648
+ // passed elsewhere in the SDK (e.g. the fade user-message).
4649
+ Color: { k: "u32", w: true }
4645
4650
  };
4651
+ var ENUM_WIDTH = { 1: "u8", 2: "u16", 4: "u32", 8: "u64" };
4646
4652
  var VEC = { Vector: "vector", VectorWS: "vector", QAngle: "qangle" };
4647
4653
  var VEC_INFO = {
4648
4654
  vector: { cls: "Vector", count: 3 },
@@ -4655,7 +4661,13 @@ function idiomaticName(raw) {
4655
4661
  const core = m && KNOWN_TAGS.has(m[1]) ? m[2] : s;
4656
4662
  return core.charAt(0).toLowerCase() + core.slice(1);
4657
4663
  }
4658
- function classifyField(type) {
4664
+ function transparentValue(catalog, className) {
4665
+ const e = catalog[className];
4666
+ if (!e || e.parent || e.fields.length !== 1) return null;
4667
+ const f = e.fields[0];
4668
+ return f.name === "m_Value" && f.type.kind === "atomic" ? f : null;
4669
+ }
4670
+ function classifyField(type, embeddable = /* @__PURE__ */ new Set()) {
4659
4671
  if (type.kind === "handle") return { accessorKind: "handle", writable: false };
4660
4672
  if (type.kind === "atomic") {
4661
4673
  const vk = VEC[type.name ?? ""];
@@ -4669,8 +4681,16 @@ function classifyField(type) {
4669
4681
  if (cm) return { accessorKind: "str", writable: false, strLen: parseInt(cm[1], 10) };
4670
4682
  return { skip: `unmapped 'unknown' type '${type.name ?? ""}'` };
4671
4683
  }
4672
- if (type.kind === "enum") return { skip: "enum byte-width absent from catalog (deferred)" };
4673
- if (type.kind === "class") return { skip: `embedded class '${type.name ?? ""}' deferred` };
4684
+ if (type.kind === "enum") {
4685
+ const w = ENUM_WIDTH[type.size ?? 0];
4686
+ if (w) return { accessorKind: w, writable: WRITE[w] !== void 0 };
4687
+ return { skip: `enum '${type.name ?? ""}' byte width not stated by the schema` };
4688
+ }
4689
+ if (type.kind === "class") {
4690
+ const n3 = type.name ?? "";
4691
+ if (embeddable.has(n3)) return { embedded: n3 };
4692
+ return { skip: `embedded class '${n3}' deferred` };
4693
+ }
4674
4694
  if (type.kind === "ptr") return { skip: "raw pointer" };
4675
4695
  return { skip: `unmapped kind '${type.kind}'` };
4676
4696
  }
@@ -4685,7 +4705,11 @@ function flattenedFields(model, className) {
4685
4705
  }
4686
4706
  return chain.flatMap((c2) => c2.ownFields);
4687
4707
  }
4688
- function buildModel(catalog, requested) {
4708
+ function buildModel(catalog, requested, embeddable = []) {
4709
+ for (const n3 of embeddable) if (!catalog[n3]) throw new Error(`gen-schema: embedded class '${n3}' is not in the catalog`);
4710
+ const restrict = embeddable.length > 0 ? new Set(embeddable) : null;
4711
+ const canEmbed = (n3) => !!catalog[n3] && transparentValue(catalog, n3) === null && (restrict === null || restrict.has(n3));
4712
+ const embedSet = new Set(Object.keys(catalog).filter(canEmbed));
4689
4713
  const inClosure = /* @__PURE__ */ new Set();
4690
4714
  for (const start of requested) {
4691
4715
  if (!catalog[start]) throw new Error(`gen-schema: requested class '${start}' is not in the catalog`);
@@ -4704,34 +4728,98 @@ function buildModel(catalog, requested) {
4704
4728
  return d;
4705
4729
  };
4706
4730
  const ordered = [...inClosure].sort((a2, b) => depth(a2) - depth(b) || (a2 < b ? -1 : a2 > b ? 1 : 0));
4707
- const classes = ordered.map((className) => {
4708
- const parent = catalog[className].parent;
4709
- const ownFields = [];
4710
- const skipped = [];
4711
- for (const f of catalog[className].fields) {
4712
- const c2 = classifyField(f.type);
4731
+ const usedEmbeds = /* @__PURE__ */ new Set();
4732
+ const classify = (f) => {
4733
+ if (f.type.kind === "class") {
4734
+ const inner = transparentValue(catalog, f.type.name ?? "");
4735
+ if (inner) {
4736
+ const c2 = classifyField(inner.type);
4737
+ if (!("skip" in c2) && !("embedded" in c2)) return { ...c2, addOffset: inner.offset };
4738
+ }
4739
+ }
4740
+ return classifyField(f.type, embedSet);
4741
+ };
4742
+ const fieldsOf = (owner, out, emb, skipped) => {
4743
+ for (const f of catalog[owner].fields) {
4744
+ const c2 = classify(f);
4713
4745
  if ("skip" in c2) {
4714
- skipped.push({ className, rawName: f.name, reason: c2.skip });
4746
+ skipped.push({ className: owner, rawName: f.name, reason: c2.skip });
4715
4747
  continue;
4716
4748
  }
4717
- ownFields.push({ propName: idiomaticName(f.name), rawName: f.name, declaringClass: className, accessorKind: c2.accessorKind, writable: c2.writable, strLen: c2.strLen });
4749
+ if ("embedded" in c2) {
4750
+ usedEmbeds.add(c2.embedded);
4751
+ emb.push({ propName: idiomaticName(f.name), rawName: f.name, declaringClass: owner, embeddedClass: c2.embedded });
4752
+ continue;
4753
+ }
4754
+ out.push({ propName: idiomaticName(f.name), rawName: f.name, declaringClass: owner, accessorKind: c2.accessorKind, writable: c2.writable, strLen: c2.strLen, addOffset: c2.addOffset });
4718
4755
  }
4719
- return { className, parent: parent && inClosure.has(parent) ? parent : null, ownFields, skipped };
4756
+ };
4757
+ const classes = ordered.map((className) => {
4758
+ const parent = catalog[className].parent;
4759
+ const ownFields = [];
4760
+ const embeddedFields = [];
4761
+ const skipped = [];
4762
+ fieldsOf(className, ownFields, embeddedFields, skipped);
4763
+ return { className, parent: parent && inClosure.has(parent) ? parent : null, ownFields, embeddedFields, skipped };
4720
4764
  });
4765
+ const embedded = [];
4766
+ const done = /* @__PURE__ */ new Set();
4767
+ for (; ; ) {
4768
+ const next = [...usedEmbeds].filter((n3) => !done.has(n3)).sort();
4769
+ if (next.length === 0) break;
4770
+ for (const className of next) {
4771
+ done.add(className);
4772
+ const fields = [];
4773
+ const embeddedFields = [];
4774
+ const skipped = [];
4775
+ const chain = [];
4776
+ for (let cur = className; cur && catalog[cur]; cur = catalog[cur].parent) chain.unshift(cur);
4777
+ for (const owner of chain) fieldsOf(owner, fields, embeddedFields, skipped);
4778
+ embedded.push({ className, fields, embeddedFields, skipped });
4779
+ }
4780
+ }
4781
+ embedded.sort((a2, b) => a2.className < b.className ? -1 : a2.className > b.className ? 1 : 0);
4782
+ const ancestors = (cls) => {
4783
+ const out = /* @__PURE__ */ new Set();
4784
+ for (let cur = catalog[cls]?.parent ?? null; cur && catalog[cur]; cur = catalog[cur].parent) out.add(cur);
4785
+ return out;
4786
+ };
4721
4787
  const byProp = /* @__PURE__ */ new Map();
4722
- for (const c2 of classes) for (const f of c2.ownFields) {
4788
+ for (const c2 of classes) for (const f of [...c2.ownFields, ...c2.embeddedFields]) {
4723
4789
  (byProp.get(f.propName) ?? byProp.set(f.propName, []).get(f.propName)).push(f);
4724
4790
  }
4725
4791
  const collisions = [];
4726
4792
  for (const [prop, fields] of byProp) {
4727
- const distinct = new Set(fields.map((f) => `${f.declaringClass}.${f.rawName}`));
4728
- if (distinct.size >= 2) {
4729
- for (const f of fields) f.propName = f.rawName;
4730
- collisions.push(`${prop} \u2190 ${[...distinct].sort().join(", ")}`);
4793
+ const decls = /* @__PURE__ */ new Map();
4794
+ for (const f of fields) (decls.get(`${f.declaringClass}.${f.rawName}`) ?? decls.set(`${f.declaringClass}.${f.rawName}`, []).get(`${f.declaringClass}.${f.rawName}`)).push(f);
4795
+ if (decls.size < 2) continue;
4796
+ const keys = [...decls.keys()].sort();
4797
+ const clsOf = (k) => decls.get(k)[0].declaringClass;
4798
+ const conflicting = /* @__PURE__ */ new Set();
4799
+ for (let i2 = 0; i2 < keys.length; i2++) {
4800
+ for (let j = i2 + 1; j < keys.length; j++) {
4801
+ const a2 = clsOf(keys[i2]), b = clsOf(keys[j]);
4802
+ if (a2 === b || ancestors(a2).has(b) || ancestors(b).has(a2)) {
4803
+ conflicting.add(keys[i2]);
4804
+ conflicting.add(keys[j]);
4805
+ }
4806
+ }
4807
+ }
4808
+ if (conflicting.size === 0) continue;
4809
+ const depthOf = (k) => ancestors(clsOf(k)).size;
4810
+ const inConflict = [...conflicting].sort((x, y) => depthOf(x) - depthOf(y) || (x < y ? -1 : 1));
4811
+ const winnerCls = clsOf(inConflict[0]);
4812
+ const soleWinner = inConflict.filter((k) => clsOf(k) === winnerCls).length === 1;
4813
+ const renamed = [];
4814
+ for (const k of inConflict) {
4815
+ if (soleWinner && k === inConflict[0]) continue;
4816
+ for (const f of decls.get(k)) f.propName = f.rawName;
4817
+ renamed.push(k);
4731
4818
  }
4819
+ collisions.push(`${prop} <- ${renamed.sort().join(", ")}${soleWinner ? ` (kept by ${winnerCls})` : ""}`);
4732
4820
  }
4733
4821
  collisions.sort();
4734
- return { classes, collisions };
4822
+ return { classes, embedded, collisions };
4735
4823
  }
4736
4824
 
4737
4825
  // src/schemagen/emit-dts.ts
@@ -4744,8 +4832,20 @@ function emitDts(model) {
4744
4832
  const vi = VEC_INFO[f.accessorKind];
4745
4833
  if (vi) vecClasses.add(vi.cls);
4746
4834
  }
4835
+ for (const e of model.embedded) for (const f of e.fields) {
4836
+ const vi = VEC_INFO[f.accessorKind];
4837
+ if (vi) vecClasses.add(vi.cls);
4838
+ }
4747
4839
  if (vecClasses.size) out.push(`import type { ${[...vecClasses].sort().join(", ")} } from "@s2script/sdk/math";`);
4748
4840
  out.push("");
4841
+ for (const e of model.embedded) {
4842
+ out.push(`export interface ${e.className} {`);
4843
+ for (const f of e.fields) {
4844
+ out.push(` ${f.writable ? "" : "readonly "}${f.propName}: ${TSTYPE[f.accessorKind]};`);
4845
+ }
4846
+ for (const f of e.embeddedFields) out.push(` readonly ${f.propName}: ${f.embeddedClass};`);
4847
+ out.push("}", "");
4848
+ }
4749
4849
  for (const c2 of model.classes) {
4750
4850
  const ext = c2.parent && names.has(c2.parent) ? ` extends ${c2.parent}` : "";
4751
4851
  out.push(`export interface ${c2.className}${ext} {`);
@@ -4753,18 +4853,66 @@ function emitDts(model) {
4753
4853
  const ro = f.writable ? "" : "readonly ";
4754
4854
  out.push(` ${ro}${f.propName}: ${TSTYPE[f.accessorKind]};`);
4755
4855
  }
4856
+ for (const f of c2.embeddedFields) out.push(` readonly ${f.propName}: ${f.embeddedClass};`);
4756
4857
  out.push("}", "");
4757
4858
  }
4859
+ out.push("/** Every wrappable entity class, by schema name. */", "export interface SchemaClasses {");
4860
+ for (const c2 of model.classes) out.push(` ${c2.className}: ${c2.className};`);
4861
+ out.push("}", "");
4862
+ out.push(
4863
+ "/**",
4864
+ " * Apply this schema class's field accessors to an entity reference.",
4865
+ " *",
4866
+ " * `createEntity` returns a bare `EntityRef` with no schema fields on it \u2014 this is how you get",
4867
+ " * them. `Player.pawn` is already wrapped; this is for entities you create or resolve yourself.",
4868
+ " *",
4869
+ " * The wrapper is a view, not a copy: it holds the ref and reads through it on every access, so",
4870
+ " * it stays correct as the entity changes and costs nothing to keep. Field reads return null",
4871
+ " * once the ref goes stale, exactly as they do on a pawn.",
4872
+ " *",
4873
+ " * @example",
4874
+ ' * const prop = createEntity("prop_dynamic");',
4875
+ " * prop.spawn();",
4876
+ ' * const fields = wrapEntity("CBaseModelEntity", prop);',
4877
+ " * fields.glow.glowing = true;",
4878
+ " */",
4879
+ "export declare function wrapEntity<K extends keyof SchemaClasses>(className: K, ref: EntityRef): SchemaClasses[K];",
4880
+ ""
4881
+ );
4758
4882
  return out.join("\n");
4759
4883
  }
4760
4884
 
4761
4885
  // src/schemagen/emit-js.ts
4762
4886
  var HEADER2 = "// GENERATED by `s2script gen-schema` from schema-catalog.json \u2014 DO NOT EDIT.";
4763
4887
  var S = JSON.stringify;
4888
+ function resolveExpr(f) {
4889
+ const base = `off(${S(f.declaringClass)},${S(f.rawName)})`;
4890
+ return f.addOffset ? `${base} + ${f.addOffset}` : base;
4891
+ }
4892
+ function accessorEntry(f, resolve13, guard2) {
4893
+ const g = guard2 === "" ? "" : `${guard2} `;
4894
+ const gs = guard2 === "" ? "" : `${guard2.replace("return null;", "return;")} `;
4895
+ let entry;
4896
+ if (f.accessorKind === "str") {
4897
+ entry = `get: function () { ${g}return this.ref.readString(${resolve13}, ${f.strLen}); }`;
4898
+ } else if (f.accessorKind === "u64" || f.accessorKind === "i64") {
4899
+ entry = `get: function () { ${g}var v = this.ref.${READ[f.accessorKind]}(${resolve13}); return v === null ? null : v.toString(); }`;
4900
+ } else if (VEC_INFO[f.accessorKind]) {
4901
+ const vi = VEC_INFO[f.accessorKind];
4902
+ 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]); }`;
4903
+ } else {
4904
+ entry = `get: function () { ${g}return this.ref.${READ[f.accessorKind]}(${resolve13}); }`;
4905
+ }
4906
+ if (f.writable) {
4907
+ const wm = WRITE[f.accessorKind];
4908
+ entry += `, set: function (v) { ${gs}var o = ${resolve13}; if (this.ref.${wm}(o, v)) this.ref.notifyStateChanged(o); }`;
4909
+ }
4910
+ return entry;
4911
+ }
4764
4912
  function emitJs(model) {
4765
- const out = [HEADER2, "(function () {", " var off = __s2_schema_offset;", " var A = {};"];
4913
+ const out = [HEADER2, "(function () {", " var off = __s2_schema_offset;", " var A = {};", " var E = {};"];
4766
4914
  const vecClasses = /* @__PURE__ */ new Set();
4767
- for (const c2 of model.classes) for (const f of flattenedFields(model, c2.className)) {
4915
+ for (const c2 of model.classes) for (const f of c2.ownFields) {
4768
4916
  const vi = VEC_INFO[f.accessorKind];
4769
4917
  if (vi) vecClasses.add(vi.cls);
4770
4918
  }
@@ -4773,36 +4921,58 @@ function emitJs(model) {
4773
4921
  }
4774
4922
  for (const c2 of model.classes) {
4775
4923
  out.push(` A[${S(c2.className)}] = {`);
4776
- for (const f of flattenedFields(model, c2.className)) {
4777
- const resolve13 = `off(${S(f.declaringClass)},${S(f.rawName)})`;
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
- }
4924
+ for (const f of c2.ownFields) {
4925
+ const entry = accessorEntry(f, resolveExpr(f), "");
4793
4926
  out.push(` ${S(f.propName)}: { ${entry} },`);
4794
4927
  }
4928
+ for (const f of c2.embeddedFields) {
4929
+ out.push(` ${S(f.propName)}: { get: function () { return embed(${S(f.embeddedClass)}, this.ref, off(${S(f.declaringClass)},${S(f.rawName)})); } },`);
4930
+ }
4795
4931
  out.push(" };");
4796
4932
  }
4933
+ for (const e of model.embedded) {
4934
+ out.push(` E[${S(e.className)}] = {`);
4935
+ for (const f of e.fields) {
4936
+ const sub = `off(${S(f.declaringClass)},${S(f.rawName)})`;
4937
+ const resolve13 = f.addOffset ? `this.base + ${sub} + ${f.addOffset}` : `this.base + ${sub}`;
4938
+ const guard2 = `if (this.base < 0 || ${sub} < 0) return null;`;
4939
+ out.push(` ${S(f.propName)}: { ${accessorEntry(f, resolve13, guard2)} },`);
4940
+ }
4941
+ for (const f of e.embeddedFields) {
4942
+ const sub = `off(${S(f.declaringClass)},${S(f.rawName)})`;
4943
+ 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); } },`);
4944
+ }
4945
+ out.push(" };");
4946
+ }
4947
+ out.push(" var P = {");
4948
+ for (const c2 of model.classes) if (c2.parent) out.push(` ${S(c2.className)}: ${S(c2.parent)},`);
4949
+ out.push(" };");
4797
4950
  out.push(
4951
+ " // Walk the ancestor chain rather than storing every inherited accessor per class. Flattening",
4952
+ " // into each table duplicated the whole chain 3.77x on the CS2 class set and grew as",
4953
+ " // O(classes * depth), so every class added inflated every other one; this is O(total fields).",
4954
+ " // Ancestors are applied FIRST so a derived class's own definition wins if a name repeats.",
4798
4955
  " function applyAccessors(proto, className) {",
4799
- " var defs = A[className]; if (!defs) return;",
4800
- " for (var name in defs) {",
4801
- " Object.defineProperty(proto, name, { get: defs[name].get, set: defs[name].set, enumerable: true, configurable: true });",
4956
+ " var chain = [], c = className;",
4957
+ " for (; c; c = P[c]) chain.unshift(c);",
4958
+ " for (var i = 0; i < chain.length; i++) {",
4959
+ " var defs = A[chain[i]]; if (!defs) continue;",
4960
+ " for (var name in defs) {",
4961
+ " Object.defineProperty(proto, name, { get: defs[name].get, set: defs[name].set, enumerable: true, configurable: true });",
4962
+ " }",
4802
4963
  " }",
4803
4964
  " }",
4804
4965
  " function wrap(className, ref) { var o = { ref: ref }; applyAccessors(o, className); return o; }",
4966
+ " function embed(className, ref, base) {",
4967
+ " var o = { ref: ref, base: base }, defs = E[className];",
4968
+ " if (defs) for (var name in defs) {",
4969
+ " Object.defineProperty(o, name, { get: defs[name].get, set: defs[name].set, enumerable: true, configurable: true });",
4970
+ " }",
4971
+ " return o;",
4972
+ " }",
4805
4973
  " globalThis.__s2pkg_cs2_schema = { applyAccessors: applyAccessors, wrap: wrap };",
4974
+ " // Merge (not overwrite): this prelude runs FIRST, and pawn.js merges onto whatever it finds.",
4975
+ " globalThis.__s2pkg_cs2 = Object.assign({}, globalThis.__s2pkg_cs2, { wrapEntity: wrap });",
4806
4976
  "})();"
4807
4977
  );
4808
4978
  return out.join("\n");
@@ -4811,12 +4981,18 @@ function emitJs(model) {
4811
4981
  // src/schemagen/gen.ts
4812
4982
  var CATALOG_PATH = "games/cs2/gamedata/schema-catalog.json";
4813
4983
  var LIST_PATH = "games/cs2/codegen-classes.json";
4984
+ var EMBED_PATH = "games/cs2/codegen-embedded.json";
4814
4985
  var JS_OUT = "games/cs2/js/schema.generated.js";
4815
4986
  var DTS_OUT = "packages/cs2/schema.generated.d.ts";
4816
4987
  function runGenSchema(repoRoot2, opts) {
4817
4988
  const catalog = JSON.parse(readFileSync13(join13(repoRoot2, CATALOG_PATH), "utf8"));
4818
4989
  const list = JSON.parse(readFileSync13(join13(repoRoot2, LIST_PATH), "utf8"));
4819
- const model = buildModel(catalog, list);
4990
+ let embed = [];
4991
+ try {
4992
+ embed = JSON.parse(readFileSync13(join13(repoRoot2, EMBED_PATH), "utf8"));
4993
+ } catch {
4994
+ }
4995
+ const model = buildModel(catalog, list, embed);
4820
4996
  const dts = emitDts(model);
4821
4997
  const js = emitJs(model);
4822
4998
  const fields = model.classes.reduce((n3, c2) => n3 + c2.ownFields.length, 0);
@@ -5004,6 +5180,7 @@ function buildNavModel(config, catalog) {
5004
5180
  source: entry.source,
5005
5181
  target: entry.target,
5006
5182
  path: entry.path,
5183
+ base: entry.base ?? [],
5007
5184
  fields,
5008
5185
  skippedKinds
5009
5186
  };
@@ -5042,10 +5219,12 @@ function emitNavJs(model) {
5042
5219
  out.push(` var ${cls} = __s2require("@s2script/sdk/math").${cls};`);
5043
5220
  }
5044
5221
  for (const w of model.wrappers) {
5045
- out.push(` function ${w.wrapper}(root, path) { this.root = root; this.path = path; }`);
5222
+ out.push(` function ${w.wrapper}(root, path, base) { this.root = root; this.path = path; this.base = base; }`);
5046
5223
  out.push(` Object.defineProperties(${w.wrapper}.prototype, {`);
5047
5224
  for (const f of w.fields) {
5048
- const resolve13 = `off(${S2(f.declaringClass)},${S2(f.rawName)})`;
5225
+ const own = `off(${S2(f.declaringClass)},${S2(f.rawName)})`;
5226
+ const withDelta = f.addOffset ? `${own} + ${f.addOffset}` : own;
5227
+ const resolve13 = `${withDelta} + this.base`;
5049
5228
  let getter;
5050
5229
  const kind = f.accessorKind;
5051
5230
  if (kind === "u64") {
@@ -5084,7 +5263,16 @@ function emitNavJs(model) {
5084
5263
  const h2 = w.path[i2];
5085
5264
  out.push(` var o${i2} = off(${S2(h2.cls)},${S2(h2.field)}); if (o${i2} < 0) return null; _p.push(o${i2});`);
5086
5265
  }
5087
- out.push(` return new ${w.wrapper}(this.ref, _p);`);
5266
+ if (w.base.length > 0) {
5267
+ out.push(` var _b = 0;`);
5268
+ for (let i2 = 0; i2 < w.base.length; i2++) {
5269
+ const h2 = w.base[i2];
5270
+ out.push(` var b${i2} = off(${S2(h2.cls)},${S2(h2.field)}); if (b${i2} < 0) return null; _b += b${i2};`);
5271
+ }
5272
+ out.push(` return new ${w.wrapper}(this.ref, _p, _b);`);
5273
+ } else {
5274
+ out.push(` return new ${w.wrapper}(this.ref, _p, 0);`);
5275
+ }
5088
5276
  out.push(` }, enumerable: true, configurable: true,`);
5089
5277
  out.push(` });`);
5090
5278
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@s2script/sdk",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
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",