overpy 9.6.1 → 9.6.3

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 +10 -8
  2. package/overpy.js +233 -56
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -594,6 +594,14 @@ Suppresses the specified warnings globally across the program. Warnings must be
594
594
  #!suppressWarnings w_type_check w_unsuitable_event
595
595
  ```
596
596
 
597
+ ## #!allowMacroRedeclaration
598
+
599
+ If specified, will replace the existing macro instead of throwing an error. Can be useful for OOP-like projects where the same codebase is used for multiple different gamemodes.
600
+
601
+ ## #!debugElementCount
602
+
603
+ Will generate a summary of the used elements per rule in the output, and add a comment with the number of elements used for each rule/condition/action.
604
+
597
605
  ## Optimizations
598
606
 
599
607
  By default, OverPy automatically optimizes gamemodes for speed. This means useless code is removed, calculations are done when possible, and function patterns are replaced with builtin functions.
@@ -606,7 +614,7 @@ For example:
606
614
 
607
615
  You can check [here](https://github.com/Zezombye/overpy/issues/33) for a list of optimizations.
608
616
 
609
- The `#!disableOptimizations` directive can be used to disable all optimizations done by the compiler. Should be only used for debugging, if you suspect that OverPy has bugs in its optimizations, or if you want to generate an unoptimize instruction for some reason.
617
+ The `#!disableOptimizations` directive can be used to disable all optimizations done by the compiler. Should be only used for debugging, if you suspect that OverPy has bugs in its optimizations, or if you want to generate an unoptimized instruction for some reason.
610
618
 
611
619
  The `#!optimizeForSize` directive prioritizes lowering the number of elements over optimizing the runtime (see [here](https://github.com/Zezombye/overpy/issues/238) for a list of optimizations).
612
620
 
@@ -823,17 +831,11 @@ The template is an OverPy expression with the following variables:
823
831
 
824
832
  Examples :
825
833
 
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.
834
+ - `#!rulePrefixTemplate f"[{$prefix}] {$rule}" if $prefix and $rule and not $isDelimiter 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
835
  - `#!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
836
 
829
837
  The expression has to evaluate to a string without arguments.
830
838
 
831
- #!rulePrefix "effects"
832
-
833
- rule "Spawn particles":
834
- #compiled rule name: (EFFECTS) Spawn particles
835
- ```
836
-
837
839
  # Advanced constructs
838
840
 
839
841
  ## Switches
package/overpy.js CHANGED
@@ -38655,6 +38655,20 @@ ${scriptText}`, {
38655
38655
  setReplacementForTeam1("getControlScoringTeam");
38656
38656
  return;
38657
38657
  }
38658
+ if (content2.startsWith("#!replaceEmptyStringByEmptyArray")) {
38659
+ if (replacementForEmptyString !== "") {
38660
+ error("A replacement for empty string has already been defined");
38661
+ }
38662
+ setReplacementForEmptyString("emptyArray");
38663
+ return;
38664
+ }
38665
+ if (content2.startsWith("#!replaceEmptyStringByVariable")) {
38666
+ if (replacementForEmptyString !== "") {
38667
+ error("A replacement for empty string has already been defined");
38668
+ }
38669
+ setReplacementForEmptyString("variable");
38670
+ return;
38671
+ }
38658
38672
  if (content2.startsWith("#!suppressWarnings ")) {
38659
38673
  var firstSpaceIndex = content2.indexOf(" ");
38660
38674
  globallySuppressedWarningTypes.push(
@@ -38681,6 +38695,18 @@ ${scriptText}`, {
38681
38695
  setRulePrefixTemplateFilestack(_rulePrefixTemplateFilestack);
38682
38696
  return;
38683
38697
  }
38698
+ if (content2.startsWith("#!useVariableForCompressionAlphabet")) {
38699
+ setUseVariableForCompressionAlphabet(true);
38700
+ return;
38701
+ }
38702
+ if (content2.startsWith("#!debugElementCount")) {
38703
+ setDebugElementCount(true);
38704
+ return;
38705
+ }
38706
+ if (content2.startsWith("#!allowMacroRedeclaration")) {
38707
+ setAllowMacroRedeclaration(true);
38708
+ return;
38709
+ }
38684
38710
  error("Unknown preprocessor directive '" + content2 + "'");
38685
38711
  }
38686
38712
  for (i = 0; i < content.length; moveCursor(1)) {
@@ -38989,7 +39015,11 @@ function parseMacro(initialMacroData) {
38989
39015
  replacement: isFunctionMacro ? trimmedMacroContent.substring(bracketPos[1] + 1).trim() : trimmedMacroContent.substring(trimmedMacroContent.indexOf(" ")).trim()
38990
39016
  };
38991
39017
  if (macros.some((m) => m.name === macro.name)) {
38992
- error("Macro '" + macro.name + "' is already defined");
39018
+ if (allowMacroRedeclaration) {
39019
+ macros.splice(macros.findIndex((m) => m.name === macro.name), 1);
39020
+ } else {
39021
+ error("Macro '" + macro.name + "' is already defined");
39022
+ }
38993
39023
  }
38994
39024
  if (macro.text === macro.replacement) {
38995
39025
  error("Macro '" + macro.name + "' references itself");
@@ -45117,8 +45147,9 @@ astParsingFunctions.__if__ = function(content) {
45117
45147
  }
45118
45148
  }
45119
45149
  }
45150
+ let isLoneIf = content.parent.childIndex === content.parent.children.length - 1 || content.parent.childIndex < content.parent.children.length - 1 && !["__elif__", "__else__"].includes(content.parent.children[content.parent.childIndex + 1].name);
45120
45151
  if (enableOptimization) {
45121
- if (content.children.length === 1 && (content.parent.childIndex === content.parent.children.length - 1 || content.parent.children[content.parent.childIndex + 1].name !== "__elif__" && content.parent.children[content.parent.childIndex + 1].name !== "__else__") && ["return", "loop", "__skip__"].includes(content.children[0].name)) {
45152
+ if (content.children.length === 1 && isLoneIf && ["return", "loop", "__skip__"].includes(content.children[0].name)) {
45122
45153
  if (currentRuleHasVariableGoto) {
45123
45154
  content.parent.children.splice(content.parent.childIndex + 1, 0, content.children[0], getAstForUselessInstruction());
45124
45155
  }
@@ -45140,7 +45171,7 @@ astParsingFunctions.__if__ = function(content) {
45140
45171
  makeChildrenUseless(content.children);
45141
45172
  }
45142
45173
  }
45143
- if (content.parent.childIndex === content.parent.children.length - 1 || content.parent.childIndex < content.parent.children.length - 1 && !["__elif__", "__else__"].includes(content.parent.children[content.parent.childIndex + 1].name)) {
45174
+ if (isLoneIf) {
45144
45175
  var includeEnd = true;
45145
45176
  if (enableOptimization && currentRuleEvent !== "__subroutine__" && content.parent.childIndex === content.parent.children.length - 1) {
45146
45177
  var root = content;
@@ -45156,6 +45187,16 @@ astParsingFunctions.__if__ = function(content) {
45156
45187
  }
45157
45188
  }
45158
45189
  }
45190
+ if (optimizeForSize2 && (content.args[0].name === "__not__" || content.children.length === 1 && includeEnd && ["__equals__", "__inequals__", "__greaterThan__", "__greaterThanOrEquals__", "__lessThan__", "__lessThanOrEquals__"].includes(content.args[0].name))) {
45191
+ let label = "__label_if_" + getUniqueNumber() + "__";
45192
+ content.parent.children.splice(content.parent.childIndex + 1, 0, ...content.children, new Ast2(label, [], [], "Label"), getAstForUselessInstruction());
45193
+ if (content.args[0].name === "__not__") {
45194
+ content.args[0] = content.args[0].args[0];
45195
+ } else {
45196
+ content.args[0] = astParsingFunctions.__not__(new Ast2("__not__", [content.args[0]]));
45197
+ }
45198
+ return new Ast2("__skipIf__", [content.args[0], new Ast2("__distanceTo__", [new Ast2(label, [], [], "Label")])]);
45199
+ }
45159
45200
  if (includeEnd) {
45160
45201
  content.parent.children.splice(content.parent.childIndex + 1, 0, getAstForEnd());
45161
45202
  }
@@ -45383,6 +45424,14 @@ astParsingFunctions.__negate__ = function(content) {
45383
45424
  };
45384
45425
 
45385
45426
  // src/compiler/functions/__not__.ts
45427
+ var inverseComparisonMapping = {
45428
+ "__equals__": "__inequals__",
45429
+ "__inequals__": "__equals__",
45430
+ "__greaterThan__": "__lessThanOrEquals__",
45431
+ "__greaterThanOrEquals__": "__lessThan__",
45432
+ "__lessThan__": "__greaterThanOrEquals__",
45433
+ "__lessThanOrEquals__": "__greaterThan__"
45434
+ };
45386
45435
  astParsingFunctions.__not__ = function(content) {
45387
45436
  if (enableOptimization) {
45388
45437
  if (isDefinitelyFalsy(content.args[0])) {
@@ -45400,6 +45449,9 @@ astParsingFunctions.__not__ = function(content) {
45400
45449
  if (content.args[0].name === ".isDead") {
45401
45450
  return new Ast2(".isAlive", [content.args[0].args[0]]);
45402
45451
  }
45452
+ if (content.args[0].name in inverseComparisonMapping) {
45453
+ return new Ast2(inverseComparisonMapping[content.args[0].name], content.args[0].args);
45454
+ }
45403
45455
  }
45404
45456
  return content;
45405
45457
  };
@@ -46819,16 +46871,18 @@ function compressToString(compressionInfo) {
46819
46871
  }
46820
46872
  function getDecompressionAst(compressedString, compressionInfo) {
46821
46873
  let { minDecimalPlace, maxDecimalPlace, offset, arrayType } = compressionInfo;
46874
+ let formulaAlphabet = useVariableForCompressionAlphabet ? "__compressionAlphabet__" : "x.last()";
46875
+ let formulaCompressedArray = useVariableForCompressionAlphabet ? `$compressedString.split(null[0])` : `[e.concat(${escapeString(alphabet2, false)}) for e in $compressedString.split(null[0])]`;
46822
46876
  if (arrayType === "number") {
46823
- let decompressionFormula = Array(Math.ceil((maxDecimalPlace - minDecimalPlace) / 2)).fill(0).map((x, i) => i).map((x) => `${Math.pow(100, x + minDecimalPlace / 2)}*x.last().strIndex(x[0].charAt(${x}))`).join(" + ");
46824
- return parseOpyMacro(`[${decompressionFormula} - ${offset} for x in [e.concat(${escapeString(alphabet2, false)}) for e in $compressedString.split(null[0])]]`, ["$compressedString"], [compressedString]);
46877
+ let decompressionFormula = Array(Math.ceil((maxDecimalPlace - minDecimalPlace) / 2)).fill(0).map((x, i) => i).map((x) => `${Math.pow(100, x + minDecimalPlace / 2)}*${formulaAlphabet}.strIndex(x.charAt(${x}))`).join(" + ");
46878
+ return parseOpyMacro(`[${decompressionFormula} - ${offset} for x in ${formulaCompressedArray}]`, ["$compressedString"], [compressedString]);
46825
46879
  } else {
46826
46880
  let decompressionFormulas = Array(3).fill(0).map((_, h) => {
46827
46881
  return Array(Math.ceil((maxDecimalPlace - minDecimalPlace) / 2)).fill(0).map((x, i) => i).map((x) => `
46828
- ${Math.pow(100, x + minDecimalPlace / 2)}*x.last().strIndex(x[0].charAt(${x + Math.ceil((maxDecimalPlace - minDecimalPlace) / 2) * h}))
46882
+ ${Math.pow(100, x + minDecimalPlace / 2)}*${formulaAlphabet}.strIndex(x.charAt(${x + Math.ceil((maxDecimalPlace - minDecimalPlace) / 2) * h}))
46829
46883
  `).join(" + ");
46830
46884
  });
46831
- return parseOpyMacro(`[vect(${decompressionFormulas[0]},${decompressionFormulas[2]},${decompressionFormulas[1]}) - vect(1,1,1)*${offset} for x in [e.concat(${escapeString(alphabet2, false)}) for e in $compressedString.split(null[0])]]`, ["$compressedString"], [compressedString]);
46885
+ return parseOpyMacro(`[vect(${decompressionFormulas[0]},${decompressionFormulas[2]},${decompressionFormulas[1]}) - vect(1,1,1)*${offset} for x in ${formulaCompressedArray}]`, ["$compressedString"], [compressedString]);
46832
46886
  }
46833
46887
  }
46834
46888
  astParsingFunctions.compressed = function(content) {
@@ -51548,7 +51602,8 @@ var actionKw = (
51548
51602
  {
51549
51603
  "name": "name",
51550
51604
  "description": "The name to be forced.",
51551
- "type": "String"
51605
+ "type": "String",
51606
+ canReplaceEmptyStringByEmptyArray: true
51552
51607
  }
51553
51608
  ],
51554
51609
  "return": "void",
@@ -53113,19 +53168,22 @@ var actionKw = (
53113
53168
  "name": "header",
53114
53169
  "description": "The text to be displayed (can be blank)",
53115
53170
  "type": "Object",
53116
- "default": null
53171
+ "default": null,
53172
+ canReplaceEmptyStringByEmptyArray: true
53117
53173
  },
53118
53174
  {
53119
53175
  "name": "subheader",
53120
53176
  "description": "The subheader text to be displayed (can be blank)",
53121
53177
  "type": "Object",
53122
- "default": null
53178
+ "default": null,
53179
+ canReplaceEmptyStringByEmptyArray: true
53123
53180
  },
53124
53181
  {
53125
53182
  "name": "text",
53126
53183
  "description": "The body text to be displayed (can be blank)",
53127
53184
  "type": "Object",
53128
- "default": null
53185
+ "default": null,
53186
+ canReplaceEmptyStringByEmptyArray: true
53129
53187
  },
53130
53188
  {
53131
53189
  "name": "location",
@@ -59619,7 +59677,10 @@ var valueFuncKw = (
59619
59677
  {
59620
59678
  "name": "condition",
59621
59679
  "description": "The mapping expression that is evaluated for each element of the copied array. Use the current array element value to reference the element of the array currently being considered.",
59622
- "type": "bool"
59680
+ "type": [
59681
+ "Object",
59682
+ "Array"
59683
+ ]
59623
59684
  }
59624
59685
  ],
59625
59686
  "isConstant": true,
@@ -60141,7 +60202,8 @@ var valueFuncKw = (
60141
60202
  {
60142
60203
  "name": "string",
60143
60204
  "description": "The String value whose character to acquire.",
60144
- "type": "String"
60205
+ "type": "String",
60206
+ canReplaceEmptyStringByEmptyArray: true
60145
60207
  },
60146
60208
  {
60147
60209
  "name": "index",
@@ -60177,12 +60239,14 @@ var valueFuncKw = (
60177
60239
  {
60178
60240
  "name": "string",
60179
60241
  "description": "The String Value from which to search for the character.",
60180
- "type": "String"
60242
+ "type": "String",
60243
+ canReplaceEmptyStringByEmptyArray: true
60181
60244
  },
60182
60245
  {
60183
60246
  "name": "character",
60184
60247
  "description": "The character for which to search",
60185
- "type": "String"
60248
+ "type": "String",
60249
+ canReplaceEmptyStringByEmptyArray: true
60186
60250
  }
60187
60251
  ],
60188
60252
  isConstant: true,
@@ -60211,17 +60275,20 @@ var valueFuncKw = (
60211
60275
  {
60212
60276
  "name": "string",
60213
60277
  "description": "The String Value with which to search for replacements.",
60214
- "type": "String"
60278
+ "type": "String",
60279
+ canReplaceEmptyStringByEmptyArray: true
60215
60280
  },
60216
60281
  {
60217
60282
  "name": "pattern",
60218
60283
  "description": "The String pattern to be replaced.",
60219
- "type": "String"
60284
+ "type": "String",
60285
+ canReplaceEmptyStringByEmptyArray: true
60220
60286
  },
60221
60287
  {
60222
60288
  "name": "replacement",
60223
60289
  "description": "The String Value with which to replace the pattern String",
60224
- "type": "String"
60290
+ "type": "String",
60291
+ canReplaceEmptyStringByEmptyArray: true
60225
60292
  }
60226
60293
  ],
60227
60294
  isConstant: true,
@@ -60250,12 +60317,14 @@ var valueFuncKw = (
60250
60317
  {
60251
60318
  "name": "string",
60252
60319
  "description": "The String Value to split.",
60253
- "type": "String"
60320
+ "type": "String",
60321
+ canReplaceEmptyStringByEmptyArray: true
60254
60322
  },
60255
60323
  {
60256
60324
  "name": "separator",
60257
60325
  "description": "The separator String with which to split the String Value.",
60258
- "type": "String"
60326
+ "type": "String",
60327
+ canReplaceEmptyStringByEmptyArray: true
60259
60328
  }
60260
60329
  ],
60261
60330
  "return": {
@@ -60286,7 +60355,8 @@ var valueFuncKw = (
60286
60355
  {
60287
60356
  "name": "string",
60288
60357
  "description": "The string value from which to build the substring.",
60289
- "type": "String"
60358
+ "type": "String",
60359
+ canReplaceEmptyStringByEmptyArray: true
60290
60360
  },
60291
60361
  {
60292
60362
  "name": "substringStartIndex",
@@ -64050,12 +64120,14 @@ var valueFuncKw = (
64050
64120
  {
64051
64121
  "name": "string",
64052
64122
  "description": "The string in which to search for the specified substring.",
64053
- "type": "String"
64123
+ "type": "String",
64124
+ canReplaceEmptyStringByEmptyArray: true
64054
64125
  },
64055
64126
  {
64056
64127
  "name": "substring",
64057
64128
  "description": "The substring for which to search.",
64058
- "type": "String"
64129
+ "type": "String",
64130
+ canReplaceEmptyStringByEmptyArray: true
64059
64131
  }
64060
64132
  ],
64061
64133
  "isConstant": true,
@@ -64083,7 +64155,8 @@ var valueFuncKw = (
64083
64155
  {
64084
64156
  "name": "string",
64085
64157
  "description": "The string whose characters to count.",
64086
- "type": "String"
64158
+ "type": "String",
64159
+ canReplaceEmptyStringByEmptyArray: true
64087
64160
  }
64088
64161
  ],
64089
64162
  "isConstant": true,
@@ -64482,6 +64555,7 @@ var valueFuncKw = (
64482
64555
  // src/compiler/astToWorkshop.ts
64483
64556
  function astRulesToWs(rules) {
64484
64557
  var compiledRules = [];
64558
+ var ruleElementCounts = [];
64485
64559
  for (var rule of rules) {
64486
64560
  setCurrentRuleConditions([]);
64487
64561
  var result = "";
@@ -64530,9 +64604,11 @@ function astRulesToWs(rules) {
64530
64604
  popRulePrefixStack();
64531
64605
  continue;
64532
64606
  }
64607
+ let elementsBefore = nbElements;
64533
64608
  if (rule.ruleAttributes.isDisabled) {
64534
64609
  result += tows("__disabled__", ruleKw) + " ";
64535
64610
  }
64611
+ let oldRuleName = rule.ruleAttributes.name;
64536
64612
  let finalRuleName = applyRulePrefixTemplate(rule);
64537
64613
  result += tows("__rule__", ruleKw) + " (";
64538
64614
  result += escapeBadWords(escapeString(finalRuleName, true));
@@ -64572,12 +64648,23 @@ function astRulesToWs(rules) {
64572
64648
  }
64573
64649
  result += "}\n\n";
64574
64650
  incrementNbElements();
64651
+ let ruleElements = nbElements - elementsBefore;
64652
+ if (debugElementCount && ruleElements > 1) {
64653
+ let { filePath } = getRuleFilePath(rule.fileStack);
64654
+ ruleElementCounts.push({ name: oldRuleName, file: filePath, elements: ruleElements });
64655
+ result = "//" + ruleElements + " element" + (ruleElements !== 1 ? "s" : "") + "\n" + result;
64656
+ }
64575
64657
  compiledRules.push(result);
64576
64658
  }
64659
+ let elementCountSummary = "";
64660
+ if (debugElementCount && ruleElementCounts.length > 0) {
64661
+ ruleElementCounts.sort((a, b) => b.elements - a.elements);
64662
+ elementCountSummary = "/* Element count: (total " + nbElements + ")\n\n" + ruleElementCounts.map((r) => ("" + r.elements).padStart(5, " ") + ": rule " + escapeString(r.name, false) + (r.file ? " (" + r.file + ")" : "")).join("\n") + "\n\n*/\n\n";
64663
+ }
64577
64664
  setOptimizationEnabled2(true);
64578
64665
  setOptimizeStrict2(false);
64579
64666
  setOptimizationForSize2(false);
64580
- return compiledRules;
64667
+ return { compiledRules, elementCountSummary };
64581
64668
  }
64582
64669
  function astRuleConditionToWs(condition) {
64583
64670
  var funcToOpMapping = {
@@ -64589,6 +64676,7 @@ function astRuleConditionToWs(condition) {
64589
64676
  __greaterThan__: ">"
64590
64677
  };
64591
64678
  var result = "";
64679
+ let nbElementsBefore = nbElements;
64592
64680
  if (condition.comment) {
64593
64681
  result += tabLevel(2) + escapeString(condition.comment.trim(), true) + "\n";
64594
64682
  }
@@ -64623,21 +64711,25 @@ function astRuleConditionToWs(condition) {
64623
64711
  incrementNbElements(Math.floor(nbHeroesInValue / 2));
64624
64712
  result += " " + funcToOpMapping[condition.name] + " ";
64625
64713
  resetNbHeroesInValue();
64626
- result += astToWs(condition.args[1]) + ";\n";
64714
+ result += astToWs(condition.args[1]) + ";";
64627
64715
  incrementNbElements(Math.floor(nbHeroesInValue / 2));
64628
64716
  } else {
64629
64717
  resetNbHeroesInValue();
64630
64718
  incrementNbElements();
64631
64719
  if (condition.name === "__not__") {
64632
- result += tabLevel(2) + astToWs(condition.args[0]) + " == " + tows("false", valueFuncKw) + ";\n";
64720
+ result += tabLevel(2) + astToWs(condition.args[0]) + " == " + tows("false", valueFuncKw) + ";";
64633
64721
  } else if (condition.type === "bool") {
64634
- result += tabLevel(2) + astToWs(condition) + " == " + tows("true", valueFuncKw) + ";\n";
64722
+ result += tabLevel(2) + astToWs(condition) + " == " + tows("true", valueFuncKw) + ";";
64635
64723
  } else {
64636
- result += tabLevel(2) + astToWs(condition) + " != " + tows("false", valueFuncKw) + ";\n";
64724
+ result += tabLevel(2) + astToWs(condition) + " != " + tows("false", valueFuncKw) + ";";
64637
64725
  }
64638
64726
  incrementNbElements(Math.floor(nbHeroesInValue / 2));
64639
64727
  }
64640
64728
  decrementNbElements();
64729
+ if (debugElementCount) {
64730
+ result += " // " + (nbElements - nbElementsBefore) + " element" + (nbElements - nbElementsBefore !== 1 ? "s" : "");
64731
+ }
64732
+ result += "\n";
64641
64733
  return result;
64642
64734
  }
64643
64735
  function astActionToWs(action, nbTabs2) {
@@ -64671,6 +64763,7 @@ function astActionToWs(action, nbTabs2) {
64671
64763
  if (action.type !== "void") {
64672
64764
  error("Expected an action, but got " + functionNameToString(action) + " which is a value", action.fileStack);
64673
64765
  }
64766
+ let nbElementsBefore = nbElements;
64674
64767
  let result = "";
64675
64768
  if (action.name === "pass" && !action.comment) {
64676
64769
  action.comment = "pass";
@@ -64679,7 +64772,11 @@ function astActionToWs(action, nbTabs2) {
64679
64772
  result += `${tabLevel(nbTabs2)}${escapeString(action.comment.trim(), true)}
64680
64773
  `;
64681
64774
  }
64682
- result += tabLevel(nbTabs2) + astToWs(action) + ";\n";
64775
+ result += tabLevel(nbTabs2) + astToWs(action) + ";";
64776
+ if (debugElementCount) {
64777
+ result += " // " + (nbElements - nbElementsBefore) + " element" + (nbElements - nbElementsBefore !== 1 ? "s" : "");
64778
+ }
64779
+ result += "\n";
64683
64780
  let oldEnableOptimization = enableOptimization;
64684
64781
  let oldOptimizeForSize = optimizeForSize2;
64685
64782
  let oldOptimizeStrict = optimizeStrict;
@@ -64745,6 +64842,24 @@ function astToWs(content) {
64745
64842
  return astToWs(new Ast2("Vector.BACKWARD"));
64746
64843
  }
64747
64844
  if (optimizeForSize2 && !(x === 0 && y === 0 && z === 0)) {
64845
+ let vectorPairMapping = {
64846
+ "1,1,0": ["left", "up"],
64847
+ "1,-1,0": ["left", "down"],
64848
+ "1,0,1": ["left", "forward"],
64849
+ "1,0,-1": ["left", "backward"],
64850
+ "-1,1,0": ["right", "up"],
64851
+ "-1,-1,0": ["right", "down"],
64852
+ "-1,0,1": ["right", "forward"],
64853
+ "-1,0,-1": ["right", "backward"],
64854
+ "0,1,1": ["up", "forward"],
64855
+ "0,1,-1": ["up", "backward"],
64856
+ "0,-1,1": ["down", "forward"],
64857
+ "0,-1,-1": ["down", "backward"]
64858
+ };
64859
+ let vectorArgs = x + "," + y + "," + z;
64860
+ if (vectorArgs in vectorPairMapping) {
64861
+ return astToWs(new Ast2("__add__", [new Ast2("Vector." + vectorPairMapping[vectorArgs][0].toUpperCase()), new Ast2("Vector." + vectorPairMapping[vectorArgs][1].toUpperCase())]));
64862
+ }
64748
64863
  if (y === 0 && z === 0) {
64749
64864
  return astToWs(new Ast2("__multiply__", [content.args[0], new Ast2("Vector.LEFT")]));
64750
64865
  }
@@ -64787,6 +64902,14 @@ function astToWs(content) {
64787
64902
  }
64788
64903
  } else if (replacementForTeam1 !== "" && content.args[i].name === "__team__" && content.args[i].args[0].name === "1") {
64789
64904
  content.args[i] = new Ast2(replacementForTeam1);
64905
+ } else if (content.args[i].name === "__customString__" && content.args[i].args[0].name === "") {
64906
+ if (argInfo.canReplaceEmptyStringByEmptyArray || replacementForEmptyString === "emptyArray") {
64907
+ content.args[i] = getAstForEmptyArray();
64908
+ } else if (replacementForEmptyString === "variable") {
64909
+ content.args[i] = new Ast2("__globalVar__", [new Ast2("__emptyString__", [], [], "GlobalVariable")]);
64910
+ } else {
64911
+ content.args[i] = new Ast2(".charAt", [getAstForEmptyArray(), getAstForNull()]);
64912
+ }
64790
64913
  }
64791
64914
  }
64792
64915
  }
@@ -65204,26 +65327,41 @@ function splitCustomString(tokens, args) {
65204
65327
  }
65205
65328
  return new Ast2("__customString__", [new Ast2(result, [], [], "CustomStringLiteral")].concat(resultArgs));
65206
65329
  }
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
- }
65330
+ function getRuleFilePath(fileStackForRule) {
65214
65331
  let fileName = "";
65215
65332
  let filePath = "";
65216
65333
  for (let k = fileStackForRule.length - 1; k >= 0; k--) {
65217
65334
  if (fileStackForRule[k].path) {
65218
65335
  let fullPath = fileStackForRule[k].path.replace(/\\/g, "/");
65219
- let baseName = fullPath.substring(fullPath.lastIndexOf("/") + 1);
65220
- fileName = baseName.replace(/\.opy$/i, "");
65336
+ fileName = fullPath.substring(fullPath.lastIndexOf("/") + 1);
65221
65337
  filePath = fullPath.startsWith(rootPath) ? fullPath.substring(rootPath.length) : fullPath;
65222
- filePath = filePath.replace(/\.opy$/i, "");
65223
65338
  break;
65224
65339
  }
65225
65340
  }
65226
- let template = rulePrefixTemplate || 'f"[{$prefix}] {$rule}" if $prefix and $rule else $rule';
65341
+ let pathParts = filePath.split("/");
65342
+ let resolvedPathParts = [];
65343
+ for (let part of pathParts) {
65344
+ if (part === "..") {
65345
+ if (resolvedPathParts.length > 0) {
65346
+ resolvedPathParts.pop();
65347
+ }
65348
+ } else if (part !== ".") {
65349
+ resolvedPathParts.push(part);
65350
+ }
65351
+ }
65352
+ filePath = resolvedPathParts.join("/");
65353
+ return { fileName, filePath };
65354
+ }
65355
+ function applyRulePrefixTemplate(rule) {
65356
+ let ruleName = rule.ruleAttributes.name;
65357
+ let prefix = currentRulePrefix;
65358
+ if (!prefix && !rulePrefixTemplate) {
65359
+ return ruleName;
65360
+ }
65361
+ let { fileName, filePath } = getRuleFilePath(rule.fileStack);
65362
+ fileName = fileName.replace(/\.opy$/i, "");
65363
+ filePath = filePath.replace(/\.opy$/i, "");
65364
+ let template = rulePrefixTemplate || 'f"[{$prefix}] {$rule}" if $prefix and $rule and not $isDelimiter else $rule';
65227
65365
  let argNames = [
65228
65366
  "$rule",
65229
65367
  "$prefix",
@@ -65526,6 +65664,12 @@ async function compile(content, language = "en-US", _rootPath = "", _mainFileNam
65526
65664
  if (enableTagsSetup) {
65527
65665
  addVariable("__holygrail__", true, -1, getInternalFileStack());
65528
65666
  }
65667
+ if (replacementForEmptyString === "variable") {
65668
+ addVariable("__emptyString__", true, -1, getInternalFileStack(), tokenize("[].charAt(null)")[0].tokens);
65669
+ }
65670
+ if (useVariableForCompressionAlphabet) {
65671
+ addVariable("__compressionAlphabet__", true, -1, getInternalFileStack(), tokenize(escapeString(alphabet2, false))[0].tokens);
65672
+ }
65529
65673
  if (translationLanguages2.length > 0) {
65530
65674
  setTranslatedStrings(importFromPoFiles());
65531
65675
  }
@@ -65664,7 +65808,7 @@ rule "Disable inspector":
65664
65808
  encounteredWarnings: uniqueEncounteredWarnings,
65665
65809
  hiddenWarnings,
65666
65810
  enumMembers,
65667
- nbElements: nbElements2,
65811
+ nbElements,
65668
65812
  activatedExtensions,
65669
65813
  spentExtensionPoints,
65670
65814
  availableExtensionPoints,
@@ -65677,16 +65821,16 @@ function compileRules(astRules) {
65677
65821
  if (DEBUG_MODE) {
65678
65822
  console.log(parsedAstRules);
65679
65823
  }
65680
- var compiledRules = astRulesToWs(parsedAstRules).join("");
65824
+ var { compiledRules, elementCountSummary } = astRulesToWs(parsedAstRules);
65681
65825
  setFileStack(getInternalFileStack());
65682
- var result = compiledCustomGameSettings;
65826
+ var result = elementCountSummary + compiledCustomGameSettings;
65683
65827
  if (!excludeVariablesInCompilation) {
65684
65828
  result += generateVariablesField();
65685
65829
  result += generateSubroutinesField();
65686
65830
  }
65687
- result += compiledRules;
65688
- if (nbElements2 > ELEMENT_LIMIT) {
65689
- warn("w_element_limit", "The gamemode is over the element limit (" + nbElements2 + " > " + ELEMENT_LIMIT + " elements)");
65831
+ result += compiledRules.join("");
65832
+ if (nbElements > ELEMENT_LIMIT) {
65833
+ warn("w_element_limit", "The gamemode is over the element limit (" + nbElements + " > " + ELEMENT_LIMIT + " elements)");
65690
65834
  }
65691
65835
  var spentExtensionPoints = 0;
65692
65836
  for (var ext of activatedExtensions) {
@@ -65787,7 +65931,7 @@ function generateVariablesField() {
65787
65931
  }
65788
65932
  }
65789
65933
  if (result) {
65790
- result = tows("__variables__", ruleKw) + " {\n" + result + "}\n";
65934
+ result = tows("__variables__", ruleKw) + " {\n" + result + "}\n\n";
65791
65935
  }
65792
65936
  return result;
65793
65937
  }
@@ -65835,7 +65979,7 @@ function generateSubroutinesField() {
65835
65979
  }
65836
65980
  }
65837
65981
  if (result) {
65838
- result = tows("__subroutines__", ruleKw) + " {\n" + result + "}\n";
65982
+ result = tows("__subroutines__", ruleKw) + " {\n" + result + "}\n\n";
65839
65983
  }
65840
65984
  return result;
65841
65985
  }
@@ -66091,7 +66235,7 @@ function compileCustomGameSettings(customGameSettings) {
66091
66235
  result2 += tabLevel(nbTabs, true) + "}";
66092
66236
  return result2;
66093
66237
  }
66094
- setCompiledCustomGameSettings(tows("__settings__", ruleKw) + deserializeObject(result) + "\n");
66238
+ setCompiledCustomGameSettings(tows("__settings__", ruleKw) + deserializeObject(result) + "\n\n");
66095
66239
  }
66096
66240
 
66097
66241
  // src/data/opy/textures.ts
@@ -66580,7 +66724,9 @@ function parseLines(lines) {
66580
66724
  }
66581
66725
  resetAstMacroLocalVariables();
66582
66726
  if (name in astMacros) {
66583
- error("Macro '" + name + "' already exists", lineMembers[0][1].fileStack);
66727
+ if (!allowMacroRedeclaration) {
66728
+ error("Macro '" + name + "' already exists", lineMembers[0][1].fileStack);
66729
+ }
66584
66730
  }
66585
66731
  if (name in funcKw) {
66586
66732
  error("Macro '" + name + "' is already a built-in function", lineMembers[0][1].fileStack);
@@ -68994,7 +69140,7 @@ Wrapping a string with \`___\` has the same caveats as putting a translated stri
68994
69140
  return: "String"
68995
69141
  },
68996
69142
  "compressed": {
68997
- "description": "Compresses in-place the specified array of numbers or vectors into a string, then returns the decompressed array. Strings take much fewer elements, so use this function if you are running out of elements.\n\nNote that numbers will get rounded to 3 decimal places, and vectors to 2 decimal places.\n\nThis function is only effective once the array has at least 18 vectors or 26 numbers.\n\nThis function can be more effective than `compress()` and `decompressNumbers()` / `decompressVectors()`, as it can apply optimizations if all numbers have a low amount of significant digits or if they are all positive.",
69143
+ "description": "Compresses in-place the specified array of numbers or vectors into a string, then returns the decompressed array. Strings take much fewer elements, so use this function if you are running out of elements.\n\nNote that numbers will get rounded to 3 decimal places, and vectors to 2 decimal places.\n\nThis function is only effective once the array has at least 5 vectors or 7 numbers (depending on the complexity; use `#!debugElementCount` to compare).\n\nThis function can be more effective than `compress()` and `decompressNumbers()` / `decompressVectors()`, as it can apply optimizations if all numbers have a low amount of significant digits or if they are all positive.",
68998
69144
  "args": [
68999
69145
  {
69000
69146
  "name": "array",
@@ -69553,9 +69699,11 @@ var replacementFor1 = "";
69553
69699
  var setReplacementFor1 = (replacement) => replacementFor1 = replacement;
69554
69700
  var replacementForTeam1 = "";
69555
69701
  var setReplacementForTeam1 = (replacement) => replacementForTeam1 = replacement;
69556
- var nbElements2;
69557
- var incrementNbElements = (amount = 1) => nbElements2 += amount;
69558
- var decrementNbElements = (amount = 1) => nbElements2 -= amount;
69702
+ var replacementForEmptyString = "";
69703
+ var setReplacementForEmptyString = (replacement) => replacementForEmptyString = replacement;
69704
+ var nbElements;
69705
+ var incrementNbElements = (amount = 1) => nbElements += amount;
69706
+ var decrementNbElements = (amount = 1) => nbElements -= amount;
69559
69707
  var nbHeroesInValue;
69560
69708
  var resetNbHeroesInValue = () => nbHeroesInValue = 0;
69561
69709
  var incrementNbHeroesInValue = (amount = 1) => nbHeroesInValue += amount;
@@ -69592,6 +69740,10 @@ var playervarInitRuleName;
69592
69740
  var setPlayervarInitRuleName = (name) => playervarInitRuleName = name;
69593
69741
  var disableInspector = false;
69594
69742
  var setDisableInspector = (disable) => disableInspector = disable;
69743
+ var debugElementCount = false;
69744
+ var setDebugElementCount = (debug3) => debugElementCount = debug3;
69745
+ var allowMacroRedeclaration = false;
69746
+ var setAllowMacroRedeclaration = (allow) => allowMacroRedeclaration = allow;
69595
69747
  var rulePrefixStack = [];
69596
69748
  var currentRulePrefix = "";
69597
69749
  var setCurrentRulePrefix = (prefix) => currentRulePrefix = prefix;
@@ -69603,6 +69755,8 @@ var rulePrefixTemplate = "";
69603
69755
  var setRulePrefixTemplate = (template) => rulePrefixTemplate = template;
69604
69756
  var rulePrefixTemplateFilestack = [];
69605
69757
  var setRulePrefixTemplateFilestack = (filestack) => rulePrefixTemplateFilestack = filestack;
69758
+ var useVariableForCompressionAlphabet = false;
69759
+ var setUseVariableForCompressionAlphabet = (use) => useVariableForCompressionAlphabet = use;
69606
69760
  var decompilerGotos;
69607
69761
  var resetDecompilerGotos = () => decompilerGotos = [];
69608
69762
  var nbTabs;
@@ -69654,7 +69808,8 @@ function resetGlobalVariables(language) {
69654
69808
  replacementFor0 = "";
69655
69809
  replacementFor1 = "";
69656
69810
  replacementForTeam1 = "";
69657
- nbElements2 = 0;
69811
+ replacementForEmptyString = "";
69812
+ nbElements = 0;
69658
69813
  activatedExtensions = [];
69659
69814
  availableExtensionPoints = 0;
69660
69815
  enableTagsSetup = false;
@@ -69671,6 +69826,8 @@ function resetGlobalVariables(language) {
69671
69826
  globalvarInitRuleName = "Initialize global variables";
69672
69827
  playervarInitRuleName = "Initialize player variables";
69673
69828
  disableInspector = false;
69829
+ debugElementCount = false;
69830
+ allowMacroRedeclaration = false;
69674
69831
  keepUnusedTranslations = false;
69675
69832
  disableTranslationSourceLines = false;
69676
69833
  usedMaps = /* @__PURE__ */ new Set();
@@ -69679,6 +69836,7 @@ function resetGlobalVariables(language) {
69679
69836
  currentRulePrefix = "";
69680
69837
  rulePrefixTemplate = "";
69681
69838
  rulePrefixTemplateFilestack = [];
69839
+ useVariableForCompressionAlphabet = false;
69682
69840
  }
69683
69841
  var operatorPrecedence = {
69684
69842
  "=": 1,
@@ -71957,10 +72115,16 @@ var key;
71957
72115
 
71958
72116
  // src/data/opy/preprocessing.ts
71959
72117
  var preprocessingDirectives = {
72118
+ "allowMacroRedeclaration": {
72119
+ "description": "If enabled, redefining a `macro` or `#!define` will not throw an error but will overwrite the previous definition. Can be useful for OOP-like projects where the same codebase is used for multiple different gamemodes."
72120
+ },
71960
72121
  "define": {
71961
72122
  "description": '**Warning**: This directive performs a text-based replacement! Use `macro` or `const` instead, unless absolutely necessary.\n\nCreates a macro, like in C/C++. Macros must be defined before any code. Examples:\n\n #!define currentSectionWalls A\n #!define GAME_NOT_STARTED 3`\n\nFunction macros are supported as well:\n\n #!define getFirstAvailableMei() [player for player in getPlayers(Team.2) if not player.isFighting][0]\n #!define spawnMei(type, location) getFirstAvailableMei().meiType = type\\\n wait(0.1)\\\n getFirstAvailableMei().teleport(location)\\\n getFirstAvailableMei().isFighting = true\n\nNote the usage of the backslashed lines.\n\nJS scripts can be inserted with the special `__script__` function:\n\n #!define addFive(x) __script__("addfive.js")\n\nwhere the `addfive.js` script contains `x+5` (no `return`).\n\nArguments of JS scripts are inserted automatically at the beginning (so `addFive(123)` would cause `var x = 123;` to be inserted). The script is then evaluated using `eval()`.\n\nA `vect()` function is also inserted, so that `vect(1,2,3)` returns an object with the correct properties and `toString()` function.\n\nWhen resolving the macro, the indentation on the macro call is prepended to each line of the replacement.\n',
71962
72123
  "snippet": "define $0"
71963
72124
  },
72125
+ "debugElementCount": {
72126
+ "description": "Generates a summary of the number of elements used by each rule at the top of the compilation result (sorted by element count descending), and adds a comment with the element count before each rule and after each condition/action."
72127
+ },
71964
72128
  "disableInspector": {
71965
72129
  "description": "Adds a rule to disable the inspector at the very start of the gamemode."
71966
72130
  },
@@ -72075,6 +72239,16 @@ rule "Integrity check":
72075
72239
  @Condition getControlScoringTeam() != Team.1
72076
72240
  print("This gamemode cannot be played!")
72077
72241
  \`\`\`
72242
+ `
72243
+ },
72244
+ "replaceEmptyStringByEmptyArray": {
72245
+ "description": `
72246
+ Replaces all instances of "" (empty string) by [] (empty array). WARNING: This might break your code in some cases (eg, \`.concat([])\` won't work because it unrolls the array)! Only use this if you are sure that it won't cause issues in your gamemode. Size optimizations must be enabled.
72247
+ `
72248
+ },
72249
+ "replaceEmptyStringByVariable": {
72250
+ "description": `
72251
+ Replaces all instances of "" (empty string) by a global variable \`__emptyString__\`. This takes one more element per empty string than \`#!replaceEmptyStringByEmptyArray\`. Size optimizations must be enabled.
72078
72252
  `
72079
72253
  },
72080
72254
  "translations": {
@@ -72125,6 +72299,9 @@ You can specify \`noDetectionRule\` to not create the rule which sets the variab
72125
72299
  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.
72126
72300
  `
72127
72301
  },
72302
+ "useVariableForCompressionAlphabet": {
72303
+ "description": `If enabled, the compression functions will use a global variable \`__compressionAlphabet__\` instead of a hardcoded string for the alphabet, which will save further elements.`
72304
+ },
72128
72305
  "extension": {
72129
72306
  "description": "You shouldn't be reading this. Contact Zezombye if you can see this.",
72130
72307
  "snippet": "You shouldn't be reading this. Contact Zezombye if you can see this."
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.6.1",
10
+ "version": "9.6.3",
11
11
  "readme": "README.md",
12
12
  "keywords": [
13
13
  "overpy",