Skip to content

Commit 663a2e1

Browse files
authored
Fix objmod output & make quiet more quiet (#1197)
1 parent 1d75464 commit 663a2e1

7 files changed

Lines changed: 10516 additions & 3401 deletions

File tree

HelperScripts/generate-obj-mappings.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ interface MethodMapping {
3636
dataPtr: number;
3737
/** "Int", "Unreal", "String", "Boolean", "Real" as found in the wurst method call */
3838
wurstCallType: string;
39+
/** Public setter parameter type used for the object value, e.g. "string" or "ArmorType" */
40+
valueParamType: string;
3941
/** true if method signature has (int level, ...) */
4042
hasLevel: boolean;
4143
}
@@ -133,6 +135,7 @@ function parseObjEditingFile(content: string): ClassDef[] {
133135
let currentClass: ClassDef | null = null;
134136
let currentMethodName: string | null = null;
135137
let currentMethodHasLevel = false;
138+
let currentMethodValueType = "";
136139
let insidePreset = false;
137140

138141
for (const line of lines) {
@@ -174,14 +177,17 @@ function parseObjEditingFile(content: string): ClassDef[] {
174177
const fnName = funcMatch[1];
175178
if (fnName.startsWith("preset") || fnName.startsWith("get")) {
176179
currentMethodName = null;
180+
currentMethodValueType = "";
177181
insidePreset = true;
178182
} else if (fnName.startsWith("set")) {
179183
insidePreset = false;
180184
currentMethodName = fnName;
181185
// Does the signature include "int level" as first parameter?
182186
currentMethodHasLevel = /\(int level[,)]/.test(line);
187+
currentMethodValueType = parseValueParamType(line, currentMethodHasLevel);
183188
} else {
184189
currentMethodName = null;
190+
currentMethodValueType = "";
185191
insidePreset = true; // skip non-set/get functions too
186192
}
187193
continue;
@@ -206,9 +212,11 @@ function parseObjEditingFile(content: string): ClassDef[] {
206212
fieldId,
207213
dataPtr,
208214
wurstCallType,
215+
valueParamType: currentMethodValueType,
209216
hasLevel: currentMethodHasLevel,
210217
});
211218
currentMethodName = null; // one mapping per method
219+
currentMethodValueType = "";
212220
continue;
213221
}
214222

@@ -225,16 +233,31 @@ function parseObjEditingFile(content: string): ClassDef[] {
225233
fieldId,
226234
dataPtr: 0, // WC3 WE stores non-level fields as ExtendedMod with dataPtr=0
227235
wurstCallType,
236+
valueParamType: currentMethodValueType,
228237
hasLevel: false,
229238
});
230239
currentMethodName = null;
240+
currentMethodValueType = "";
231241
continue;
232242
}
233243
}
234244

235245
return classes;
236246
}
237247

248+
function parseValueParamType(functionLine: string, hasLevel: boolean): string {
249+
const paramsMatch = functionLine.match(/\((.*)\)/);
250+
if (!paramsMatch) return "";
251+
const params = paramsMatch[1]
252+
.split(",")
253+
.map((p) => p.trim())
254+
.filter((p) => p.length > 0);
255+
const valueParam = hasLevel ? params[1] : params[0];
256+
if (!valueParam) return "";
257+
const typeMatch = valueParam.match(/^(?:vararg\s+)?([A-Za-z_]\w*)\s+\w+$/);
258+
return typeMatch ? typeMatch[1] : "";
259+
}
260+
238261
// ---------------------------------------------------------------------------
239262
// Resolve all methods for a class including inherited ones
240263
// ---------------------------------------------------------------------------
@@ -287,11 +310,13 @@ function resolveAllMethods(
287310
// "itemFieldMethods": { ... }
288311
// }
289312
//
290-
// Field entry is a 3-element array [methodName, hasLevel, isBool] to keep the
313+
// Field entry is a compact array:
314+
// [methodName, hasLevel, isBool, storageValueType, parameterType].
315+
// The Java reader still accepts the old 3-element shape for compatibility.
291316
// file compact.
292317
// ---------------------------------------------------------------------------
293318

294-
type FieldEntry = [string, boolean, boolean]; // [methodName, hasLevel, isBool]
319+
type FieldEntry = [string, boolean, boolean, string, string];
295320

296321
function generateJson(
297322
abilityIdMap: Map<string, string>,
@@ -335,7 +360,7 @@ function generateJson(
335360
for (const m of cls.methods) {
336361
const key = `${m.fieldId}:${m.dataPtr}`;
337362
if (!(key in own)) {
338-
own[key] = [m.methodName, m.hasLevel, m.wurstCallType === "Boolean"];
363+
own[key] = [m.methodName, m.hasLevel, m.wurstCallType === "Boolean", m.wurstCallType, m.valueParamType];
339364
}
340365
}
341366
// sort by key for deterministic output
@@ -356,7 +381,7 @@ function generateJson(
356381
for (const m of all) {
357382
const key = `${m.fieldId}:${m.dataPtr}`;
358383
if (!(key in result)) {
359-
result[key] = [m.methodName, m.hasLevel, m.wurstCallType === "Boolean"];
384+
result[key] = [m.methodName, m.hasLevel, m.wurstCallType === "Boolean", m.wurstCallType, m.valueParamType];
360385
}
361386
}
362387
return Object.fromEntries(Object.entries(result).sort(([a], [b]) => a.localeCompare(b)));

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java

Lines changed: 148 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.jetbrains.annotations.NotNull;
2222

2323
import java.io.*;
24+
import java.math.BigDecimal;
2425
import java.nio.charset.StandardCharsets;
2526
import java.nio.file.Files;
2627
import java.nio.file.Path;
@@ -556,6 +557,7 @@ public void exportToWurst(ObjMod<? extends ObjMod.Obj> dataStore,
556557
try (BufferedWriter out = Files.newBufferedWriter(outFile, StandardCharsets.UTF_8)) {
557558
out.write("package WurstExportedObjects_" + fileType.getExt() + "\n");
558559
out.write("import ObjEditingNatives\n");
560+
out.write("import ObjEditingCommons\n");
559561
// Add the appropriate stdlib wrapper import for the file type so generated
560562
// code using e.g. AbilityDefinitionSlow can reference that class directly.
561563
switch (fileType) {
@@ -675,6 +677,13 @@ private static boolean tryExportWithWrapper(Appendable out, ObjectFileType fileT
675677
return false;
676678
}
677679

680+
for (ObjMod.Obj.Mod m : mods) {
681+
StdlibObjectMappings.FieldMethodInfo info = fieldMethods.get(fieldKey(m, fileType));
682+
if (info != null && !canUseWrapperForMod(m, info)) {
683+
return false;
684+
}
685+
}
686+
678687
out.append("@compiletime function create_").append(fileType.getExt()).append("_").append(newId)
679688
.append("()\n");
680689
out.append("\tnew ").append(wrapperClass).append("(").append(constructorArgs).append(")\n");
@@ -686,7 +695,7 @@ private static boolean tryExportWithWrapper(Appendable out, ObjectFileType fileT
686695
if (info.hasLevel() && m instanceof ObjMod.Obj.ExtendedMod) {
687696
out.append(String.valueOf(((ObjMod.Obj.ExtendedMod) m).getLevel())).append(", ");
688697
}
689-
out.append(formatModValue(m, info.isBoolField())).append(")\n");
698+
out.append(formatWrapperValue(m, info)).append(")\n");
690699
} else {
691700
// No wrapper method for this field — emit as a commented raw call so the
692701
// user can see what needs to be handled and add it manually.
@@ -714,6 +723,36 @@ static String fieldKey(ObjMod.Obj.Mod m, ObjectFileType fileType) {
714723
return m.toString() + ":0";
715724
}
716725

726+
private static boolean canUseWrapperForMod(ObjMod.Obj.Mod m, StdlibObjectMappings.FieldMethodInfo info) {
727+
if (!info.parameterType().isEmpty() && !isPrimitiveParameter(info.parameterType())
728+
&& !isEnumParameter(info.parameterType())) {
729+
return false;
730+
}
731+
if (isEnumParameter(info.parameterType())) {
732+
return enumConstantForObjectString(info.parameterType(), m.getVal().toString()) != null;
733+
}
734+
return true;
735+
}
736+
737+
private static boolean isEnumParameter(String parameterType) {
738+
return ENUM_OBJECT_STRING_TO_CONSTANT.containsKey(parameterType);
739+
}
740+
741+
private static boolean isPrimitiveParameter(String parameterType) {
742+
return switch (parameterType) {
743+
case "int", "string", "real", "bool", "boolean" -> true;
744+
default -> false;
745+
};
746+
}
747+
748+
private static String formatWrapperValue(ObjMod.Obj.Mod m, StdlibObjectMappings.FieldMethodInfo info) {
749+
String enumConstant = enumConstantForObjectString(info.parameterType(), m.getVal().toString());
750+
if (enumConstant != null) {
751+
return enumConstant;
752+
}
753+
return formatModValue(m, info.isBoolField());
754+
}
755+
717756
/** Formats a mod value for use in generated Wurst source. */
718757
static String formatModValue(ObjMod.Obj.Mod m, boolean isBoolField) {
719758
if (isBoolField) {
@@ -722,9 +761,117 @@ static String formatModValue(ObjMod.Obj.Mod m, boolean isBoolField) {
722761
if (m.getValType() == ObjMod.ValType.STRING) {
723762
return Utils.escapeString(m.getVal().toString());
724763
}
764+
if (m.getValType() == ObjMod.ValType.REAL || m.getValType() == ObjMod.ValType.UNREAL) {
765+
return formatRealLiteral(m.getVal().toString());
766+
}
725767
return m.getVal().toString();
726768
}
727769

770+
private static String formatRealLiteral(String value) {
771+
try {
772+
String plain = new BigDecimal(value).toPlainString();
773+
return plain.contains(".") ? plain : plain + ".0";
774+
} catch (NumberFormatException e) {
775+
return value;
776+
}
777+
}
778+
779+
private static @Nullable String enumConstantForObjectString(String parameterType, String value) {
780+
Map<String, String> values = ENUM_OBJECT_STRING_TO_CONSTANT.get(parameterType);
781+
if (values == null) {
782+
return null;
783+
}
784+
String constant = values.get(value);
785+
return constant == null ? null : parameterType + "." + constant;
786+
}
787+
788+
private static Map<String, String> enumConstants(String... valueConstantPairs) {
789+
Map<String, String> result = new LinkedHashMap<>();
790+
for (int i = 0; i < valueConstantPairs.length; i += 2) {
791+
result.put(valueConstantPairs[i], valueConstantPairs[i + 1]);
792+
}
793+
return Collections.unmodifiableMap(result);
794+
}
795+
796+
private static final Map<String, Map<String, String>> ENUM_OBJECT_STRING_TO_CONSTANT = Map.of(
797+
"Race", enumConstants(
798+
"commoner", "Commoner",
799+
"creeps", "Creeps",
800+
"critters", "Critters",
801+
"demon", "Demon",
802+
"human", "Human",
803+
"naga", "Naga",
804+
"nightelf", "Nightelf",
805+
"orc", "Orc",
806+
"other", "Other",
807+
"undead", "Undead",
808+
"unknown", "Unknown"
809+
),
810+
"MovementType", enumConstants(
811+
"", "None",
812+
"foot", "Foot",
813+
"horse", "Horse",
814+
"fly", "Fly",
815+
"hover", "Hover",
816+
"float", "Float",
817+
"amph", "Amphipic"
818+
),
819+
"ArmorType", enumConstants(
820+
"small", "Small",
821+
"medium", "Medium",
822+
"large", "Large",
823+
"fort", "Fortified",
824+
"normal", "Normal",
825+
"hero", "Hero",
826+
"divine", "Divine",
827+
"none", "Unarmored"
828+
),
829+
"AttackType", enumConstants(
830+
"unknown", "Unknown",
831+
"normal", "Normal",
832+
"pierce", "Pierce",
833+
"siege", "Siege",
834+
"spells", "Spells",
835+
"chaos", "Chaos",
836+
"magic", "Magic",
837+
"hero", "Hero"
838+
),
839+
"WeaponType", enumConstants(
840+
"_", "None",
841+
"normal", "Normal",
842+
"instant", "Instant",
843+
"artillery", "Artillery",
844+
"aline", "ArtilleryLine",
845+
"missile", "Missile",
846+
"msplash", "MissileSplash",
847+
"mbounce", "MissileBounce",
848+
"mline", "MissileLine"
849+
),
850+
"WeaponSound", enumConstants(
851+
"Nothing", "Nothing",
852+
"AxeMediumChop", "AxeMediumChop",
853+
"MetalHeavyBash", "MetalHeavyBash",
854+
"MetalHeavyChop", "MetalHeavyChop",
855+
"MetalHeavySlice", "MetalHeavySlice",
856+
"MetalLightChop", "MetalLightChop",
857+
"MetalLightSlice", "MetalLightSlice",
858+
"MetalMediumBash", "MetalMediumBash",
859+
"MetalMediumChop", "MetalMediumChop",
860+
"MetalMediumSlice", "MetalMediumSlice",
861+
"RockHeavyBash", "RockHeavyBash",
862+
"WoodHeavyBash", "WoodHeavyBash",
863+
"WoodLightBash", "WoodLightBash",
864+
"WoodMediumBash", "WoodMediumBash"
865+
),
866+
"ArmorSoundType", enumConstants(
867+
"Ethereal", "Ethereal",
868+
"Flesh", "Flesh",
869+
"Wood", "Wood",
870+
"Stone", "Stone",
871+
"Metal", "Metal"
872+
)
873+
);
874+
728875
/** Appends a single raw field-setter call (e.g. ..setLvlDataUnreal(...)) to {@code out}. */
729876
private static void appendRawMod(Appendable out, ObjMod.Obj.Mod m, ObjectFileType fileType) throws IOException {
730877
if (m instanceof ObjMod.Obj.ExtendedMod ext) {

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/StdlibObjectMappings.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,14 @@ private StdlibObjectMappings() {}
2929
/**
3030
* Info about a wrapper method that sets a specific object field.
3131
*
32-
* @param methodName the Wurst method name, e.g. {@code "setCooldown"}
33-
* @param hasLevel true if the method signature is {@code setXxx(int level, T value)}
34-
* @param isBoolField true if the Wurst method accepts a {@code bool} (stored as int 0/1)
32+
* @param methodName the Wurst method name, e.g. {@code "setCooldown"}
33+
* @param hasLevel true if the method signature is {@code setXxx(int level, T value)}
34+
* @param isBoolField true if the Wurst method accepts a {@code bool} (stored as int 0/1)
35+
* @param storageValueType the underlying {@code def.set*} value type, e.g. {@code "String"}
36+
* @param parameterType the public setter value parameter type, e.g. {@code "ArmorType"}
3537
*/
36-
public record FieldMethodInfo(String methodName, boolean hasLevel, boolean isBoolField) {}
38+
public record FieldMethodInfo(String methodName, boolean hasLevel, boolean isBoolField,
39+
String storageValueType, String parameterType) {}
3740

3841
/**
3942
* Maps base ability ID (4-char, e.g. {@code "Aslo"}) to the stdlib wrapper class name
@@ -179,10 +182,14 @@ private static Map<String, FieldMethodInfo> parseFlatFieldMethods(JsonObject obj
179182
Map<String, FieldMethodInfo> result = new LinkedHashMap<>(obj.size() * 2);
180183
for (Map.Entry<String, JsonElement> e : obj.entrySet()) {
181184
var arr = e.getValue().getAsJsonArray();
185+
String storageValueType = arr.size() > 3 ? arr.get(3).getAsString() : "";
186+
String parameterType = arr.size() > 4 ? arr.get(4).getAsString() : "";
182187
result.put(e.getKey(), new FieldMethodInfo(
183188
arr.get(0).getAsString(),
184189
arr.get(1).getAsBoolean(),
185-
arr.get(2).getAsBoolean()
190+
arr.get(2).getAsBoolean(),
191+
storageValueType,
192+
parameterType
186193
));
187194
}
188195
return result;

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/gui/WurstGuiCliImpl.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package de.peeeq.wurstscript.gui;
22

33
import de.peeeq.wurstscript.attributes.CompileError;
4+
import de.peeeq.wurstscript.attributes.CompileError.ErrorType;
45

56
/**
67
* implementation for use with cli interfaces
@@ -19,9 +20,27 @@ public WurstGuiCliImpl(boolean compactOutput) {
1920

2021
@Override
2122
public void sendError(CompileError err) {
23+
if (compactOutput && isGeneratedJassNameResolutionWarning(err)) {
24+
return;
25+
}
2226
super.sendError(err);
2327
}
2428

29+
private boolean isGeneratedJassNameResolutionWarning(CompileError err) {
30+
if (err.getErrorType() != ErrorType.WARNING) {
31+
return false;
32+
}
33+
String message = err.getMessage();
34+
if (!message.contains("Could not find variable") && !message.contains("Could not find a function")) {
35+
return false;
36+
}
37+
String source = err.getSource().getFile().replace('\\', '/').toLowerCase();
38+
return source.endsWith("war3map.j")
39+
|| source.endsWith("output.j")
40+
|| source.contains("/_build/")
41+
|| source.startsWith("_build/");
42+
}
43+
2544
@Override
2645
public void sendProgress(String msg) {
2746
}

0 commit comments

Comments
 (0)