overpy 9.5.14 → 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 +334 -65
  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
@@ -38423,9 +38423,9 @@ var LogicalLine = class {
38423
38423
  var Token = class {
38424
38424
  text;
38425
38425
  fileStack;
38426
- constructor(text, fileStack8) {
38426
+ constructor(text, fileStack7) {
38427
38427
  this.text = text;
38428
- this.fileStack = fileStack8;
38428
+ this.fileStack = fileStack7;
38429
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;
38430
38430
  this.fileStack[this.fileStack.length - 1].endLine = this.fileStack[this.fileStack.length - 1].startLine + this.text.split("\n").length - 1;
38431
38431
  }
@@ -38569,9 +38569,13 @@ ${scriptText}`, {
38569
38569
  }
38570
38570
  if (content2.startsWith("#!translateWithPlayerVar")) {
38571
38571
  setUsePlayerVarForTranslations(true);
38572
- if (content2.startsWith("#!translateWithPlayerVar noDetectionRule")) {
38572
+ let args = content2.substring("#!translateWithPlayerVar".length).trim().split(" ");
38573
+ if (args.includes("noDetectionRule")) {
38573
38574
  setGenerateRuleForTranslationsPlayerVar(false);
38574
38575
  }
38576
+ if (args.includes("noTlErr")) {
38577
+ setTranslationUseTlErr(false);
38578
+ }
38575
38579
  return;
38576
38580
  }
38577
38581
  if (content2.startsWith("#!disableInspector")) {
@@ -38658,6 +38662,25 @@ ${scriptText}`, {
38658
38662
  );
38659
38663
  return;
38660
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
+ }
38661
38684
  error("Unknown preprocessor directive '" + content2 + "'");
38662
38685
  }
38663
38686
  for (i = 0; i < content.length; moveCursor(1)) {
@@ -38753,7 +38776,11 @@ ${scriptText}`, {
38753
38776
  staticMember: true,
38754
38777
  fileStackMemberType: "normal"
38755
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);
38756
38782
  result.push(...tokenize(importedFileContent));
38783
+ result.push(popLine);
38757
38784
  fileStack2.pop();
38758
38785
  moveCursor(j2 - i - 1);
38759
38786
  }
@@ -39587,13 +39614,13 @@ function parseAstMacro(macro) {
39587
39614
  if (!(macro.name in astMacros)) {
39588
39615
  error("Unknown macro '" + macro.name + "'", macro.fileStack);
39589
39616
  }
39590
- function setMacroFilestack(ast, fileStack8) {
39591
- ast.fileStack = [...fileStack8, ast.fileStack[ast.fileStack.length - 1]];
39617
+ function setMacroFilestack(ast, fileStack7) {
39618
+ ast.fileStack = [...fileStack7, ast.fileStack[ast.fileStack.length - 1]];
39592
39619
  for (var arg of ast.args) {
39593
- setMacroFilestack(arg, fileStack8);
39620
+ setMacroFilestack(arg, fileStack7);
39594
39621
  }
39595
39622
  for (var child of ast.children) {
39596
- setMacroFilestack(child, fileStack8);
39623
+ setMacroFilestack(child, fileStack7);
39597
39624
  }
39598
39625
  }
39599
39626
  let result = astMacros[macro.name].lines.map((line) => line.clone());
@@ -39962,31 +39989,31 @@ function getAstForArgDefault(arg) {
39962
39989
  }
39963
39990
 
39964
39991
  // src/utils/varNames.ts
39965
- function translateSubroutineToPy(content, fileStack8) {
39992
+ function translateSubroutineToPy(content, fileStack7) {
39966
39993
  content = content.trim();
39967
39994
  content = translateNameToAvoidKeywords(content, "subroutine");
39968
39995
  if (subroutines.map((x) => x.name).includes(content)) {
39969
39996
  return content;
39970
39997
  }
39971
39998
  if (defaultSubroutineNames.includes(content)) {
39972
- addSubroutine(content, defaultSubroutineNames.indexOf(content), fileStack8);
39999
+ addSubroutine(content, defaultSubroutineNames.indexOf(content), fileStack7);
39973
40000
  return content;
39974
40001
  }
39975
40002
  error("Unknown subroutine '" + content + "'");
39976
40003
  }
39977
- function translateSubroutineToWs(content, fileStack8) {
40004
+ function translateSubroutineToWs(content, fileStack7) {
39978
40005
  for (var i = 0; i < subroutines.length; i++) {
39979
40006
  if (subroutines[i].name === content) {
39980
40007
  return content;
39981
40008
  }
39982
40009
  }
39983
40010
  if (defaultSubroutineNames.includes(content)) {
39984
- addSubroutine(content, defaultSubroutineNames.indexOf(content), fileStack8);
40011
+ addSubroutine(content, defaultSubroutineNames.indexOf(content), fileStack7);
39985
40012
  return content;
39986
40013
  }
39987
40014
  error("Undeclared subroutine '" + content + "'");
39988
40015
  }
39989
- function addSubroutine(content, index, fileStack8, isFromDefStatement = false) {
40016
+ function addSubroutine(content, index, fileStack7, isFromDefStatement = false) {
39990
40017
  if (reservedSubroutineNames.includes(content)) {
39991
40018
  error("Subroutine name '" + content + "' is a built-in function or keyword");
39992
40019
  }
@@ -39994,7 +40021,7 @@ function addSubroutine(content, index, fileStack8, isFromDefStatement = false) {
39994
40021
  subroutines.push({
39995
40022
  name: content,
39996
40023
  index: index ?? subroutines.length,
39997
- fileStack: fileStack8,
40024
+ fileStack: fileStack7,
39998
40025
  isFromDefStatement
39999
40026
  });
40000
40027
  }
@@ -40007,20 +40034,20 @@ function translateNameToAvoidKeywords(initialName, nameType) {
40007
40034
  }
40008
40035
  return initialName;
40009
40036
  }
40010
- function translateVarToPy(content, isGlobalVariable, fileStack8) {
40037
+ function translateVarToPy(content, isGlobalVariable, fileStack7) {
40011
40038
  content = content.trim();
40012
40039
  content = translateNameToAvoidKeywords(content, isGlobalVariable ? "globalvar" : "playervar");
40013
40040
  var varArray = isGlobalVariable ? globalVariables : playerVariables;
40014
40041
  if (varArray.map((x) => x.name).includes(content)) {
40015
40042
  return content;
40016
40043
  } else if (defaultVarNames.includes(content)) {
40017
- addVariable(content, isGlobalVariable, defaultVarNames.indexOf(content), fileStack8);
40044
+ addVariable(content, isGlobalVariable, defaultVarNames.indexOf(content), fileStack7);
40018
40045
  return content;
40019
40046
  } else {
40020
40047
  error("Unknown variable '" + content + "'");
40021
40048
  }
40022
40049
  }
40023
- function translateVarToWs(content, isGlobalVariable, fileStack8) {
40050
+ function translateVarToWs(content, isGlobalVariable, fileStack7) {
40024
40051
  var varArray = isGlobalVariable ? globalVariables : playerVariables;
40025
40052
  for (var i = 0; i < varArray.length; i++) {
40026
40053
  if (varArray[i].name === content) {
@@ -40028,12 +40055,12 @@ function translateVarToWs(content, isGlobalVariable, fileStack8) {
40028
40055
  }
40029
40056
  }
40030
40057
  if (defaultVarNames.includes(content)) {
40031
- addVariable(content, isGlobalVariable, defaultVarNames.indexOf(content), fileStack8);
40058
+ addVariable(content, isGlobalVariable, defaultVarNames.indexOf(content), fileStack7);
40032
40059
  return content;
40033
40060
  }
40034
40061
  error("Undeclared " + (isGlobalVariable ? "global" : "player") + " variable '" + content + "'");
40035
40062
  }
40036
- function addVariable(content, isGlobalVariable, index, fileStack8, initValue = null) {
40063
+ function addVariable(content, isGlobalVariable, index, fileStack7, initValue = null) {
40037
40064
  if ((isGlobalVariable ? "" : ".") + content in astConstants) {
40038
40065
  error("Variable name '" + content + "' is already declared as a macro");
40039
40066
  }
@@ -40047,7 +40074,7 @@ function addVariable(content, isGlobalVariable, index, fileStack8, initValue = n
40047
40074
  if (isGlobalVariable) {
40048
40075
  globalVariables.push({
40049
40076
  name: content,
40050
- fileStack: fileStack8,
40077
+ fileStack: fileStack7,
40051
40078
  index
40052
40079
  });
40053
40080
  if (initValue) {
@@ -40056,7 +40083,7 @@ function addVariable(content, isGlobalVariable, index, fileStack8, initValue = n
40056
40083
  } else {
40057
40084
  playerVariables.push({
40058
40085
  name: content,
40059
- fileStack: fileStack8,
40086
+ fileStack: fileStack7,
40060
40087
  index
40061
40088
  });
40062
40089
  if (initValue) {
@@ -45604,7 +45631,7 @@ astParsingFunctions.__rule__ = function(content) {
45604
45631
  foundLabel = true;
45605
45632
  }
45606
45633
  computeDistanceTo(content3.children[i3]);
45607
- 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)) {
45608
45635
  debug("Increasing distanceTo count for label " + label + ": function '" + content3.children[i3].name + "'");
45609
45636
  count++;
45610
45637
  }
@@ -45994,7 +46021,7 @@ function getAstForTranslatedString(content, replacements = []) {
45994
46021
  let replacementNames = [];
45995
46022
  let replacementMacro = "";
45996
46023
  if (content.parent?.name === "spacesForString") {
45997
- 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);
45998
46025
  opyMacro += ".split(__overpyTranslationHelper__)";
45999
46026
  } else if (isTranslatedStringLiteral) {
46000
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"];
@@ -46007,7 +46034,7 @@ function getAstForTranslatedString(content, replacements = []) {
46007
46034
  "Vector.BACKWARD": "(0.00, 0.00, -1.00)"
46008
46035
  };
46009
46036
  let translationStrings = content.args.map((x) => x.name.replaceAll("{}", "{0}"));
46010
- let rawString = "\uFF34\uFF2C\uFF25\uFF52\uFF52\uEC48" + translationStrings.join("\uEC48");
46037
+ let rawString = (useTlErr ? "\uFF34\uFF2C\uFF25\uFF52\uFF52\uEC48" : "") + translationStrings.join("\uEC48");
46011
46038
  if (getUtf8Length(rawString) <= STR_MAX_LENGTH && replacements.length <= STR_MAX_ARGS) {
46012
46039
  if (replacements.length > 0) {
46013
46040
  replacementMacro = ".format(" + replacements.map((x, i) => "$arg" + i).join(", ") + ")";
@@ -47507,36 +47534,59 @@ astParsingFunctions.spacesForString = function(content) {
47507
47534
  };
47508
47535
 
47509
47536
  // src/compiler/functions/splitDictArray.ts
47510
- astParsingFunctions.splitDictArray = function(content) {
47511
- if (content.args[0].name !== "__dict__") {
47512
- error("First argument of splitDictArray() must be a dictionary", content.args[0].fileStack);
47513
- }
47514
- if (content.args[1].name !== "__array__") {
47515
- error("Second argument of splitDictArray() must be a literal array", content.args[1].fileStack);
47516
- }
47517
- for (let elem of content.args[1].args) {
47518
- if (elem.name !== "__dict__") {
47519
- error("Second argument of splitDictArray() must be a literal array of dictionaries", elem.fileStack);
47520
- }
47521
- }
47522
- let variableKeys = content.args[0].args.map((x) => x.args[0].name);
47523
- let variables = content.args[0].args.map((x) => x.args[1]);
47524
- let arrays = Array(variableKeys.length).fill(0).map(() => getAstForEmptyArray());
47525
- for (let dict of content.args[1].args) {
47526
- let dictKeys = dict.args.map((x) => x.args[0].name);
47527
- let dictValues = dict.args.map((x) => x.args[1]);
47528
- for (let [i, key] of variableKeys.entries()) {
47529
- if (!dict.args.some((x) => x.args[0].name === key)) {
47530
- arrays[i].args.push(getAstForNull());
47531
- } else {
47532
- 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
+ }
47533
47564
  }
47534
- }
47535
- for (let key of dictKeys) {
47536
- if (!variableKeys.includes(key)) {
47537
- 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
+ }
47538
47569
  }
47539
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
+ }, []);
47540
47590
  }
47541
47591
  if (content.args[2].name === "true") {
47542
47592
  arrays = arrays.map((x) => {
@@ -47549,7 +47599,7 @@ astParsingFunctions.splitDictArray = function(content) {
47549
47599
  }
47550
47600
  let assignments = arrays.map((x, i) => new Ast2("__assignTo__", [variables[i], x]));
47551
47601
  if (!content.parent) {
47552
- error("Could not find parent of splitDictArray");
47602
+ error("Could not find parent of " + content.name + "()");
47553
47603
  }
47554
47604
  content.parent.children.splice(content.parent.childIndex + 1, 0, ...assignments.slice(1));
47555
47605
  return assignments[0];
@@ -47730,6 +47780,18 @@ function parseAstRules(rules) {
47730
47780
  rulesResult.push(rule);
47731
47781
  continue;
47732
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
+ }
47733
47795
  for (let i2 = 0; i2 < rule.children.length; i2++) {
47734
47796
  setFileStack(rule.children[i2].fileStack);
47735
47797
  if (rule.children[i2].name in astMacros) {
@@ -64456,11 +64518,24 @@ function astRulesToWs(rules) {
64456
64518
  compiledRules.push("//Strict optimizations enabled\n");
64457
64519
  continue;
64458
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
+ }
64459
64533
  if (rule.ruleAttributes.isDisabled) {
64460
64534
  result += tows("__disabled__", ruleKw) + " ";
64461
64535
  }
64536
+ let finalRuleName = applyRulePrefixTemplate(rule);
64462
64537
  result += tows("__rule__", ruleKw) + " (";
64463
- result += escapeBadWords(escapeString(rule.ruleAttributes.name, true));
64538
+ result += escapeBadWords(escapeString(finalRuleName, true));
64464
64539
  result += ") {\n";
64465
64540
  result += tabLevel(1) + tows("__event__", ruleKw) + " {\n";
64466
64541
  result += tabLevel(2) + tows(rule.ruleAttributes.event, eventKw) + ";\n";
@@ -65129,6 +65204,70 @@ function splitCustomString(tokens, args) {
65129
65204
  }
65130
65205
  return new Ast2("__customString__", [new Ast2(result, [], [], "CustomStringLiteral")].concat(resultArgs));
65131
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
+ }
65132
65271
 
65133
65272
  // src/compiler/translations.ts
65134
65273
  var import_pofile = __toESM(require_po());
@@ -65140,7 +65279,7 @@ function getLeadingAndTrailingWhitespace(str) {
65140
65279
  trailingWhitespace
65141
65280
  };
65142
65281
  }
65143
- function getTranslatedString(str, context, fileStack8) {
65282
+ function getTranslatedString(str, context, fileStack7) {
65144
65283
  let lineNb = null;
65145
65284
  let fileName = null;
65146
65285
  if (translationLanguages2.length === 0) {
@@ -65161,10 +65300,10 @@ function getTranslatedString(str, context, fileStack8) {
65161
65300
  let { leadingWhitespace, actualString: actualStringWithNewlines, trailingWhitespace } = getLeadingAndTrailingWhitespace(str);
65162
65301
  let lines = actualStringWithNewlines.split("\n").map((x) => getLeadingAndTrailingWhitespace(x));
65163
65302
  let actualString = lines.map((x) => x.actualString).join("\n");
65164
- for (let i = fileStack8.length - 1; i >= 0; i--) {
65165
- if (fileStack8[i].name.endsWith(".opy")) {
65166
- lineNb = fileStack8[i].startLine;
65167
- 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();
65168
65307
  break;
65169
65308
  }
65170
65309
  }
@@ -65318,6 +65457,7 @@ async function compile(content, language = "en-US", _rootPath = "", _mainFileNam
65318
65457
  setFileStack([
65319
65458
  {
65320
65459
  name: mainFileName || "<main>",
65460
+ path: rootPath + mainFileName,
65321
65461
  startLine: 1,
65322
65462
  startCol: 1,
65323
65463
  endCol: null,
@@ -65379,7 +65519,7 @@ async function compile(content, language = "en-US", _rootPath = "", _mainFileNam
65379
65519
  }
65380
65520
  }).join("0");
65381
65521
  if (usePlayerVarForTranslations) {
65382
- addVariable("__languageIndex__", false, -1, getInternalFileStack(), tokenize("1.1")[0].tokens);
65522
+ addVariable("__languageIndex__", false, -1, getInternalFileStack(), tokenize(useTlErr ? "1.1" : "0.1")[0].tokens);
65383
65523
  }
65384
65524
  addVariable("__overpyTranslationHelper__", true, -1, getInternalFileStack(), tokenize(escapeString("\uEC480" + translationConstantString, false) + ".split(null[0])")[0].tokens);
65385
65525
  }
@@ -65414,7 +65554,7 @@ async function compile(content, language = "en-US", _rootPath = "", _mainFileNam
65414
65554
  @Event eachPlayer
65415
65555
  @Condition eventPlayer.hasSpawned()
65416
65556
  @Condition not eventPlayer.isDummy()
65417
- @Condition eventPlayer.__languageIndex__ == 1.1
65557
+ @Condition eventPlayer.__languageIndex__ == ${useTlErr ? "1.1" : "0.1"}
65418
65558
  eventPlayer.__languageIndex__.append(eventPlayer.getFacingDirection())
65419
65559
  eventPlayer.startFacing(
65420
65560
  directionFromAngles(10*${escapeString("\uEC480" + translationConstantString, false)}.split(null[0]).index(${translationLanguageConstantOpy}.split([])), 5),
@@ -65430,7 +65570,7 @@ async function compile(content, language = "en-US", _rootPath = "", _mainFileNam
65430
65570
 
65431
65571
  eventPlayer.stopFacing()
65432
65572
  eventPlayer.setFacing(eventPlayer.__languageIndex__.last(), Relativity.TO_WORLD)
65433
- eventPlayer.__languageIndex__ = eventPlayer.__languageIndex__[0]
65573
+ eventPlayer.__languageIndex__ = eventPlayer.__languageIndex__${useTlErr ? "[0]" : " - 1"}
65434
65574
  `;
65435
65575
  translationSetupRule = tokenize(translationSetupRule);
65436
65576
  translationSetupRule = parseLines(translationSetupRule)[0];
@@ -67758,10 +67898,10 @@ function parseType(tokens) {
67758
67898
  var OpyError = class _OpyError extends Error {
67759
67899
  fileStack;
67760
67900
  severity = "error";
67761
- constructor(message, fileStack8) {
67901
+ constructor(message, fileStack7) {
67762
67902
  super(message);
67763
67903
  this.name = "OpyError";
67764
- this.fileStack = fileStack8;
67904
+ this.fileStack = fileStack7;
67765
67905
  Object.setPrototypeOf(this, _OpyError.prototype);
67766
67906
  }
67767
67907
  };
@@ -67921,12 +68061,12 @@ function getFileStackRange(tokens) {
67921
68061
  result[result.length - 1].endCol = lastTokenFilestack.endCol;
67922
68062
  return result;
67923
68063
  }
67924
- function displayFileStack(fileStack8) {
67925
- if (!fileStack8 || fileStack8.length === 0) {
68064
+ function displayFileStack(fileStack7) {
68065
+ if (!fileStack7 || fileStack7.length === 0) {
67926
68066
  return "";
67927
68067
  }
67928
68068
  let result = "";
67929
- for (const file of fileStack8.toReversed()) {
68069
+ for (const file of fileStack7.toReversed()) {
67930
68070
  result += `
67931
68071
  | `;
67932
68072
  if (file.startLine !== null && file.startCol !== null) {
@@ -67961,6 +68101,9 @@ function upperCaseToCamelCase(str) {
67961
68101
  result = result[0].toLowerCase() + result.substring(1);
67962
68102
  return result;
67963
68103
  }
68104
+ function toTitleCase(str) {
68105
+ return str.replace(/[\p{Letter}']+/gu, (txt) => txt.charAt(0).toUpperCase() + txt.substring(1));
68106
+ }
67964
68107
  function isNumber(x) {
67965
68108
  if (("" + x).trim() === "" || x === null) {
67966
68109
  return false;
@@ -68481,6 +68624,23 @@ var opyInternalFuncs = {
68481
68624
  "Vector"
68482
68625
  ]
68483
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
+ },
68484
68644
  "__rule__": {
68485
68645
  "args": null,
68486
68646
  "return": "void"
@@ -69220,6 +69380,8 @@ waveLengths = [3, 8, null]
69220
69380
  \`\`\`
69221
69381
 
69222
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.
69223
69385
  `,
69224
69386
  "args": [
69225
69387
  {
@@ -69266,6 +69428,48 @@ If the third argument is set to \`true\`, arrays will be compressed if they are
69266
69428
  "isConstant": true,
69267
69429
  "return": "unsigned int"
69268
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
+ },
69269
69473
  ".toArray": {
69270
69474
  "description": "Get an array of the values of an enum.",
69271
69475
  "args": [
@@ -69378,6 +69582,8 @@ var usePlayerVarForTranslations;
69378
69582
  var setUsePlayerVarForTranslations = (use) => usePlayerVarForTranslations = use;
69379
69583
  var generateRuleForTranslationsPlayerVar;
69380
69584
  var setGenerateRuleForTranslationsPlayerVar = (generate) => generateRuleForTranslationsPlayerVar = generate;
69585
+ var useTlErr;
69586
+ var setTranslationUseTlErr = (use) => useTlErr = use;
69381
69587
  var excludeVariablesInCompilation;
69382
69588
  var setExcludeVariablesInCompilation = (exclude) => excludeVariablesInCompilation = exclude;
69383
69589
  var globalvarInitRuleName;
@@ -69386,6 +69592,17 @@ var playervarInitRuleName;
69386
69592
  var setPlayervarInitRuleName = (name) => playervarInitRuleName = name;
69387
69593
  var disableInspector = false;
69388
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;
69389
69606
  var decompilerGotos;
69390
69607
  var resetDecompilerGotos = () => decompilerGotos = [];
69391
69608
  var nbTabs;
@@ -69449,6 +69666,7 @@ function resetGlobalVariables(language) {
69449
69666
  translationLanguageConstantOpy = "";
69450
69667
  usePlayerVarForTranslations = false;
69451
69668
  generateRuleForTranslationsPlayerVar = true;
69669
+ useTlErr = true;
69452
69670
  excludeVariablesInCompilation = false;
69453
69671
  globalvarInitRuleName = "Initialize global variables";
69454
69672
  playervarInitRuleName = "Initialize player variables";
@@ -69457,6 +69675,10 @@ function resetGlobalVariables(language) {
69457
69675
  disableTranslationSourceLines = false;
69458
69676
  usedMaps = /* @__PURE__ */ new Set();
69459
69677
  postCompileHook = null;
69678
+ rulePrefixStack = [];
69679
+ currentRulePrefix = "";
69680
+ rulePrefixTemplate = "";
69681
+ rulePrefixTemplateFilestack = [];
69460
69682
  }
69461
69683
  var operatorPrecedence = {
69462
69684
  "=": 1,
@@ -71899,6 +72121,8 @@ If using translations, this can save a lot of elements. However, it will make tr
71899
72121
  If your gamemode changes the facing direction on spawn, you must modify it so that it changes it once \`eventPlayer.__languageIndex__ != 1.1\`.
71900
72122
 
71901
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.
71902
72126
  `
71903
72127
  },
71904
72128
  "extension": {
@@ -71940,6 +72164,51 @@ content.toString();
71940
72164
  \`\`\`
71941
72165
  `,
71942
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"
71943
72212
  }
71944
72213
  };
71945
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.14",
10
+ "version": "9.6.0",
11
11
  "readme": "README.md",
12
12
  "keywords": [
13
13
  "overpy",