xiv-strat-board 0.1.2 → 0.2.0

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/README.md CHANGED
@@ -75,7 +75,7 @@ Decodes a share code to a strategy board.
75
75
 
76
76
  ```typescript
77
77
  interface StrategyBoard {
78
- name?: string; // Max 7 characters
78
+ name?: string; // Max 20 characters
79
79
  boardBackground?: BackgroundType;
80
80
  objects: StrategyObject[];
81
81
  }
@@ -89,7 +89,7 @@ interface StrategyObject {
89
89
  typeId?: number; // Optional numeric ID (overrides type)
90
90
  x: number; // 0-512, center at 256
91
91
  y: number; // 0-384, center at 192
92
- size?: number; // 1-255, default 100
92
+ size?: number; // 10-200, default 100 (text always 100)
93
93
  background?: BackgroundType;
94
94
 
95
95
  // Color (only for line_aoe, line, text)
@@ -97,8 +97,8 @@ interface StrategyObject {
97
97
  transparency?: number; // 0-255
98
98
 
99
99
  // AoE properties
100
- arcAngle?: number; // 10-360 for fan_aoe and donut
101
- donutRadius?: number; // 0-255 for donut inner radius
100
+ arcAngle?: number; // 0-360, intervals of 10
101
+ donutRadius?: number; // 0-255 (0 = full circle/no hole)
102
102
 
103
103
  // Line AoE properties
104
104
  width?: number; // Width for line_aoe
@@ -117,7 +117,7 @@ interface StrategyObject {
117
117
  verticalCount?: number; // Vertical count for linear_knockback
118
118
 
119
119
  // Text objects
120
- text?: string; // Text content for text objects
120
+ text?: string; // Text content (max 30 chars)
121
121
 
122
122
  // Flip states
123
123
  horizontalFlip?: boolean;
package/dist/index.cjs CHANGED
@@ -371,24 +371,6 @@ var BOARD_BACKGROUND_IDS = {
371
371
  grey_circle: 6,
372
372
  grey_square: 7
373
373
  };
374
- var OBJECT_BACKGROUND_TYPES = {
375
- 0: "none",
376
- 1: "checkered",
377
- 2: "checkered_circle",
378
- 3: "checkered_square",
379
- 4: "grey",
380
- 5: "grey_circle",
381
- 6: "grey_square"
382
- };
383
- var OBJECT_BACKGROUND_IDS = {
384
- none: 0,
385
- checkered: 1,
386
- checkered_circle: 2,
387
- checkered_square: 3,
388
- grey: 4,
389
- grey_circle: 5,
390
- grey_square: 6
391
- };
392
374
 
393
375
  // src/validation.ts
394
376
  var BOUNDS = {
@@ -401,9 +383,9 @@ var BOUNDS = {
401
383
  /** Maximum Y coordinate */
402
384
  maxY: 484,
403
385
  /** Minimum size percentage */
404
- minSize: 1,
386
+ minSize: 10,
405
387
  /** Maximum size percentage */
406
- maxSize: 255,
388
+ maxSize: 200,
407
389
  /** Minimum arc angle */
408
390
  minArc: 0,
409
391
  /** Maximum arc angle */
@@ -414,8 +396,10 @@ var BOUNDS = {
414
396
  maxDonut: 255,
415
397
  /** Maximum number of objects per board */
416
398
  maxObjects: 50,
399
+ /** Maximum number of text objects per board */
400
+ maxTextObjects: 8,
417
401
  /** Maximum name length (bytes) */
418
- maxNameLength: 7
402
+ maxNameLength: 20
419
403
  };
420
404
  var VALID_CIPHER_CHARS = new Set(
421
405
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-_".split("")
@@ -490,19 +474,6 @@ function parseHexColor(color) {
490
474
  b: parseInt(hex.slice(4, 6), 16)
491
475
  };
492
476
  }
493
- function validateBackground(value) {
494
- if (typeof value === "number") {
495
- if (value >= 0 && value <= 6) {
496
- return value;
497
- }
498
- return 0;
499
- }
500
- if (typeof value === "string") {
501
- const id = OBJECT_BACKGROUND_IDS[value.toLowerCase()];
502
- return id ?? 0;
503
- }
504
- return 0;
505
- }
506
477
  function validateBoardBackground(value) {
507
478
  if (typeof value === "string") {
508
479
  const id = BOARD_BACKGROUND_IDS[value.toLowerCase()];
@@ -553,16 +524,26 @@ function sanitizeObject(obj, index) {
553
524
  typeId,
554
525
  x: sanitizeCoordinate(obj.x, true),
555
526
  y: sanitizeCoordinate(obj.y, false),
556
- size: sanitizeSize(obj.size),
557
- background: validateBackground(obj.background),
527
+ size: typeId === 100 ? 100 : sanitizeSize(obj.size),
528
+ angle: Math.round(obj.angle ?? 0),
558
529
  colorR: sanitizeColorComponent(colorR),
559
530
  colorG: sanitizeColorComponent(colorG),
560
531
  colorB: sanitizeColorComponent(colorB),
561
532
  transparency: sanitizeColorComponent(obj.transparency ?? 0),
562
- arcAngle: clamp(Math.round(obj.arcAngle ?? 0), BOUNDS.minArc, BOUNDS.maxArc),
533
+ arcAngle: clamp(Math.round((obj.arcAngle ?? 0) / 10) * 10, BOUNDS.minArc, BOUNDS.maxArc),
563
534
  donutRadius: clamp(Math.round(obj.donutRadius ?? 0), BOUNDS.minDonut, BOUNDS.maxDonut),
535
+ width: Math.round(obj.width ?? 0),
536
+ height: Math.round(obj.height ?? 0),
537
+ endX: sanitizeCoordinate(obj.endX ?? 0, true),
538
+ endY: sanitizeCoordinate(obj.endY ?? 0, false),
539
+ displayCount: Math.round(obj.displayCount ?? 0),
540
+ horizontalCount: Math.round(obj.horizontalCount ?? 0),
541
+ verticalCount: Math.round(obj.verticalCount ?? 0),
542
+ text: typeof obj.text === "string" ? obj.text.slice(0, 30) : "",
564
543
  hidden: Boolean(obj.hidden),
565
- locked: Boolean(obj.locked)
544
+ locked: Boolean(obj.locked),
545
+ horizontalFlip: Boolean(obj.horizontalFlip),
546
+ verticalFlip: Boolean(obj.verticalFlip)
566
547
  };
567
548
  }
568
549
  function sanitizeBoard(board) {
@@ -575,6 +556,10 @@ function sanitizeBoard(board) {
575
556
  if (board.objects.length > BOUNDS.maxObjects) {
576
557
  throw new Error(`Board has too many objects (max ${BOUNDS.maxObjects})`);
577
558
  }
559
+ const textCount = board.objects.filter((obj) => obj.type === "text" || obj.typeId === 100).length;
560
+ if (textCount > BOUNDS.maxTextObjects) {
561
+ throw new Error(`Board has too many text objects (max ${BOUNDS.maxTextObjects})`);
562
+ }
578
563
  return {
579
564
  name: sanitizeName(board.name),
580
565
  boardBackground: validateBoardBackground(board.boardBackground),
@@ -798,15 +783,9 @@ function parseBinary(data) {
798
783
  x: positions[i]?.x ?? 0,
799
784
  y: positions[i]?.y ?? 0
800
785
  };
801
- obj.size = sizes[i] && sizes[i] > 0 ? sizes[i] : 100;
802
- const rotatableTypes = /* @__PURE__ */ new Set([10, 11, 12, 15, 16, 17, 18, 19, 110]);
803
- if (rotatableTypes.has(iconId)) {
804
- if (backgrounds[i] !== void 0 && backgrounds[i] !== 0) {
805
- obj.angle = backgrounds[i];
806
- }
807
- } else if (backgrounds[i] !== void 0 && backgrounds[i] > 0) {
808
- const bgName = OBJECT_BACKGROUND_TYPES[backgrounds[i]];
809
- obj.background = bgName ?? backgrounds[i];
786
+ obj.size = iconId === 100 ? 100 : sizes[i] && sizes[i] > 0 ? sizes[i] : 100;
787
+ if (iconId !== 100 && backgrounds[i] !== void 0 && backgrounds[i] !== 0) {
788
+ obj.angle = backgrounds[i];
810
789
  }
811
790
  const colorableTypes = /* @__PURE__ */ new Set([11, 12, 100]);
812
791
  if (colors[i]) {
@@ -852,10 +831,10 @@ function parseBinary(data) {
852
831
  obj.verticalCount = tag11[i];
853
832
  }
854
833
  } else if (iconId === 17) {
855
- if (tag10[i] && tag10[i] > 0) {
834
+ if (tag10[i] !== void 0 && tag10[i] > 0) {
856
835
  obj.arcAngle = tag10[i];
857
836
  }
858
- if (tag11[i] && tag11[i] > 0) {
837
+ if (tag11[i] !== void 0) {
859
838
  obj.donutRadius = tag11[i];
860
839
  }
861
840
  } else {
@@ -894,9 +873,20 @@ function parseBinary(data) {
894
873
  function buildBinary(data) {
895
874
  const { name, boardBackground, objects } = data;
896
875
  const n = objects.length;
876
+ const nameBytes = new TextEncoder().encode(name.slice(0, 20));
877
+ const namePaddedLen = Math.max(8, nameBytes.length + 1 + 3 & -4);
878
+ const headerSize = 28 + namePaddedLen;
897
879
  const tag4Size = n <= 1 ? 8 : 6 + n * 2;
898
880
  const tag7Size = 6 + n + (n % 2 === 1 ? 1 : 0);
899
- const bufferSize = n === 0 ? 36 + 8 : 36 + n * 4 + tag4Size + (6 + n * 4) + (6 + n * 2) + tag7Size + (6 + n * 4) + (6 + n * 2) + (6 + n * 2) + (6 + n * 2) + 8;
881
+ let textContentSize = 0;
882
+ for (const obj of objects) {
883
+ if (obj.typeId === 100 && obj.text) {
884
+ const textLen = new TextEncoder().encode(obj.text).length;
885
+ const paddedLen = Math.max(8, textLen + 1 + 3 & -4);
886
+ textContentSize += 4 + paddedLen;
887
+ }
888
+ }
889
+ const bufferSize = n === 0 ? headerSize + 8 : headerSize + n * 4 + textContentSize + tag4Size + (6 + n * 4) + (6 + n * 2) + tag7Size + (6 + n * 4) + (6 + n * 2) + (6 + n * 2) + (6 + n * 2) + 8;
900
890
  const buffer = new ArrayBuffer(bufferSize);
901
891
  const view = new DataView(buffer);
902
892
  let pos = 0;
@@ -922,33 +912,45 @@ function buildBinary(data) {
922
912
  writeUint32(0);
923
913
  writeUint16(0);
924
914
  writeUint16(1);
925
- writeUint16(8);
926
- const nameBytes = new TextEncoder().encode(name.slice(0, 7));
927
- for (let i = 0; i < 8; i++) {
915
+ writeUint16(namePaddedLen);
916
+ for (let i = 0; i < namePaddedLen; i++) {
928
917
  writeUint8(nameBytes[i] ?? 0);
929
918
  }
930
919
  for (const obj of objects) {
931
920
  writeUint16(2);
932
921
  writeUint16(obj.typeId);
922
+ if (obj.typeId === 100 && obj.text) {
923
+ const textBytes = new TextEncoder().encode(obj.text);
924
+ const paddedLen = Math.max(8, textBytes.length + 1 + 3 & -4);
925
+ writeUint16(3);
926
+ writeUint16(paddedLen);
927
+ for (let i = 0; i < paddedLen; i++) {
928
+ writeUint8(textBytes[i] ?? 0);
929
+ }
930
+ }
933
931
  }
934
932
  if (n === 0) ; else if (n === 1) {
935
933
  writeUint16(4);
936
934
  writeUint16(1);
937
935
  const obj = objects[0];
938
936
  let flagsVal = 1;
939
- if (obj.hidden) {
940
- flagsVal = 0;
941
- } else if (obj.locked) {
942
- flagsVal = 9;
943
- }
937
+ if (obj.hidden) flagsVal &= -2;
938
+ if (obj.horizontalFlip) flagsVal |= 2;
939
+ if (obj.verticalFlip) flagsVal |= 4;
940
+ if (obj.locked) flagsVal |= 8;
944
941
  writeUint16(1);
945
942
  writeUint16(flagsVal);
946
943
  } else {
947
944
  writeUint16(4);
948
945
  writeUint16(1);
949
946
  writeUint16(n);
950
- for (let i = 0; i < n; i++) {
951
- writeUint16(1);
947
+ for (const obj of objects) {
948
+ let flagsVal = 1;
949
+ if (obj.hidden) flagsVal &= -2;
950
+ if (obj.horizontalFlip) flagsVal |= 2;
951
+ if (obj.verticalFlip) flagsVal |= 4;
952
+ if (obj.locked) flagsVal |= 8;
953
+ writeUint16(flagsVal);
952
954
  }
953
955
  }
954
956
  if (n > 0) {
@@ -963,7 +965,7 @@ function buildBinary(data) {
963
965
  writeUint16(1);
964
966
  writeUint16(n);
965
967
  for (const obj of objects) {
966
- writeUint16(obj.background);
968
+ writeInt16(obj.angle);
967
969
  }
968
970
  writeUint16(7);
969
971
  writeUint16(0);
@@ -987,19 +989,41 @@ function buildBinary(data) {
987
989
  writeUint16(1);
988
990
  writeUint16(n);
989
991
  for (const obj of objects) {
990
- writeUint16(obj.arcAngle);
992
+ if (obj.typeId === 11) {
993
+ writeUint16(obj.width);
994
+ } else if (obj.typeId === 12) {
995
+ writeUint16(Math.round(obj.endX * 10));
996
+ } else if (obj.typeId === 110) {
997
+ writeUint16(obj.horizontalCount);
998
+ } else {
999
+ writeUint16(obj.arcAngle);
1000
+ }
991
1001
  }
992
1002
  writeUint16(11);
993
1003
  writeUint16(1);
994
1004
  writeUint16(n);
995
1005
  for (const obj of objects) {
996
- writeUint16(obj.donutRadius);
1006
+ if (obj.typeId === 11) {
1007
+ writeUint16(obj.height);
1008
+ } else if (obj.typeId === 12) {
1009
+ writeUint16(Math.round(obj.endY * 10));
1010
+ } else if (obj.typeId === 15) {
1011
+ writeUint16(obj.displayCount);
1012
+ } else if (obj.typeId === 110) {
1013
+ writeUint16(obj.verticalCount);
1014
+ } else {
1015
+ writeUint16(obj.donutRadius);
1016
+ }
997
1017
  }
998
1018
  writeUint16(12);
999
1019
  writeUint16(1);
1000
1020
  writeUint16(n);
1001
- for (let i = 0; i < n; i++) {
1002
- writeUint16(0);
1021
+ for (const obj of objects) {
1022
+ if (obj.typeId === 12) {
1023
+ writeUint16(obj.height);
1024
+ } else {
1025
+ writeUint16(0);
1026
+ }
1003
1027
  }
1004
1028
  }
1005
1029
  writeUint16(3);