• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wurstscript / WurstScript / 271

29 Sep 2025 12:12PM UTC coverage: 64.649% (+2.4%) from 62.222%
271

Pull #1096

circleci

Frotty
Merge branch 'perf-improvements' of https://github.com/wurstscript/WurstScript into perf-improvements
Pull Request #1096: Perf improvements

18202 of 28155 relevant lines covered (64.65%)

0.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/RunMap.java
1
package de.peeeq.wurstio.languageserver.requests;
2

3
import com.google.common.collect.Lists;
4
import com.google.common.io.Files;
5
import config.WurstProjectConfig;
6
import config.WurstProjectConfigData;
7
import de.peeeq.wurstio.gui.WurstGuiImpl;
8
import de.peeeq.wurstio.languageserver.ModelManager;
9
import de.peeeq.wurstio.languageserver.WFile;
10
import de.peeeq.wurstio.languageserver.WurstLanguageServer;
11
import de.peeeq.wurstscript.WLogger;
12
import de.peeeq.wurstscript.attributes.CompileError;
13
import de.peeeq.wurstscript.gui.WurstGui;
14
import de.peeeq.wurstscript.utils.Utils;
15
import net.moonlightflower.wc3libs.port.GameVersion;
16
import net.moonlightflower.wc3libs.port.Orient;
17
import org.apache.commons.lang.RandomStringUtils;
18
import org.apache.commons.lang.StringUtils;
19
import org.eclipse.lsp4j.MessageType;
20
import org.jetbrains.annotations.NotNull;
21
import org.jetbrains.annotations.Nullable;
22

23
import javax.swing.*;
24
import javax.swing.filechooser.FileSystemView;
25
import java.io.File;
26
import java.io.FileNotFoundException;
27
import java.io.IOException;
28
import java.nio.file.Path;
29
import java.nio.file.Paths;
30
import java.time.Duration;
31
import java.util.Arrays;
32
import java.util.List;
33
import java.util.Optional;
34

35
import static de.peeeq.wurstio.languageserver.ProjectConfigBuilder.FILE_NAME;
36
import static net.moonlightflower.wc3libs.port.GameVersion.VERSION_1_31;
37
import static net.moonlightflower.wc3libs.port.GameVersion.VERSION_1_32;
38

39
/**
40
 * Created by peter on 16.05.16.
41
 */
42
public class RunMap extends MapRequest {
43

44
    private @Nullable File customTarget = null;
×
45

46

47
    public RunMap(WurstLanguageServer langServer, WFile workspaceRoot, Optional<String> wc3Path, Optional<File> map,
48
                  List<String> compileArgs, Optional<String> gameExePath) {
49
        super(langServer, map, compileArgs, workspaceRoot, wc3Path, gameExePath);
×
50
        safeCompilation = SafetyLevel.QuickAndDirty;
×
51
    }
×
52

53
    @Override
54
    public Object execute(ModelManager modelManager) throws IOException {
55
        WLogger.info("Execute RunMap, \nwc3Path =" + wc3Path
×
56
            + ",\n map = " + map
57
            + ",\n compileArgs = " + compileArgs
58
            + ",\n workspaceRoot = " + workspaceRoot
59
            + ",\n runArgs = " + compileArgs
60
        );
61

62
        if (modelManager.hasErrors()) {
×
63
            throw new RequestFailedException(MessageType.Error, "Fix errors in your code before running.\n" + modelManager.getFirstErrorDescription());
×
64
        }
65

66
        WurstProjectConfigData projectConfig = WurstProjectConfig.INSTANCE.loadProject(workspaceRoot.getFile().toPath().resolve(FILE_NAME));
×
67
        if (projectConfig == null) {
×
68
            throw new RequestFailedException(MessageType.Error, FILE_NAME + " file doesn't exist or is invalid. " +
×
69
                "Please install your project using grill or the wurst setup tool.");
70
        }
71

72
        // TODO use normal compiler for this, avoid code duplication
73
        WurstGui gui = new WurstGuiImpl(getWorkspaceAbsolute());
×
74
        try {
75
            String ok = compileMap(modelManager, gui, projectConfig);
×
76
            if (ok != null) return ok;
×
77
        } catch (CompileError e) {
×
78
            WLogger.info(e);
×
79
            throw new RequestFailedException(MessageType.Error, "A compilation error occurred when running the map:\n" + e);
×
80
        } catch (Exception e) {
×
81
            WLogger.warning("Exception occurred", e);
×
82
            throw new RequestFailedException(MessageType.Error, "An exception was thrown when running the map:\n" + e);
×
83
        } finally {
84
            if (gui.getErrorCount() == 0) {
×
85
                gui.sendFinished();
×
86
            }
87
        }
88
        return "ok"; // TODO
×
89
    }
90

91
    @Nullable
92
    private String compileMap(ModelManager modelManager, WurstGui gui, WurstProjectConfigData projectConfig) throws Exception {
93
        if (map.isPresent() && !map.get().exists()) {
×
94
            throw new RequestFailedException(MessageType.Error, map.get().getAbsolutePath() + " does not exist.");
×
95
        }
96

97
        gui.sendProgress("Copying map");
×
98

99
        // first we copy in same location to ensure validity
100
        File buildDir = getBuildDir();
×
101
        if (map.isPresent()) {
×
102
            mapLastModified = map.get().lastModified();
×
103
            mapPath = map.get().getAbsolutePath();
×
104
        }
105
        Optional<File> testMap = map.map($ -> new File(buildDir, "WurstRunMap.w3x"));
×
106
        CompilationResult result = compileScript(modelManager, gui, testMap, projectConfig, buildDir, false);
×
107

108
        if (runArgs.isHotReload()) {
×
109
            // call jhcr update
110
            gui.sendProgress("Calling JHCR update");
×
111
            callJhcrUpdate(result.script);
×
112

113
            // if we are just reloading the mapscript with JHCR, we are done here
114
            gui.sendProgress("update complete");
×
115
            return "ok";
×
116
        }
117

118

119
        if (testMap.isPresent()) {
×
120
            startGame(gui, testMap, result);
×
121
        }
122
        return null;
×
123
    }
124

125
    private void startGame(WurstGui gui, Optional<File> testMap, CompilationResult result) throws Exception {
126
        injectMapData(gui, testMap, result);
×
127

128
        timeTaker.beginPhase("Starting Warcraft 3");
×
129
        gui.sendProgress("Starting Warcraft 3...");
×
130

131
        File mapCopy = copyToWarcraftMapDir(testMap.get());
×
132

133
        WLogger.info("Starting wc3 ... ");
×
134
        String path = "";
×
135
        if (customTarget != null) {
×
136
            path = new File(customTarget, testMap.get().getName()).getAbsolutePath();
×
137
        } else if (mapCopy != null) {
×
138
            path = mapCopy.getAbsolutePath();
×
139
        }
140

141

142
        if (!path.isEmpty()) {
×
143
            // now start the map
144
            File gameExe = w3data.getGameExe().get();
×
145
            if (!w3data.getWc3PatchVersion().isPresent()) {
×
146
                throw new RequestFailedException(MessageType.Error, wc3Path + " does not exist.");
×
147
            }
148
            List<String> cmd = Lists.newArrayList(gameExe.getAbsolutePath());
×
149
            Optional<String> wc3RunArgs = langServer.getConfigProvider().getWc3RunArgs();
×
150
            if (!wc3RunArgs.isPresent() || StringUtils.isBlank(wc3RunArgs.get())) {
×
151
                if (w3data.getWc3PatchVersion().get().compareTo(VERSION_1_32) >= 0) {
×
152
                    cmd.add("-launch");
×
153
                }
154
                if (w3data.getWc3PatchVersion().get().compareTo(VERSION_1_31) < 0) {
×
155
                    cmd.add("-window");
×
156
                } else {
157
                    cmd.add("-windowmode");
×
158
                    cmd.add("windowed");
×
159
                }
160
            } else {
161
                cmd.addAll(Arrays.asList(wc3RunArgs.get().split("\\s+")));
×
162
            }
163
            cmd.add("-loadfile");
×
164
            cmd.add(path);
×
165

166
            if (Orient.isLinuxSystem()) {
×
167
                // run with wine
168
                cmd.add(0, "wine");
×
169
            }
170
            timeTaker.endPhase();
×
171

172
            gui.sendProgress("running " + cmd);
×
173
            Runtime.getRuntime().exec(cmd.toArray(new String[0]));
×
174
            timeTaker.endPhase();
×
175
            timeTaker.printReport();
×
176
        }
177
    }
×
178

179

180
    private void callJhcrUpdate(File mapScript) throws IOException, InterruptedException {
181
        File mapScriptFolder = mapScript.getParentFile();
×
182
        File commonJ = new File(mapScriptFolder, "common.j");
×
183
        File blizzardJ = new File(mapScriptFolder, "blizzard.j");
×
184
        if (!commonJ.exists()) {
×
185
            throw new IOException("Could not find file " + commonJ.getAbsolutePath());
×
186
        }
187

188
        if (!blizzardJ.exists()) {
×
189
            throw new IOException("Could not find file " + blizzardJ.getAbsolutePath());
×
190
        }
191

192
        Path customMapDataPath = getCustomMapDataPath();
×
193

194
        ProcessBuilder pb = new ProcessBuilder(langServer.getConfigProvider().getJhcrExe(), "update", mapScript.getName(), "--asm",
×
195
            "--preload-path", customMapDataPath.toAbsolutePath().toString());
×
196
        pb.directory(mapScriptFolder);
×
197
        Utils.ExecResult $ = Utils.exec(pb, Duration.ofSeconds(30), System.err::println);
×
198
    }
×
199

200
    /**
201
     * Tries to find the path where the wc3 CustomMapData is stored.
202
     * For example this could be in:
203
     * C:\\Users\\Peter\\Documents\\Warcraft III\\CustomMapData
204
     */
205
    private Path getCustomMapDataPath() {
206
        String customMapDataPath = langServer.getConfigProvider().getConfig("customMapDataPath", "");
×
207
        if (!customMapDataPath.isEmpty()) {
×
208
            return Paths.get(customMapDataPath);
×
209
        }
210

211
        Path documents;
212
        try {
213
            documents = FileSystemView.getFileSystemView().getDefaultDirectory().toPath();
×
214
        } catch (Throwable t) {
×
215
            WLogger.info(t);
×
216
            Path homeFolder = Paths.get(System.getProperty("user.home"));
×
217
            documents = homeFolder.resolve("Documents");
×
218
        }
×
219
        return documents.resolve(Paths.get("Warcraft III", "CustomMapData"));
×
220
    }
221

222
    @NotNull
223
    private String getWorkspaceAbsolute() {
224
        try {
225
            return workspaceRoot.getFile().getAbsolutePath();
×
226
        } catch (FileNotFoundException e) {
×
227
            throw new RequestFailedException(MessageType.Error, "Could not open workspace root: ", e);
×
228
        }
229
    }
230

231
    /**
232
     * Copies the map to the wc3 map directory
233
     * <p>
234
     * This directory depends on warcraft version and whether we are on windows or wine is used.
235
     */
236
    private File copyToWarcraftMapDir(File testMap) throws IOException {
237
        String testMapName = "WurstTestMap.w3x";
×
238
        for (String arg : compileArgs) {
×
239
            if (arg.startsWith("-runmapTarget")) {
×
240
                String path = arg.substring(arg.indexOf(" ") + 1);
×
241
                // copy the map to the specified directory
242
                customTarget = new File(path);
×
243
                if (customTarget.exists() && customTarget.isDirectory()) {
×
244
                    File testMap2 = new File(customTarget, testMapName);
×
245
                    Files.copy(testMap, testMap2);
×
246
                    return testMap2;
×
247
                } else {
248
                    WLogger.severe("Directory specified via -runmapTarget does not exists or is not a directory");
×
249
                }
250
            }
251
        }
×
252

253
        File myDocumentsFolder = FileSystemView.getFileSystemView().getDefaultDirectory();
×
254
        Optional<String> documentPath = findMapDocumentPath(testMapName, myDocumentsFolder);
×
255

256
        // copy the map to the appropriate directory
257
        Optional<File> testFolder = documentPath.map(path -> new File(path, "Maps" + File.separator + "Test"));
×
258
        if (testFolder.isPresent() && (testFolder.get().mkdirs() || testFolder.get().exists())) {
×
259
            File testMap2 = new File(testFolder.get(), testMapName);
×
260
            while (true) {
261
                try {
262
                    Files.copy(testMap, testMap2);
×
263
                    break;
×
264
                } catch (IOException ex) {
×
265
                    JFrame jf = new JFrame();
×
266
                    jf.setAlwaysOnTop(true);
×
267
                    Object[] options = { "Retry", "Rename", "Cancel" };
×
268
                    int result = JOptionPane.showOptionDialog(jf, "Can't write to target map file, it's probably in use.",
×
269
                        "Run Map", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE,
270
                        null, options, null);
271
                    if (result == JOptionPane.CANCEL_OPTION) {
×
272
                        return null;
×
273
                    } else if(result == JOptionPane.NO_OPTION) {
×
274
                        testMap2 = new File(testFolder.get(), testMapName + RandomStringUtils.randomNumeric(3));
×
275
                    }
276
                }
×
277

278
            }
279

280
            return testMap2;
×
281
        } else {
282
            WLogger.severe("Could not create Test folder");
×
283
        }
284
        return null;
×
285
    }
286

287
    private Optional<String> findMapDocumentPath(String testMapName, File myDocumentsFolder) {
288
        Optional<String> documentPath = Optional.of(
×
289
            langServer.getConfigProvider().getMapDocumentPath().orElseGet(
×
290
                () -> myDocumentsFolder.getAbsolutePath() + File.separator + "Warcraft III"));
×
291

292
        if (!new File(documentPath.get()).exists()) {
×
293
            WLogger.info("Warcraft folder " + documentPath + " does not exist.");
×
294
            // Try wine default:
295
            documentPath = Optional.of(System.getProperty("user.home")
×
296
                + "/.wine/drive_c/users/" + System.getProperty("user.name") + "/My Documents/Warcraft III");
×
297
            if (!new File(documentPath.get()).exists()) {
×
298
                WLogger.severe("Severe: Wine Warcraft folder " + documentPath + " does not exist.");
×
299
            }
300
        }
301

302
        if (w3data.getWc3PatchVersion().get().compareTo(new GameVersion("1.27.9")) <= 0) {
×
303
            // 1.27 and lower compat
304
            WLogger.info("Version 1.27 or lower detected, changing file location");
×
305
            documentPath = wc3Path;
×
306
        } else {
307
            // For 1.28+ the wc3/maps/test folder must not contain a map of the same name
308
            Optional<File> oldFile = wc3Path.map(
×
309
                w3p -> new File(w3p, "Maps" + File.separator + "Test" + File.separator + testMapName));
×
310
            if (oldFile.isPresent() && oldFile.get().exists()) {
×
311
                if (!oldFile.get().delete()) {
×
312
                    WLogger.severe("Cannot delete old Wurst Test Map");
×
313
                }
314
            }
315
        }
316
        return documentPath;
×
317
    }
318

319

320
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc