Skip to content

Commit c8331bd

Browse files
authored
better patch level support (#1189)
1 parent 5d56cf0 commit c8331bd

9 files changed

Lines changed: 408 additions & 40 deletions

File tree

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ModelManagerImpl.java

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -443,23 +443,26 @@ private Set<String> providedPackages(CompilationUnit c) {
443443
}
444444

445445
private CompilationUnit compileFromJar(WurstGui gui, String filename) throws IOException {
446-
InputStream source = this.getClass().getResourceAsStream("/" + filename);
447-
File sourceFile;
448-
if (source == null) {
449-
WLogger.severe("could not find " + filename + " in jar");
450-
System.err.println("could not find " + filename + " in jar");
451-
sourceFile = new File("./resources/" + filename);
446+
File sourceFile = findProjectCoreJassFile(filename).orElse(null);
447+
if (sourceFile != null) {
448+
sourceFile = copyCoreJassToBuildRoot(sourceFile, filename);
452449
} else {
453-
try {
454-
File buildDir = getBuildDir();
455-
//noinspection ResultOfMethodCallIgnored
456-
buildDir.mkdirs();
457-
sourceFile = new File(buildDir, filename);
458-
if (!sourceFile.exists()) {
450+
InputStream source = this.getClass().getResourceAsStream("/" + filename);
451+
if (source == null) {
452+
WLogger.severe("could not find " + filename + " in jar");
453+
System.err.println("could not find " + filename + " in jar");
454+
sourceFile = new File("./resources/" + filename);
455+
} else {
456+
try {
457+
File buildDir = getBuildDir();
458+
//noinspection ResultOfMethodCallIgnored
459+
buildDir.mkdirs();
460+
sourceFile = new File(buildDir, filename);
459461
java.nio.file.Files.copy(source, sourceFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
462+
WLogger.info("Loaded bundled fallback " + filename);
463+
} finally {
464+
source.close();
460465
}
461-
} finally {
462-
source.close();
463466
}
464467
}
465468

@@ -472,6 +475,26 @@ private CompilationUnit compileFromJar(WurstGui gui, String filename) throws IOE
472475
}
473476
}
474477

478+
private Optional<File> findProjectCoreJassFile(String filename) {
479+
File projectCopy = new File(getBuildDir(), filename);
480+
if (projectCopy.exists()) {
481+
return Optional.of(projectCopy);
482+
}
483+
return Optional.empty();
484+
}
485+
486+
private File copyCoreJassToBuildRoot(File sourceFile, String filename) throws IOException {
487+
File buildDir = getBuildDir();
488+
//noinspection ResultOfMethodCallIgnored
489+
buildDir.mkdirs();
490+
File buildCopy = new File(buildDir, filename);
491+
if (!sourceFile.getCanonicalFile().equals(buildCopy.getCanonicalFile())) {
492+
java.nio.file.Files.copy(sourceFile.toPath(), buildCopy.toPath(), StandardCopyOption.REPLACE_EXISTING);
493+
}
494+
WLogger.info("Loaded project " + filename + " from " + sourceFile.getAbsolutePath());
495+
return buildCopy;
496+
}
497+
475498
private File getBuildDir() {
476499
return new File(projectPath, "_build");
477500
}

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilder.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public static MapRequest.CompilationResult apply(WurstProjectConfigData projectC
5252
result.script = mapScript;
5353

5454
// Calculate hash of the project config for caching
55-
String configHash = calculateProjectConfigHash(projectConfig);
55+
String configHash = calculateProjectConfigHash(projectConfig, buildDir);
5656

5757
W3I w3I;
5858
boolean configNeedsApplying = false;
@@ -124,7 +124,7 @@ public static MapRequest.CompilationResult apply(WurstProjectConfigData projectC
124124
/**
125125
* Calculate a hash of the project configuration to detect changes
126126
*/
127-
private static String calculateProjectConfigHash(WurstProjectConfigData projectConfig) {
127+
private static String calculateProjectConfigHash(WurstProjectConfigData projectConfig, File buildDir) {
128128
try {
129129
// Serialize the relevant parts of the config
130130
StringBuilder sb = new StringBuilder();
@@ -178,6 +178,9 @@ private static String calculateProjectConfigHash(WurstProjectConfigData projectC
178178
.append(",").append(flags.getShowWavesOnCliffShores())
179179
.append(",").append(flags.getShowWavesOnRollingShores())
180180
.append("\n");
181+
WurstBuildConfig buildConfig = buildConfigFromBuildDir(buildDir);
182+
sb.append("scriptMode:").append(buildConfig.scriptMode()).append("\n");
183+
sb.append("wc3Patch:").append(buildConfig.wc3Patch()).append("\n");
181184

182185
return ImportFile.calculateHash(sb.toString().getBytes(StandardCharsets.UTF_8));
183186
} catch (Exception e) {
@@ -200,9 +203,9 @@ private static void applyBuildMapData(WurstProjectConfigData projectConfig, File
200203
if (w3data.getWc3PatchVersion().isPresent()) {
201204
w3I.injectConfigsInJassScript(inputStream, sw, w3data.getWc3PatchVersion().get());
202205
} else {
203-
GameVersion version = GameVersion.VERSION_1_32;
206+
GameVersion version = buildConfigFromBuildDir(buildDir).fallbackGameVersion();
204207
WLogger.info(
205-
"Failed to determine installed game version. Falling back to " + version
208+
"Failed to determine installed game version. Falling back to wurst.build patch target: " + version
206209
);
207210
w3I.injectConfigsInJassScript(inputStream, sw, version);
208211
}
@@ -212,6 +215,14 @@ private static void applyBuildMapData(WurstProjectConfigData projectConfig, File
212215
}
213216
}
214217

218+
private static WurstBuildConfig buildConfigFromBuildDir(File buildDir) {
219+
java.nio.file.Path projectRoot = buildDir.toPath().getParent();
220+
if (projectRoot == null) {
221+
projectRoot = java.nio.file.Path.of(".");
222+
}
223+
return WurstBuildConfig.fromBuildFile(projectRoot.resolve(FILE_NAME));
224+
}
225+
215226

216227
private static void prepareW3I(WurstProjectConfigData projectConfig, W3I w3I) {
217228
WurstProjectBuildMapData buildMapData = projectConfig.getBuildMapData();
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package de.peeeq.wurstio.languageserver;
2+
3+
import config.WurstProjectConfigData;
4+
import de.peeeq.wurstscript.WLogger;
5+
import net.moonlightflower.wc3libs.port.GameVersion;
6+
7+
import java.io.IOException;
8+
import java.lang.reflect.Method;
9+
import java.nio.file.Files;
10+
import java.nio.file.Path;
11+
import java.util.ArrayList;
12+
import java.util.List;
13+
import java.util.Locale;
14+
import java.util.Optional;
15+
16+
import static de.peeeq.wurstio.languageserver.ProjectConfigBuilder.FILE_NAME;
17+
18+
public final class WurstBuildConfig {
19+
20+
public enum ScriptMode {
21+
LUA,
22+
JASS
23+
}
24+
25+
public enum Wc3Patch {
26+
REFORGED,
27+
PRE_129
28+
}
29+
30+
private final Optional<ScriptMode> scriptMode;
31+
private final Optional<Wc3Patch> wc3Patch;
32+
33+
private WurstBuildConfig(Optional<ScriptMode> scriptMode, Optional<Wc3Patch> wc3Patch) {
34+
this.scriptMode = scriptMode;
35+
this.wc3Patch = wc3Patch;
36+
}
37+
38+
public static WurstBuildConfig empty() {
39+
return new WurstBuildConfig(Optional.empty(), Optional.empty());
40+
}
41+
42+
public static WurstBuildConfig fromWorkspaceRoot(WFile workspaceRoot) {
43+
return fromBuildFile(Path.of(workspaceRoot.toString(), FILE_NAME));
44+
}
45+
46+
public static WurstBuildConfig fromProject(WurstProjectConfigData projectConfig, WFile workspaceRoot) {
47+
WurstBuildConfig fileConfig = fromWorkspaceRoot(workspaceRoot);
48+
Optional<ScriptMode> scriptMode = readEnumGetter(projectConfig, "getScriptMode", ScriptMode::valueOf)
49+
.or(fileConfig::scriptMode);
50+
Optional<Wc3Patch> wc3Patch = readEnumGetter(projectConfig, "getWc3Patch", WurstBuildConfig::parsePatchName)
51+
.or(fileConfig::wc3Patch);
52+
return new WurstBuildConfig(scriptMode, wc3Patch);
53+
}
54+
55+
static WurstBuildConfig fromBuildFile(Path buildFile) {
56+
if (!Files.exists(buildFile)) {
57+
return empty();
58+
}
59+
Optional<ScriptMode> scriptMode = Optional.empty();
60+
Optional<Wc3Patch> wc3Patch = Optional.empty();
61+
try {
62+
for (String rawLine : Files.readAllLines(buildFile)) {
63+
String line = stripComment(rawLine).trim();
64+
if (line.isEmpty() || Character.isWhitespace(rawLine.charAt(0))) {
65+
continue;
66+
}
67+
int colon = line.indexOf(':');
68+
if (colon < 0) {
69+
continue;
70+
}
71+
String key = line.substring(0, colon).trim();
72+
String value = normalizeScalar(line.substring(colon + 1).trim());
73+
if (key.equals("scriptMode")) {
74+
scriptMode = parseScriptMode(value);
75+
} else if (key.equals("wc3Patch")) {
76+
wc3Patch = parsePatch(value);
77+
}
78+
}
79+
} catch (IOException e) {
80+
WLogger.warning("Could not read " + buildFile + " for build settings", e);
81+
}
82+
return new WurstBuildConfig(scriptMode, wc3Patch);
83+
}
84+
85+
public Optional<ScriptMode> scriptMode() {
86+
return scriptMode;
87+
}
88+
89+
public Optional<Wc3Patch> wc3Patch() {
90+
return wc3Patch;
91+
}
92+
93+
public Wc3Patch wc3PatchOrReforged() {
94+
return wc3Patch.orElse(Wc3Patch.REFORGED);
95+
}
96+
97+
public GameVersion fallbackGameVersion() {
98+
if (wc3PatchOrReforged() == Wc3Patch.PRE_129) {
99+
return new GameVersion("1.28");
100+
}
101+
return GameVersion.VERSION_1_32;
102+
}
103+
104+
public List<String> applyToCompileArgs(List<String> compileArgs) {
105+
if (!scriptMode.isPresent()) {
106+
return compileArgs;
107+
}
108+
List<String> result = new ArrayList<>();
109+
for (String arg : compileArgs) {
110+
if (!"-lua".equals(arg)) {
111+
result.add(arg);
112+
}
113+
}
114+
if (scriptMode.get() == ScriptMode.LUA) {
115+
result.add("-lua");
116+
}
117+
return result;
118+
}
119+
120+
public boolean shouldUseReforgedLaunchArgs(Optional<GameVersion> detectedVersion) {
121+
return detectedVersion
122+
.map(version -> version.compareTo(GameVersion.VERSION_1_32) >= 0)
123+
.orElse(wc3PatchOrReforged() == Wc3Patch.REFORGED);
124+
}
125+
126+
public boolean shouldUseClassicWindowArg(Optional<GameVersion> detectedVersion) {
127+
return detectedVersion
128+
.map(version -> version.compareTo(GameVersion.VERSION_1_31) < 0)
129+
.orElse(wc3PatchOrReforged() == Wc3Patch.PRE_129);
130+
}
131+
132+
public boolean shouldCopyRunMapToWarcraftMapDir(Optional<GameVersion> detectedVersion) {
133+
return detectedVersion
134+
.map(version -> version.compareTo(GameVersion.VERSION_1_32) < 0)
135+
.orElse(wc3PatchOrReforged() == Wc3Patch.PRE_129);
136+
}
137+
138+
public boolean shouldUseInstallDirForMaps(Optional<GameVersion> detectedVersion) {
139+
return detectedVersion.orElseGet(this::fallbackGameVersion)
140+
.compareTo(new GameVersion("1.27.9")) <= 0;
141+
}
142+
143+
private static String stripComment(String line) {
144+
int commentStart = line.indexOf('#');
145+
return commentStart >= 0 ? line.substring(0, commentStart) : line;
146+
}
147+
148+
private static String normalizeScalar(String value) {
149+
String result = value;
150+
if ((result.startsWith("\"") && result.endsWith("\""))
151+
|| (result.startsWith("'") && result.endsWith("'"))) {
152+
result = result.substring(1, result.length() - 1);
153+
}
154+
return result.trim();
155+
}
156+
157+
private static Optional<ScriptMode> parseScriptMode(String value) {
158+
String normalized = value.toUpperCase(Locale.ROOT);
159+
try {
160+
return Optional.of(ScriptMode.valueOf(normalized));
161+
} catch (IllegalArgumentException e) {
162+
WLogger.warning("Ignoring unknown scriptMode in wurst.build: " + value);
163+
return Optional.empty();
164+
}
165+
}
166+
167+
private static Optional<Wc3Patch> parsePatch(String value) {
168+
try {
169+
return Optional.of(parsePatchName(value));
170+
} catch (IllegalArgumentException e) {
171+
WLogger.warning("Ignoring unknown wc3Patch in wurst.build: " + value);
172+
return Optional.empty();
173+
}
174+
}
175+
176+
private static Wc3Patch parsePatchName(String value) {
177+
String normalized = value.toUpperCase(Locale.ROOT)
178+
.replace(".", "_")
179+
.replace("-", "_");
180+
if (normalized.equals("PRE1_29")) {
181+
normalized = "PRE_129";
182+
}
183+
return Wc3Patch.valueOf(normalized);
184+
}
185+
186+
private interface EnumParser<T> {
187+
T parse(String value);
188+
}
189+
190+
private static <T> Optional<T> readEnumGetter(WurstProjectConfigData projectConfig, String getterName, EnumParser<T> parser) {
191+
try {
192+
Method getter = projectConfig.getClass().getMethod(getterName);
193+
Object value = getter.invoke(projectConfig);
194+
if (value == null) {
195+
return Optional.empty();
196+
}
197+
return Optional.of(parser.parse(value.toString()));
198+
} catch (NoSuchMethodException ignored) {
199+
return Optional.empty();
200+
} catch (Exception e) {
201+
WLogger.warning("Could not read " + getterName + " from wurst.build config", e);
202+
return Optional.empty();
203+
}
204+
}
205+
}

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/WurstCommands.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,16 +125,19 @@ public static List<String> getCompileArgs(WFile rootPath, String... additionalAr
125125
Path configFile = Paths.get(rootPath.toString(), "wurst_run.args");
126126
if (Files.exists(configFile)) {
127127
try (Stream<String> lines = Files.lines(configFile)) {
128-
return Stream.concat(
128+
List<String> args = Stream.concat(
129129
lines.filter(s -> s.startsWith("-")),
130130
Stream.of(additionalArgs)
131131
).collect(Collectors.toList());
132+
return WurstBuildConfig.fromWorkspaceRoot(rootPath).applyToCompileArgs(args);
132133
}
133134
} else {
134135

135136
String cfg = String.join("\n", defaultArgs) + "\n";
136137
Files.write(configFile, cfg.getBytes(Charsets.UTF_8));
137-
return defaultArgs;
138+
return WurstBuildConfig.fromWorkspaceRoot(rootPath).applyToCompileArgs(
139+
Stream.concat(defaultArgs.stream(), Stream.of(additionalArgs)).collect(Collectors.toList())
140+
);
138141
}
139142
} catch (IOException e) {
140143
throw new RuntimeException("Could not access wurst_run.args config file", e);

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -808,7 +808,7 @@ protected File loadMapScript(Optional<File> mapCopy, ModelManager modelManager,
808808
"RunArg noExtractMapScript is set but no war3map.j is provided inside the wurst folder");
809809
}
810810
}
811-
if (MapRequest.mapLastModified > lastMapModified || !MapRequest.mapPath.equals(lastMapPath)) {
811+
if (shouldExtractMapScript(scriptFile)) {
812812
lastMapModified = MapRequest.mapLastModified;
813813
lastMapPath = MapRequest.mapPath;
814814
System.out.println("Map not cached yet, extracting script");
@@ -851,6 +851,19 @@ protected File loadMapScript(Optional<File> mapCopy, ModelManager modelManager,
851851
return scriptFile;
852852
}
853853

854+
private static boolean shouldExtractMapScript(File scriptFile) {
855+
if (!scriptFile.exists()) {
856+
return true;
857+
}
858+
if (!MapRequest.mapPath.equals(lastMapPath)) {
859+
return true;
860+
}
861+
if (MapRequest.mapLastModified > lastMapModified) {
862+
return true;
863+
}
864+
return MapRequest.mapLastModified > scriptFile.lastModified();
865+
}
866+
854867
private static void ensureScriptIsSynced(ModelManager modelManager, File scriptFile) {
855868
CompilationUnit compilationUnit = modelManager.getCompilationUnit(WFile.create(scriptFile));
856869
if (compilationUnit == null) {
@@ -924,7 +937,8 @@ private W3InstallationData getBestW3InstallationData() throws RequestFailedExcep
924937
W3InstallationData w3data = new W3InstallationData(langServer, new File(wc3Path.get()),
925938
this instanceof RunMap && !runArgs.isHotReload());
926939
if (w3data.getWc3PatchVersion().isEmpty()) {
927-
throw new RequestFailedException(MessageType.Error, "Could not find Warcraft III installation at specified path: " + wc3Path);
940+
WLogger.warning("Could not determine Warcraft III version at specified path: " + wc3Path
941+
+ ". Falling back to wurst.build patch target.");
928942
}
929943

930944
return w3data;

0 commit comments

Comments
 (0)