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

wurstscript / WurstScript / 203

18 Oct 2023 02:20PM UTC coverage: 63.758% (+0.3%) from 63.447%
203

push

circleci

web-flow
Update deps, improve performance, JHCR fixes (#1080)

- update dependencies
- update stdlib verison for unit tests
- only apply nullsetting when `-opt` is enabled to save some build time for testing
- minor performance improvements
- make build deterministic
- apply build map config before compilation
- hot code reload now more reliable

17246 of 27049 relevant lines covered (63.76%)

0.64 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.TimeTaker;
8
import de.peeeq.wurstio.gui.WurstGuiImpl;
9
import de.peeeq.wurstio.languageserver.ConfigProvider;
10
import de.peeeq.wurstio.languageserver.ModelManager;
11
import de.peeeq.wurstio.languageserver.ProjectConfigBuilder;
12
import de.peeeq.wurstio.languageserver.WFile;
13
import de.peeeq.wurstio.mpq.MpqEditor;
14
import de.peeeq.wurstio.mpq.MpqEditorFactory;
15
import de.peeeq.wurstscript.WLogger;
16
import de.peeeq.wurstscript.attributes.CompileError;
17
import de.peeeq.wurstscript.gui.WurstGui;
18
import de.peeeq.wurstscript.utils.Utils;
19
import net.moonlightflower.wc3libs.bin.app.W3I;
20
import net.moonlightflower.wc3libs.port.GameVersion;
21
import net.moonlightflower.wc3libs.port.Orient;
22
import org.apache.commons.lang.RandomStringUtils;
23
import org.apache.commons.lang.StringUtils;
24
import org.eclipse.lsp4j.MessageType;
25
import org.jetbrains.annotations.NotNull;
26
import org.jetbrains.annotations.Nullable;
27

28
import javax.swing.*;
29
import javax.swing.filechooser.FileSystemView;
30
import java.io.File;
31
import java.io.FileNotFoundException;
32
import java.io.IOException;
33
import java.nio.file.Path;
34
import java.nio.file.Paths;
35
import java.time.Duration;
36
import java.util.List;
37
import java.util.Optional;
38
import java.util.Arrays;
39

40
import static de.peeeq.wurstio.languageserver.ProjectConfigBuilder.FILE_NAME;
41
import static net.moonlightflower.wc3libs.port.GameVersion.*;
42

43
/**
44
 * Created by peter on 16.05.16.
45
 */
46
public class RunMap extends MapRequest {
47

48
    private @Nullable File customTarget = null;
×
49

50

51
    public RunMap(ConfigProvider configProvider, WFile workspaceRoot, Optional<String> wc3Path, Optional<File> map,
52
            List<String> compileArgs) {
53
        super(configProvider, map, compileArgs, workspaceRoot, wc3Path);
×
54
        safeCompilation = SafetyLevel.QuickAndDirty;
×
55
    }
×
56

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

66
        if (modelManager.hasErrors()) {
×
67
            throw new RequestFailedException(MessageType.Error, "Fix errors in your code before running.\n" + modelManager.getFirstErrorDescription());
×
68
        }
69

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

76
        // TODO use normal compiler for this, avoid code duplication
77
        WLogger.info("received runMap command: map=" + map + ", wc3dir=" + wc3Path + ", args=" + compileArgs);
×
78
        WurstGui gui = new WurstGuiImpl(getWorkspaceAbsolute());
×
79
        try {
80
            if (map.isPresent() && !map.get().exists()) {
×
81
                throw new RequestFailedException(MessageType.Error, map.get().getAbsolutePath() + " does not exist.");
×
82
            }
83

84
            gui.sendProgress("Copying map");
×
85

86
            // first we copy in same location to ensure validity
87
            File buildDir = getBuildDir();
×
88
            Optional<File> testMap = map.map($ -> new File(buildDir, "WurstRunMap.w3x"));
×
89
            CompilationResult result = compileScript(modelManager, gui, testMap, projectConfig, buildDir, false);
×
90

91
            if (runArgs.isHotReload()) {
×
92
                // call jhcr update
93
                gui.sendProgress("Calling JHCR update");
×
94
                callJhcrUpdate(result.script);
×
95

96
                // if we are just reloading the mapscript with JHCR, we are done here
97
                gui.sendProgress("update complete");
×
98
                return "ok";
×
99
            }
100

101

102
            if (testMap.isPresent()) {
×
103

104
                injectMapData(gui, testMap, result);
×
105

106
                File mapCopy = copyToWarcraftMapDir(testMap.get());
×
107

108
                gui.sendProgress("Starting Warcraft 3...");
×
109
                WLogger.info("Starting wc3 ... ");
×
110
                String path = "";
×
111
                if (customTarget != null) {
×
112
                    path = new File(customTarget, testMap.get().getName()).getAbsolutePath();
×
113
                } else if (mapCopy != null) {
×
114
                    path = mapCopy.getAbsolutePath();
×
115
                }
116

117

118
                if (path.length() > 0) {
×
119
                    // now start the map
120
                    File gameExe = w3data.getGameExe().get();
×
121
                    if (!w3data.getWc3PatchVersion().isPresent()) {
×
122
                        throw new RequestFailedException(MessageType.Error, wc3Path + " does not exist.");
×
123
                    }
124
                    List<String> cmd = Lists.newArrayList(gameExe.getAbsolutePath());
×
125
                    Optional<String> wc3RunArgs = configProvider.getWc3RunArgs();
×
126
                    if (!wc3RunArgs.isPresent() || StringUtils.isBlank(wc3RunArgs.get())) {
×
127
                        if (w3data.getWc3PatchVersion().get().compareTo(VERSION_1_32) >= 0) {
×
128
                            cmd.add("-launch");
×
129
                        }
130
                            if (w3data.getWc3PatchVersion().get().compareTo(VERSION_1_31) < 0) {
×
131
                                cmd.add("-window");
×
132
                            } else {
133
                                cmd.add("-windowmode");
×
134
                                cmd.add("windowed");
×
135
                            }
136
                    } else {
137
                            cmd.addAll(Arrays.asList(wc3RunArgs.get().split("\\s+")));
×
138
                    }
139
                    cmd.add("-loadfile");
×
140
                    cmd.add(path);
×
141

142
                    if (Orient.isLinuxSystem()) {
×
143
                        // run with wine
144
                        cmd.add(0, "wine");
×
145
                    }
146

147
                    gui.sendProgress("running " + cmd);
×
148
                    Runtime.getRuntime().exec(cmd.toArray(new String[0]));
×
149
                    timeTaker.printReport();
×
150
                }
151
            }
152
        } catch (CompileError e) {
×
153
            WLogger.info(e);
×
154
            throw new RequestFailedException(MessageType.Error, "A compilation error occurred when running the map:\n" + e);
×
155
        } catch (Exception e) {
×
156
            WLogger.warning("Exception occurred", e);
×
157
            throw new RequestFailedException(MessageType.Error, "An exception was thrown when running the map:\n" + e);
×
158
        } finally {
159
            if (gui.getErrorCount() == 0) {
×
160
                gui.sendFinished();
×
161
            }
162
        }
163
        return "ok"; // TODO
×
164
    }
165

166

167
    private void callJhcrUpdate(File mapScript) throws IOException, InterruptedException {
168
        File mapScriptFolder = mapScript.getParentFile();
×
169
        File commonJ = new File(mapScriptFolder, "common.j");
×
170
        File blizzardJ = new File(mapScriptFolder, "blizzard.j");
×
171
        if (!commonJ.exists()) {
×
172
            throw new IOException("Could not find file " + commonJ.getAbsolutePath());
×
173
        }
174

175
        if (!blizzardJ.exists()) {
×
176
            throw new IOException("Could not find file " + blizzardJ.getAbsolutePath());
×
177
        }
178

179
        Path customMapDataPath = getCustomMapDataPath();
×
180

181
        ProcessBuilder pb = new ProcessBuilder(configProvider.getJhcrExe(), "update", mapScript.getName(), "--asm",
×
182
            "--preload-path", customMapDataPath.toAbsolutePath().toString());
×
183
        pb.directory(mapScriptFolder);
×
184
        Utils.ExecResult $ = Utils.exec(pb, Duration.ofSeconds(30), System.err::println);
×
185
    }
×
186

187
    /**
188
     * Tries to find the path where the wc3 CustomMapData is stored.
189
     * For example this could be in:
190
     * C:\\Users\\Peter\\Documents\\Warcraft III\\CustomMapData
191
     */
192
    private Path getCustomMapDataPath() {
193
        String customMapDataPath = configProvider.getConfig("customMapDataPath", "");
×
194
        if (!customMapDataPath.isEmpty()) {
×
195
            return Paths.get(customMapDataPath);
×
196
        }
197

198
        Path documents;
199
        try {
200
            documents = FileSystemView.getFileSystemView().getDefaultDirectory().toPath();
×
201
        } catch (Throwable t) {
×
202
            WLogger.info(t);
×
203
            Path homeFolder = Paths.get(System.getProperty("user.home"));
×
204
            documents = homeFolder.resolve("Documents");
×
205
        }
×
206
        return documents.resolve(Paths.get("Warcraft III", "CustomMapData"));
×
207
    }
208

209
    @NotNull
210
    private String getWorkspaceAbsolute() {
211
        try {
212
            return workspaceRoot.getFile().getAbsolutePath();
×
213
        } catch (FileNotFoundException e) {
×
214
            throw new RequestFailedException(MessageType.Error, "Could not open workspace root: ", e);
×
215
        }
216
    }
217

218
    /**
219
     * Copies the map to the wc3 map directory
220
     * <p>
221
     * This directory depends on warcraft version and whether we are on windows or wine is used.
222
     */
223
    private File copyToWarcraftMapDir(File testMap) throws IOException {
224
        String testMapName = "WurstTestMap.w3x";
×
225
        for (String arg : compileArgs) {
×
226
            if (arg.startsWith("-runmapTarget")) {
×
227
                String path = arg.substring(arg.indexOf(" ") + 1);
×
228
                // copy the map to the specified directory
229
                customTarget = new File(path);
×
230
                if (customTarget.exists() && customTarget.isDirectory()) {
×
231
                    File testMap2 = new File(customTarget, testMapName);
×
232
                    Files.copy(testMap, testMap2);
×
233
                    return testMap2;
×
234
                } else {
235
                    WLogger.severe("Directory specified via -runmapTarget does not exists or is not a directory");
×
236
                }
237
            }
238
        }
×
239

240
        File myDocumentsFolder = FileSystemView.getFileSystemView().getDefaultDirectory();
×
241
        Optional<String> documentPath = findMapDocumentPath(testMapName, myDocumentsFolder);
×
242

243
        // copy the map to the appropriate directory
244
        Optional<File> testFolder = documentPath.map(path -> new File(path, "Maps" + File.separator + "Test"));
×
245
        if (testFolder.isPresent() && (testFolder.get().mkdirs() || testFolder.get().exists())) {
×
246
            File testMap2 = new File(testFolder.get(), testMapName);
×
247
            while (true) {
248
                try {
249
                    Files.copy(testMap, testMap2);
×
250
                    break;
×
251
                } catch (IOException ex) {
×
252
                    JFrame jf = new JFrame();
×
253
                    jf.setAlwaysOnTop(true);
×
254
                    Object[] options = { "Retry", "Rename", "Cancel" };
×
255
                    int result = JOptionPane.showOptionDialog(jf, "Can't write to target map file, it's probably in use.",
×
256
                        "Run Map", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE,
257
                        null, options, null);
258
                    if (result == JOptionPane.CANCEL_OPTION) {
×
259
                        return null;
×
260
                    } else if(result == JOptionPane.NO_OPTION) {
×
261
                        testMap2 = new File(testFolder.get(), testMapName + RandomStringUtils.randomNumeric(3));
×
262
                    }
263
                }
×
264

265
            }
266

267
            return testMap2;
×
268
        } else {
269
            WLogger.severe("Could not create Test folder");
×
270
        }
271
        return null;
×
272
    }
273

274
    private Optional<String> findMapDocumentPath(String testMapName, File myDocumentsFolder) {
275
        Optional<String> documentPath = Optional.of(
×
276
            configProvider.getMapDocumentPath().orElseGet(
×
277
                () -> myDocumentsFolder.getAbsolutePath() + File.separator + "Warcraft III"));
×
278

279
        if (!new File(documentPath.get()).exists()) {
×
280
            WLogger.info("Warcraft folder " + documentPath + " does not exist.");
×
281
            // Try wine default:
282
            documentPath = Optional.of(System.getProperty("user.home")
×
283
                + "/.wine/drive_c/users/" + System.getProperty("user.name") + "/My Documents/Warcraft III");
×
284
            if (!new File(documentPath.get()).exists()) {
×
285
                WLogger.severe("Severe: Wine Warcraft folder " + documentPath + " does not exist.");
×
286
            }
287
        }
288

289
        if (w3data.getWc3PatchVersion().get().compareTo(new GameVersion("1.27.9")) <= 0) {
×
290
            // 1.27 and lower compat
291
            WLogger.info("Version 1.27 or lower detected, changing file location");
×
292
            documentPath = wc3Path;
×
293
        } else {
294
            // For 1.28+ the wc3/maps/test folder must not contain a map of the same name
295
            Optional<File> oldFile = wc3Path.map(
×
296
                w3p -> new File(w3p, "Maps" + File.separator + "Test" + File.separator + testMapName));
×
297
            if (oldFile.isPresent() && oldFile.get().exists()) {
×
298
                if (!oldFile.get().delete()) {
×
299
                    WLogger.severe("Cannot delete old Wurst Test Map");
×
300
                }
301
            }
302
        }
303
        return documentPath;
×
304
    }
305

306

307
}
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

© 2026 Coveralls, Inc