overpy 9.6.1 → 9.6.2

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 +185 -48
  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,14 @@ ${scriptText}`, {
38681
38695
  setRulePrefixTemplateFilestack(_rulePrefixTemplateFilestack);
38682
38696
  return;
38683
38697
  }
38698
+ if (content2.startsWith("#!debugElementCount")) {
38699
+ setDebugElementCount(true);
38700
+ return;
38701
+ }
38702
+ if (content2.startsWith("#!allowMacroRedeclaration")) {
38703
+ setAllowMacroRedeclaration(true);
38704
+ return;
38705
+ }
38684
38706
  error("Unknown preprocessor directive '" + content2 + "'");
38685
38707
  }
38686
38708
  for (i = 0; i < content.length; moveCursor(1)) {
@@ -38989,7 +39011,11 @@ function parseMacro(initialMacroData) {
38989
39011
  replacement: isFunctionMacro ? trimmedMacroContent.substring(bracketPos[1] + 1).trim() : trimmedMacroContent.substring(trimmedMacroContent.indexOf(" ")).trim()
38990
39012
  };
38991
39013
  if (macros.some((m) => m.name === macro.name)) {
38992
- error("Macro '" + macro.name + "' is already defined");
39014
+ if (allowMacroRedeclaration) {
39015
+ macros.splice(macros.findIndex((m) => m.name === macro.name), 1);
39016
+ } else {
39017
+ error("Macro '" + macro.name + "' is already defined");
39018
+ }
38993
39019
  }
38994
39020
  if (macro.text === macro.replacement) {
38995
39021
  error("Macro '" + macro.name + "' references itself");
@@ -51548,7 +51574,8 @@ var actionKw = (
51548
51574
  {
51549
51575
  "name": "name",
51550
51576
  "description": "The name to be forced.",
51551
- "type": "String"
51577
+ "type": "String",
51578
+ canReplaceEmptyStringByEmptyArray: true
51552
51579
  }
51553
51580
  ],
51554
51581
  "return": "void",
@@ -53113,19 +53140,22 @@ var actionKw = (
53113
53140
  "name": "header",
53114
53141
  "description": "The text to be displayed (can be blank)",
53115
53142
  "type": "Object",
53116
- "default": null
53143
+ "default": null,
53144
+ canReplaceEmptyStringByEmptyArray: true
53117
53145
  },
53118
53146
  {
53119
53147
  "name": "subheader",
53120
53148
  "description": "The subheader text to be displayed (can be blank)",
53121
53149
  "type": "Object",
53122
- "default": null
53150
+ "default": null,
53151
+ canReplaceEmptyStringByEmptyArray: true
53123
53152
  },
53124
53153
  {
53125
53154
  "name": "text",
53126
53155
  "description": "The body text to be displayed (can be blank)",
53127
53156
  "type": "Object",
53128
- "default": null
53157
+ "default": null,
53158
+ canReplaceEmptyStringByEmptyArray: true
53129
53159
  },
53130
53160
  {
53131
53161
  "name": "location",
@@ -60141,7 +60171,8 @@ var valueFuncKw = (
60141
60171
  {
60142
60172
  "name": "string",
60143
60173
  "description": "The String value whose character to acquire.",
60144
- "type": "String"
60174
+ "type": "String",
60175
+ canReplaceEmptyStringByEmptyArray: true
60145
60176
  },
60146
60177
  {
60147
60178
  "name": "index",
@@ -60177,12 +60208,14 @@ var valueFuncKw = (
60177
60208
  {
60178
60209
  "name": "string",
60179
60210
  "description": "The String Value from which to search for the character.",
60180
- "type": "String"
60211
+ "type": "String",
60212
+ canReplaceEmptyStringByEmptyArray: true
60181
60213
  },
60182
60214
  {
60183
60215
  "name": "character",
60184
60216
  "description": "The character for which to search",
60185
- "type": "String"
60217
+ "type": "String",
60218
+ canReplaceEmptyStringByEmptyArray: true
60186
60219
  }
60187
60220
  ],
60188
60221
  isConstant: true,
@@ -60211,17 +60244,20 @@ var valueFuncKw = (
60211
60244
  {
60212
60245
  "name": "string",
60213
60246
  "description": "The String Value with which to search for replacements.",
60214
- "type": "String"
60247
+ "type": "String",
60248
+ canReplaceEmptyStringByEmptyArray: true
60215
60249
  },
60216
60250
  {
60217
60251
  "name": "pattern",
60218
60252
  "description": "The String pattern to be replaced.",
60219
- "type": "String"
60253
+ "type": "String",
60254
+ canReplaceEmptyStringByEmptyArray: true
60220
60255
  },
60221
60256
  {
60222
60257
  "name": "replacement",
60223
60258
  "description": "The String Value with which to replace the pattern String",
60224
- "type": "String"
60259
+ "type": "String",
60260
+ canReplaceEmptyStringByEmptyArray: true
60225
60261
  }
60226
60262
  ],
60227
60263
  isConstant: true,
@@ -60250,12 +60286,14 @@ var valueFuncKw = (
60250
60286
  {
60251
60287
  "name": "string",
60252
60288
  "description": "The String Value to split.",
60253
- "type": "String"
60289
+ "type": "String",
60290
+ canReplaceEmptyStringByEmptyArray: true
60254
60291
  },
60255
60292
  {
60256
60293
  "name": "separator",
60257
60294
  "description": "The separator String with which to split the String Value.",
60258
- "type": "String"
60295
+ "type": "String",
60296
+ canReplaceEmptyStringByEmptyArray: true
60259
60297
  }
60260
60298
  ],
60261
60299
  "return": {
@@ -60286,7 +60324,8 @@ var valueFuncKw = (
60286
60324
  {
60287
60325
  "name": "string",
60288
60326
  "description": "The string value from which to build the substring.",
60289
- "type": "String"
60327
+ "type": "String",
60328
+ canReplaceEmptyStringByEmptyArray: true
60290
60329
  },
60291
60330
  {
60292
60331
  "name": "substringStartIndex",
@@ -64050,12 +64089,14 @@ var valueFuncKw = (
64050
64089
  {
64051
64090
  "name": "string",
64052
64091
  "description": "The string in which to search for the specified substring.",
64053
- "type": "String"
64092
+ "type": "String",
64093
+ canReplaceEmptyStringByEmptyArray: true
64054
64094
  },
64055
64095
  {
64056
64096
  "name": "substring",
64057
64097
  "description": "The substring for which to search.",
64058
- "type": "String"
64098
+ "type": "String",
64099
+ canReplaceEmptyStringByEmptyArray: true
64059
64100
  }
64060
64101
  ],
64061
64102
  "isConstant": true,
@@ -64083,7 +64124,8 @@ var valueFuncKw = (
64083
64124
  {
64084
64125
  "name": "string",
64085
64126
  "description": "The string whose characters to count.",
64086
- "type": "String"
64127
+ "type": "String",
64128
+ canReplaceEmptyStringByEmptyArray: true
64087
64129
  }
64088
64130
  ],
64089
64131
  "isConstant": true,
@@ -64482,6 +64524,7 @@ var valueFuncKw = (
64482
64524
  // src/compiler/astToWorkshop.ts
64483
64525
  function astRulesToWs(rules) {
64484
64526
  var compiledRules = [];
64527
+ var ruleElementCounts = [];
64485
64528
  for (var rule of rules) {
64486
64529
  setCurrentRuleConditions([]);
64487
64530
  var result = "";
@@ -64530,9 +64573,11 @@ function astRulesToWs(rules) {
64530
64573
  popRulePrefixStack();
64531
64574
  continue;
64532
64575
  }
64576
+ let elementsBefore = nbElements;
64533
64577
  if (rule.ruleAttributes.isDisabled) {
64534
64578
  result += tows("__disabled__", ruleKw) + " ";
64535
64579
  }
64580
+ let oldRuleName = rule.ruleAttributes.name;
64536
64581
  let finalRuleName = applyRulePrefixTemplate(rule);
64537
64582
  result += tows("__rule__", ruleKw) + " (";
64538
64583
  result += escapeBadWords(escapeString(finalRuleName, true));
@@ -64572,12 +64617,23 @@ function astRulesToWs(rules) {
64572
64617
  }
64573
64618
  result += "}\n\n";
64574
64619
  incrementNbElements();
64620
+ let ruleElements = nbElements - elementsBefore;
64621
+ if (debugElementCount) {
64622
+ let { filePath } = getRuleFilePath(rule.fileStack);
64623
+ ruleElementCounts.push({ name: oldRuleName, file: filePath, elements: ruleElements });
64624
+ result = "//" + ruleElements + " element" + (ruleElements !== 1 ? "s" : "") + "\n" + result;
64625
+ }
64575
64626
  compiledRules.push(result);
64576
64627
  }
64628
+ let elementCountSummary = "";
64629
+ if (debugElementCount && ruleElementCounts.length > 0) {
64630
+ ruleElementCounts.sort((a, b) => b.elements - a.elements);
64631
+ 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";
64632
+ }
64577
64633
  setOptimizationEnabled2(true);
64578
64634
  setOptimizeStrict2(false);
64579
64635
  setOptimizationForSize2(false);
64580
- return compiledRules;
64636
+ return { compiledRules, elementCountSummary };
64581
64637
  }
64582
64638
  function astRuleConditionToWs(condition) {
64583
64639
  var funcToOpMapping = {
@@ -64589,6 +64645,7 @@ function astRuleConditionToWs(condition) {
64589
64645
  __greaterThan__: ">"
64590
64646
  };
64591
64647
  var result = "";
64648
+ let nbElementsBefore = nbElements;
64592
64649
  if (condition.comment) {
64593
64650
  result += tabLevel(2) + escapeString(condition.comment.trim(), true) + "\n";
64594
64651
  }
@@ -64623,21 +64680,25 @@ function astRuleConditionToWs(condition) {
64623
64680
  incrementNbElements(Math.floor(nbHeroesInValue / 2));
64624
64681
  result += " " + funcToOpMapping[condition.name] + " ";
64625
64682
  resetNbHeroesInValue();
64626
- result += astToWs(condition.args[1]) + ";\n";
64683
+ result += astToWs(condition.args[1]) + ";";
64627
64684
  incrementNbElements(Math.floor(nbHeroesInValue / 2));
64628
64685
  } else {
64629
64686
  resetNbHeroesInValue();
64630
64687
  incrementNbElements();
64631
64688
  if (condition.name === "__not__") {
64632
- result += tabLevel(2) + astToWs(condition.args[0]) + " == " + tows("false", valueFuncKw) + ";\n";
64689
+ result += tabLevel(2) + astToWs(condition.args[0]) + " == " + tows("false", valueFuncKw) + ";";
64633
64690
  } else if (condition.type === "bool") {
64634
- result += tabLevel(2) + astToWs(condition) + " == " + tows("true", valueFuncKw) + ";\n";
64691
+ result += tabLevel(2) + astToWs(condition) + " == " + tows("true", valueFuncKw) + ";";
64635
64692
  } else {
64636
- result += tabLevel(2) + astToWs(condition) + " != " + tows("false", valueFuncKw) + ";\n";
64693
+ result += tabLevel(2) + astToWs(condition) + " != " + tows("false", valueFuncKw) + ";";
64637
64694
  }
64638
64695
  incrementNbElements(Math.floor(nbHeroesInValue / 2));
64639
64696
  }
64640
64697
  decrementNbElements();
64698
+ if (debugElementCount) {
64699
+ result += " // " + (nbElements - nbElementsBefore) + " element" + (nbElements - nbElementsBefore !== 1 ? "s" : "");
64700
+ }
64701
+ result += "\n";
64641
64702
  return result;
64642
64703
  }
64643
64704
  function astActionToWs(action, nbTabs2) {
@@ -64671,6 +64732,7 @@ function astActionToWs(action, nbTabs2) {
64671
64732
  if (action.type !== "void") {
64672
64733
  error("Expected an action, but got " + functionNameToString(action) + " which is a value", action.fileStack);
64673
64734
  }
64735
+ let nbElementsBefore = nbElements;
64674
64736
  let result = "";
64675
64737
  if (action.name === "pass" && !action.comment) {
64676
64738
  action.comment = "pass";
@@ -64679,7 +64741,11 @@ function astActionToWs(action, nbTabs2) {
64679
64741
  result += `${tabLevel(nbTabs2)}${escapeString(action.comment.trim(), true)}
64680
64742
  `;
64681
64743
  }
64682
- result += tabLevel(nbTabs2) + astToWs(action) + ";\n";
64744
+ result += tabLevel(nbTabs2) + astToWs(action) + ";";
64745
+ if (debugElementCount) {
64746
+ result += " // " + (nbElements - nbElementsBefore) + " element" + (nbElements - nbElementsBefore !== 1 ? "s" : "");
64747
+ }
64748
+ result += "\n";
64683
64749
  let oldEnableOptimization = enableOptimization;
64684
64750
  let oldOptimizeForSize = optimizeForSize2;
64685
64751
  let oldOptimizeStrict = optimizeStrict;
@@ -64745,6 +64811,24 @@ function astToWs(content) {
64745
64811
  return astToWs(new Ast2("Vector.BACKWARD"));
64746
64812
  }
64747
64813
  if (optimizeForSize2 && !(x === 0 && y === 0 && z === 0)) {
64814
+ let vectorPairMapping = {
64815
+ "1,1,0": ["left", "up"],
64816
+ "1,-1,0": ["left", "down"],
64817
+ "1,0,1": ["left", "forward"],
64818
+ "1,0,-1": ["left", "backward"],
64819
+ "-1,1,0": ["right", "up"],
64820
+ "-1,-1,0": ["right", "down"],
64821
+ "-1,0,1": ["right", "forward"],
64822
+ "-1,0,-1": ["right", "backward"],
64823
+ "0,1,1": ["up", "forward"],
64824
+ "0,1,-1": ["up", "backward"],
64825
+ "0,-1,1": ["down", "forward"],
64826
+ "0,-1,-1": ["down", "backward"]
64827
+ };
64828
+ let vectorArgs = x + "," + y + "," + z;
64829
+ if (vectorArgs in vectorPairMapping) {
64830
+ return astToWs(new Ast2("__add__", [new Ast2("Vector." + vectorPairMapping[vectorArgs][0].toUpperCase()), new Ast2("Vector." + vectorPairMapping[vectorArgs][1].toUpperCase())]));
64831
+ }
64748
64832
  if (y === 0 && z === 0) {
64749
64833
  return astToWs(new Ast2("__multiply__", [content.args[0], new Ast2("Vector.LEFT")]));
64750
64834
  }
@@ -64787,6 +64871,14 @@ function astToWs(content) {
64787
64871
  }
64788
64872
  } else if (replacementForTeam1 !== "" && content.args[i].name === "__team__" && content.args[i].args[0].name === "1") {
64789
64873
  content.args[i] = new Ast2(replacementForTeam1);
64874
+ } else if (content.args[i].name === "__customString__" && content.args[i].args[0].name === "") {
64875
+ if (argInfo.canReplaceEmptyStringByEmptyArray || replacementForEmptyString === "emptyArray") {
64876
+ content.args[i] = getAstForEmptyArray();
64877
+ } else if (replacementForEmptyString === "variable") {
64878
+ content.args[i] = new Ast2("__globalVar__", [new Ast2("__emptyString__", [], [], "GlobalVariable")]);
64879
+ } else {
64880
+ content.args[i] = new Ast2(".charAt", [getAstForEmptyArray(), getAstForNull()]);
64881
+ }
64790
64882
  }
64791
64883
  }
64792
64884
  }
@@ -65204,26 +65296,41 @@ function splitCustomString(tokens, args) {
65204
65296
  }
65205
65297
  return new Ast2("__customString__", [new Ast2(result, [], [], "CustomStringLiteral")].concat(resultArgs));
65206
65298
  }
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
- }
65299
+ function getRuleFilePath(fileStackForRule) {
65214
65300
  let fileName = "";
65215
65301
  let filePath = "";
65216
65302
  for (let k = fileStackForRule.length - 1; k >= 0; k--) {
65217
65303
  if (fileStackForRule[k].path) {
65218
65304
  let fullPath = fileStackForRule[k].path.replace(/\\/g, "/");
65219
- let baseName = fullPath.substring(fullPath.lastIndexOf("/") + 1);
65220
- fileName = baseName.replace(/\.opy$/i, "");
65305
+ fileName = fullPath.substring(fullPath.lastIndexOf("/") + 1);
65221
65306
  filePath = fullPath.startsWith(rootPath) ? fullPath.substring(rootPath.length) : fullPath;
65222
- filePath = filePath.replace(/\.opy$/i, "");
65223
65307
  break;
65224
65308
  }
65225
65309
  }
65226
- let template = rulePrefixTemplate || 'f"[{$prefix}] {$rule}" if $prefix and $rule else $rule';
65310
+ let pathParts = filePath.split("/");
65311
+ let resolvedPathParts = [];
65312
+ for (let part of pathParts) {
65313
+ if (part === "..") {
65314
+ if (resolvedPathParts.length > 0) {
65315
+ resolvedPathParts.pop();
65316
+ }
65317
+ } else if (part !== ".") {
65318
+ resolvedPathParts.push(part);
65319
+ }
65320
+ }
65321
+ filePath = resolvedPathParts.join("/");
65322
+ return { fileName, filePath };
65323
+ }
65324
+ function applyRulePrefixTemplate(rule) {
65325
+ let ruleName = rule.ruleAttributes.name;
65326
+ let prefix = currentRulePrefix;
65327
+ if (!prefix && !rulePrefixTemplate) {
65328
+ return ruleName;
65329
+ }
65330
+ let { fileName, filePath } = getRuleFilePath(rule.fileStack);
65331
+ fileName = fileName.replace(/\.opy$/i, "");
65332
+ filePath = filePath.replace(/\.opy$/i, "");
65333
+ let template = rulePrefixTemplate || 'f"[{$prefix}] {$rule}" if $prefix and $rule and not $isDelimiter else $rule';
65227
65334
  let argNames = [
65228
65335
  "$rule",
65229
65336
  "$prefix",
@@ -65526,6 +65633,9 @@ async function compile(content, language = "en-US", _rootPath = "", _mainFileNam
65526
65633
  if (enableTagsSetup) {
65527
65634
  addVariable("__holygrail__", true, -1, getInternalFileStack());
65528
65635
  }
65636
+ if (replacementForEmptyString === "variable") {
65637
+ addVariable("__emptyString__", true, -1, getInternalFileStack(), tokenize("[].charAt(null)")[0].tokens);
65638
+ }
65529
65639
  if (translationLanguages2.length > 0) {
65530
65640
  setTranslatedStrings(importFromPoFiles());
65531
65641
  }
@@ -65664,7 +65774,7 @@ rule "Disable inspector":
65664
65774
  encounteredWarnings: uniqueEncounteredWarnings,
65665
65775
  hiddenWarnings,
65666
65776
  enumMembers,
65667
- nbElements: nbElements2,
65777
+ nbElements,
65668
65778
  activatedExtensions,
65669
65779
  spentExtensionPoints,
65670
65780
  availableExtensionPoints,
@@ -65677,16 +65787,16 @@ function compileRules(astRules) {
65677
65787
  if (DEBUG_MODE) {
65678
65788
  console.log(parsedAstRules);
65679
65789
  }
65680
- var compiledRules = astRulesToWs(parsedAstRules).join("");
65790
+ var { compiledRules, elementCountSummary } = astRulesToWs(parsedAstRules);
65681
65791
  setFileStack(getInternalFileStack());
65682
- var result = compiledCustomGameSettings;
65792
+ var result = elementCountSummary + compiledCustomGameSettings;
65683
65793
  if (!excludeVariablesInCompilation) {
65684
65794
  result += generateVariablesField();
65685
65795
  result += generateSubroutinesField();
65686
65796
  }
65687
- result += compiledRules;
65688
- if (nbElements2 > ELEMENT_LIMIT) {
65689
- warn("w_element_limit", "The gamemode is over the element limit (" + nbElements2 + " > " + ELEMENT_LIMIT + " elements)");
65797
+ result += compiledRules.join("");
65798
+ if (nbElements > ELEMENT_LIMIT) {
65799
+ warn("w_element_limit", "The gamemode is over the element limit (" + nbElements + " > " + ELEMENT_LIMIT + " elements)");
65690
65800
  }
65691
65801
  var spentExtensionPoints = 0;
65692
65802
  for (var ext of activatedExtensions) {
@@ -65787,7 +65897,7 @@ function generateVariablesField() {
65787
65897
  }
65788
65898
  }
65789
65899
  if (result) {
65790
- result = tows("__variables__", ruleKw) + " {\n" + result + "}\n";
65900
+ result = tows("__variables__", ruleKw) + " {\n" + result + "}\n\n";
65791
65901
  }
65792
65902
  return result;
65793
65903
  }
@@ -65835,7 +65945,7 @@ function generateSubroutinesField() {
65835
65945
  }
65836
65946
  }
65837
65947
  if (result) {
65838
- result = tows("__subroutines__", ruleKw) + " {\n" + result + "}\n";
65948
+ result = tows("__subroutines__", ruleKw) + " {\n" + result + "}\n\n";
65839
65949
  }
65840
65950
  return result;
65841
65951
  }
@@ -66091,7 +66201,7 @@ function compileCustomGameSettings(customGameSettings) {
66091
66201
  result2 += tabLevel(nbTabs, true) + "}";
66092
66202
  return result2;
66093
66203
  }
66094
- setCompiledCustomGameSettings(tows("__settings__", ruleKw) + deserializeObject(result) + "\n");
66204
+ setCompiledCustomGameSettings(tows("__settings__", ruleKw) + deserializeObject(result) + "\n\n");
66095
66205
  }
66096
66206
 
66097
66207
  // src/data/opy/textures.ts
@@ -66580,7 +66690,9 @@ function parseLines(lines) {
66580
66690
  }
66581
66691
  resetAstMacroLocalVariables();
66582
66692
  if (name in astMacros) {
66583
- error("Macro '" + name + "' already exists", lineMembers[0][1].fileStack);
66693
+ if (!allowMacroRedeclaration) {
66694
+ error("Macro '" + name + "' already exists", lineMembers[0][1].fileStack);
66695
+ }
66584
66696
  }
66585
66697
  if (name in funcKw) {
66586
66698
  error("Macro '" + name + "' is already a built-in function", lineMembers[0][1].fileStack);
@@ -69553,9 +69665,11 @@ var replacementFor1 = "";
69553
69665
  var setReplacementFor1 = (replacement) => replacementFor1 = replacement;
69554
69666
  var replacementForTeam1 = "";
69555
69667
  var setReplacementForTeam1 = (replacement) => replacementForTeam1 = replacement;
69556
- var nbElements2;
69557
- var incrementNbElements = (amount = 1) => nbElements2 += amount;
69558
- var decrementNbElements = (amount = 1) => nbElements2 -= amount;
69668
+ var replacementForEmptyString = "";
69669
+ var setReplacementForEmptyString = (replacement) => replacementForEmptyString = replacement;
69670
+ var nbElements;
69671
+ var incrementNbElements = (amount = 1) => nbElements += amount;
69672
+ var decrementNbElements = (amount = 1) => nbElements -= amount;
69559
69673
  var nbHeroesInValue;
69560
69674
  var resetNbHeroesInValue = () => nbHeroesInValue = 0;
69561
69675
  var incrementNbHeroesInValue = (amount = 1) => nbHeroesInValue += amount;
@@ -69592,6 +69706,10 @@ var playervarInitRuleName;
69592
69706
  var setPlayervarInitRuleName = (name) => playervarInitRuleName = name;
69593
69707
  var disableInspector = false;
69594
69708
  var setDisableInspector = (disable) => disableInspector = disable;
69709
+ var debugElementCount = false;
69710
+ var setDebugElementCount = (debug3) => debugElementCount = debug3;
69711
+ var allowMacroRedeclaration = false;
69712
+ var setAllowMacroRedeclaration = (allow) => allowMacroRedeclaration = allow;
69595
69713
  var rulePrefixStack = [];
69596
69714
  var currentRulePrefix = "";
69597
69715
  var setCurrentRulePrefix = (prefix) => currentRulePrefix = prefix;
@@ -69654,7 +69772,8 @@ function resetGlobalVariables(language) {
69654
69772
  replacementFor0 = "";
69655
69773
  replacementFor1 = "";
69656
69774
  replacementForTeam1 = "";
69657
- nbElements2 = 0;
69775
+ replacementForEmptyString = "";
69776
+ nbElements = 0;
69658
69777
  activatedExtensions = [];
69659
69778
  availableExtensionPoints = 0;
69660
69779
  enableTagsSetup = false;
@@ -69671,6 +69790,8 @@ function resetGlobalVariables(language) {
69671
69790
  globalvarInitRuleName = "Initialize global variables";
69672
69791
  playervarInitRuleName = "Initialize player variables";
69673
69792
  disableInspector = false;
69793
+ debugElementCount = false;
69794
+ allowMacroRedeclaration = false;
69674
69795
  keepUnusedTranslations = false;
69675
69796
  disableTranslationSourceLines = false;
69676
69797
  usedMaps = /* @__PURE__ */ new Set();
@@ -71957,10 +72078,16 @@ var key;
71957
72078
 
71958
72079
  // src/data/opy/preprocessing.ts
71959
72080
  var preprocessingDirectives = {
72081
+ "allowMacroRedeclaration": {
72082
+ "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."
72083
+ },
71960
72084
  "define": {
71961
72085
  "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
72086
  "snippet": "define $0"
71963
72087
  },
72088
+ "debugElementCount": {
72089
+ "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."
72090
+ },
71964
72091
  "disableInspector": {
71965
72092
  "description": "Adds a rule to disable the inspector at the very start of the gamemode."
71966
72093
  },
@@ -72075,6 +72202,16 @@ rule "Integrity check":
72075
72202
  @Condition getControlScoringTeam() != Team.1
72076
72203
  print("This gamemode cannot be played!")
72077
72204
  \`\`\`
72205
+ `
72206
+ },
72207
+ "replaceEmptyStringByEmptyArray": {
72208
+ "description": `
72209
+ 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.
72210
+ `
72211
+ },
72212
+ "replaceEmptyStringByVariable": {
72213
+ "description": `
72214
+ 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
72215
  `
72079
72216
  },
72080
72217
  "translations": {
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.2",
11
11
  "readme": "README.md",
12
12
  "keywords": [
13
13
  "overpy",