From 1902cfcc29843018d788b2b5b766426510ffb300 Mon Sep 17 00:00:00 2001 From: Frotty Date: Wed, 3 Jun 2026 01:03:15 +0200 Subject: [PATCH 1/4] Support pre 1.24 patches --- .../de/peeeq/wurstio/CompilationProcess.java | 2 +- .../peeeq/wurstio/WurstCompilerJassImpl.java | 2 +- .../languageserver/WurstBuildConfig.java | 11 ++++ .../languageserver/requests/MapRequest.java | 8 ++- .../java/de/peeeq/wurstscript/RunArgs.java | 25 +++++++- .../de/peeeq/wurstscript/WurstChecker.java | 8 ++- .../validation/WurstValidator.java | 23 ++++++++ .../tests/CompilationUnitTests.java | 57 +++++++++++++++++++ .../wurstscript/tests/WurstScriptTest.java | 13 +++++ 9 files changed, 144 insertions(+), 5 deletions(-) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java index eb70be15f..8bfe9cc50 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java @@ -107,7 +107,7 @@ public CompilationProcess(WurstGui gui, RunArgs runArgs) { File outputMapscript = timeTaker.measure("Print Jass", () -> writeMapscript(mapScript)); - if (!runArgs.isDisablePjass()) { + if (!runArgs.isDisablePjass() && !runArgs.isLegacyJassTypeChecks()) { boolean pjassError = timeTaker.measure("Run PJass", () -> runPjass(outputMapscript)); if (pjassError) return null; diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java index df8f58e3a..d38b50eca 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java @@ -81,7 +81,7 @@ public WurstCompilerJassImpl(TimeTaker timeTaker, @Nullable File projectFolder, this.runArgs = runArgs; this.errorHandler = new ErrorHandler(gui); this.parser = new WurstParser(errorHandler, gui); - this.checker = new WurstChecker(gui, errorHandler); + this.checker = new WurstChecker(gui, errorHandler, runArgs.isLegacyJassTypeChecks()); this.mapFileMpq = mapFileMpq; } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/WurstBuildConfig.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/WurstBuildConfig.java index 732851a5c..215d1571a 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/WurstBuildConfig.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/WurstBuildConfig.java @@ -88,6 +88,17 @@ public Wc3Patch wc3PatchOrReforged() { return wc3Patch().orElse(Wc3Patch.REFORGED); } + /** + * Whether the configured target is a patch before 1.24. These legacy patches ship + * Blizzard common.j/blizzard.j with return-type mismatches the Jass VM tolerates, + * so Jass type checks are relaxed and PJass is skipped for them. + */ + public boolean isPre124() { + return configuredGameVersion() + .map(version -> version.compareTo(new GameVersion("1.24")) < 0) + .orElse(false); + } + public GameVersion fallbackGameVersion() { return configuredGameVersion().orElse(GameVersion.VERSION_1_32); } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java index fe8916aa6..89500d51c 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java @@ -107,6 +107,12 @@ public MapRequest(WurstLanguageServer langServer, Optional map, List configuredVersion = this instanceof RunMap ? Optional.empty() @@ -198,7 +204,7 @@ protected File compileMap(File projectFolder, WurstGui gui, Optional mapCo File outFile = new File(buildDir, BUILD_COMPILED_JASS_NAME); Files.write(compiledMapScript.getBytes(Charsets.UTF_8), outFile); - if (!runArgs.isDisablePjass()) { + if (!runArgs.isDisablePjass() && !runArgs.isLegacyJassTypeChecks()) { gui.sendProgress("Running PJass"); timeTaker.beginPhase("Pjass execution"); Pjass.Result pJassResult = Pjass.runPjass(outFile, diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/RunArgs.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/RunArgs.java index 3717c05ab..221ba2ec5 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/RunArgs.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/RunArgs.java @@ -46,6 +46,7 @@ public class RunArgs { private final RunOption optionFixInstall; private final RunOption optionCopyMap; private final RunOption optionDisablePjass; + private final RunOption optionLegacyJassChecks; private final RunOption optionShowVersion; private final RunOption optionPrettyPrint; private final RunOption optionMeasureTimes; @@ -55,11 +56,22 @@ public class RunArgs { private final RunOption optionTestTimeout; private int functionSplitLimit = 10000; + /** + * When set, the build targets a legacy patch (pre-1.24) whose Blizzard-provided + * common.j/blizzard.j contain type mismatches the weakly-typed Jass VM tolerates + * (e.g. returning a real where an integer is declared). For such targets, Jass + * return-type mismatches are downgraded to warnings and PJass is skipped on the + * generated script, since PJass would reject the same Blizzard code. + */ + private boolean legacyJassTypeChecks = false; + private final RunOption optionBuild; public RunArgs with(String... additionalArgs) { - return new RunArgs(Stream.concat(Stream.of(args), Stream.of(additionalArgs)) + RunArgs result = new RunArgs(Stream.concat(Stream.of(args), Stream.of(additionalArgs)) .toArray(String[]::new)); + result.legacyJassTypeChecks = this.legacyJassTypeChecks; + return result; } private static class RunOption { @@ -126,6 +138,8 @@ public RunArgs(String... args) { optionHelp = addOption("help", "Prints this help message."); optionDisablePjass = addOption("noPJass", "Disables PJass checks for the generated code."); + optionLegacyJassChecks = addOption("legacyJassChecks", "Relax Jass type checks for legacy (pre-1.24) targets whose Blizzard-provided " + + "common.j/blizzard.j contain return-type mismatches the Jass VM tolerates. Also skips PJass on the generated script."); optionHotStartmap = addOption("hotstart", "Uses Jass Hot Code Reload (JHCR) to start the map."); optionHotReload = addOption("hotreload", "Reloads the mapscript after running the map with Jass Hot Code Reload (JHCR)."); @@ -244,6 +258,15 @@ public void setMapFile(String file) { mapFile = file; } + /** See {@link #legacyJassTypeChecks}. Set via the {@code -legacyJassChecks} flag or programmatically. */ + public boolean isLegacyJassTypeChecks() { + return legacyJassTypeChecks || optionLegacyJassChecks.isSet; + } + + public void setLegacyJassTypeChecks(boolean legacyJassTypeChecks) { + this.legacyJassTypeChecks = legacyJassTypeChecks; + } + public @Nullable String getOutFile() { return outFile; } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/WurstChecker.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/WurstChecker.java index a0acd43f2..f5d50c488 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/WurstChecker.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/WurstChecker.java @@ -16,10 +16,16 @@ public class WurstChecker { private final WurstGui gui; private final ErrorHandler errorHandler; + private final boolean legacyJassTypeChecks; public WurstChecker(WurstGui gui, ErrorHandler errorHandler) { + this(gui, errorHandler, false); + } + + public WurstChecker(WurstGui gui, ErrorHandler errorHandler, boolean legacyJassTypeChecks) { this.gui = gui; this.errorHandler = errorHandler; + this.legacyJassTypeChecks = legacyJassTypeChecks; } public void checkProg(WurstModel root, Collection toCheck) { @@ -48,7 +54,7 @@ public void checkProg(WurstModel root, Collection toCheck) { // validate the resource: - WurstValidator validator = new WurstValidator(root); + WurstValidator validator = new WurstValidator(root, legacyJassTypeChecks); validator.validate(toCheck); } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java index d8df0a417..23f8ef358 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java @@ -58,8 +58,20 @@ private enum Phase { LIGHT, HEAVY } private final HashMap> wrapperCalls = new HashMap<>(); private final Map> classVarInitOrderCache = new HashMap<>(); + /** + * When true, the build targets a legacy patch (pre-1.24) whose Blizzard-provided + * Jass contains return-type mismatches the Jass VM tolerates. Such mismatches in + * input Jass are reported as warnings instead of errors. + */ + private final boolean legacyJassTypeChecks; + public WurstValidator(WurstModel root) { + this(root, false); + } + + public WurstValidator(WurstModel root, boolean legacyJassTypeChecks) { this.prog = root; + this.legacyJassTypeChecks = legacyJassTypeChecks; } public void validate(Collection toCheck) { @@ -2106,6 +2118,17 @@ private void checkReturnInFunc(StmtReturn s, FunctionImplementation func) { return; } + if (legacyJassTypeChecks && Utils.isJassCode(s)) { + // Pre-1.24 Blizzard common.j/blizzard.j contain functions that + // return a value whose type does not match the declared return type + // (e.g. returning a trigger as event, a real as integer, or a widget + // as destructable). The weakly-typed Jass VM tolerates these, and we + // cannot edit the game-provided scripts, so for legacy targets we + // downgrade to a warning for input Jass instead of failing the build. + s.addWarning("Cannot return " + returnedType + ", expected expression of type " + returnType); + return; + } + s.addError("Cannot return " + returnedType + ", expected expression of type " + returnType); } } diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/CompilationUnitTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/CompilationUnitTests.java index d75d0b17c..b3cfdfdc7 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/CompilationUnitTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/CompilationUnitTests.java @@ -138,4 +138,61 @@ public void wurstCallingJassFunctionIsOk() { ); } + /** + * Pre-1.24 Blizzard common.j/blizzard.j contain return-type mismatches that the + * weakly-typed Jass VM tolerates (e.g. returning a real where an integer is declared). + * For legacy targets these must not fail the build for input Jass; they are reported + * as warnings instead. + */ + @Test + public void jassReturnTypeMismatchIsWarningForLegacyTarget() { + test() + .setStopOnFirstError(false) + .executeProg(false) + .legacyJassTypeChecks() + .expectWarning("Cannot return") + .compilationUnits( + compilationUnit("blizzard.j", + // real returned where integer is declared (cf. GetFadeFromSeconds) + "function getFadeFromSeconds takes real seconds returns integer", + " return 128 / seconds", + "endfunction" + ) + ); + } + + /** Without a legacy target, the same Jass mismatch must remain a hard error. */ + @Test + public void jassReturnTypeMismatchIsErrorByDefault() { + test() + .setStopOnFirstError(false) + .executeProg(false) + .expectError("Cannot return") + .compilationUnits( + compilationUnit("blizzard.j", + "function getFadeFromSeconds takes real seconds returns integer", + " return 128 / seconds", + "endfunction" + ) + ); + } + + /** The same return-type mismatch in user Wurst code must always be an error. */ + @Test + public void wurstReturnTypeMismatchIsError() { + test() + .setStopOnFirstError(false) + .executeProg(false) + .legacyJassTypeChecks() + .expectError("Cannot return") + .compilationUnits( + compilationUnit("mylib.wurst", + "package mylib", + "function badReturn() returns int", + " return 1.5", + "endpackage" + ) + ); + } + } diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstScriptTest.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstScriptTest.java index a270f044d..908b60d81 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstScriptTest.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstScriptTest.java @@ -90,6 +90,7 @@ class TestConfig { private boolean testLua = false; private boolean luaOnly = false; private boolean uncheckedDispatch = false; + private boolean legacyJassTypeChecks = false; TestConfig(String name) { this.name = name; @@ -152,6 +153,15 @@ public TestConfig uncheckedDispatch(boolean b) { return this; } + public TestConfig legacyJassTypeChecks() { + return legacyJassTypeChecks(true); + } + + public TestConfig legacyJassTypeChecks(boolean b) { + this.legacyJassTypeChecks = b; + return this; + } + TestConfig expectError(String expectedError) { this.expectedError = expectedError; return this; @@ -221,6 +231,9 @@ private CompilationResult testScript() { if (runCompiletimeFunctions) { runArgs = runArgs.with("-runcompiletimefunctions"); } + if (legacyJassTypeChecks) { + runArgs.setLegacyJassTypeChecks(true); + } WurstGui gui = new WurstGuiCliImpl(); WurstCompilerJassImpl compiler = new WurstCompilerJassImpl(null, gui, null, runArgs); From b19ab904859bac754b0aad58a0432865e5c764f7 Mon Sep 17 00:00:00 2001 From: Frotty Date: Wed, 3 Jun 2026 21:36:26 +0200 Subject: [PATCH 2/4] better runmap, slim down fat jar --- de.peeeq.wurstscript/build.gradle | 17 +- .../languageserver/requests/RunMap.java | 180 +++++++++++++----- .../tests/MapRequestPatchTargetTests.java | 76 +++++++- 3 files changed, 222 insertions(+), 51 deletions(-) diff --git a/de.peeeq.wurstscript/build.gradle b/de.peeeq.wurstscript/build.gradle index 9a55173fa..fc04785bc 100644 --- a/de.peeeq.wurstscript/build.gradle +++ b/de.peeeq.wurstscript/build.gradle @@ -109,7 +109,7 @@ dependencies { implementation 'com.github.albfernandez:juniversalchardet:2.4.0' implementation 'org.xerial:sqlite-jdbc:3.46.1.3' implementation 'com.github.inwc3:jmpq3:e28f6999c0' - implementation 'com.github.inwc3:wc3libs:a69318d921' + implementation 'com.github.inwc3:wc3libs:548f34a424' implementation 'com.github.wurstscript:wurst-project-config:2c7ccd1a5f' implementation('com.github.wurstscript:wurstsetup:393cf5ea39') { exclude group: 'org.eclipse.jgit', module: 'org.eclipse.jgit' @@ -290,6 +290,21 @@ shadowJar { exclude 'it/unimi/dsi/fastutil/bytes/**' exclude 'it/unimi/dsi/fastutil/booleans/**' exclude 'it/unimi/dsi/fastutil/io/**' + + // sqlite-jdbc bundles ~13.5 MB of native libs for 24 platforms. The compiler needs Java (64-bit only), so keep + // only the desktop platforms it actually runs on (Windows/macOS/Linux on x86_64 + aarch64, incl. Apple Silicon) + // and drop the rest (~10 MB saved). Keep this list in sync if sqlite-jdbc adds platforms. + exclude 'org/sqlite/native/FreeBSD/**' + exclude 'org/sqlite/native/Linux-Android/**' + exclude 'org/sqlite/native/Linux-Musl/**' + exclude 'org/sqlite/native/Linux/ppc64/**' + exclude 'org/sqlite/native/Linux/riscv64/**' + exclude 'org/sqlite/native/Linux/arm/**' + exclude 'org/sqlite/native/Linux/armv6/**' + exclude 'org/sqlite/native/Linux/armv7/**' + exclude 'org/sqlite/native/Linux/x86/**' + exclude 'org/sqlite/native/Windows/armv7/**' + exclude 'org/sqlite/native/Windows/x86/**' } def fatJar = shadowJar.archiveFile.map { it.asFile } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/RunMap.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/RunMap.java index a69bcebaf..4f61edab3 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/RunMap.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/RunMap.java @@ -77,6 +77,9 @@ public Object execute(ModelManager modelManager) throws IOException { } catch (CompileError e) { WLogger.info(e); throw new RequestFailedException(MessageType.Error, "A compilation error occurred when running the map:\n" + e); + } catch (RequestFailedException e) { + // Already carries an actionable message and the intended MessageType; don't bury it in a generic wrapper. + throw e; } catch (Exception e) { WLogger.warning("Exception occurred", e); throw new RequestFailedException(MessageType.Error, "An exception was thrown when running the map:\n" + e); @@ -138,70 +141,130 @@ private void startGame(WurstGui gui, CompilationResult result) throws Exception timeTaker.beginPhase("Starting Warcraft 3"); gui.sendProgress("Starting Warcraft 3..."); - File mapCopy = cachedMapFile.get(); W3InstallationData launchData = resolveLaunchData(); if (launchData == null) { throw new RequestFailedException(MessageType.Info, "Run canceled."); } + + // The auto-detected installation may be stale/incompatible (e.g. an old root launcher stub that the OS + // refuses to start). When launching fails, surface an actionable message and let the user pick a different + // Warcraft III folder, then retry with that selection (which also re-decides map placement). + while (true) { + try { + launchGame(gui, launchData, cachedMapFile.get()); + timeTaker.endPhase(); + timeTaker.printReport(); + return; + } catch (IOException e) { + WLogger.warning("Could not launch Warcraft III", e); + launchData = recoverFromLaunchFailure(launchData, e); + } + } + } + + private void launchGame(WurstGui gui, W3InstallationData launchData, File cachedMapFile) throws IOException { Optional detectedGameVersion = launchData.getWc3PatchVersion(); + File mapCopy = cachedMapFile; if (buildConfig.shouldCopyRunMapToWarcraftMapDir(detectedGameVersion)) { - mapCopy = copyToWarcraftMapDir(cachedMapFile.get(), launchData); + mapCopy = copyToWarcraftMapDir(cachedMapFile, launchData); } - WLogger.info("Starting wc3 ... "); String path = ""; if (customTarget != null) { - path = new File(customTarget, cachedMapFile.get().getName()).getAbsolutePath(); + path = new File(customTarget, cachedMapFile.getName()).getAbsolutePath(); } else if (mapCopy != null) { path = mapCopy.getAbsolutePath(); } + if (path.isEmpty()) { + return; + } - if (!path.isEmpty()) { - // now start the map - File gameExe = launchData.getGameExe() - .orElseThrow(() -> new RequestFailedException(MessageType.Error, wc3Path + " does not exist.")); - List cmd = Lists.newArrayList(gameExe.getAbsolutePath()); - Optional wc3RunArgs = langServer.getConfigProvider().getWc3RunArgs(); - if (!wc3RunArgs.isPresent() || StringUtils.isBlank(wc3RunArgs.get())) { - if (buildConfig.shouldUseReforgedLaunchArgs(detectedGameVersion)) { - cmd.add("-launch"); - } - if (buildConfig.shouldUseClassicWindowArg(detectedGameVersion)) { - cmd.add("-window"); - } else { - cmd.add("-windowmode"); - cmd.add("windowed"); - } + // now start the map + File gameExe = launchData.getGameExe() + .orElseThrow(() -> new RequestFailedException(MessageType.Error, wc3Path + " does not exist.")); + List cmd = buildLaunchCommand(gameExe, path, detectedGameVersion); + + gui.sendProgress("running " + cmd); + Runtime.getRuntime().exec(cmd.toArray(new String[0])); + } + + private List buildLaunchCommand(File gameExe, String mapPath, Optional detectedGameVersion) { + List cmd = Lists.newArrayList(gameExe.getAbsolutePath()); + Optional wc3RunArgs = langServer.getConfigProvider().getWc3RunArgs(); + if (!wc3RunArgs.isPresent() || StringUtils.isBlank(wc3RunArgs.get())) { + if (buildConfig.shouldUseReforgedLaunchArgs(detectedGameVersion)) { + cmd.add("-launch"); + } + if (buildConfig.shouldUseClassicWindowArg(detectedGameVersion)) { + cmd.add("-window"); } else { - cmd.addAll(Arrays.asList(wc3RunArgs.get().split("\\s+"))); + cmd.add("-windowmode"); + cmd.add("windowed"); } - cmd.add("-loadfile"); - cmd.add(path); + } else { + cmd.addAll(Arrays.asList(wc3RunArgs.get().split("\\s+"))); + } + cmd.add("-loadfile"); + cmd.add(mapPath); - if (Orient.isLinuxSystem()) { - // run with wine - cmd.add(0, "wine"); - } - timeTaker.endPhase(); + if (Orient.isLinuxSystem()) { + // run with wine + cmd.add(0, "wine"); + } + return cmd; + } - gui.sendProgress("running " + cmd); - Runtime.getRuntime().exec(cmd.toArray(new String[0])); - timeTaker.endPhase(); - timeTaker.printReport(); + /** + * Turns a launch failure into something the user can act on. In interactive mode the user may pick a different + * Warcraft III folder to retry; otherwise (headless/CLI) a clear error is raised. Cancelling stops the run quietly. + */ + private W3InstallationData recoverFromLaunchFailure(W3InstallationData launchData, IOException failure) { + String message = launchFailureMessage(launchData.getGameExe().orElse(null), failure); + WLogger.warning(message); + if (GraphicsEnvironment.isHeadless()) { + throw new RequestFailedException(MessageType.Error, message); } + Object[] options = {"Choose Warcraft III folder", "Cancel"}; + int result = JOptionPane.showOptionDialog( + null, + message, + "Could not launch Warcraft III", + JOptionPane.DEFAULT_OPTION, + JOptionPane.ERROR_MESSAGE, + null, + options, + options[0] + ); + if (result != 0) { + throw new RequestFailedException(MessageType.Info, "Run canceled."); + } + return chooseAlternateGamePath() + .orElseThrow(() -> new RequestFailedException(MessageType.Info, "Run canceled.")); + } + + static String launchFailureMessage(@Nullable File gameExe, IOException failure) { + String exeText = gameExe != null ? gameExe.getAbsolutePath() : "the auto-detected Warcraft III executable"; + StringBuilder message = new StringBuilder(); + message.append("Could not launch Warcraft III.\n\n"); + message.append("Auto-detected executable: ").append(exeText).append("\n"); + String reason = failure.getMessage(); + if (reason != null && !reason.isBlank()) { + message.append("The operating system refused to start it: ").append(reason.trim()).append("\n"); + } + message.append("\nThis usually means an outdated or incompatible Warcraft III installation was auto-detected. "); + message.append("Select a different Warcraft III folder, set \"wc3path\" in your Wurst settings, "); + message.append("or pin \"wc3Patch\" in wurst.build."); + return message.toString(); } private W3InstallationData resolveLaunchData() { W3InstallationData launchData = w3data; - while (shouldWarnClientPatchMismatch(launchData)) { - String projectTarget = buildConfig.wc3PatchName().orElse("configured patch"); - String clientTarget = launchData.getWc3PatchVersion() - .map(RunMap::describeClientVersion) - .orElse("unknown Warcraft III version"); - String message = "This project targets " + projectTarget + ", but the selected Warcraft III client looks like " - + clientTarget + ". The map may not start correctly."; + // When the project pins a wc3Patch, only auto-launch an install we can confirm is patch-compliant. + // A mismatching OR unverifiable client is delegated to the user instead of "randomly" launching and failing. + while (!isLaunchDataPatchCompliant(launchData)) { + String message = patchComplianceMessage(launchData); WLogger.warning(message); MismatchChoice choice = chooseMismatchAction(message); @@ -217,19 +280,42 @@ private W3InstallationData resolveLaunchData() { } launchData = selected.get(); } - if (buildConfig.wc3Patch().isPresent() && launchData.getWc3PatchVersion().isEmpty()) { - WLogger.warning("Could not determine Warcraft III client version. If the map does not start, select a matching Warcraft III installation."); - } return launchData; } - private boolean shouldWarnClientPatchMismatch(W3InstallationData launchData) { - Optional projectKind = buildConfig.wc3Patch(); - Optional clientVersion = launchData.getWc3PatchVersion(); - if (projectKind.isEmpty() || clientVersion.isEmpty()) { + private boolean isLaunchDataPatchCompliant(W3InstallationData launchData) { + return isPatchCompliant(buildConfig.wc3Patch(), launchData.getWc3PatchVersion()); + } + + /** + * A launch install is patch-compliant when the project pins no patch (nothing to validate against), or the + * detected client version is known and belongs to the same patch family as the pinned {@code wc3Patch}. An + * unknown client version against a pinned patch is treated as non-compliant so the user is asked to choose, + * rather than launching a client that may silently fail to start. + */ + static boolean isPatchCompliant(Optional projectKind, Optional clientVersion) { + if (projectKind.isEmpty()) { + return true; + } + if (clientVersion.isEmpty()) { return false; } - return projectKind.get() != kindForVersion(clientVersion.get()); + return projectKind.get() == kindForVersion(clientVersion.get()); + } + + private String patchComplianceMessage(W3InstallationData launchData) { + String projectTarget = buildConfig.wc3PatchName().orElse("configured patch"); + Optional clientVersion = launchData.getWc3PatchVersion(); + if (clientVersion.isPresent()) { + return "This project targets " + projectTarget + ", but the selected Warcraft III client looks like " + + describeClientVersion(clientVersion.get()) + ".\n\n" + + "Choose a matching Warcraft III folder, or set \"wc3path\"."; + } + String exe = launchData.getGameExe().map(File::getAbsolutePath).orElse("the auto-detected installation"); + return "This project targets " + projectTarget + ", but the version of the auto-detected Warcraft III could not " + + "be determined:\n" + exe + "\n\n" + + "Choose your Warcraft III folder (or set \"wc3path\") so a matching client is used, " + + "instead of launching one that may not start."; } private static WurstBuildConfig.Wc3Patch kindForVersion(GameVersion version) { diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/MapRequestPatchTargetTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/MapRequestPatchTargetTests.java index 83818f714..9c4c0d141 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/MapRequestPatchTargetTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/MapRequestPatchTargetTests.java @@ -2,6 +2,7 @@ import de.peeeq.wurstio.languageserver.ModelManager; import de.peeeq.wurstio.languageserver.WFile; +import de.peeeq.wurstio.languageserver.WurstBuildConfig; import de.peeeq.wurstio.languageserver.requests.MapRequest; import de.peeeq.wurstio.languageserver.requests.RunMap; import de.peeeq.wurstio.utils.W3InstallationData; @@ -10,6 +11,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; import java.io.PrintStream; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; @@ -124,21 +126,89 @@ public void configuredVersionSurvivesManualPathLoad() throws Exception { public void gameVersionParseFailurePrintsShortMessageOnly() throws Exception { Path fakeExe = Files.writeString(Files.createTempFile("bad-warcraft", ".exe"), "not a PE file"); ByteArrayOutputStream stdout = new ByteArrayOutputStream(); + ByteArrayOutputStream stderr = new ByteArrayOutputStream(); PrintStream previousOut = System.out; + PrintStream previousErr = System.err; try { System.setOut(new PrintStream(stdout, true, StandardCharsets.UTF_8)); + System.setErr(new PrintStream(stderr, true, StandardCharsets.UTF_8)); W3InstallationData data = new W3InstallationData(Optional.of(fakeExe.toFile()), Optional.empty()); assertFalse(data.getWc3PatchVersion().isPresent()); } finally { System.setOut(previousOut); + System.setErr(previousErr); } String output = stdout.toString(StandardCharsets.UTF_8); assertTrue(output.contains("Could not parse game version from configured executable")); - assertFalse(output.contains("VersionExtractionException"), output); - assertFalse(output.contains("dorkbox.peParser"), output); - assertFalse(output.contains("\tat "), output); + + // The wc3libs PE-parser trace previously leaked to stderr via printStackTrace; it must not appear on + // either stream. This guards the wc3libs dependency pin from regressing back to the noisy behaviour. + String combined = output + stderr.toString(StandardCharsets.UTF_8); + assertFalse(combined.contains("VersionExtractionException"), combined); + assertFalse(combined.contains("dorkbox.peParser"), combined); + assertFalse(combined.contains("\tat "), combined); + } + + @Test + public void launchFailureMessageIsActionableWithoutStackTrace() throws Exception { + File exe = new File("C:\\Program Files (x86)\\Warcraft III\\Warcraft III.exe"); + IOException failure = new IOException("Cannot run program \"" + exe.getAbsolutePath() + + "\": CreateProcess error=216, This version of %1 is not compatible with the version of Windows you're running."); + + String message = launchFailureMessage(exe, failure); + + assertTrue(message.contains(exe.getAbsolutePath()), message); + assertTrue(message.contains("wc3path"), message); + assertTrue(message.contains("wc3Patch"), message); + assertTrue(message.contains("error=216"), message); + // Must stay short and human-readable: no leaked stack trace. + assertFalse(message.contains("\tat "), message); + assertFalse(message.contains("VersionExtractionException"), message); + } + + @Test + public void launchFailureMessageHandlesUnknownExecutable() throws Exception { + String message = launchFailureMessage(null, new IOException("CreateProcess error=216")); + + assertTrue(message.contains("Could not launch Warcraft III"), message); + assertTrue(message.contains("wc3path"), message); + assertFalse(message.contains("null"), message); + } + + private static String launchFailureMessage(File gameExe, IOException failure) throws Exception { + Method method = RunMap.class.getDeclaredMethod("launchFailureMessage", File.class, IOException.class); + method.setAccessible(true); + return (String) method.invoke(null, gameExe, failure); + } + + @Test + public void patchComplianceRequiresKnownMatchingClientWhenPinned() throws Exception { + // No pinned patch: nothing to validate against, so any auto-detected client is acceptable. + assertTrue(isPatchCompliant(Optional.empty(), Optional.of(new GameVersion("1.31")))); + assertTrue(isPatchCompliant(Optional.empty(), Optional.empty())); + + // Pinned Reforged: only a Reforged-family client is compliant. + assertTrue(isPatchCompliant(Optional.of(WurstBuildConfig.Wc3Patch.REFORGED), Optional.of(GameVersion.VERSION_1_32))); + assertFalse(isPatchCompliant(Optional.of(WurstBuildConfig.Wc3Patch.REFORGED), Optional.of(new GameVersion("1.31")))); + + // Pinned Classic: a 1.31 client matches; a pre-1.29 client does not. + assertTrue(isPatchCompliant(Optional.of(WurstBuildConfig.Wc3Patch.CLASSIC), Optional.of(new GameVersion("1.31")))); + assertFalse(isPatchCompliant(Optional.of(WurstBuildConfig.Wc3Patch.CLASSIC), Optional.of(new GameVersion("1.28")))); + + // Pinned patch but undetectable client version: treated as non-compliant so the user is asked to choose, + // rather than blind-launching a possibly-wrong client. + assertFalse(isPatchCompliant(Optional.of(WurstBuildConfig.Wc3Patch.REFORGED), Optional.empty())); + assertFalse(isPatchCompliant(Optional.of(WurstBuildConfig.Wc3Patch.CLASSIC), Optional.empty())); + assertFalse(isPatchCompliant(Optional.of(WurstBuildConfig.Wc3Patch.PRE_129), Optional.empty())); + } + + private static boolean isPatchCompliant(Optional projectKind, + Optional clientVersion) throws Exception { + Method method = RunMap.class.getDeclaredMethod("isPatchCompliant", Optional.class, Optional.class); + method.setAccessible(true); + return (boolean) method.invoke(null, projectKind, clientVersion); } private static Path projectWithPatch(String patch) throws Exception { From be13827c53cbb2f69a0059dbd49ebab199ad84ff Mon Sep 17 00:00:00 2001 From: Frotty Date: Thu, 4 Jun 2026 00:26:45 +0200 Subject: [PATCH 3/4] use shared parser --- de.peeeq.wurstscript/build.gradle | 40 +++- de.peeeq.wurstscript/deploy.gradle | 2 +- .../de/peeeq/wurstio/CompilationProcess.java | 4 +- .../wurstio/CompiletimeFunctionRunner.java | 2 +- .../src/main/java/de/peeeq/wurstio/Main.java | 6 +- .../peeeq/wurstio/WurstCompilerJassImpl.java | 2 +- .../interpreter/CompiletimeNatives.java | 4 +- .../languageserver/ProjectConfigBuilder.java | 174 +++++++++--------- .../languageserver/WurstBuildConfig.java | 32 +--- .../languageserver/requests/BuildMap.java | 6 +- .../languageserver/requests/CliBuildMap.java | 6 +- .../languageserver/requests/MapRequest.java | 6 +- .../languageserver/requests/RunMap.java | 6 +- .../languageserver/requests/RunTests.java | 4 +- .../de/peeeq/wurstscript/WurstCompiler.java | 2 +- .../tests/HotReloadPipelineTests.java | 4 +- .../tests/LuaTranslationTests.java | 4 +- .../tests/WurstBuildConfigTests.java | 4 +- .../wurstscript/tests/WurstScriptTest.java | 6 +- 19 files changed, 159 insertions(+), 155 deletions(-) diff --git a/de.peeeq.wurstscript/build.gradle b/de.peeeq.wurstscript/build.gradle index fc04785bc..59e8c6d50 100644 --- a/de.peeeq.wurstscript/build.gradle +++ b/de.peeeq.wurstscript/build.gradle @@ -110,11 +110,7 @@ dependencies { implementation 'org.xerial:sqlite-jdbc:3.46.1.3' implementation 'com.github.inwc3:jmpq3:e28f6999c0' implementation 'com.github.inwc3:wc3libs:548f34a424' - implementation 'com.github.wurstscript:wurst-project-config:2c7ccd1a5f' - implementation('com.github.wurstscript:wurstsetup:393cf5ea39') { - exclude group: 'org.eclipse.jgit', module: 'org.eclipse.jgit' - exclude group: 'org.eclipse.jgit', module: 'org.eclipse.jgit.ssh.apache' - } + implementation 'com.github.wurstscript:wurst-project-config:348fcd4ef5' implementation 'org.slf4j:slf4j-api:2.0.17' implementation 'ch.qos.logback:logback-classic:1.5.20' testImplementation 'org.eclipse.jgit:org.eclipse.jgit:6.7.0.202309050840-r' @@ -291,6 +287,40 @@ shadowJar { exclude 'it/unimi/dsi/fastutil/booleans/**' exclude 'it/unimi/dsi/fastutil/io/**' + // Within the kept objects/ints packages the compiler only uses the open-hash map/set, linked-open variants, + // array list and FIFO queue for Object2Object, Object2Int, Reference2Object, Reference2Boolean and Int2Object. + // Drop the rest (~4.6 MB): sorted (tree) maps, custom-hash maps, and the cross-primitive value families whose + // value-type packages are already excluded above. If you start using another fastutil collection, remove the + // matching exclude here. + exclude 'it/unimi/dsi/fastutil/**/*AVLTreeMap*' + exclude 'it/unimi/dsi/fastutil/**/*RBTreeMap*' + exclude 'it/unimi/dsi/fastutil/**/*OpenCustomHashMap*' + exclude 'it/unimi/dsi/fastutil/objects/Object2Double*' + exclude 'it/unimi/dsi/fastutil/objects/Object2Float*' + exclude 'it/unimi/dsi/fastutil/objects/Object2Long*' + exclude 'it/unimi/dsi/fastutil/objects/Object2Short*' + exclude 'it/unimi/dsi/fastutil/objects/Object2Byte*' + exclude 'it/unimi/dsi/fastutil/objects/Object2Char*' + exclude 'it/unimi/dsi/fastutil/objects/Object2Boolean*' + exclude 'it/unimi/dsi/fastutil/objects/Object2Reference*' + exclude 'it/unimi/dsi/fastutil/objects/Reference2Double*' + exclude 'it/unimi/dsi/fastutil/objects/Reference2Float*' + exclude 'it/unimi/dsi/fastutil/objects/Reference2Long*' + exclude 'it/unimi/dsi/fastutil/objects/Reference2Short*' + exclude 'it/unimi/dsi/fastutil/objects/Reference2Byte*' + exclude 'it/unimi/dsi/fastutil/objects/Reference2Char*' + exclude 'it/unimi/dsi/fastutil/objects/Reference2Int*' + exclude 'it/unimi/dsi/fastutil/objects/Reference2Reference*' + exclude 'it/unimi/dsi/fastutil/ints/Int2Double*' + exclude 'it/unimi/dsi/fastutil/ints/Int2Float*' + exclude 'it/unimi/dsi/fastutil/ints/Int2Long*' + exclude 'it/unimi/dsi/fastutil/ints/Int2Short*' + exclude 'it/unimi/dsi/fastutil/ints/Int2Byte*' + exclude 'it/unimi/dsi/fastutil/ints/Int2Char*' + exclude 'it/unimi/dsi/fastutil/ints/Int2Boolean*' + exclude 'it/unimi/dsi/fastutil/ints/Int2Int*' + exclude 'it/unimi/dsi/fastutil/ints/Int2Reference*' + // sqlite-jdbc bundles ~13.5 MB of native libs for 24 platforms. The compiler needs Java (64-bit only), so keep // only the desktop platforms it actually runs on (Windows/macOS/Linux on x86_64 + aarch64, incl. Apple Silicon) // and drop the rest (~10 MB saved). Keep this list in sync if sqlite-jdbc adds platforms. diff --git a/de.peeeq.wurstscript/deploy.gradle b/de.peeeq.wurstscript/deploy.gradle index 3e67d1b1e..bae051470 100644 --- a/de.peeeq.wurstscript/deploy.gradle +++ b/de.peeeq.wurstscript/deploy.gradle @@ -178,7 +178,7 @@ tasks.register("jlinkRuntime25", Exec) { "--no-header-files", "--no-man-pages", "--strip-debug", - "--compress=zip-6", + "--compress=zip-9", "--output", outDir.absolutePath } } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java index 8bfe9cc50..dc60e61fa 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java @@ -1,6 +1,6 @@ package de.peeeq.wurstio; -import config.WurstProjectConfigData; +import org.wurstscript.projectconfig.WurstProjectConfigData; import de.peeeq.wurstio.languageserver.requests.RunTests; import de.peeeq.wurstio.mpq.MpqEditor; import de.peeeq.wurstio.utils.FileUtils; @@ -85,7 +85,7 @@ public CompilationProcess(WurstGui gui, RunArgs runArgs) { () -> runTests(compiler.getImTranslator(), compiler, runArgs.getTestTimeout(), runArgs.getTestFilter())); } - timeTaker.measure("Run compiletime functions", () ->compiler.runCompiletime(new WurstProjectConfigData(), isProd, false)); + timeTaker.measure("Run compiletime functions", () ->compiler.runCompiletime(WurstProjectConfigData.empty(), isProd, false)); JassProg jassProg = timeTaker.measure("Transform program to Jass", compiler::transformProgToJass); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompiletimeFunctionRunner.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompiletimeFunctionRunner.java index 45b94ad8a..4767bc3b9 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompiletimeFunctionRunner.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompiletimeFunctionRunner.java @@ -4,7 +4,7 @@ import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import config.WurstProjectConfigData; +import org.wurstscript.projectconfig.WurstProjectConfigData; import de.peeeq.wurstio.intermediateLang.interpreter.CompiletimeNatives; import de.peeeq.wurstio.intermediateLang.interpreter.ProgramStateIO; import de.peeeq.wurstio.jassinterpreter.InterpreterException; diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/Main.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/Main.java index 710fe0b19..0f6bb78ac 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/Main.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/Main.java @@ -2,8 +2,8 @@ import com.google.common.base.Charsets; import com.google.common.io.Files; -import config.WurstProjectConfig; -import config.WurstProjectConfigData; +import org.wurstscript.projectconfig.WurstProjectConfigData; +import org.wurstscript.projectconfig.WurstProjectConfigReader; import de.peeeq.wurstio.compilationserver.WurstServer; import de.peeeq.wurstio.gui.AboutDialog; import de.peeeq.wurstio.gui.WurstGuiImpl; @@ -124,7 +124,7 @@ public static void main(String[] args) { if (runArgs.isBuild() && runArgs.getInputmap() != null && workspaceroot != null) { Path root = Paths.get(workspaceroot); Path inputMap = root.resolve(runArgs.getInputmap()); - WurstProjectConfigData projectConfig = WurstProjectConfig.INSTANCE.loadProject(root.resolve(FILE_NAME)); + WurstProjectConfigData projectConfig = WurstProjectConfigReader.load(root.resolve(FILE_NAME)); if (java.nio.file.Files.exists(inputMap) && projectConfig != null) { CliBuildMap cliBuildMap = new CliBuildMap( WFile.create(root.toFile()), diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java index d38b50eca..bd559408e 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java @@ -6,7 +6,7 @@ import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.io.Files; -import config.WurstProjectConfigData; +import org.wurstscript.projectconfig.WurstProjectConfigData; import de.peeeq.wurstio.languageserver.requests.RequestFailedException; import de.peeeq.wurstio.map.importer.ImportFile; import de.peeeq.wurstio.mpq.MpqEditor; diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/CompiletimeNatives.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/CompiletimeNatives.java index 96aa66dec..11d188e46 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/CompiletimeNatives.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/CompiletimeNatives.java @@ -1,7 +1,7 @@ package de.peeeq.wurstio.intermediateLang.interpreter; -import config.WurstProjectConfigData; +import org.wurstscript.projectconfig.WurstProjectConfigData; import de.peeeq.wurstio.jassinterpreter.InterpreterException; import de.peeeq.wurstio.jassinterpreter.ReflectionBasedNativeProvider; import de.peeeq.wurstio.objectreader.ObjectHelper; @@ -175,7 +175,7 @@ public void compileError(ILconstString msg) { } public ILconstString getMapName() { - return new ILconstString(projectConfigData.getBuildMapData().getName()); + return new ILconstString(projectConfigData.buildMapData().name()); } public ILconstString getBuildDate() { diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilder.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilder.java index 12670bc7d..c8af822ad 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilder.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilder.java @@ -1,7 +1,7 @@ package de.peeeq.wurstio.languageserver; import com.google.common.io.Files; -import config.*; +import org.wurstscript.projectconfig.*; import de.peeeq.wurstio.languageserver.requests.MapRequest; import de.peeeq.wurstio.languageserver.requests.RequestFailedException; import de.peeeq.wurstio.map.importer.ImportFile; @@ -43,11 +43,11 @@ public static MapRequest.CompilationResult apply(WurstProjectConfigData projectC File mapScript, File buildDir, RunArgs runArgs, W3InstallationData w3data, String outputScriptName) throws IOException { - if (projectConfig.getProjectName().isEmpty()) { + if (projectConfig.projectName().isEmpty()) { throw new RequestFailedException(MessageType.Error, "wurst.build is missing projectName."); } - WurstProjectBuildMapData buildMapData = projectConfig.getBuildMapData(); + WurstProjectBuildMapData buildMapData = projectConfig.buildMapData(); MapRequest.CompilationResult result = new MapRequest.CompilationResult(); result.script = mapScript; @@ -77,7 +77,7 @@ public static MapRequest.CompilationResult apply(WurstProjectConfigData projectC } // Only apply buildMapData if config changed or name is present - if (configNeedsApplying && StringUtils.isNotBlank(buildMapData.getName())) { + if (configNeedsApplying && StringUtils.isNotBlank(buildMapData.name())) { WLogger.info("Applying buildMapData config"); applyBuildMapData(projectConfig, mapScript, buildDir, w3data, w3I, result, configHash, outputScriptName); } else if (!configNeedsApplying) { @@ -91,7 +91,7 @@ public static MapRequest.CompilationResult apply(WurstProjectConfigData projectC || mapScript.lastModified() > cachedInjectedScript.lastModified(); if (!cachedScriptStale) { result.script = cachedInjectedScript; - } else if (StringUtils.isNotBlank(buildMapData.getName())) { + } else if (StringUtils.isNotBlank(buildMapData.name())) { WLogger.info("war3map.j changed or cached script missing, re-injecting config"); applyBuildMapData(projectConfig, mapScript, buildDir, w3data, w3I, result, configHash, outputScriptName); } @@ -128,55 +128,55 @@ private static String calculateProjectConfigHash(WurstProjectConfigData projectC try { // Serialize the relevant parts of the config StringBuilder sb = new StringBuilder(); - WurstProjectBuildMapData buildMapData = projectConfig.getBuildMapData(); + WurstProjectBuildMapData buildMapData = projectConfig.buildMapData(); - sb.append("name:").append(buildMapData.getName()).append("\n"); - sb.append("author:").append(buildMapData.getAuthor()).append("\n"); + sb.append("name:").append(buildMapData.name()).append("\n"); + sb.append("author:").append(buildMapData.author()).append("\n"); // Scenario data - WurstProjectBuildScenarioData scenario = buildMapData.getScenarioData(); - sb.append("suggestedPlayers:").append(scenario.getSuggestedPlayers()).append("\n"); - sb.append("description:").append(scenario.getDescription()).append("\n"); - - if (scenario.getLoadingScreen() != null) { - WurstProjectBuildLoadingScreenData ls = scenario.getLoadingScreen(); - sb.append("loadingScreen.model:").append(ls.getModel()).append("\n"); - sb.append("loadingScreen.background:").append(ls.getBackground()).append("\n"); - sb.append("loadingScreen.title:").append(ls.getTitle()).append("\n"); - sb.append("loadingScreen.subtitle:").append(ls.getSubTitle()).append("\n"); - sb.append("loadingScreen.text:").append(ls.getText()).append("\n"); + WurstProjectBuildScenarioData scenario = buildMapData.scenarioData(); + sb.append("suggestedPlayers:").append(scenario.suggestedPlayers()).append("\n"); + sb.append("description:").append(scenario.description()).append("\n"); + + if (scenario.loadingScreen() != null) { + WurstProjectBuildLoadingScreenData ls = scenario.loadingScreen(); + sb.append("loadingScreen.model:").append(ls.model()).append("\n"); + sb.append("loadingScreen.background:").append(ls.background()).append("\n"); + sb.append("loadingScreen.title:").append(ls.title()).append("\n"); + sb.append("loadingScreen.subtitle:").append(ls.subTitle()).append("\n"); + sb.append("loadingScreen.text:").append(ls.text()).append("\n"); } // Players - for (WurstProjectBuildPlayer player : buildMapData.getPlayers()) { - sb.append("player:").append(player.getId()) - .append(",").append(player.getName()) - .append(",").append(player.getRace()) - .append(",").append(player.getController()) - .append(",").append(player.getFixedStartLoc()) + for (WurstProjectBuildPlayer player : buildMapData.players()) { + sb.append("player:").append(player.id()) + .append(",").append(player.name()) + .append(",").append(player.race()) + .append(",").append(player.controller()) + .append(",").append(player.fixedStartLoc()) .append("\n"); } // Forces - for (WurstProjectBuildForce force : buildMapData.getForces()) { - sb.append("force:").append(force.getName()) - .append(",").append(force.getFlags().getAllied()) - .append(",").append(force.getFlags().getAlliedVictory()) - .append(",").append(force.getFlags().getSharedVision()) - .append(",").append(force.getFlags().getSharedControl()) - .append(",").append(force.getFlags().getSharedControlAdvanced()) + for (WurstProjectBuildForce force : buildMapData.forces()) { + sb.append("force:").append(force.name()) + .append(",").append(force.flags().allied()) + .append(",").append(force.flags().alliedVictory()) + .append(",").append(force.flags().sharedVision()) + .append(",").append(force.flags().sharedControl()) + .append(",").append(force.flags().sharedControlAdvanced()) .append("players:"); - for (int id : force.getPlayerIds()) { + for (int id : force.playerIds()) { sb.append(id).append(","); } sb.append("\n"); } // Option flags - WurstProjectBuildOptionFlagsData flags = buildMapData.getOptionsFlags(); - sb.append("flags:").append(flags.getForcesFixed()) - .append(",").append(flags.getShowWavesOnCliffShores()) - .append(",").append(flags.getShowWavesOnRollingShores()) + WurstProjectBuildOptionFlagsData flags = buildMapData.optionsFlags(); + sb.append("flags:").append(flags.forcesFixed()) + .append(",").append(flags.showWavesOnCliffShores()) + .append(",").append(flags.showWavesOnRollingShores()) .append("\n"); WurstBuildConfig buildConfig = buildConfigFromBuildDir(buildDir); sb.append("scriptMode:").append(buildConfig.scriptMode()).append("\n"); @@ -233,70 +233,70 @@ private static WurstBuildConfig buildConfigFromBuildDir(File buildDir) { private static void prepareW3I(WurstProjectConfigData projectConfig, W3I w3I) { - WurstProjectBuildMapData buildMapData = projectConfig.getBuildMapData(); - if (StringUtils.isNotBlank(buildMapData.getName())) { - w3I.setMapName(buildMapData.getName()); + WurstProjectBuildMapData buildMapData = projectConfig.buildMapData(); + if (StringUtils.isNotBlank(buildMapData.name())) { + w3I.setMapName(buildMapData.name()); } - if (StringUtils.isNotBlank(buildMapData.getAuthor())) { - w3I.setMapAuthor(buildMapData.getAuthor()); + if (StringUtils.isNotBlank(buildMapData.author())) { + w3I.setMapAuthor(buildMapData.author()); } applyScenarioData(w3I, buildMapData); - if (!buildMapData.getPlayers().isEmpty()) { + if (!buildMapData.players().isEmpty()) { applyPlayers(projectConfig, w3I); } - if (!buildMapData.getForces().isEmpty()) { + if (!buildMapData.forces().isEmpty()) { applyForces(projectConfig, w3I); } applyOptionFlags(projectConfig, w3I); } private static void applyOptionFlags(WurstProjectConfigData projectConfig, W3I w3I) { - WurstProjectBuildOptionFlagsData optionsFlags = projectConfig.getBuildMapData().getOptionsFlags(); - w3I.setFlag(MapFlag.HIDE_MINIMAP, optionsFlags.getForcesFixed() || w3I.getFlag(MapFlag.HIDE_MINIMAP)); - w3I.setFlag(MapFlag.FIXED_PLAYER_FORCE_SETTING, optionsFlags.getForcesFixed() || w3I.getFlag(MapFlag.FIXED_PLAYER_FORCE_SETTING)); - w3I.setFlag(MapFlag.MASKED_AREAS_PARTIALLY_VISIBLE, optionsFlags.getForcesFixed() || w3I.getFlag(MapFlag.MASKED_AREAS_PARTIALLY_VISIBLE)); - w3I.setFlag(MapFlag.SHOW_WATER_WAVES_ON_CLIFF_SHORES, optionsFlags.getShowWavesOnCliffShores() || w3I.getFlag(MapFlag.SHOW_WATER_WAVES_ON_CLIFF_SHORES)); - w3I.setFlag(MapFlag.SHOW_WATER_WAVES_ON_ROLLING_SHORES, optionsFlags.getShowWavesOnRollingShores() || w3I.getFlag(MapFlag.SHOW_WATER_WAVES_ON_ROLLING_SHORES)); + WurstProjectBuildOptionFlagsData optionsFlags = projectConfig.buildMapData().optionsFlags(); + w3I.setFlag(MapFlag.HIDE_MINIMAP, optionsFlags.forcesFixed() || w3I.getFlag(MapFlag.HIDE_MINIMAP)); + w3I.setFlag(MapFlag.FIXED_PLAYER_FORCE_SETTING, optionsFlags.forcesFixed() || w3I.getFlag(MapFlag.FIXED_PLAYER_FORCE_SETTING)); + w3I.setFlag(MapFlag.MASKED_AREAS_PARTIALLY_VISIBLE, optionsFlags.forcesFixed() || w3I.getFlag(MapFlag.MASKED_AREAS_PARTIALLY_VISIBLE)); + w3I.setFlag(MapFlag.SHOW_WATER_WAVES_ON_CLIFF_SHORES, optionsFlags.showWavesOnCliffShores() || w3I.getFlag(MapFlag.SHOW_WATER_WAVES_ON_CLIFF_SHORES)); + w3I.setFlag(MapFlag.SHOW_WATER_WAVES_ON_ROLLING_SHORES, optionsFlags.showWavesOnRollingShores() || w3I.getFlag(MapFlag.SHOW_WATER_WAVES_ON_ROLLING_SHORES)); } private static void applyScenarioData(W3I w3I, WurstProjectBuildMapData buildMapData) { - WurstProjectBuildScenarioData scenarioData = buildMapData.getScenarioData(); - if (StringUtils.isNotBlank(scenarioData.getSuggestedPlayers())) { - w3I.setPlayersRecommendedAmount(scenarioData.getSuggestedPlayers()); + WurstProjectBuildScenarioData scenarioData = buildMapData.scenarioData(); + if (StringUtils.isNotBlank(scenarioData.suggestedPlayers())) { + w3I.setPlayersRecommendedAmount(scenarioData.suggestedPlayers()); } - if (StringUtils.isNotBlank(scenarioData.getDescription())) { - w3I.setMapDescription(scenarioData.getDescription()); + if (StringUtils.isNotBlank(scenarioData.description())) { + w3I.setMapDescription(scenarioData.description()); } - if (scenarioData.getLoadingScreen() != null) { - applyLoadingScreen(w3I, scenarioData.getLoadingScreen()); + if (scenarioData.loadingScreen() != null) { + applyLoadingScreen(w3I, scenarioData.loadingScreen()); } } private static void applyLoadingScreen(W3I w3I, WurstProjectBuildLoadingScreenData loadingScreenData) { - if (StringUtils.isNotBlank(loadingScreenData.getModel())) { - w3I.getLoadingScreen().setBackground(new LoadingScreenBackground.CustomBackground(new File(loadingScreenData.getModel()))); - } else if (StringUtils.isNotBlank(loadingScreenData.getBackground())) { - w3I.getLoadingScreen().setBackground(LoadingScreenBackground.PresetBackground.findByName(loadingScreenData.getBackground())); + if (StringUtils.isNotBlank(loadingScreenData.model())) { + w3I.getLoadingScreen().setBackground(new LoadingScreenBackground.CustomBackground(new File(loadingScreenData.model()))); + } else if (StringUtils.isNotBlank(loadingScreenData.background())) { + w3I.getLoadingScreen().setBackground(LoadingScreenBackground.PresetBackground.findByName(loadingScreenData.background())); } - w3I.getLoadingScreen().setTitle(loadingScreenData.getTitle()); - w3I.getLoadingScreen().setSubtitle(loadingScreenData.getSubTitle()); - w3I.getLoadingScreen().setText(loadingScreenData.getText()); + w3I.getLoadingScreen().setTitle(loadingScreenData.title()); + w3I.getLoadingScreen().setSubtitle(loadingScreenData.subTitle()); + w3I.getLoadingScreen().setText(loadingScreenData.text()); } private static void applyForces(WurstProjectConfigData projectConfig, W3I w3I) { w3I.clearForces(); - ArrayList forces = projectConfig.getBuildMapData().getForces(); + List forces = projectConfig.buildMapData().forces(); for (WurstProjectBuildForce wforce : forces) { W3I.Force force = new Force(); - force.setName(wforce.getName()); - force.setFlag(W3I.Force.Flags.Flag.ALLIED, wforce.getFlags().getAllied()); - force.setFlag(W3I.Force.Flags.Flag.ALLIED_VICTORY, wforce.getFlags().getAlliedVictory()); - force.setFlag(W3I.Force.Flags.Flag.SHARED_VISION, wforce.getFlags().getSharedVision()); - force.setFlag(W3I.Force.Flags.Flag.SHARED_UNIT_CONTROL, wforce.getFlags().getSharedControl()); - force.setFlag(W3I.Force.Flags.Flag.SHARED_UNIT_CONTROL_ADVANCED, wforce.getFlags().getSharedControlAdvanced()); - force.addPlayerNums(wforce.getPlayerIds()); + force.setName(wforce.name()); + force.setFlag(W3I.Force.Flags.Flag.ALLIED, wforce.flags().allied()); + force.setFlag(W3I.Force.Flags.Flag.ALLIED_VICTORY, wforce.flags().alliedVictory()); + force.setFlag(W3I.Force.Flags.Flag.SHARED_VISION, wforce.flags().sharedVision()); + force.setFlag(W3I.Force.Flags.Flag.SHARED_UNIT_CONTROL, wforce.flags().sharedControl()); + force.setFlag(W3I.Force.Flags.Flag.SHARED_UNIT_CONTROL_ADVANCED, wforce.flags().sharedControlAdvanced()); + force.addPlayerNums(wforce.playerIds().stream().mapToInt(Integer::intValue).toArray()); w3I.addForce(force); } w3I.setFlag(MapFlag.USE_CUSTOM_FORCES, true); @@ -305,17 +305,17 @@ private static void applyForces(WurstProjectConfigData projectConfig, W3I w3I) { private static void applyPlayers(WurstProjectConfigData projectConfig, W3I w3I) { List existing = new ArrayList<>(w3I.getPlayers()); w3I.getPlayers().clear(); - ArrayList players = projectConfig.getBuildMapData().getPlayers(); + List players = projectConfig.buildMapData().players(); for (WurstProjectBuildPlayer wplayer : players) { Optional old = Optional.empty(); for (Player player2 : existing) { - if (player2.getNum() == wplayer.getId()) { + if (player2.getNum() == wplayer.id()) { old = Optional.of(player2); break; } } W3I.Player player = new Player(); - player.setNum(wplayer.getId()); + player.setNum(wplayer.id()); w3I.addPlayer(player); old.ifPresent(player1 -> applyExistingPlayerConfig(player1, player)); @@ -335,36 +335,36 @@ private static void applyExistingPlayerConfig(W3I.Player oldPlayer, W3I.Player p } private static void setVolatilePlayerConfig(WurstProjectBuildPlayer wplayer, W3I.Player player) { - if (wplayer.getName() != null) { - player.setName(wplayer.getName()); + if (wplayer.name() != null) { + player.setName(wplayer.name()); } - if (wplayer.getRace() != null) { - W3I.Player.UnitRace val = W3I.Player.UnitRace.valueOf(wplayer.getRace().toString()); + if (wplayer.race() != null) { + W3I.Player.UnitRace val = W3I.Player.UnitRace.valueOf(wplayer.race().toString()); if (val != null) { player.setRace(val); } } - if (wplayer.getController() != null) { - net.moonlightflower.wc3libs.dataTypes.app.Controller val1 = Controller.valueOf(wplayer.getController().toString()); + if (wplayer.controller() != null) { + net.moonlightflower.wc3libs.dataTypes.app.Controller val1 = Controller.valueOf(wplayer.controller().toString()); if (val1 != null) { player.setType(val1); } } - if (wplayer.getFixedStartLoc() != null) { - player.setStartPosFixed(wplayer.getFixedStartLoc() ? 1 : 0); + if (wplayer.fixedStartLoc() != null) { + player.setStartPosFixed(wplayer.fixedStartLoc() ? 1 : 0); } } private static void applyMapHeader(WurstProjectConfigData projectConfig, File targetMap) throws IOException { boolean shouldWrite = false; MapHeader mapHeader = MapHeader.ofFile(targetMap); - if (!projectConfig.getBuildMapData().getPlayers().isEmpty()) { - mapHeader.setMaxPlayersCount(projectConfig.getBuildMapData().getPlayers().size()); + if (!projectConfig.buildMapData().players().isEmpty()) { + mapHeader.setMaxPlayersCount(projectConfig.buildMapData().players().size()); shouldWrite = true; } - if (StringUtils.isNotBlank(projectConfig.getBuildMapData().getName())) { - mapHeader.setMapName(projectConfig.getBuildMapData().getName()); + if (StringUtils.isNotBlank(projectConfig.buildMapData().name())) { + mapHeader.setMapName(projectConfig.buildMapData().name()); shouldWrite = true; } if (shouldWrite) { diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/WurstBuildConfig.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/WurstBuildConfig.java index 215d1571a..f7508e407 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/WurstBuildConfig.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/WurstBuildConfig.java @@ -1,12 +1,11 @@ package de.peeeq.wurstio.languageserver; -import config.WurstProjectConfigData; import de.peeeq.wurstscript.WLogger; import net.moonlightflower.wc3libs.port.GameVersion; import org.wurstscript.projectconfig.Wc3PatchTarget; +import org.wurstscript.projectconfig.WurstProjectConfigData; import java.io.IOException; -import java.lang.reflect.Method; import java.nio.file.Path; import java.util.List; import java.util.Optional; @@ -48,11 +47,9 @@ public static WurstBuildConfig fromProject(WurstProjectConfigData projectConfig, if (projectConfig == null) { return fileConfig; } - Optional scriptMode = readStringGetter(projectConfig, "getScriptMode") - .flatMap(WurstBuildConfig::parseSharedScriptMode) + Optional scriptMode = Optional.ofNullable(projectConfig.scriptMode()) .or(fileConfig.sharedConfig::scriptMode); - Optional wc3Patch = readStringGetter(projectConfig, "getWc3Patch") - .flatMap(Wc3PatchTarget::parse) + Optional wc3Patch = Wc3PatchTarget.parse(projectConfig.wc3Patch()) .or(fileConfig.sharedConfig::wc3Patch); return new WurstBuildConfig(new org.wurstscript.projectconfig.WurstBuildConfig(scriptMode, wc3Patch)); } @@ -130,14 +127,6 @@ private static Optional versionString(Optional version) { return version == null ? Optional.empty() : version.map(GameVersion::toString); } - private static Optional parseSharedScriptMode(String value) { - try { - return Optional.of(org.wurstscript.projectconfig.ScriptMode.valueOf(value.trim().toUpperCase())); - } catch (IllegalArgumentException | NullPointerException e) { - return Optional.empty(); - } - } - private static Wc3Patch patchKind(Wc3PatchTarget target) { if (target.kind() == Wc3PatchTarget.Kind.PRE_129) { return Wc3Patch.PRE_129; @@ -148,19 +137,4 @@ private static Wc3Patch patchKind(Wc3PatchTarget target) { return Wc3Patch.REFORGED; } - private static Optional readStringGetter(WurstProjectConfigData projectConfig, String getterName) { - try { - Method getter = projectConfig.getClass().getMethod(getterName); - Object value = getter.invoke(projectConfig); - if (value == null) { - return Optional.empty(); - } - return Optional.of(value.toString()); - } catch (NoSuchMethodException ignored) { - return Optional.empty(); - } catch (Exception e) { - WLogger.debug("Could not read " + getterName + " from wurst.build config: " + e); - return Optional.empty(); - } - } } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/BuildMap.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/BuildMap.java index 62d48fa52..c70f2a2ea 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/BuildMap.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/BuildMap.java @@ -1,7 +1,7 @@ package de.peeeq.wurstio.languageserver.requests; -import config.WurstProjectConfig; -import config.WurstProjectConfigData; +import org.wurstscript.projectconfig.WurstProjectConfigData; +import org.wurstscript.projectconfig.WurstProjectConfigReader; import de.peeeq.wurstio.gui.WurstGuiImpl; import de.peeeq.wurstio.languageserver.ModelManager; import de.peeeq.wurstio.languageserver.WFile; @@ -34,7 +34,7 @@ public Object execute(ModelManager modelManager) throws IOException { throw new RequestFailedException(MessageType.Error, "Fix errors in your code before building a release.\n" + modelManager.getFirstErrorDescription()); } - WurstProjectConfigData projectConfig = WurstProjectConfig.INSTANCE.loadProject(workspaceRoot.getFile().toPath().resolve(FILE_NAME)); + WurstProjectConfigData projectConfig = WurstProjectConfigReader.load(workspaceRoot.getFile().toPath().resolve(FILE_NAME)); if (projectConfig == null) { throw new RequestFailedException(MessageType.Error, FILE_NAME + " file doesn't exist or is invalid. " + "Please install your project using grill or the wurst setup tool."); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/CliBuildMap.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/CliBuildMap.java index b95964999..c191d157f 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/CliBuildMap.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/CliBuildMap.java @@ -1,7 +1,7 @@ package de.peeeq.wurstio.languageserver.requests; -import config.WurstProjectConfig; -import config.WurstProjectConfigData; +import org.wurstscript.projectconfig.WurstProjectConfigData; +import org.wurstscript.projectconfig.WurstProjectConfigReader; import de.peeeq.wurstio.languageserver.ModelManager; import de.peeeq.wurstio.languageserver.WFile; import de.peeeq.wurstscript.WLogger; @@ -35,7 +35,7 @@ public Object execute(ModelManager modelManager) throws IOException { throw new RequestFailedException(MessageType.Error, "Fix errors in your code before building a release.\n" + modelManager.getFirstErrorDescription()); } - WurstProjectConfigData projectConfig = WurstProjectConfig.INSTANCE.loadProject(workspaceRoot.getFile().toPath().resolve(FILE_NAME)); + WurstProjectConfigData projectConfig = WurstProjectConfigReader.load(workspaceRoot.getFile().toPath().resolve(FILE_NAME)); if (projectConfig == null) { throw new RequestFailedException(MessageType.Error, FILE_NAME + " file doesn't exist or is invalid. " + "Please install your project using grill or the wurst setup tool."); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java index 89500d51c..b6f505251 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java @@ -2,7 +2,7 @@ import com.google.common.base.Charsets; import com.google.common.io.Files; -import config.WurstProjectConfigData; +import org.wurstscript.projectconfig.WurstProjectConfigData; import de.peeeq.wurstio.Pjass; import de.peeeq.wurstio.TimeTaker; import de.peeeq.wurstio.UtilsIO; @@ -793,8 +793,8 @@ protected File ensureWritableBuildOutput(File targetMapFile, boolean isFinalWrit } private static File getBuildOutputMapFile(WurstProjectConfigData projectConfig, File buildDir) { - String fileName = projectConfig.getBuildMapData().getFileName(); - return new File(buildDir, fileName.isEmpty() ? projectConfig.getProjectName() + ".w3x" : fileName + ".w3x"); + String fileName = projectConfig.buildMapData().fileName(); + return new File(buildDir, fileName.isEmpty() ? projectConfig.projectName() + ".w3x" : fileName + ".w3x"); } private static boolean startsWith(byte[] data, byte[] prefix) { diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/RunMap.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/RunMap.java index 4f61edab3..499595571 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/RunMap.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/RunMap.java @@ -2,8 +2,8 @@ import com.google.common.collect.Lists; import com.google.common.io.Files; -import config.WurstProjectConfig; -import config.WurstProjectConfigData; +import org.wurstscript.projectconfig.WurstProjectConfigData; +import org.wurstscript.projectconfig.WurstProjectConfigReader; import de.peeeq.wurstio.gui.WurstGuiImpl; import de.peeeq.wurstio.languageserver.ModelManager; import de.peeeq.wurstio.languageserver.WFile; @@ -64,7 +64,7 @@ public Object execute(ModelManager modelManager) throws IOException { throw new RequestFailedException(MessageType.Error, "Fix errors in your code before running.\n" + modelManager.getFirstErrorDescription()); } - WurstProjectConfigData projectConfig = WurstProjectConfig.INSTANCE.loadProject(workspaceRoot.getFile().toPath().resolve(FILE_NAME)); + WurstProjectConfigData projectConfig = WurstProjectConfigReader.load(workspaceRoot.getFile().toPath().resolve(FILE_NAME)); if (projectConfig == null) { throw new RequestFailedException(MessageType.Error, FILE_NAME + " file doesn't exist or is invalid. " + "Please install your project using grill or the wurst setup tool."); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/RunTests.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/RunTests.java index 7c6738f56..475ff0a72 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/RunTests.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/RunTests.java @@ -2,7 +2,7 @@ import com.google.common.base.Preconditions; import com.google.common.collect.Lists; -import config.WurstProjectConfigData; +import org.wurstscript.projectconfig.WurstProjectConfigData; import de.peeeq.wurstio.CompiletimeFunctionRunner; import de.peeeq.wurstio.jassinterpreter.InterpreterException; import de.peeeq.wurstio.jassinterpreter.ReflectionNativeProvider; @@ -163,7 +163,7 @@ public TestResult runTests(ImTranslator translator, ImProg imProg, Optional e assertTrue("unexpected compile errors: " + gui.getErrorList(), gui.getErrorList().isEmpty()); compiler.translateProgToIm(model); - compiler.runCompiletime(new WurstProjectConfigData(), false, false); + compiler.runCompiletime(WurstProjectConfigData.empty(), false, false); LuaCompilationUnit luaCode = compiler.transformProgToLua(); StringBuilder sb = new StringBuilder(); diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstBuildConfigTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstBuildConfigTests.java index 98fc8146f..17d8492af 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstBuildConfigTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstBuildConfigTests.java @@ -1,6 +1,6 @@ package tests.wurstscript.tests; -import config.WurstProjectConfigData; +import org.wurstscript.projectconfig.WurstProjectConfigData; import de.peeeq.wurstio.languageserver.WFile; import de.peeeq.wurstio.languageserver.ProjectConfigBuilder; import de.peeeq.wurstio.languageserver.WurstBuildConfig; @@ -230,7 +230,7 @@ private static String calculateProjectConfigHash(File buildDir) throws Exception File.class ); method.setAccessible(true); - return (String) method.invoke(null, new WurstProjectConfigData(), buildDir); + return (String) method.invoke(null, WurstProjectConfigData.empty(), buildDir); } private static GameVersion effectiveConfigInjectionVersion( diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstScriptTest.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstScriptTest.java index 908b60d81..65178a524 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstScriptTest.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstScriptTest.java @@ -2,7 +2,7 @@ import com.google.common.base.Charsets; import com.google.common.io.Files; -import config.WurstProjectConfigData; +import org.wurstscript.projectconfig.WurstProjectConfigData; import de.peeeq.wurstio.Pjass; import de.peeeq.wurstio.Pjass.Result; import de.peeeq.wurstio.UtilsIO; @@ -497,7 +497,7 @@ private void translateAndTestLua(String name, boolean executeProg, WurstGui gui, compiler.translateProgToIm(model); - compiler.runCompiletime(new WurstProjectConfigData(), false, false); + compiler.runCompiletime(WurstProjectConfigData.empty(), false, false); LuaCompilationUnit luaCode = compiler.transformProgToLua(); checkLuaRootPurity(luaCode); @@ -916,7 +916,7 @@ private void translateAndTest(String name, boolean executeProg, } } - compiler.runCompiletime(new WurstProjectConfigData(), false, false); + compiler.runCompiletime(WurstProjectConfigData.empty(), false, false); JassProg prog = compiler.transformProgToJass(); writeJassImProg(name, gui, imProg); if (gui.getErrorCount() > 0) { From 3d7d6541c184bbc135f378eb81b7f0a6cfd98dd2 Mon Sep 17 00:00:00 2001 From: Frotty Date: Thu, 4 Jun 2026 00:42:30 +0200 Subject: [PATCH 4/4] review fix --- .../languageserver/requests/MapRequest.java | 21 ++++++++++++------- .../tests/MapRequestPatchTargetTests.java | 21 +++++++++++++++++++ 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java index b6f505251..a291ed517 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java @@ -79,6 +79,9 @@ public abstract class MapRequest extends UserRequest { public static final String BUILD_COMPILED_LUA_NAME = "02_compiled.lua"; public static final String BUILD_JHCR_SCRIPT_NAME = "03_jhcr_war3map.j"; + /** RunArgs flag (see {@link RunArgs}) that relaxes Jass type checks and skips PJass; injected for pre-1.24 targets. */ + private static final String LEGACY_JASS_CHECKS_ARG = "-legacyJassChecks"; + /** * makes the compilation slower, but more safe by discarding results from the editor and working on a copy of the model */ @@ -102,17 +105,21 @@ public MapRequest(WurstLanguageServer langServer, Optional map, List wc3Path, Optional gameExePath) { this.langServer = langServer; this.map = map; - this.compileArgs = compileArgs; this.workspaceRoot = workspaceRoot; - this.runArgs = new RunArgs(compileArgs); this.wc3Path = wc3Path; this.buildConfig = WurstBuildConfig.fromWorkspaceRoot(workspaceRoot); - // Patches before 1.24 ship Blizzard common.j/blizzard.j with type mismatches - // that the Jass VM tolerates (e.g. real returned as integer). Relax Jass - // type checks and skip PJass for such targets so these stock scripts compile. - if (buildConfig.isPre124()) { - runArgs.setLegacyJassTypeChecks(true); + // Patches before 1.24 ship Blizzard common.j/blizzard.j with type mismatches that the + // Jass VM tolerates (e.g. a real returned where an integer is declared). Relax Jass type + // checks and skip PJass for such targets so these stock scripts compile. Carry the flag + // through compileArgs so every RunArgs rebuilt from it (compileScript/compileMap) sees it, + // not just this request-level field. + if (buildConfig.isPre124() && !compileArgs.contains(LEGACY_JASS_CHECKS_ARG)) { + List legacyArgs = new ArrayList<>(compileArgs); + legacyArgs.add(LEGACY_JASS_CHECKS_ARG); + compileArgs = legacyArgs; } + this.compileArgs = compileArgs; + this.runArgs = new RunArgs(compileArgs); if (gameExePath.isPresent() && StringUtils.isNotBlank(gameExePath.get())) { Optional configuredVersion = this instanceof RunMap ? Optional.empty() diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/MapRequestPatchTargetTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/MapRequestPatchTargetTests.java index 9c4c0d141..78b5139d0 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/MapRequestPatchTargetTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/MapRequestPatchTargetTests.java @@ -5,6 +5,7 @@ import de.peeeq.wurstio.languageserver.WurstBuildConfig; import de.peeeq.wurstio.languageserver.requests.MapRequest; import de.peeeq.wurstio.languageserver.requests.RunMap; +import de.peeeq.wurstscript.RunArgs; import de.peeeq.wurstio.utils.W3InstallationData; import net.moonlightflower.wc3libs.port.GameVersion; import org.testng.annotations.Test; @@ -211,6 +212,22 @@ private static boolean isPatchCompliant(Optional proj return (boolean) method.invoke(null, projectKind, clientVersion); } + @Test + public void pre124PatchPropagatesLegacyJassChecksToCompileArgs() throws Exception { + // Pre-1.24: the flag must be carried in compileArgs so the fresh RunArgs that + // compileScript/compileMap build (RunArgs(compileArgs)) actually relaxes Jass checks + skips PJass. + TestMapRequest legacy = new TestMapRequest(projectWithPatch("v1.23"), Optional.empty(), Optional.empty()); + assertTrue(legacy.exposedCompileArgs().contains("-legacyJassChecks"), + "pre-1.24 build must carry -legacyJassChecks in compileArgs: " + legacy.exposedCompileArgs()); + assertTrue(new RunArgs(legacy.exposedCompileArgs().toArray(new String[0])).isLegacyJassTypeChecks(), + "a RunArgs rebuilt from compileArgs must report legacy Jass checks"); + + // Modern target: flag must not be injected. + TestMapRequest modern = new TestMapRequest(projectWithPatch("v2.0"), Optional.empty(), Optional.empty()); + assertFalse(modern.exposedCompileArgs().contains("-legacyJassChecks"), + "modern build must not carry the legacy flag: " + modern.exposedCompileArgs()); + } + private static Path projectWithPatch(String patch) throws Exception { Path project = Files.createTempDirectory("wurst-map-request-patch"); Files.createDirectories(project.resolve("wurst")); @@ -234,6 +251,10 @@ Optional gameExe() { return w3data.getGameExe(); } + List exposedCompileArgs() { + return compileArgs; + } + @Override public Object execute(ModelManager modelManager) { return null;