hoi4-agent-tools 3.3.0 → 3.4.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 (89) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/hoi4_agent_tools/core/condition-evaluator.js +20 -8
  3. package/dist/hoi4_agent_tools/core/helper-expansion.js +3 -0
  4. package/dist/hoi4_agent_tools/core/index.js +1 -0
  5. package/dist/hoi4_agent_tools/core/job-worker-host.js +19 -1
  6. package/dist/hoi4_agent_tools/core/operation-result.js +4 -1
  7. package/dist/hoi4_agent_tools/event/comparison-selection.js +78 -0
  8. package/dist/hoi4_agent_tools/event/helper-expansion.js +48 -0
  9. package/dist/hoi4_agent_tools/event/operations.js +32 -10
  10. package/dist/hoi4_agent_tools/event/service.js +21 -2
  11. package/dist/hoi4_agent_tools/focus/compact.js +241 -27
  12. package/dist/hoi4_agent_tools/focus/layout.js +119 -8
  13. package/dist/hoi4_agent_tools/focus/render.js +62 -0
  14. package/dist/hoi4_agent_tools/focus/scenario.js +99 -0
  15. package/dist/hoi4_agent_tools/focus/tool-operations.js +30 -4
  16. package/dist/hoi4_agent_tools/gui/assets.js +54 -4
  17. package/dist/hoi4_agent_tools/gui/layout.js +935 -104
  18. package/dist/hoi4_agent_tools/gui/renderer.js +47 -17
  19. package/dist/hoi4_agent_tools/gui/shader.js +515 -0
  20. package/dist/hoi4_agent_tools/gui/source-graph.js +166 -6
  21. package/dist/hoi4_agent_tools/gui/studio.js +245 -19
  22. package/dist/hoi4_agent_tools/gui/tool-operations.js +9 -0
  23. package/dist/hoi4_agent_tools/gui/validators.js +41 -12
  24. package/dist/hoi4_agent_tools/map/operations.js +70 -1
  25. package/dist/hoi4_agent_tools/map/render.js +70 -1
  26. package/dist/hoi4_agent_tools/map/service.js +30 -6
  27. package/dist/hoi4_agent_tools/map/tool-operations.js +67 -1
  28. package/dist/hoi4_agent_tools/mcp/tools/event.js +10 -1
  29. package/dist/hoi4_agent_tools/mcp/tools/focus.js +14 -4
  30. package/dist/hoi4_agent_tools/mcp/tools/gui.js +5 -1
  31. package/dist/hoi4_agent_tools/mcp/tools/map.js +12 -2
  32. package/dist/hoi4_agent_tools/mcp/tools/probability.js +5 -0
  33. package/dist/hoi4_agent_tools/mechanic/interpreter.js +91 -30
  34. package/dist/hoi4_agent_tools/mechanic/source.js +19 -4
  35. package/dist/hoi4_agent_tools/probability/evaluation.js +3 -0
  36. package/dist/hoi4_agent_tools/probability/operations.js +8 -0
  37. package/dist/hoi4_agent_tools/probability/service.js +33 -12
  38. package/dist/hoi4_agent_tools/probability/source-analysis.js +1 -0
  39. package/dist/hoi4_agent_tools/schemas/event.js +8 -0
  40. package/dist/hoi4_agent_tools/schemas/focus-requests.js +40 -1
  41. package/dist/hoi4_agent_tools/schemas/gui-requests.js +8 -0
  42. package/dist/hoi4_agent_tools/schemas/map-requests.js +22 -1
  43. package/dist/hoi4_agent_tools/schemas/technology.js +7 -0
  44. package/dist/hoi4_agent_tools/technology/graph.js +25 -10
  45. package/dist/hoi4_agent_tools/technology/operations.js +29 -1
  46. package/dist/hoi4_agent_tools/technology/render.js +287 -31
  47. package/dist/hoi4_agent_tools/technology/service.js +56 -8
  48. package/dist/hoi4_agent_tools/technology/source-analysis.js +166 -3
  49. package/dist/hoi4_agent_tools/version.js +1 -1
  50. package/docs/events.md +4 -2
  51. package/docs/focus.md +10 -0
  52. package/docs/gui.md +50 -3
  53. package/docs/jobs.md +1 -0
  54. package/docs/map.md +8 -0
  55. package/docs/mechanics.md +2 -1
  56. package/docs/probability.md +3 -1
  57. package/docs/technology.md +11 -2
  58. package/package.json +1 -1
  59. package/schemas/configuration.schema.json +1 -1
  60. package/schemas/continuous-focus-palette.schema.json +1 -1
  61. package/schemas/custom-weighted-pool.schema.json +1 -1
  62. package/schemas/decision-inspect-input.schema.json +1 -1
  63. package/schemas/event-feature-manifest.schema.json +1 -1
  64. package/schemas/focus-plan.schema.json +1 -1
  65. package/schemas/focus-planning-sidecar.schema.json +1 -1
  66. package/schemas/gui-animation-source.schema.json +1 -1
  67. package/schemas/gui-helper.schema.json +1 -1
  68. package/schemas/gui-scenario.schema.json +1 -1
  69. package/schemas/helper-expansion-request.schema.json +1 -1
  70. package/schemas/helper-expansion-summary.schema.json +1 -1
  71. package/schemas/impact-inspect-input.schema.json +1 -1
  72. package/schemas/map-operation.schema.json +1 -1
  73. package/schemas/mechanic-test-input.schema.json +1 -1
  74. package/schemas/operation-result.schema.json +1 -1
  75. package/schemas/package-check-input.schema.json +1 -1
  76. package/schemas/package-manifest.schema.json +1 -1
  77. package/schemas/probability-analysis-result.schema.json +1 -1
  78. package/schemas/probability-compare-input.schema.json +1 -1
  79. package/schemas/probability-evaluate-input.schema.json +1 -1
  80. package/schemas/probability-inspect-input.schema.json +1 -1
  81. package/schemas/probability-render-input.schema.json +1 -1
  82. package/schemas/probability-scenario-set.schema.json +1 -1
  83. package/schemas/probability-sequence-input.schema.json +1 -1
  84. package/schemas/probability-simulate-input.schema.json +1 -1
  85. package/schemas/probability-sweep-input.schema.json +1 -1
  86. package/schemas/scenario-suite.schema.json +1 -1
  87. package/schemas/scenario-test-input.schema.json +1 -1
  88. package/schemas/shared-scenario.schema.json +1 -1
  89. package/server.json +2 -2
@@ -29,7 +29,7 @@ const explicitGuiElementTypes = new Set([
29
29
  'scrollableTextBoxType',
30
30
  'browserType',
31
31
  'mapIconType',
32
- ]);
32
+ ].map((type) => type.toLowerCase()));
33
33
  const spriteTypes = new Set([
34
34
  'spritetype',
35
35
  'textspritetype',
@@ -65,11 +65,18 @@ const modelledElementAttributes = new Set([
65
65
  'spacing',
66
66
  'slotsize',
67
67
  'max_slots_horizontal',
68
+ 'max_slots_vertical',
68
69
  'priority',
69
70
  'minValue',
70
71
  'maxValue',
71
72
  'startValue',
72
73
  'centerposition',
74
+ 'vertical_alignment',
75
+ 'hide',
76
+ 'hidden',
77
+ 'visible',
78
+ 'enabled',
79
+ 'context_aware_text',
73
80
  ]);
74
81
  const ignoredElementAttributes = new Set([
75
82
  'margin',
@@ -105,7 +112,16 @@ const ignoredElementAttributes = new Set([
105
112
  'web_link',
106
113
  'cost',
107
114
  ]);
108
- export function guiElementAttributeFidelity(key) {
115
+ export function guiElementAttributeFidelity(key, elementType) {
116
+ if (elementType?.toLowerCase() === 'extendedscrollbartype' &&
117
+ ['horizontal', 'slider', 'track', 'decreaseButton', 'increaseButton'].some((candidate) => candidate.toLowerCase() === key.toLowerCase()))
118
+ return 'modelled';
119
+ if (/^(?:containerwindowtype|windowtype)$/iu.test(elementType ?? '') &&
120
+ ['verticalScrollbar', 'horizontalScrollbar', 'autohide_scrollbars', 'margin'].some((candidate) => candidate.toLowerCase() === key.toLowerCase()))
121
+ return 'structural';
122
+ if (elementType?.toLowerCase() === 'scrollbartype' &&
123
+ ['horizontal', 'slider', 'track', 'leftbutton', 'rightbutton', 'borderSize'].some((candidate) => candidate.toLowerCase() === key.toLowerCase()))
124
+ return 'modelled';
109
125
  const modelled = [...modelledElementAttributes].find((candidate) => candidate.toLowerCase() === key.toLowerCase());
110
126
  if (modelled !== undefined)
111
127
  return 'modelled';
@@ -282,9 +298,9 @@ function raw(document, node) {
282
298
  function isGuiElement(assignment) {
283
299
  if (assignment.value.type !== 'block')
284
300
  return false;
285
- if (explicitGuiElementTypes.has(assignment.key.value))
301
+ if (explicitGuiElementTypes.has(assignment.key.value.toLowerCase()))
286
302
  return true;
287
- return (assignment.key.value.endsWith('Type') && firstScalar(assignment.value, 'name') !== undefined);
303
+ return (/type$/iu.test(assignment.key.value) && firstScalar(assignment.value, 'name') !== undefined);
288
304
  }
289
305
  function fileKind(file) {
290
306
  const lower = file.relativePath.toLowerCase();
@@ -296,6 +312,8 @@ function fileKind(file) {
296
312
  return 'gfx_file';
297
313
  if (lower.includes('common/scripted_guis/') && lower.endsWith('.txt'))
298
314
  return 'scripted_gui_file';
315
+ if (lower.includes('common/focus_inlay_windows/') && lower.endsWith('.txt'))
316
+ return 'scripted_gui_file';
299
317
  if (lower.includes('common/scripted_localisation/') && lower.endsWith('.txt'))
300
318
  return 'scripted_localisation_file';
301
319
  if (lower.endsWith('.txt')) {
@@ -367,7 +385,7 @@ function indexGuiElements(document, file, fileNodeId, nodes, edges, elements) {
367
385
  unsupportedAttributes: assignments(assignment.value)
368
386
  .filter((child) => !isGuiElement(child))
369
387
  .map(({ key }) => key.value)
370
- .filter((key) => guiElementAttributeFidelity(key) === 'unsupported')
388
+ .filter((key) => guiElementAttributeFidelity(key, assignment.key.value) === 'unsupported')
371
389
  .sort((a, b) => compareCodeUnits(a, b)),
372
390
  rawSource: raw(document, assignment),
373
391
  definitionOrder: order,
@@ -419,12 +437,18 @@ function indexSpritesAndFonts(document, file, fileNodeId, nodes, edges, sprites,
419
437
  const id = deterministicId('gui_sprite', { path: file.displayPath, name });
420
438
  const texturePath = firstScalarInsensitive(child, 'texturefile', 'textureFile', 'textureFile1');
421
439
  const texturePath2 = firstScalarInsensitive(child, 'textureFile2');
440
+ const primaryColour = colourFrom(child, 'color', 'colour');
441
+ const secondaryColour = colourFrom(child, 'colortwo', 'colourtwo');
422
442
  const staticFallback = firstScalarInsensitive(child, 'static_fallback', 'staticFallback');
423
443
  const animationRateFps = numberScalar(child, 'animation_rate_fps');
424
444
  const looping = boolScalar(child, 'looping');
425
445
  const playOnShow = boolScalar(child, 'play_on_show');
426
446
  const pauseOnLoop = numberScalar(child, 'pause_on_loop');
427
447
  const effectFile = firstScalarInsensitive(child, 'effectFile');
448
+ const shaderFeatures = assignments(child)
449
+ .filter(({ key, value }) => /^(?:animation|masking_texture|maskingtexture|shader_defines)$/iu.test(key.value) &&
450
+ (value.type === 'block' || !/^(?:no|false|0)$/iu.test(value.value)))
451
+ .map(({ key }) => key.value);
428
452
  const declaredSize = sizeFrom(child);
429
453
  const borderSize = nestedSizeFrom(child, 'borderSize');
430
454
  const tilingCenter = boolScalar(child, 'tilingCenter');
@@ -438,6 +462,8 @@ function indexSpritesAndFonts(document, file, fileNodeId, nodes, edges, sprites,
438
462
  spriteType: assignment.key.value,
439
463
  ...(texturePath === undefined ? {} : { texturePath }),
440
464
  ...(texturePath2 === undefined ? {} : { texturePath2 }),
465
+ ...(primaryColour === undefined ? {} : { primaryColour }),
466
+ ...(secondaryColour === undefined ? {} : { secondaryColour }),
441
467
  frameCount: Math.trunc(numberScalar(child, 'noOfFrames', 'noofframes') ?? 1),
442
468
  frameAnimated: spriteType === 'frameanimatedspritetype',
443
469
  ...(animationRateFps === undefined ? {} : { animationRateFps }),
@@ -445,6 +471,7 @@ function indexSpritesAndFonts(document, file, fileNodeId, nodes, edges, sprites,
445
471
  ...(playOnShow === undefined ? {} : { playOnShow }),
446
472
  ...(pauseOnLoop === undefined ? {} : { pauseOnLoop }),
447
473
  ...(effectFile === undefined ? {} : { effectFile }),
474
+ ...(shaderFeatures.length === 0 ? {} : { shaderFeatures }),
448
475
  ...(staticFallback === undefined ? {} : { staticFallback }),
449
476
  ...(declaredSize === undefined ? {} : { declaredSize }),
450
477
  ...(borderSize === undefined ? {} : { borderSize }),
@@ -594,6 +621,128 @@ function constantTriggerResult(value) {
594
621
  return false;
595
622
  return undefined;
596
623
  }
624
+ function indexFocusInlays(document, file, fileNodeId, nodes, edges, scriptedGuis) {
625
+ for (const assignment of assignments(document.root)) {
626
+ if (assignment.value.type !== 'block')
627
+ continue;
628
+ const block = assignment.value;
629
+ const windowName = firstScalarInsensitive(block, 'window_name');
630
+ if (windowName === undefined)
631
+ continue;
632
+ const name = assignment.key.value;
633
+ const id = deterministicId('scripted_gui', { path: file.displayPath, name });
634
+ const location = nodeLocation(document, assignment, name);
635
+ const imageDefinitions = namedAssignments(block, 'scripted_images').flatMap((image) => image.value.type !== 'block'
636
+ ? []
637
+ : [
638
+ {
639
+ elementName: image.key.value,
640
+ location: nodeLocation(document, image, image.key.value),
641
+ choices: assignments(image.value).map((choice) => ({
642
+ spriteName: choice.key.value,
643
+ conditionExpression: choice.value.type === 'block'
644
+ ? raw(document, choice.value)
645
+ : `{ always = ${choice.value.value} }`,
646
+ location: nodeLocation(document, choice, choice.key.value),
647
+ })),
648
+ },
649
+ ]);
650
+ const triggerDefinitions = [];
651
+ const effectDefinitions = [];
652
+ for (const button of namedAssignments(block, 'scripted_buttons')) {
653
+ if (button.value.type !== 'block')
654
+ continue;
655
+ const available = childBlocks(button.value, 'available')[0];
656
+ const effect = childBlocks(button.value, 'click_effect')[0];
657
+ if (available !== undefined) {
658
+ const triggerName = `${button.key.value}_click_enabled`;
659
+ const constantResult = constantTriggerResult(available);
660
+ triggerDefinitions.push({
661
+ name: triggerName,
662
+ elementName: button.key.value,
663
+ rawSource: `${triggerName} = ${raw(document, available)}`,
664
+ conditionExpression: raw(document, available),
665
+ location: nodeLocation(document, button, button.key.value),
666
+ ...(constantResult === undefined ? {} : { constantResult }),
667
+ });
668
+ }
669
+ if (effect !== undefined)
670
+ effectDefinitions.push({
671
+ name: `${button.key.value}_click`,
672
+ elementName: button.key.value,
673
+ costs: directEffectCosts(effect),
674
+ rawSource: raw(document, effect),
675
+ location: nodeLocation(document, button, button.key.value),
676
+ });
677
+ }
678
+ const propertyDefinitions = namedAssignments(block, 'scripted_progressbars').map((bar) => ({
679
+ elementName: bar.key.value,
680
+ attributes: bar.value.type === 'block'
681
+ ? sourceValueToProperty(bar.value, constantsFor(document))
682
+ : {},
683
+ rawSource: raw(document, bar),
684
+ location: nodeLocation(document, bar, bar.key.value),
685
+ }));
686
+ const visible = childBlocks(block, 'visible')[0];
687
+ const definition = {
688
+ id,
689
+ name,
690
+ sourcePath: file.displayPath,
691
+ location,
692
+ windowName,
693
+ contextType: 'country_context',
694
+ interfaceKind: 'focus-inlay',
695
+ internal: boolScalar(block, 'internal') ?? false,
696
+ ...(visible === undefined ? {} : { visibleExpression: raw(document, visible) }),
697
+ effects: effectDefinitions.map(({ name }) => name),
698
+ effectDefinitions,
699
+ triggers: triggerDefinitions.map(({ name }) => name),
700
+ triggerDefinitions,
701
+ properties: [
702
+ ...new Set([
703
+ ...imageDefinitions.map(({ elementName }) => elementName),
704
+ ...propertyDefinitions.map(({ elementName }) => elementName),
705
+ ]),
706
+ ],
707
+ propertyDefinitions,
708
+ dynamicLists: [],
709
+ dynamicListDefinitions: [],
710
+ aiWeights: [],
711
+ aiEnabled: false,
712
+ rawSource: raw(document, assignment),
713
+ imageDefinitions,
714
+ };
715
+ addDomainEntry(scriptedGuis, definition, 'focus inlay');
716
+ addNode(nodes, {
717
+ id,
718
+ name,
719
+ kind: 'scripted_gui',
720
+ path: file.displayPath,
721
+ location,
722
+ metadata: { interfaceKind: 'focus-inlay', internal: definition.internal, windowName },
723
+ });
724
+ addEdge(edges, 'contains', fileNodeId, id, true, {}, location);
725
+ for (const [kind, actions] of [
726
+ ['scripted_effect', definition.effects],
727
+ ['scripted_trigger', definition.triggers],
728
+ ]) {
729
+ for (const action of actions) {
730
+ const childId = deterministicId(kind === 'scripted_effect' ? 'gui_effect' : 'gui_trigger', {
731
+ id,
732
+ [kind === 'scripted_effect' ? 'effect' : 'trigger']: action,
733
+ });
734
+ addNode(nodes, {
735
+ id: childId,
736
+ name: action,
737
+ kind,
738
+ path: file.displayPath,
739
+ metadata: { scriptedGui: name, element: actionElementName(action) },
740
+ });
741
+ addEdge(edges, 'contains', id, childId, true);
742
+ }
743
+ }
744
+ }
745
+ }
597
746
  function indexScriptedGuis(document, file, fileNodeId, nodes, edges, scriptedGuis) {
598
747
  for (const root of childBlocks(document.root, 'scripted_gui')) {
599
748
  for (const assignment of assignments(root)) {
@@ -618,6 +767,7 @@ function indexScriptedGuis(document, file, fileNodeId, nodes, edges, scriptedGui
618
767
  elementName: actionElementName(trigger.key.value),
619
768
  ...(constantResult === undefined ? {} : { constantResult }),
620
769
  rawSource: raw(document, trigger),
770
+ conditionExpression: raw(document, trigger.value),
621
771
  location: nodeLocation(document, trigger, trigger.key.value),
622
772
  };
623
773
  });
@@ -985,6 +1135,12 @@ function linkGraph(nodes, edges, elements, sprites, fonts, scriptedGuis, scripte
985
1135
  }
986
1136
  }
987
1137
  for (const gui of scriptedGuis) {
1138
+ for (const image of gui.imageDefinitions ?? []) {
1139
+ for (const choice of image.choices) {
1140
+ const sprite = spriteByName.get(choice.spriteName.toLocaleLowerCase('en-US'));
1141
+ addEdge(edges, 'uses_sprite', gui.id, sprite?.id ?? `sprite:${choice.spriteName}`, sprite !== undefined, { elementName: image.elementName, spriteName: choice.spriteName }, choice.location);
1142
+ }
1143
+ }
988
1144
  if (gui.windowName !== undefined) {
989
1145
  const window = elementByName.get(gui.windowName);
990
1146
  addEdge(edges, 'window', gui.id, window?.id ?? `gui_element:${gui.windowName}`, window !== undefined, { windowName: gui.windowName }, gui.location);
@@ -1043,6 +1199,7 @@ function linkGraph(nodes, edges, elements, sprites, fonts, scriptedGuis, scripte
1043
1199
  }
1044
1200
  const localisationAttributeNames = [
1045
1201
  'text',
1202
+ 'context_aware_text',
1046
1203
  'buttonText',
1047
1204
  'pdx_tooltip',
1048
1205
  'pdx_tooltip_delayed',
@@ -1267,7 +1424,10 @@ export function buildGuiSourceGraph(files, sharedIndex) {
1267
1424
  indexSpritesAndFonts(document, file, fileNodeId, nodes, edges, sprites, fonts, textColours);
1268
1425
  }
1269
1426
  if (kind === 'scripted_gui_file') {
1270
- indexScriptedGuis(document, file, fileNodeId, nodes, edges, scriptedGuis);
1427
+ if (normalized.startsWith('common/focus_inlay_windows/'))
1428
+ indexFocusInlays(document, file, fileNodeId, nodes, edges, scriptedGuis);
1429
+ else
1430
+ indexScriptedGuis(document, file, fileNodeId, nodes, edges, scriptedGuis);
1271
1431
  }
1272
1432
  if (kind === 'scripted_localisation_file') {
1273
1433
  indexScriptedLocalisation(document, file, fileNodeId, nodes, edges, scriptedLocalisation);