overpy 9.5.13 → 9.6.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.
Files changed (3) hide show
  1. package/README.md +59 -4
  2. package/overpy.js +335 -68
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -791,6 +791,49 @@ content.toString();
791
791
 
792
792
  The hook script does not need to declare a wrapper function; writing statements that transform `content` is enough.
793
793
 
794
+ ## #!rulePrefix
795
+
796
+ Sets a prefix for all subsequent rules. The prefix is applied to the rule name using a template (by default, `[prefix] ruleName`).
797
+
798
+ If a `#!rulePrefix` directive is in an included file, it only takes effect for rules within that file (and its child includes, if they don't have their own `#!rulePrefix`), after the directive. When the included file ends, the prefix is restored to what it was before the include.
799
+
800
+ ```hs
801
+ #!rulePrefix "Effects"
802
+
803
+ rule "Spawn particles":
804
+ #compiled rule name: [Effects] Spawn particles
805
+
806
+ #!rulePrefix ""
807
+ rule "Unprefixed rule":
808
+ #compiled rule name: Unprefixed rule
809
+ ```
810
+
811
+ ## #!rulePrefixTemplate
812
+
813
+ Defines a global template for how rule prefixes are applied to rule names. Can only be defined once and has effect on all rules (even those declared before it).
814
+
815
+ The template is an OverPy expression with the following variables:
816
+
817
+ - `$rule`: the current rule name
818
+ - `$isDelimiter`: true if the rule has `@Delimiter`
819
+ - `$prefix`: the current prefix
820
+ - `$file`: the file name without extension
821
+ - `$path`: the relative path to the main file (backslashes replaced by slashes)
822
+ - Titlecase/lower/upper variations: `$prefixTitle`, `$prefixUpper`, `$prefixLower`, `$fileTitle`, `$fileUpper`, `$fileLower`, `$pathTitle`, `$pathUpper`, `$pathLower`
823
+
824
+ Examples :
825
+
826
+ - `#!rulePrefixTemplate f"[{$prefix}] {$rule}" if $prefix and $rule else $rule` (default): adds the prefix in square brackets before the rule name, if the prefix and rule name are not empty. This is the default if this directive is unspecified.
827
+ - `#!rulePrefixTemplate f"[{$pathTitle.replace('_', ' ')}] {$rule}" if $rule and not $isDelimiter else $rule`": if you have an `heroes/junker_queen.opy` file, will yield rule names like `"[Heroes/Junker Queen] Spawn particles"`. This is the default if the directive is specified without an expression (just `#!rulePrefixTemplate`).
828
+
829
+ The expression has to evaluate to a string without arguments.
830
+
831
+ #!rulePrefix "effects"
832
+
833
+ rule "Spawn particles":
834
+ #compiled rule name: (EFFECTS) Spawn particles
835
+ ```
836
+
794
837
  # Advanced constructs
795
838
 
796
839
  ## Switches
@@ -911,9 +954,9 @@ OverPy will store the array in a string and automatically decompress it, which t
911
954
 
912
955
  For more control over the compression (eg if you have separate arrays to compress), you can use the `compress` and `decompressNumbers`/`decompressVectors` functions.
913
956
 
914
- ## splitDictArray
957
+ ## splitDictArray/tabular
915
958
 
916
- Maps an array of dictionaries to variables. For example:
959
+ `splitDictArray` maps an array of dictionaries to variables. For example:
917
960
 
918
961
  ```python
919
962
  splitDictArray({
@@ -933,6 +976,16 @@ waveHeroes = [Hero.ANA, Hero.SOLDIER, Hero.HAMMOND]
933
976
  waveLengths = [3, 8, null]
934
977
  ```
935
978
 
979
+ For a terser syntax, `tabular` can be used, where the second argument is a raw array (not an array of arrays!)
980
+
981
+ ```
982
+ tabular([waveHeroes, waveLengths], [
983
+ Hero.ANA, 3,
984
+ Hero.SOLDIER, 8,
985
+ Hero.HAMMOND, null,
986
+ ])
987
+ ```
988
+
936
989
  If the third argument is specified and set to `true`, string compression is automatically used for number and vector arrays.
937
990
 
938
991
  ## 2d/3d array assignment
@@ -1004,9 +1057,11 @@ This also means that, when used in a variable, you cannot use a translated strin
1004
1057
 
1005
1058
  Last, you can use the `#!translateWithPlayerVar` directive to store the player's language in a variable using a rule which uses the `.startFacing()` function when the player spawns (the language is determined based on the player's facing direction).
1006
1059
 
1007
- If using translations, this can save a lot of elements. However, it will make translated strings not display correctly for spectators; you will have to wrap them with the `__` function (which behaves the same as the `_` function, except it will not use the `__languageIndex__` player variable).
1060
+ If using translations, this can save a lot of elements. However, it will make translated strings not display correctly for spectators. There are two options if your gamemode must cater to spectators:
1061
+ - Either wrap the strings with the `__` function (which behaves the same as the `_` function, except it will not use the `__languageIndex__` player variable).
1062
+ - Or, if you are short on elements, do `#!translateWithPlayerVar noTlErr`. This disables `TlErr` (which may cause you to badly setup the translations, so test thoroughly if disabling it!) but makes the `__languageIndex__` be 0-indexed, which enables spectators to see the default language (but it will not be translated).
1008
1063
 
1009
- If your gamemode changes the facing direction on spawn, you must modify it so that it changes it once `eventPlayer.__languageIndex__ != 1.1`.
1064
+ If your gamemode changes the facing direction on spawn, you must modify it so that it changes it once `eventPlayer.__languageIndex__ != 1.1` or 0.1 if using `noTlErr`.
1010
1065
 
1011
1066
  The `___` function is the same as the `_` function, but will never resolve the translation, even if in a display action. You must wrap it with the `_` function to resolve it.
1012
1067
 
package/overpy.js CHANGED
@@ -20415,7 +20415,6 @@ var customGameSettingsSchema = (
20415
20415
  "mercy",
20416
20416
  "moira",
20417
20417
  "roadhog",
20418
- "sojourn",
20419
20418
  "symmetra",
20420
20419
  "torbjorn",
20421
20420
  "winston",
@@ -38424,9 +38423,9 @@ var LogicalLine = class {
38424
38423
  var Token = class {
38425
38424
  text;
38426
38425
  fileStack;
38427
- constructor(text, fileStack8) {
38426
+ constructor(text, fileStack7) {
38428
38427
  this.text = text;
38429
- this.fileStack = fileStack8;
38428
+ this.fileStack = fileStack7;
38430
38429
  this.fileStack[this.fileStack.length - 1].endCol = this.fileStack[this.fileStack.length - 1].startCol + this.text.split("\n")[this.text.split("\n").length - 1].length;
38431
38430
  this.fileStack[this.fileStack.length - 1].endLine = this.fileStack[this.fileStack.length - 1].startLine + this.text.split("\n").length - 1;
38432
38431
  }
@@ -38570,9 +38569,13 @@ ${scriptText}`, {
38570
38569
  }
38571
38570
  if (content2.startsWith("#!translateWithPlayerVar")) {
38572
38571
  setUsePlayerVarForTranslations(true);
38573
- if (content2.startsWith("#!translateWithPlayerVar noDetectionRule")) {
38572
+ let args = content2.substring("#!translateWithPlayerVar".length).trim().split(" ");
38573
+ if (args.includes("noDetectionRule")) {
38574
38574
  setGenerateRuleForTranslationsPlayerVar(false);
38575
38575
  }
38576
+ if (args.includes("noTlErr")) {
38577
+ setTranslationUseTlErr(false);
38578
+ }
38576
38579
  return;
38577
38580
  }
38578
38581
  if (content2.startsWith("#!disableInspector")) {
@@ -38659,6 +38662,25 @@ ${scriptText}`, {
38659
38662
  );
38660
38663
  return;
38661
38664
  }
38665
+ if (content2.startsWith("#!rulePrefix ")) {
38666
+ let prefix = content2.substring("#!rulePrefix ".length).trim();
38667
+ addToken("__rulePrefix__");
38668
+ addToken("(");
38669
+ addToken(prefix);
38670
+ addToken(")");
38671
+ return;
38672
+ }
38673
+ if (content2.startsWith("#!rulePrefixTemplate")) {
38674
+ if (rulePrefixTemplate !== "") {
38675
+ error("A rule prefix template has already been defined");
38676
+ }
38677
+ let template = content2.substring("#!rulePrefixTemplate".length).trim() || `f"[{$pathTitle.replace('_', ' ')}] {$rule}" if $rule and not $isDelimiter else $rule`;
38678
+ setRulePrefixTemplate(template);
38679
+ let _rulePrefixTemplateFilestack = getFileStackCopy();
38680
+ _rulePrefixTemplateFilestack[_rulePrefixTemplateFilestack.length - 1].startCol = content2.indexOf(template) + 1;
38681
+ setRulePrefixTemplateFilestack(_rulePrefixTemplateFilestack);
38682
+ return;
38683
+ }
38662
38684
  error("Unknown preprocessor directive '" + content2 + "'");
38663
38685
  }
38664
38686
  for (i = 0; i < content.length; moveCursor(1)) {
@@ -38754,7 +38776,11 @@ ${scriptText}`, {
38754
38776
  staticMember: true,
38755
38777
  fileStackMemberType: "normal"
38756
38778
  });
38779
+ let pushLine = new LogicalLine(0, [new Token("__pushRulePrefixStack__", getFileStackCopy())]);
38780
+ let popLine = new LogicalLine(0, [new Token("__popRulePrefixStack__", getFileStackCopy())]);
38781
+ result.push(pushLine);
38757
38782
  result.push(...tokenize(importedFileContent));
38783
+ result.push(popLine);
38758
38784
  fileStack2.pop();
38759
38785
  moveCursor(j2 - i - 1);
38760
38786
  }
@@ -39588,13 +39614,13 @@ function parseAstMacro(macro) {
39588
39614
  if (!(macro.name in astMacros)) {
39589
39615
  error("Unknown macro '" + macro.name + "'", macro.fileStack);
39590
39616
  }
39591
- function setMacroFilestack(ast, fileStack8) {
39592
- ast.fileStack = [...fileStack8, ast.fileStack[ast.fileStack.length - 1]];
39617
+ function setMacroFilestack(ast, fileStack7) {
39618
+ ast.fileStack = [...fileStack7, ast.fileStack[ast.fileStack.length - 1]];
39593
39619
  for (var arg of ast.args) {
39594
- setMacroFilestack(arg, fileStack8);
39620
+ setMacroFilestack(arg, fileStack7);
39595
39621
  }
39596
39622
  for (var child of ast.children) {
39597
- setMacroFilestack(child, fileStack8);
39623
+ setMacroFilestack(child, fileStack7);
39598
39624
  }
39599
39625
  }
39600
39626
  let result = astMacros[macro.name].lines.map((line) => line.clone());
@@ -39963,31 +39989,31 @@ function getAstForArgDefault(arg) {
39963
39989
  }
39964
39990
 
39965
39991
  // src/utils/varNames.ts
39966
- function translateSubroutineToPy(content, fileStack8) {
39992
+ function translateSubroutineToPy(content, fileStack7) {
39967
39993
  content = content.trim();
39968
39994
  content = translateNameToAvoidKeywords(content, "subroutine");
39969
39995
  if (subroutines.map((x) => x.name).includes(content)) {
39970
39996
  return content;
39971
39997
  }
39972
39998
  if (defaultSubroutineNames.includes(content)) {
39973
- addSubroutine(content, defaultSubroutineNames.indexOf(content), fileStack8);
39999
+ addSubroutine(content, defaultSubroutineNames.indexOf(content), fileStack7);
39974
40000
  return content;
39975
40001
  }
39976
40002
  error("Unknown subroutine '" + content + "'");
39977
40003
  }
39978
- function translateSubroutineToWs(content, fileStack8) {
40004
+ function translateSubroutineToWs(content, fileStack7) {
39979
40005
  for (var i = 0; i < subroutines.length; i++) {
39980
40006
  if (subroutines[i].name === content) {
39981
40007
  return content;
39982
40008
  }
39983
40009
  }
39984
40010
  if (defaultSubroutineNames.includes(content)) {
39985
- addSubroutine(content, defaultSubroutineNames.indexOf(content), fileStack8);
40011
+ addSubroutine(content, defaultSubroutineNames.indexOf(content), fileStack7);
39986
40012
  return content;
39987
40013
  }
39988
40014
  error("Undeclared subroutine '" + content + "'");
39989
40015
  }
39990
- function addSubroutine(content, index, fileStack8, isFromDefStatement = false) {
40016
+ function addSubroutine(content, index, fileStack7, isFromDefStatement = false) {
39991
40017
  if (reservedSubroutineNames.includes(content)) {
39992
40018
  error("Subroutine name '" + content + "' is a built-in function or keyword");
39993
40019
  }
@@ -39995,7 +40021,7 @@ function addSubroutine(content, index, fileStack8, isFromDefStatement = false) {
39995
40021
  subroutines.push({
39996
40022
  name: content,
39997
40023
  index: index ?? subroutines.length,
39998
- fileStack: fileStack8,
40024
+ fileStack: fileStack7,
39999
40025
  isFromDefStatement
40000
40026
  });
40001
40027
  }
@@ -40008,20 +40034,20 @@ function translateNameToAvoidKeywords(initialName, nameType) {
40008
40034
  }
40009
40035
  return initialName;
40010
40036
  }
40011
- function translateVarToPy(content, isGlobalVariable, fileStack8) {
40037
+ function translateVarToPy(content, isGlobalVariable, fileStack7) {
40012
40038
  content = content.trim();
40013
40039
  content = translateNameToAvoidKeywords(content, isGlobalVariable ? "globalvar" : "playervar");
40014
40040
  var varArray = isGlobalVariable ? globalVariables : playerVariables;
40015
40041
  if (varArray.map((x) => x.name).includes(content)) {
40016
40042
  return content;
40017
40043
  } else if (defaultVarNames.includes(content)) {
40018
- addVariable(content, isGlobalVariable, defaultVarNames.indexOf(content), fileStack8);
40044
+ addVariable(content, isGlobalVariable, defaultVarNames.indexOf(content), fileStack7);
40019
40045
  return content;
40020
40046
  } else {
40021
40047
  error("Unknown variable '" + content + "'");
40022
40048
  }
40023
40049
  }
40024
- function translateVarToWs(content, isGlobalVariable, fileStack8) {
40050
+ function translateVarToWs(content, isGlobalVariable, fileStack7) {
40025
40051
  var varArray = isGlobalVariable ? globalVariables : playerVariables;
40026
40052
  for (var i = 0; i < varArray.length; i++) {
40027
40053
  if (varArray[i].name === content) {
@@ -40029,12 +40055,12 @@ function translateVarToWs(content, isGlobalVariable, fileStack8) {
40029
40055
  }
40030
40056
  }
40031
40057
  if (defaultVarNames.includes(content)) {
40032
- addVariable(content, isGlobalVariable, defaultVarNames.indexOf(content), fileStack8);
40058
+ addVariable(content, isGlobalVariable, defaultVarNames.indexOf(content), fileStack7);
40033
40059
  return content;
40034
40060
  }
40035
40061
  error("Undeclared " + (isGlobalVariable ? "global" : "player") + " variable '" + content + "'");
40036
40062
  }
40037
- function addVariable(content, isGlobalVariable, index, fileStack8, initValue = null) {
40063
+ function addVariable(content, isGlobalVariable, index, fileStack7, initValue = null) {
40038
40064
  if ((isGlobalVariable ? "" : ".") + content in astConstants) {
40039
40065
  error("Variable name '" + content + "' is already declared as a macro");
40040
40066
  }
@@ -40048,7 +40074,7 @@ function addVariable(content, isGlobalVariable, index, fileStack8, initValue = n
40048
40074
  if (isGlobalVariable) {
40049
40075
  globalVariables.push({
40050
40076
  name: content,
40051
- fileStack: fileStack8,
40077
+ fileStack: fileStack7,
40052
40078
  index
40053
40079
  });
40054
40080
  if (initValue) {
@@ -40057,7 +40083,7 @@ function addVariable(content, isGlobalVariable, index, fileStack8, initValue = n
40057
40083
  } else {
40058
40084
  playerVariables.push({
40059
40085
  name: content,
40060
- fileStack: fileStack8,
40086
+ fileStack: fileStack7,
40061
40087
  index
40062
40088
  });
40063
40089
  if (initValue) {
@@ -41780,7 +41806,7 @@ var heroKw = (
41780
41806
  "junkerQueen": {
41781
41807
  "secondaryFire": {
41782
41808
  "guid": "00000000D3A4",
41783
- "en-US": "Jagged Blade",
41809
+ "en-US": "Jagged Blade Gracie",
41784
41810
  "de-DE": "Gezackte Klinge",
41785
41811
  "es-ES": "Cuchilla dentada",
41786
41812
  "es-MX": "Hoja dentada",
@@ -43030,7 +43056,6 @@ var heroKw = (
43030
43056
  },
43031
43057
  "sojourn": {
43032
43058
  "secondaryFire": {
43033
- "guid": "00000000796B",
43034
43059
  "en-US": "Charged Shot"
43035
43060
  },
43036
43061
  "ability1": {
@@ -45606,7 +45631,7 @@ astParsingFunctions.__rule__ = function(content) {
45606
45631
  foundLabel = true;
45607
45632
  }
45608
45633
  computeDistanceTo(content3.children[i3]);
45609
- if (content3.children[i3].type !== "Label" && !["__enableOptimizations__", "__disableOptimizations__", "__enableOptimizeForSize__", "__disableOptimizeForSize__", "__enableOptimizeStrict__", "__disableOptimizeStrict__"].includes(content3.children[i3].name)) {
45634
+ if (content3.children[i3].type !== "Label" && !["__enableOptimizations__", "__disableOptimizations__", "__enableOptimizeForSize__", "__disableOptimizeForSize__", "__enableOptimizeStrict__", "__disableOptimizeStrict__", "__rulePrefix__", "__pushRulePrefixStack__", "__popRulePrefixStack__"].includes(content3.children[i3].name)) {
45610
45635
  debug("Increasing distanceTo count for label " + label + ": function '" + content3.children[i3].name + "'");
45611
45636
  count++;
45612
45637
  }
@@ -45996,7 +46021,7 @@ function getAstForTranslatedString(content, replacements = []) {
45996
46021
  let replacementNames = [];
45997
46022
  let replacementMacro = "";
45998
46023
  if (content.parent?.name === "spacesForString") {
45999
- opyMacro += escapeString("\uFF34\uFF2C\uFF25\uFF52\uFF52\uEC48" + content.args.map((x) => getBestSpaces(Object.keys(spaces).map(Number), getStrVisualLength(x.name)).map((j) => spaces[j]).join("")).join("\uEC48"), false);
46024
+ opyMacro += escapeString((useTlErr ? "\uFF34\uFF2C\uFF25\uFF52\uFF52\uEC48" : "") + content.args.map((x) => getBestSpaces(Object.keys(spaces).map(Number), getStrVisualLength(x.name)).map((j) => spaces[j]).join("")).join("\uEC48"), false);
46000
46025
  opyMacro += ".split(__overpyTranslationHelper__)";
46001
46026
  } else if (isTranslatedStringLiteral) {
46002
46027
  let separators = ["Vector.UP", "Vector.DOWN", "Vector.LEFT", "Vector.RIGHT", "Vector.FORWARD", "Vector.BACKWARD", "1876650.25", "1876651.25", "1876652.25", "1876653.25", "1876654.25", "1876655.25", "1876656.25", "1876657.25", "1876658.25", "1876659.25"];
@@ -46009,7 +46034,7 @@ function getAstForTranslatedString(content, replacements = []) {
46009
46034
  "Vector.BACKWARD": "(0.00, 0.00, -1.00)"
46010
46035
  };
46011
46036
  let translationStrings = content.args.map((x) => x.name.replaceAll("{}", "{0}"));
46012
- let rawString = "\uFF34\uFF2C\uFF25\uFF52\uFF52\uEC48" + translationStrings.join("\uEC48");
46037
+ let rawString = (useTlErr ? "\uFF34\uFF2C\uFF25\uFF52\uFF52\uEC48" : "") + translationStrings.join("\uEC48");
46013
46038
  if (getUtf8Length(rawString) <= STR_MAX_LENGTH && replacements.length <= STR_MAX_ARGS) {
46014
46039
  if (replacements.length > 0) {
46015
46040
  replacementMacro = ".format(" + replacements.map((x, i) => "$arg" + i).join(", ") + ")";
@@ -47509,36 +47534,59 @@ astParsingFunctions.spacesForString = function(content) {
47509
47534
  };
47510
47535
 
47511
47536
  // src/compiler/functions/splitDictArray.ts
47512
- astParsingFunctions.splitDictArray = function(content) {
47513
- if (content.args[0].name !== "__dict__") {
47514
- error("First argument of splitDictArray() must be a dictionary", content.args[0].fileStack);
47515
- }
47516
- if (content.args[1].name !== "__array__") {
47517
- error("Second argument of splitDictArray() must be a literal array", content.args[1].fileStack);
47518
- }
47519
- for (let elem of content.args[1].args) {
47520
- if (elem.name !== "__dict__") {
47521
- error("Second argument of splitDictArray() must be a literal array of dictionaries", elem.fileStack);
47522
- }
47523
- }
47524
- let variableKeys = content.args[0].args.map((x) => x.args[0].name);
47525
- let variables = content.args[0].args.map((x) => x.args[1]);
47526
- let arrays = Array(variableKeys.length).fill(0).map(() => getAstForEmptyArray());
47527
- for (let dict of content.args[1].args) {
47528
- let dictKeys = dict.args.map((x) => x.args[0].name);
47529
- let dictValues = dict.args.map((x) => x.args[1]);
47530
- for (let [i, key] of variableKeys.entries()) {
47531
- if (!dict.args.some((x) => x.args[0].name === key)) {
47532
- arrays[i].args.push(getAstForNull());
47533
- } else {
47534
- arrays[i].args.push(dictValues[dictKeys.findIndex((x) => x === key)]);
47537
+ astParsingFunctions.splitDictArray = astParsingFunctions.tabular = function(content) {
47538
+ let variables = [];
47539
+ let arrays = [];
47540
+ if (content.name === "splitDictArray") {
47541
+ if (content.args[0].name !== "__dict__") {
47542
+ error("First argument of " + content.name + "() must be a dictionary", content.args[0].fileStack);
47543
+ }
47544
+ if (content.args[1].name !== "__array__") {
47545
+ error("Second argument of " + content.name + "() must be a literal array", content.args[1].fileStack);
47546
+ }
47547
+ for (let elem of content.args[1].args) {
47548
+ if (elem.name !== "__dict__") {
47549
+ error("Second argument of " + content.name + "() must be a literal array of dictionaries", elem.fileStack);
47550
+ }
47551
+ }
47552
+ variables = content.args[0].args.map((x) => x.args[1]);
47553
+ let variableKeys = content.args[0].args.map((x) => x.args[0].name);
47554
+ arrays = Array(variableKeys.length).fill(0).map(() => getAstForEmptyArray());
47555
+ for (let dict of content.args[1].args) {
47556
+ let dictKeys = dict.args.map((x) => x.args[0].name);
47557
+ let dictValues = dict.args.map((x) => x.args[1]);
47558
+ for (let [i, key] of variableKeys.entries()) {
47559
+ if (!dict.args.some((x) => x.args[0].name === key)) {
47560
+ arrays[i].args.push(getAstForNull());
47561
+ } else {
47562
+ arrays[i].args.push(dictValues[dictKeys.findIndex((x) => x === key)]);
47563
+ }
47535
47564
  }
47536
- }
47537
- for (let key of dictKeys) {
47538
- if (!variableKeys.includes(key)) {
47539
- error("Key '" + key + "' in dictionary is not defined in the first argument of splitDictArray()", dict.fileStack);
47565
+ for (let key of dictKeys) {
47566
+ if (!variableKeys.includes(key)) {
47567
+ error("Key '" + key + "' in dictionary is not defined in the first argument of " + content.name + "()", dict.fileStack);
47568
+ }
47540
47569
  }
47541
47570
  }
47571
+ } else {
47572
+ if (content.args[0].name !== "__array__") {
47573
+ error("First argument of " + content.name + "() must be a literal array", content.args[0].fileStack);
47574
+ }
47575
+ if (content.args[1].name !== "__array__") {
47576
+ error("Second argument of " + content.name + "() must be a literal array", content.args[1].fileStack);
47577
+ }
47578
+ variables = content.args[0].args;
47579
+ if (content.args[1].args.length % variables.length !== 0) {
47580
+ error("Second argument of " + content.name + "() must have a length that is a multiple of " + variables.length + " (length is " + content.args[1].args.length + ")", content.args[1].fileStack);
47581
+ }
47582
+ arrays = content.args[1].args.reduce((acc, x, i) => {
47583
+ let arrayIndex = i % variables.length;
47584
+ if (!acc[arrayIndex]) {
47585
+ acc[arrayIndex] = getAstForEmptyArray();
47586
+ }
47587
+ acc[arrayIndex].args.push(x);
47588
+ return acc;
47589
+ }, []);
47542
47590
  }
47543
47591
  if (content.args[2].name === "true") {
47544
47592
  arrays = arrays.map((x) => {
@@ -47551,7 +47599,7 @@ astParsingFunctions.splitDictArray = function(content) {
47551
47599
  }
47552
47600
  let assignments = arrays.map((x, i) => new Ast2("__assignTo__", [variables[i], x]));
47553
47601
  if (!content.parent) {
47554
- error("Could not find parent of splitDictArray");
47602
+ error("Could not find parent of " + content.name + "()");
47555
47603
  }
47556
47604
  content.parent.children.splice(content.parent.childIndex + 1, 0, ...assignments.slice(1));
47557
47605
  return assignments[0];
@@ -47732,6 +47780,18 @@ function parseAstRules(rules) {
47732
47780
  rulesResult.push(rule);
47733
47781
  continue;
47734
47782
  }
47783
+ if (rule.name === "__rulePrefix__") {
47784
+ rulesResult.push(rule);
47785
+ continue;
47786
+ }
47787
+ if (rule.name === "__pushRulePrefixStack__") {
47788
+ rulesResult.push(rule);
47789
+ continue;
47790
+ }
47791
+ if (rule.name === "__popRulePrefixStack__") {
47792
+ rulesResult.push(rule);
47793
+ continue;
47794
+ }
47735
47795
  for (let i2 = 0; i2 < rule.children.length; i2++) {
47736
47796
  setFileStack(rule.children[i2].fileStack);
47737
47797
  if (rule.children[i2].name in astMacros) {
@@ -64458,11 +64518,24 @@ function astRulesToWs(rules) {
64458
64518
  compiledRules.push("//Strict optimizations enabled\n");
64459
64519
  continue;
64460
64520
  }
64521
+ if (rule.name === "__rulePrefix__") {
64522
+ setCurrentRulePrefix(rule.args[0].args[0].name);
64523
+ continue;
64524
+ }
64525
+ if (rule.name === "__pushRulePrefixStack__") {
64526
+ pushRulePrefixStack();
64527
+ continue;
64528
+ }
64529
+ if (rule.name === "__popRulePrefixStack__") {
64530
+ popRulePrefixStack();
64531
+ continue;
64532
+ }
64461
64533
  if (rule.ruleAttributes.isDisabled) {
64462
64534
  result += tows("__disabled__", ruleKw) + " ";
64463
64535
  }
64536
+ let finalRuleName = applyRulePrefixTemplate(rule);
64464
64537
  result += tows("__rule__", ruleKw) + " (";
64465
- result += escapeBadWords(escapeString(rule.ruleAttributes.name, true));
64538
+ result += escapeBadWords(escapeString(finalRuleName, true));
64466
64539
  result += ") {\n";
64467
64540
  result += tabLevel(1) + tows("__event__", ruleKw) + " {\n";
64468
64541
  result += tabLevel(2) + tows(rule.ruleAttributes.event, eventKw) + ";\n";
@@ -65131,6 +65204,70 @@ function splitCustomString(tokens, args) {
65131
65204
  }
65132
65205
  return new Ast2("__customString__", [new Ast2(result, [], [], "CustomStringLiteral")].concat(resultArgs));
65133
65206
  }
65207
+ function applyRulePrefixTemplate(rule) {
65208
+ let ruleName = rule.ruleAttributes.name;
65209
+ let prefix = currentRulePrefix;
65210
+ let fileStackForRule = rule.fileStack;
65211
+ if (!prefix && !rulePrefixTemplate) {
65212
+ return ruleName;
65213
+ }
65214
+ let fileName = "";
65215
+ let filePath = "";
65216
+ for (let k = fileStackForRule.length - 1; k >= 0; k--) {
65217
+ if (fileStackForRule[k].path) {
65218
+ let fullPath = fileStackForRule[k].path.replace(/\\/g, "/");
65219
+ let baseName = fullPath.substring(fullPath.lastIndexOf("/") + 1);
65220
+ fileName = baseName.replace(/\.opy$/i, "");
65221
+ filePath = fullPath.startsWith(rootPath) ? fullPath.substring(rootPath.length) : fullPath;
65222
+ filePath = filePath.replace(/\.opy$/i, "");
65223
+ break;
65224
+ }
65225
+ }
65226
+ let template = rulePrefixTemplate || 'f"[{$prefix}] {$rule}" if $prefix and $rule else $rule';
65227
+ let argNames = [
65228
+ "$rule",
65229
+ "$prefix",
65230
+ "$file",
65231
+ "$path",
65232
+ "$isDelimiter",
65233
+ "$prefixTitle",
65234
+ "$prefixUpper",
65235
+ "$prefixLower",
65236
+ "$fileTitle",
65237
+ "$fileUpper",
65238
+ "$fileLower",
65239
+ "$pathTitle",
65240
+ "$pathUpper",
65241
+ "$pathLower"
65242
+ ];
65243
+ let argValues = [
65244
+ ruleName,
65245
+ prefix,
65246
+ fileName,
65247
+ filePath,
65248
+ !!rule.ruleAttributes.isDelimiter,
65249
+ toTitleCase(prefix),
65250
+ prefix.toUpperCase(),
65251
+ prefix.toLowerCase(),
65252
+ toTitleCase(fileName),
65253
+ fileName.toUpperCase(),
65254
+ fileName.toLowerCase(),
65255
+ toTitleCase(filePath),
65256
+ filePath.toUpperCase(),
65257
+ filePath.toLowerCase()
65258
+ ];
65259
+ let argAsts = argValues.map((v) => v === true || v === false ? getAstForBool(v) : getAstForCustomString(v));
65260
+ let oldFileStack = getFileStackCopy();
65261
+ fileStack2.push(...rulePrefixTemplateFilestack);
65262
+ let resultAst = parseOpyMacro(template, argNames, argAsts);
65263
+ resultAst.parent = new Ast2("__rulePrefix__");
65264
+ resultAst = parseAst(resultAst);
65265
+ setFileStack(oldFileStack);
65266
+ if (resultAst.name !== "__customString__" || resultAst.args.length > 1) {
65267
+ error("Could not resolve rule prefix template to a plain string");
65268
+ }
65269
+ return resultAst.args[0].name;
65270
+ }
65134
65271
 
65135
65272
  // src/compiler/translations.ts
65136
65273
  var import_pofile = __toESM(require_po());
@@ -65142,7 +65279,7 @@ function getLeadingAndTrailingWhitespace(str) {
65142
65279
  trailingWhitespace
65143
65280
  };
65144
65281
  }
65145
- function getTranslatedString(str, context, fileStack8) {
65282
+ function getTranslatedString(str, context, fileStack7) {
65146
65283
  let lineNb = null;
65147
65284
  let fileName = null;
65148
65285
  if (translationLanguages2.length === 0) {
@@ -65163,10 +65300,10 @@ function getTranslatedString(str, context, fileStack8) {
65163
65300
  let { leadingWhitespace, actualString: actualStringWithNewlines, trailingWhitespace } = getLeadingAndTrailingWhitespace(str);
65164
65301
  let lines = actualStringWithNewlines.split("\n").map((x) => getLeadingAndTrailingWhitespace(x));
65165
65302
  let actualString = lines.map((x) => x.actualString).join("\n");
65166
- for (let i = fileStack8.length - 1; i >= 0; i--) {
65167
- if (fileStack8[i].name.endsWith(".opy")) {
65168
- lineNb = fileStack8[i].startLine;
65169
- fileName = fileStack8[i].name.replaceAll(/\\/g, "/").split("/").pop();
65303
+ for (let i = fileStack7.length - 1; i >= 0; i--) {
65304
+ if (fileStack7[i].name.endsWith(".opy")) {
65305
+ lineNb = fileStack7[i].startLine;
65306
+ fileName = fileStack7[i].name.replaceAll(/\\/g, "/").split("/").pop();
65170
65307
  break;
65171
65308
  }
65172
65309
  }
@@ -65320,6 +65457,7 @@ async function compile(content, language = "en-US", _rootPath = "", _mainFileNam
65320
65457
  setFileStack([
65321
65458
  {
65322
65459
  name: mainFileName || "<main>",
65460
+ path: rootPath + mainFileName,
65323
65461
  startLine: 1,
65324
65462
  startCol: 1,
65325
65463
  endCol: null,
@@ -65381,7 +65519,7 @@ async function compile(content, language = "en-US", _rootPath = "", _mainFileNam
65381
65519
  }
65382
65520
  }).join("0");
65383
65521
  if (usePlayerVarForTranslations) {
65384
- addVariable("__languageIndex__", false, -1, getInternalFileStack(), tokenize("1.1")[0].tokens);
65522
+ addVariable("__languageIndex__", false, -1, getInternalFileStack(), tokenize(useTlErr ? "1.1" : "0.1")[0].tokens);
65385
65523
  }
65386
65524
  addVariable("__overpyTranslationHelper__", true, -1, getInternalFileStack(), tokenize(escapeString("\uEC480" + translationConstantString, false) + ".split(null[0])")[0].tokens);
65387
65525
  }
@@ -65416,7 +65554,7 @@ async function compile(content, language = "en-US", _rootPath = "", _mainFileNam
65416
65554
  @Event eachPlayer
65417
65555
  @Condition eventPlayer.hasSpawned()
65418
65556
  @Condition not eventPlayer.isDummy()
65419
- @Condition eventPlayer.__languageIndex__ == 1.1
65557
+ @Condition eventPlayer.__languageIndex__ == ${useTlErr ? "1.1" : "0.1"}
65420
65558
  eventPlayer.__languageIndex__.append(eventPlayer.getFacingDirection())
65421
65559
  eventPlayer.startFacing(
65422
65560
  directionFromAngles(10*${escapeString("\uEC480" + translationConstantString, false)}.split(null[0]).index(${translationLanguageConstantOpy}.split([])), 5),
@@ -65432,7 +65570,7 @@ async function compile(content, language = "en-US", _rootPath = "", _mainFileNam
65432
65570
 
65433
65571
  eventPlayer.stopFacing()
65434
65572
  eventPlayer.setFacing(eventPlayer.__languageIndex__.last(), Relativity.TO_WORLD)
65435
- eventPlayer.__languageIndex__ = eventPlayer.__languageIndex__[0]
65573
+ eventPlayer.__languageIndex__ = eventPlayer.__languageIndex__${useTlErr ? "[0]" : " - 1"}
65436
65574
  `;
65437
65575
  translationSetupRule = tokenize(translationSetupRule);
65438
65576
  translationSetupRule = parseLines(translationSetupRule)[0];
@@ -67760,10 +67898,10 @@ function parseType(tokens) {
67760
67898
  var OpyError = class _OpyError extends Error {
67761
67899
  fileStack;
67762
67900
  severity = "error";
67763
- constructor(message, fileStack8) {
67901
+ constructor(message, fileStack7) {
67764
67902
  super(message);
67765
67903
  this.name = "OpyError";
67766
- this.fileStack = fileStack8;
67904
+ this.fileStack = fileStack7;
67767
67905
  Object.setPrototypeOf(this, _OpyError.prototype);
67768
67906
  }
67769
67907
  };
@@ -67923,12 +68061,12 @@ function getFileStackRange(tokens) {
67923
68061
  result[result.length - 1].endCol = lastTokenFilestack.endCol;
67924
68062
  return result;
67925
68063
  }
67926
- function displayFileStack(fileStack8) {
67927
- if (!fileStack8 || fileStack8.length === 0) {
68064
+ function displayFileStack(fileStack7) {
68065
+ if (!fileStack7 || fileStack7.length === 0) {
67928
68066
  return "";
67929
68067
  }
67930
68068
  let result = "";
67931
- for (const file of fileStack8.toReversed()) {
68069
+ for (const file of fileStack7.toReversed()) {
67932
68070
  result += `
67933
68071
  | `;
67934
68072
  if (file.startLine !== null && file.startCol !== null) {
@@ -67963,6 +68101,9 @@ function upperCaseToCamelCase(str) {
67963
68101
  result = result[0].toLowerCase() + result.substring(1);
67964
68102
  return result;
67965
68103
  }
68104
+ function toTitleCase(str) {
68105
+ return str.replace(/[\p{Letter}']+/gu, (txt) => txt.charAt(0).toUpperCase() + txt.substring(1));
68106
+ }
67966
68107
  function isNumber(x) {
67967
68108
  if (("" + x).trim() === "" || x === null) {
67968
68109
  return false;
@@ -68483,6 +68624,23 @@ var opyInternalFuncs = {
68483
68624
  "Vector"
68484
68625
  ]
68485
68626
  },
68627
+ "__popRulePrefixStack__": {
68628
+ "args": null,
68629
+ "return": "void"
68630
+ },
68631
+ "__pushRulePrefixStack__": {
68632
+ "args": null,
68633
+ "return": "void"
68634
+ },
68635
+ "__rulePrefix__": {
68636
+ "args": [
68637
+ {
68638
+ "name": "prefix",
68639
+ "type": "CustomStringLiteral"
68640
+ }
68641
+ ],
68642
+ "return": "void"
68643
+ },
68486
68644
  "__rule__": {
68487
68645
  "args": null,
68488
68646
  "return": "void"
@@ -69222,6 +69380,8 @@ waveLengths = [3, 8, null]
69222
69380
  \`\`\`
69223
69381
 
69224
69382
  If the third argument is set to \`true\`, arrays will be compressed if they are arrays of literal numbers or vectors.
69383
+
69384
+ Also check the \`tabular\` function for a more concise syntax.
69225
69385
  `,
69226
69386
  "args": [
69227
69387
  {
@@ -69268,6 +69428,48 @@ If the third argument is set to \`true\`, arrays will be compressed if they are
69268
69428
  "isConstant": true,
69269
69429
  "return": "unsigned int"
69270
69430
  },
69431
+ "tabular": {
69432
+ "description": `
69433
+ Maps an array of arrays to variables (same as \`splitDictArray()\` with shorter syntax). For example:
69434
+ \`\`\`python
69435
+ tabular([waveHeroes,waveLengths], [
69436
+ Hero.ANA, 3,
69437
+ Hero.SOLDIER, 8,
69438
+ Hero.HAMMOND, null,
69439
+ ])
69440
+ \`\`\`
69441
+
69442
+ Will yield the following:
69443
+
69444
+ \`\`\`python
69445
+ waveHeroes = [Hero.ANA, Hero.SOLDIER, Hero.HAMMOND]
69446
+ waveLengths = [3, 8, null]
69447
+ \`\`\`
69448
+
69449
+ If the third argument is set to \`true\`, arrays will be compressed if they are arrays of literal numbers or vectors.
69450
+ `,
69451
+ "args": [
69452
+ {
69453
+ "name": "variables",
69454
+ "description": "A dictionary mapping the keys to the variables to be assigned to.",
69455
+ "type": "Dict"
69456
+ },
69457
+ {
69458
+ "name": "values",
69459
+ "description": "An array of dictionaries describing the values to be assigned to the variables.",
69460
+ "type": {
69461
+ "Array": "Dict"
69462
+ }
69463
+ },
69464
+ {
69465
+ "name": "compress",
69466
+ "description": "Set to true to compress the arrays if they are arrays of literal numbers or vectors.",
69467
+ "type": "bool",
69468
+ "default": false
69469
+ }
69470
+ ],
69471
+ "return": "void"
69472
+ },
69271
69473
  ".toArray": {
69272
69474
  "description": "Get an array of the values of an enum.",
69273
69475
  "args": [
@@ -69380,6 +69582,8 @@ var usePlayerVarForTranslations;
69380
69582
  var setUsePlayerVarForTranslations = (use) => usePlayerVarForTranslations = use;
69381
69583
  var generateRuleForTranslationsPlayerVar;
69382
69584
  var setGenerateRuleForTranslationsPlayerVar = (generate) => generateRuleForTranslationsPlayerVar = generate;
69585
+ var useTlErr;
69586
+ var setTranslationUseTlErr = (use) => useTlErr = use;
69383
69587
  var excludeVariablesInCompilation;
69384
69588
  var setExcludeVariablesInCompilation = (exclude) => excludeVariablesInCompilation = exclude;
69385
69589
  var globalvarInitRuleName;
@@ -69388,6 +69592,17 @@ var playervarInitRuleName;
69388
69592
  var setPlayervarInitRuleName = (name) => playervarInitRuleName = name;
69389
69593
  var disableInspector = false;
69390
69594
  var setDisableInspector = (disable) => disableInspector = disable;
69595
+ var rulePrefixStack = [];
69596
+ var currentRulePrefix = "";
69597
+ var setCurrentRulePrefix = (prefix) => currentRulePrefix = prefix;
69598
+ var pushRulePrefixStack = () => rulePrefixStack.push(currentRulePrefix);
69599
+ var popRulePrefixStack = () => {
69600
+ currentRulePrefix = rulePrefixStack.pop() ?? "";
69601
+ };
69602
+ var rulePrefixTemplate = "";
69603
+ var setRulePrefixTemplate = (template) => rulePrefixTemplate = template;
69604
+ var rulePrefixTemplateFilestack = [];
69605
+ var setRulePrefixTemplateFilestack = (filestack) => rulePrefixTemplateFilestack = filestack;
69391
69606
  var decompilerGotos;
69392
69607
  var resetDecompilerGotos = () => decompilerGotos = [];
69393
69608
  var nbTabs;
@@ -69451,6 +69666,7 @@ function resetGlobalVariables(language) {
69451
69666
  translationLanguageConstantOpy = "";
69452
69667
  usePlayerVarForTranslations = false;
69453
69668
  generateRuleForTranslationsPlayerVar = true;
69669
+ useTlErr = true;
69454
69670
  excludeVariablesInCompilation = false;
69455
69671
  globalvarInitRuleName = "Initialize global variables";
69456
69672
  playervarInitRuleName = "Initialize player variables";
@@ -69459,6 +69675,10 @@ function resetGlobalVariables(language) {
69459
69675
  disableTranslationSourceLines = false;
69460
69676
  usedMaps = /* @__PURE__ */ new Set();
69461
69677
  postCompileHook = null;
69678
+ rulePrefixStack = [];
69679
+ currentRulePrefix = "";
69680
+ rulePrefixTemplate = "";
69681
+ rulePrefixTemplateFilestack = [];
69462
69682
  }
69463
69683
  var operatorPrecedence = {
69464
69684
  "=": 1,
@@ -71901,6 +72121,8 @@ If using translations, this can save a lot of elements. However, it will make tr
71901
72121
  If your gamemode changes the facing direction on spawn, you must modify it so that it changes it once \`eventPlayer.__languageIndex__ != 1.1\`.
71902
72122
 
71903
72123
  You can specify \`noDetectionRule\` to not create the rule which sets the variable to the player's language, in which case you'll have to define the rule yourself; the variable must be set to the language as defined in the order specified in the \`#!translations\` directive, where the first language is index 1, and must be set to 1 by default if no language could be determined.
72124
+
72125
+ You can also specify \`noTlErr\` to have spectators view the default language when viewing a translated string (the \`__languageIndex__\` variable is now 0-indexed instead of 1-indexed). Keep in mind that, if translations aren't used properly, you may not see it if you playtest with the default language.
71904
72126
  `
71905
72127
  },
71906
72128
  "extension": {
@@ -71942,6 +72164,51 @@ content.toString();
71942
72164
  \`\`\`
71943
72165
  `,
71944
72166
  "snippet": 'postCompileHook "$0"'
72167
+ },
72168
+ "rulePrefix": {
72169
+ "description": `
72170
+ Sets a prefix for all subsequent rules in the current file and its included child files (unless overridden). The prefix is applied to the rule name using the rule prefix template.
72171
+
72172
+ If a \`#!rulePrefix\` directive is in an included file, it only takes effect for the rules within that file (and its child includes, if they don't have their own \`#!rulePrefix\`), after the directive.
72173
+
72174
+ Example:
72175
+
72176
+ \`\`\`hs
72177
+ #!rulePrefix "Effects"
72178
+
72179
+ rule "Spawn particles":
72180
+ #compiled rule name: [Effects] Spawn particles
72181
+ \`\`\`
72182
+
72183
+ To clear the prefix for subsequent rules, use an empty string:
72184
+
72185
+ \`\`\`hs
72186
+ #!rulePrefix ""
72187
+ \`\`\`
72188
+ `,
72189
+ "snippet": 'rulePrefix "$0"'
72190
+ },
72191
+ "rulePrefixTemplate": {
72192
+ "description": `
72193
+ Defines a global template for how rule prefixes are applied to rule names. Can only be defined once. Has effect on all rules, even those declared before this directive.
72194
+
72195
+ The template is an OverPy expression with the following variables:
72196
+
72197
+ - \`$rule\`: the current rule name
72198
+ - \`$isDelimiter\`: true if the rule is @Delimiter
72199
+ - \`$prefix\`: the current prefix (set via \`#!rulePrefix\`)
72200
+ - \`$file\`: the file name without extension
72201
+ - \`$path\`: the relative path to the main file (backslashes replaced by slashes)
72202
+ - Titlecase/lower/upper variations: \`$prefixTitle\`, \`$prefixUpper\`, \`$prefixLower\`, \`$fileTitle\`, \`$fileUpper\`, \`$fileLower\`, \`$pathTitle\`, \`$pathUpper\`, \`$pathLower\`
72203
+
72204
+ Examples :
72205
+
72206
+ - \`#!rulePrefixTemplate f"[{$prefix}] {$rule}" if $prefix and $rule else $rule\` (default): adds the prefix in square brackets before the rule name, if the prefix and rule name are not empty. This is the default if this directive is unspecified.
72207
+ - \`#!rulePrefixTemplate f"[{$pathTitle.replace('_', ' ')}] {$rule}" if $rule and not $isDelimiter else $rule\`": if you have an \`heroes/junker_queen.opy\` file, will yield rule names like "[Heroes/Junker Queen] Spawn particles". This is the default if the directive is specified without an expression (just \`#!rulePrefixTemplate\`).
72208
+
72209
+ The expression has to evaluate to a string without arguments.
72210
+ `,
72211
+ "snippet": "rulePrefixTemplate $0"
71945
72212
  }
71946
72213
  };
71947
72214
  postLoadTasks.push({
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "url": "https://github.com/Zezombye/overpy"
8
8
  },
9
9
  "description": "High-level language for the Overwatch Workshop, with decompilation and compilation.",
10
- "version": "9.5.13",
10
+ "version": "9.6.0",
11
11
  "readme": "README.md",
12
12
  "keywords": [
13
13
  "overpy",