• 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

44.44
de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java
1
package de.peeeq.wurstio.intermediateLang.interpreter;
2

3
import com.google.common.collect.Maps;
4
import de.peeeq.wurstio.mpq.MpqEditor;
5
import de.peeeq.wurstio.objectreader.ObjectFileType;
6
import de.peeeq.wurstscript.WLogger;
7
import de.peeeq.wurstscript.gui.WurstGui;
8
import de.peeeq.wurstscript.intermediatelang.interpreter.ProgramState;
9
import de.peeeq.wurstscript.jassIm.ImProg;
10
import de.peeeq.wurstscript.jassIm.ImStmt;
11
import de.peeeq.wurstscript.utils.Utils;
12
import net.moonlightflower.wc3libs.bin.ObjMod;
13
import net.moonlightflower.wc3libs.bin.Wc3BinInputStream;
14
import net.moonlightflower.wc3libs.bin.Wc3BinOutputStream;
15
import net.moonlightflower.wc3libs.bin.app.objMod.*;
16
import net.moonlightflower.wc3libs.dataTypes.app.War3Int;
17
import net.moonlightflower.wc3libs.dataTypes.app.War3String;
18
import net.moonlightflower.wc3libs.txt.WTS;
19
import org.eclipse.jdt.annotation.Nullable;
20
import org.jetbrains.annotations.NotNull;
21

22
import java.io.*;
23
import java.nio.charset.StandardCharsets;
24
import java.nio.file.Files;
25
import java.nio.file.Path;
26
import java.util.List;
27
import java.util.Map;
28
import java.util.Optional;
29

30
public class ProgramStateIO extends ProgramState {
31

32
    private static final int GENERATED_BY_WURST = 42;
33
    private @Nullable ImStmt lastStatement;
34
    private @Nullable
35
    final MpqEditor mpqEditor;
36
    private final Map<ObjectFileType, ObjMod<? extends ObjMod.Obj>> dataStoreMap = Maps.newLinkedHashMap();
1✔
37
    private int id = 0;
1✔
38
    private final Map<String, ObjMod.Obj> objDefinitions = Maps.newLinkedHashMap();
1✔
39
    private PrintStream outStream = System.err;
1✔
40
    private @Nullable WTS trigStrings = null;
1✔
41
    private final Optional<File> mapFile;
42

43
    public ProgramStateIO(Optional<File> mapFile, @Nullable MpqEditor mpqEditor, WurstGui gui, ImProg prog, boolean isCompiletime) {
44
        super(gui, prog, isCompiletime);
1✔
45
        this.mapFile = mapFile;
1✔
46
        this.mpqEditor = mpqEditor;
1✔
47
    }
1✔
48

49
    @Override
50
    public void setLastStatement(ImStmt s) {
51
        lastStatement = s;
1✔
52
    }
1✔
53

54
    @Override
55
    public @Nullable ImStmt getLastStatement() {
56
        return lastStatement;
×
57
    }
58

59
    @Override
60
    public WurstGui getGui() {
61
        return super.getGui();
×
62
    }
63

64
    private String getTrigString(int id) {
65
        return loadTrigStrings().getEntry(id);
×
66
    }
67

68
    private WTS loadTrigStrings() {
69
        WTS res = trigStrings;
×
70
        if (res != null) {
×
71
            return res;
×
72
        }
73
        try {
74
            byte[] wts = mpqEditor.extractFile("war3map.wts");
×
75
            res = new WTS(new ByteArrayInputStream(wts));
×
76
        } catch (Exception e) {
×
77
            // dummy result
78
            res = new WTS();
×
79
            WLogger.warning("Could not load trigger strings");
×
80
            WLogger.info(e);
×
81
        }
×
82
        trigStrings = res;
×
83
        return res;
×
84
    }
85

86
    ObjMod<? extends ObjMod.Obj> getDataStore(String fileExtension) {
87
        return getDataStore(ObjectFileType.fromExt(fileExtension));
1✔
88
    }
89

90

91
    private ObjMod<? extends ObjMod.Obj> getDataStore(ObjectFileType filetype) throws Error {
92
        ObjMod<? extends ObjMod.Obj> dataStore = dataStoreMap.get(filetype);
1✔
93
        if (dataStore != null) {
1✔
94
            return dataStore;
1✔
95
        }
96
        if (mpqEditor == null) {
1✔
97
            // without a map: create empty object file
98
            dataStore = filetypeToObjmod(filetype);
1✔
99
            dataStore.setFormat(ObjMod.EncodingFormat.OBJ_0x2);
1✔
100
            dataStoreMap.put(filetype, dataStore);
1✔
101
            return dataStore;
1✔
102
        }
103

104
        try {
105
            // extract specific object file:
106
            String fileName = "war3map." + filetype.getExt();
×
107
            String skinFileName = "war3mapSkin." + filetype.getExt();
×
108
            try {
109
                if (mpqEditor.hasFile(fileName)) {
×
110
                    byte[] w3_ = mpqEditor.extractFile(fileName);
×
111
                    Wc3BinInputStream in = new Wc3BinInputStream(new ByteArrayInputStream(w3_));
×
112
                    switch (filetype) {
×
113
                        case UNITS:
114
                            W3U w3u = new W3U(in);
×
115
                            if (mpqEditor.hasFile(skinFileName)) {
×
116
                                byte[] w3s_ = mpqEditor.extractFile(skinFileName);
×
117
                                Wc3BinInputStream inS = new Wc3BinInputStream(new ByteArrayInputStream(w3s_));
×
118
                                W3U skin = new W3U(inS);
×
119
                                w3u.merge(skin);
×
120
                                mpqEditor.deleteFile(skinFileName);
×
121
                            }
122
                            dataStore = w3u;
×
123
                            break;
×
124
                        case ITEMS:
125
                            W3T w3t = new W3T(in);
×
126
                            if (mpqEditor.hasFile(skinFileName)) {
×
127
                                byte[] w3s_ = mpqEditor.extractFile(skinFileName);
×
128
                                Wc3BinInputStream inS = new Wc3BinInputStream(new ByteArrayInputStream(w3s_));
×
129
                                W3T skin = new W3T(inS);
×
130
                                w3t.merge(skin);
×
131
                                mpqEditor.deleteFile(skinFileName);
×
132
                            }
133
                            dataStore = w3t;
×
134
                            break;
×
135
                        case DESTRUCTABLES:
136
                            W3B w3b = new W3B(in);
×
137
                            if (mpqEditor.hasFile(skinFileName)) {
×
138
                                byte[] w3s_ = mpqEditor.extractFile(skinFileName);
×
139
                                Wc3BinInputStream inS = new Wc3BinInputStream(new ByteArrayInputStream(w3s_));
×
140
                                W3B skin = new W3B(inS);
×
141
                                w3b.merge(skin);
×
142
                                mpqEditor.deleteFile(skinFileName);
×
143
                            }
144
                            dataStore = w3b;
×
145
                            break;
×
146
                        case DOODADS:
147
                            W3D w3d = new W3D(in);
×
148
                            if (mpqEditor.hasFile(skinFileName)) {
×
149
                                byte[] w3s_ = mpqEditor.extractFile(skinFileName);
×
150
                                Wc3BinInputStream inS = new Wc3BinInputStream(new ByteArrayInputStream(w3s_));
×
151
                                W3D skin = new W3D(inS);
×
152
                                w3d.merge(skin);
×
153
                                mpqEditor.deleteFile(skinFileName);
×
154
                            }
155
                            dataStore = w3d;
×
156
                            break;
×
157
                        case ABILITIES:
158
                            W3A w3a = new W3A(in);
×
159
                            if (mpqEditor.hasFile(skinFileName)) {
×
160
                                byte[] w3s_ = mpqEditor.extractFile(skinFileName);
×
161
                                Wc3BinInputStream inS = new Wc3BinInputStream(new ByteArrayInputStream(w3s_));
×
162
                                W3A skin = new W3A(inS);
×
163
                                w3a.merge(skin);
×
164
                                mpqEditor.deleteFile(skinFileName);
×
165
                            }
166
                            dataStore = w3a;
×
167
                            break;
×
168
                        case BUFFS:
169
                            W3H w3h = new W3H(in);
×
170
                            if (mpqEditor.hasFile(skinFileName)) {
×
171
                                byte[] w3s_ = mpqEditor.extractFile(skinFileName);
×
172
                                Wc3BinInputStream inS = new Wc3BinInputStream(new ByteArrayInputStream(w3s_));
×
173
                                W3H skin = new W3H(inS);
×
174
                                w3h.merge(skin);
×
175
                                mpqEditor.deleteFile(skinFileName);
×
176
                            }
177
                            dataStore = w3h;
×
178
                            break;
×
179
                        case UPGRADES:
180
                            W3Q w3q = new W3Q(in);
×
181
                            if (mpqEditor.hasFile(skinFileName)) {
×
182
                                byte[] w3s_ = mpqEditor.extractFile(skinFileName);
×
183
                                Wc3BinInputStream inS = new Wc3BinInputStream(new ByteArrayInputStream(w3s_));
×
184
                                W3Q skin = new W3Q(inS);
×
185
                                w3q.merge(skin);
×
186
                                mpqEditor.deleteFile(skinFileName);
×
187
                            }
188
                            dataStore = w3q;
×
189
                            break;
190
                    }
191

192
                    in.close();
×
193
                    replaceTrigStrings(dataStore);
×
194

195
                } else {
×
196
                    dataStore = filetypeToObjmod(filetype);
×
197
                    dataStore.setFormat(ObjMod.EncodingFormat.OBJ_0x2);
×
198
                }
199
            } catch (IOException | InterruptedException e) {
×
200
                // TODO maybe tell the user, that something has gone wrong
201
                WLogger.info("Could not extract file: " + fileName);
×
202
                WLogger.info(e);
×
203
                dataStore = filetypeToObjmod(filetype);
×
204
                dataStore.setFormat(ObjMod.EncodingFormat.OBJ_0x2);
×
205
            }
×
206
            dataStoreMap.put(filetype, dataStore);
×
207

208
            // clean datastore, remove all objects created by wurst
209
            deleteWurstObjects(dataStore);
×
210
        } catch (Exception e) {
×
211
            WLogger.severe(e);
×
212
            throw new Error(e);
×
213
        }
×
214

215
        return dataStore;
×
216
    }
217

218
    @NotNull
219
    private ObjMod<? extends ObjMod.Obj> filetypeToObjmod(ObjectFileType filetype) {
220
        switch (filetype) {
1✔
221
            case UNITS:
222
                return new W3U();
1✔
223
            case ITEMS:
224
                return new W3T();
1✔
225
            case DESTRUCTABLES:
226
                return new W3B();
1✔
227
            case DOODADS:
228
                return new W3D();
1✔
229
            case ABILITIES:
230
                return new W3A();
1✔
231
            case BUFFS:
232
                return new W3H();
1✔
233
            case UPGRADES:
234
                return new W3Q();
1✔
235
        }
236
        return null;
×
237
    }
238

239
    private void replaceTrigStrings(ObjMod<? extends ObjMod.Obj> dataStore) {
240
        replaceTrigStringsInTable(dataStore.getOrigObjs());
×
241
        replaceTrigStringsInTable(dataStore.getCustomObjs());
×
242
    }
×
243

244
    private void replaceTrigStringsInTable(List<? extends ObjMod.Obj> modifiedTable) {
245
        for (ObjMod.Obj od : modifiedTable) {
×
246
            for (ObjMod.Obj.Mod mod : od.getMods()) {
×
247
                if (mod.getValType() == ObjMod.ValType.STRING && mod.getVal() instanceof War3String) {
×
248
                    War3String stringVal = (War3String) mod.getVal();
×
249
                    if (stringVal.getVal().startsWith("TRIGSTR_")) {
×
250
                        try {
251
                            int id = Integer.parseInt(stringVal.getVal().substring("TRIGSTR_".length()), 10);
×
252
                            String newVal = getTrigString(id);
×
253
                            stringVal.set_val(newVal);
×
254
                        } catch (NumberFormatException e) {
×
255
                            // ignore
256
                        }
×
257
                    }
258
                }
259
            }
×
260
        }
×
261
    }
×
262

263
    private void deleteWurstObjects(ObjMod<? extends ObjMod.Obj> dataStore) {
264
        for (ObjMod.Obj next : dataStore.getCustomObjs()) {
×
265
            for (ObjMod.Obj.Mod om : next.getMods()) {
×
266
                if (om.getId().getVal().equals("wurs") && om.getVal() instanceof War3Int) {
×
267
                    War3Int val = (War3Int) om.getVal();
×
268
                    if (val.getVal() == GENERATED_BY_WURST) {
×
269
                        dataStore.removeObj(next.getId());
×
270
                        break;
×
271
                    }
272
                }
273
            }
×
274
        }
×
275
    }
×
276

277

278
    String addObjectDefinition(ObjMod.Obj objDef) {
279
        id++;
1✔
280
        String key = "obj" + id;
1✔
281
        objDefinitions.put(key, objDef);
1✔
282
        return key;
1✔
283
    }
284

285
    ObjMod.Obj getObjectDefinition(String key) {
286
        return objDefinitions.get(key);
1✔
287
    }
288

289
    @Override
290
    public void writeBack(boolean inject) {
291
        gui.sendProgress("Writing back generated objects");
1✔
292

293
        for (ObjectFileType fileType : ObjectFileType.values()) {
1✔
294
            WLogger.info("Writing back " + fileType);
1✔
295
            ObjMod<? extends ObjMod.Obj> dataStore = getDataStore(fileType);
1✔
296
            if (!dataStore.getObjsList().isEmpty()) {
1✔
297
                WLogger.info("Writing back filetype " + fileType);
1✔
298
                writebackObjectFile(dataStore, fileType, inject);
1✔
299
            } else {
300
                WLogger.info("Writing back empty for " + fileType);
1✔
301
            }
302
        }
303
        writeW3oFile();
1✔
304
    }
1✔
305

306
    private void writeW3oFile() {
307
        Optional<File> objFile = getObjectEditingOutputFolder().map(fo -> new File(fo, "wurstCreatedObjects.w3o"));
1✔
308
        try (Wc3BinOutputStream objFileStream = new Wc3BinOutputStream(objFile.get())) {
1✔
309
            objFileStream.writeInt32(1); // version
1✔
310
            for (ObjectFileType fileType : ObjectFileType.values()) {
1✔
311
                ObjMod<? extends ObjMod.Obj> dataStore = getDataStore(fileType);
1✔
312
                if (!dataStore.getObjs().isEmpty()) {
1✔
313
                    objFileStream.writeInt32(1); // exists
1✔
314
                    dataStore.write(objFileStream, ObjMod.EncodingFormat.OBJ_0x2);
1✔
315
                } else {
316
                    objFileStream.writeInt32(0); // does not exist
1✔
317
                }
318
            }
319
        } catch (Error | IOException e) {
×
320
            WLogger.severe(e);
×
321
        }
1✔
322
    }
1✔
323

324
    private void writebackObjectFile(ObjMod<? extends ObjMod.Obj> dataStore, ObjectFileType fileType, boolean inject) throws Error {
325

326
        try {
327
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
1✔
328
            Wc3BinOutputStream out = new Wc3BinOutputStream(baos);
1✔
329
            Optional<File> folder = getObjectEditingOutputFolder();
1✔
330

331
            dataStore.write(out, ObjMod.EncodingFormat.OBJ_0x2);
1✔
332

333
            out.close();
1✔
334
            byte[] w3_ = baos.toByteArray();
1✔
335

336
            exportToWurst(dataStore, fileType, new File(folder.get(), "WurstExportedObjects_" + fileType.getExt() + ".wurst.txt").toPath());
1✔
337

338
            if (inject) {
1✔
339
                if (mpqEditor == null) {
×
340
                    throw new RuntimeException("Map file must be given with '-injectobjects' option.");
×
341
                }
342
                String filenameInMpq = "war3map." + fileType.getExt();
×
343
                mpqEditor.deleteFile(filenameInMpq);
×
344
                mpqEditor.insertFile(filenameInMpq, w3_);
×
345
            }
346
        } catch (Exception e) {
×
347
            WLogger.severe(e);
×
348
            throw new Error(e);
×
349
        }
1✔
350

351
    }
1✔
352

353
    public void exportToWurst(ObjMod<? extends ObjMod.Obj> dataStore,
354
                              ObjectFileType fileType, Path outFile) throws IOException {
355
        try (BufferedWriter out = Files.newBufferedWriter(outFile, StandardCharsets.UTF_8)) {
1✔
356
            out.write("package WurstExportedObjects_" + fileType.getExt() + "\n");
1✔
357
            out.write("import ObjEditingNatives\n\n");
1✔
358

359
            out.write("// Modified Table (contains all custom objects)\n\n");
1✔
360
            exportToWurst(dataStore.getCustomObjs(), fileType, out);
1✔
361

362
            out.write("// Original Table (contains all modified default objects)\n" +
1✔
363
                "// Wurst does not support modifying default objects\n" +
364
                "// but you can copy these functions and replace 'xxxx' with a new, custom id.\n\n");
365
            exportToWurst(dataStore.getOrigObjs(), fileType, out);
1✔
366
        }
367
    }
1✔
368

369

370
    public void exportToWurst(List<? extends ObjMod.Obj> customObjs, ObjectFileType fileType, Appendable out) throws IOException {
371
        for (ObjMod.Obj obj : customObjs) {
1✔
372
            String oldId = obj.getBaseId().getVal();
1✔
373
            String newId = (obj.getNewId() != null ? obj.getNewId().getVal() : "xxxx");
1✔
374
            out.append("@compiletime function create_").append(fileType.getExt()).append("_").append(newId)
1✔
375
                .append("()\n");
1✔
376
            out.append("        let def = createObjectDefinition(\"").append(fileType.getExt()).append("\", '");
1✔
377
            out.append(newId);
1✔
378
            out.append("', '");
1✔
379
            out.append(oldId);
1✔
380
            out.append("')\n");
1✔
381
            for (ObjMod.Obj.Mod m : obj.getMods()) {
1✔
382
                if (fileType.usesLevels() && m instanceof ObjMod.Obj.ExtendedMod) {
1✔
383
                    ObjMod.Obj.ExtendedMod extendedMod = (ObjMod.Obj.ExtendedMod) m;
1✔
384
                    out.append("\t..setLvlData").append(valTypeToFuncPostfix(extendedMod.getValType())).append("(\"");
1✔
385
                    out.append(m.toString());
1✔
386
                    out.append("\", ")
1✔
387
                        .append(String.valueOf(extendedMod.getLevel()))
1✔
388
                        .append(", ")
1✔
389
                        .append(String.valueOf(extendedMod.getDataPt())).append(", ")
1✔
390
                        .append((extendedMod.getValType() == ObjMod.ValType.STRING) ?
1✔
391
                            Utils.escapeString(extendedMod.getVal().toString()) :
1✔
392
                            extendedMod.getVal().toString())
1✔
393
                        .append(")\n");
1✔
394
                } else {
1✔
395
                    out.append("\t..set").append(valTypeToFuncPostfix(m.getValType())).append("(\"");
1✔
396
                    out.append(m.toString());
1✔
397
                    out.append("\", ").append((m.getValType() == ObjMod.ValType.STRING) ?
1✔
398
                        Utils.escapeString(m.getVal().toString()) :
1✔
399
                        m.getVal().toString()).append(")\n");
1✔
400
                }
401
            }
1✔
402
            out.append("\n\n");
1✔
403
        }
1✔
404
    }
1✔
405

406
    private String valTypeToFuncPostfix(ObjMod.ValType valType) {
407
        switch (valType) {
1✔
408
            case INT:
409
                return "Int";
1✔
410
            case REAL:
411
                return "Real";
1✔
412
            case UNREAL:
413
                return "Unreal";
1✔
414
            case STRING:
415
                return "String";
1✔
416
        }
417
        return "Int";
×
418
    }
419

420

421
    private Optional<File> getObjectEditingOutputFolder() {
422
        if (!mapFile.isPresent()) {
1✔
423
            File folder = new File("_build", "objectEditingOutput");
1✔
424
            folder.mkdirs();
1✔
425
            return Optional.of(folder);
1✔
426
        }
427
        Optional<File> folder = mapFile.map(fi -> new File(fi.getParent(), "objectEditingOutput"));
×
428
        if (!folder.get().exists() && !folder.get().mkdirs()) {
×
429
            WLogger.info("Could not create folder " + folder.map(File::getAbsoluteFile));
×
430
            return Optional.empty();
×
431
        }
432
        return folder;
×
433
    }
434

435
    @Override
436
    public PrintStream getOutStream() {
437
        return outStream;
×
438
    }
439

440
    @Override
441
    public void setOutStream(PrintStream os) {
442
        outStream = os;
1✔
443
    }
1✔
444

445

446
}
447

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