xiv-strat-board 0.1.3 → 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,15 +371,6 @@ var BOARD_BACKGROUND_IDS = {
371
371
  grey_circle: 6,
372
372
  grey_square: 7
373
373
  };
374
- var OBJECT_BACKGROUND_IDS = {
375
- none: 0,
376
- checkered: 1,
377
- checkered_circle: 2,
378
- checkered_square: 3,
379
- grey: 4,
380
- grey_circle: 5,
381
- grey_square: 6
382
- };
383
374
 
384
375
  // src/validation.ts
385
376
  var BOUNDS = {
@@ -392,9 +383,9 @@ var BOUNDS = {
392
383
  /** Maximum Y coordinate */
393
384
  maxY: 484,
394
385
  /** Minimum size percentage */
395
- minSize: 1,
386
+ minSize: 10,
396
387
  /** Maximum size percentage */
397
- maxSize: 255,
388
+ maxSize: 200,
398
389
  /** Minimum arc angle */
399
390
  minArc: 0,
400
391
  /** Maximum arc angle */
@@ -405,8 +396,10 @@ var BOUNDS = {
405
396
  maxDonut: 255,
406
397
  /** Maximum number of objects per board */
407
398
  maxObjects: 50,
399
+ /** Maximum number of text objects per board */
400
+ maxTextObjects: 8,
408
401
  /** Maximum name length (bytes) */
409
- maxNameLength: 7
402
+ maxNameLength: 20
410
403
  };
411
404
  var VALID_CIPHER_CHARS = new Set(
412
405
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-_".split("")
@@ -481,19 +474,6 @@ function parseHexColor(color) {
481
474
  b: parseInt(hex.slice(4, 6), 16)
482
475
  };
483
476
  }
484
- function validateBackground(value) {
485
- if (typeof value === "number") {
486
- if (value >= 0 && value <= 6) {
487
- return value;
488
- }
489
- return 0;
490
- }
491
- if (typeof value === "string") {
492
- const id = OBJECT_BACKGROUND_IDS[value.toLowerCase()];
493
- return id ?? 0;
494
- }
495
- return 0;
496
- }
497
477
  function validateBoardBackground(value) {
498
478
  if (typeof value === "string") {
499
479
  const id = BOARD_BACKGROUND_IDS[value.toLowerCase()];
@@ -544,16 +524,26 @@ function sanitizeObject(obj, index) {
544
524
  typeId,
545
525
  x: sanitizeCoordinate(obj.x, true),
546
526
  y: sanitizeCoordinate(obj.y, false),
547
- size: sanitizeSize(obj.size),
548
- background: validateBackground(obj.background),
527
+ size: typeId === 100 ? 100 : sanitizeSize(obj.size),
528
+ angle: Math.round(obj.angle ?? 0),
549
529
  colorR: sanitizeColorComponent(colorR),
550
530
  colorG: sanitizeColorComponent(colorG),
551
531
  colorB: sanitizeColorComponent(colorB),
552
532
  transparency: sanitizeColorComponent(obj.transparency ?? 0),
553
- 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),
554
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) : "",
555
543
  hidden: Boolean(obj.hidden),
556
- locked: Boolean(obj.locked)
544
+ locked: Boolean(obj.locked),
545
+ horizontalFlip: Boolean(obj.horizontalFlip),
546
+ verticalFlip: Boolean(obj.verticalFlip)
557
547
  };
558
548
  }
559
549
  function sanitizeBoard(board) {
@@ -566,6 +556,10 @@ function sanitizeBoard(board) {
566
556
  if (board.objects.length > BOUNDS.maxObjects) {
567
557
  throw new Error(`Board has too many objects (max ${BOUNDS.maxObjects})`);
568
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
+ }
569
563
  return {
570
564
  name: sanitizeName(board.name),
571
565
  boardBackground: validateBoardBackground(board.boardBackground),
@@ -789,7 +783,7 @@ function parseBinary(data) {
789
783
  x: positions[i]?.x ?? 0,
790
784
  y: positions[i]?.y ?? 0
791
785
  };
792
- obj.size = sizes[i] && sizes[i] > 0 ? sizes[i] : 100;
786
+ obj.size = iconId === 100 ? 100 : sizes[i] && sizes[i] > 0 ? sizes[i] : 100;
793
787
  if (iconId !== 100 && backgrounds[i] !== void 0 && backgrounds[i] !== 0) {
794
788
  obj.angle = backgrounds[i];
795
789
  }
@@ -837,10 +831,10 @@ function parseBinary(data) {
837
831
  obj.verticalCount = tag11[i];
838
832
  }
839
833
  } else if (iconId === 17) {
840
- if (tag10[i] && tag10[i] > 0) {
834
+ if (tag10[i] !== void 0 && tag10[i] > 0) {
841
835
  obj.arcAngle = tag10[i];
842
836
  }
843
- if (tag11[i] && tag11[i] > 0) {
837
+ if (tag11[i] !== void 0) {
844
838
  obj.donutRadius = tag11[i];
845
839
  }
846
840
  } else {
@@ -879,9 +873,20 @@ function parseBinary(data) {
879
873
  function buildBinary(data) {
880
874
  const { name, boardBackground, objects } = data;
881
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;
882
879
  const tag4Size = n <= 1 ? 8 : 6 + n * 2;
883
880
  const tag7Size = 6 + n + (n % 2 === 1 ? 1 : 0);
884
- 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;
885
890
  const buffer = new ArrayBuffer(bufferSize);
886
891
  const view = new DataView(buffer);
887
892
  let pos = 0;
@@ -907,33 +912,45 @@ function buildBinary(data) {
907
912
  writeUint32(0);
908
913
  writeUint16(0);
909
914
  writeUint16(1);
910
- writeUint16(8);
911
- const nameBytes = new TextEncoder().encode(name.slice(0, 7));
912
- for (let i = 0; i < 8; i++) {
915
+ writeUint16(namePaddedLen);
916
+ for (let i = 0; i < namePaddedLen; i++) {
913
917
  writeUint8(nameBytes[i] ?? 0);
914
918
  }
915
919
  for (const obj of objects) {
916
920
  writeUint16(2);
917
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
+ }
918
931
  }
919
932
  if (n === 0) ; else if (n === 1) {
920
933
  writeUint16(4);
921
934
  writeUint16(1);
922
935
  const obj = objects[0];
923
936
  let flagsVal = 1;
924
- if (obj.hidden) {
925
- flagsVal = 0;
926
- } else if (obj.locked) {
927
- flagsVal = 9;
928
- }
937
+ if (obj.hidden) flagsVal &= -2;
938
+ if (obj.horizontalFlip) flagsVal |= 2;
939
+ if (obj.verticalFlip) flagsVal |= 4;
940
+ if (obj.locked) flagsVal |= 8;
929
941
  writeUint16(1);
930
942
  writeUint16(flagsVal);
931
943
  } else {
932
944
  writeUint16(4);
933
945
  writeUint16(1);
934
946
  writeUint16(n);
935
- for (let i = 0; i < n; i++) {
936
- 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);
937
954
  }
938
955
  }
939
956
  if (n > 0) {
@@ -948,7 +965,7 @@ function buildBinary(data) {
948
965
  writeUint16(1);
949
966
  writeUint16(n);
950
967
  for (const obj of objects) {
951
- writeUint16(obj.background);
968
+ writeInt16(obj.angle);
952
969
  }
953
970
  writeUint16(7);
954
971
  writeUint16(0);
@@ -972,19 +989,41 @@ function buildBinary(data) {
972
989
  writeUint16(1);
973
990
  writeUint16(n);
974
991
  for (const obj of objects) {
975
- 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
+ }
976
1001
  }
977
1002
  writeUint16(11);
978
1003
  writeUint16(1);
979
1004
  writeUint16(n);
980
1005
  for (const obj of objects) {
981
- 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
+ }
982
1017
  }
983
1018
  writeUint16(12);
984
1019
  writeUint16(1);
985
1020
  writeUint16(n);
986
- for (let i = 0; i < n; i++) {
987
- writeUint16(0);
1021
+ for (const obj of objects) {
1022
+ if (obj.typeId === 12) {
1023
+ writeUint16(obj.height);
1024
+ } else {
1025
+ writeUint16(0);
1026
+ }
988
1027
  }
989
1028
  }
990
1029
  writeUint16(3);