Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions HelperScripts/generate-obj-mappings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ interface MethodMapping {
dataPtr: number;
/** "Int", "Unreal", "String", "Boolean", "Real" as found in the wurst method call */
wurstCallType: string;
/** Public setter parameter type used for the object value, e.g. "string" or "ArmorType" */
valueParamType: string;
/** true if method signature has (int level, ...) */
hasLevel: boolean;
}
Expand Down Expand Up @@ -133,6 +135,7 @@ function parseObjEditingFile(content: string): ClassDef[] {
let currentClass: ClassDef | null = null;
let currentMethodName: string | null = null;
let currentMethodHasLevel = false;
let currentMethodValueType = "";
let insidePreset = false;

for (const line of lines) {
Expand Down Expand Up @@ -174,14 +177,17 @@ function parseObjEditingFile(content: string): ClassDef[] {
const fnName = funcMatch[1];
if (fnName.startsWith("preset") || fnName.startsWith("get")) {
currentMethodName = null;
currentMethodValueType = "";
insidePreset = true;
} else if (fnName.startsWith("set")) {
insidePreset = false;
currentMethodName = fnName;
// Does the signature include "int level" as first parameter?
currentMethodHasLevel = /\(int level[,)]/.test(line);
currentMethodValueType = parseValueParamType(line, currentMethodHasLevel);
} else {
currentMethodName = null;
currentMethodValueType = "";
insidePreset = true; // skip non-set/get functions too
}
continue;
Expand All @@ -206,9 +212,11 @@ function parseObjEditingFile(content: string): ClassDef[] {
fieldId,
dataPtr,
wurstCallType,
valueParamType: currentMethodValueType,
hasLevel: currentMethodHasLevel,
});
currentMethodName = null; // one mapping per method
currentMethodValueType = "";
continue;
}

Expand All @@ -225,16 +233,31 @@ function parseObjEditingFile(content: string): ClassDef[] {
fieldId,
dataPtr: 0, // WC3 WE stores non-level fields as ExtendedMod with dataPtr=0
wurstCallType,
valueParamType: currentMethodValueType,
hasLevel: false,
});
currentMethodName = null;
currentMethodValueType = "";
continue;
}
}

return classes;
}

function parseValueParamType(functionLine: string, hasLevel: boolean): string {
const paramsMatch = functionLine.match(/\((.*)\)/);
if (!paramsMatch) return "";
const params = paramsMatch[1]
.split(",")
.map((p) => p.trim())
.filter((p) => p.length > 0);
const valueParam = hasLevel ? params[1] : params[0];
if (!valueParam) return "";
const typeMatch = valueParam.match(/^(?:vararg\s+)?([A-Za-z_]\w*)\s+\w+$/);
return typeMatch ? typeMatch[1] : "";
}

// ---------------------------------------------------------------------------
// Resolve all methods for a class including inherited ones
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -287,11 +310,13 @@ function resolveAllMethods(
// "itemFieldMethods": { ... }
// }
//
// Field entry is a 3-element array [methodName, hasLevel, isBool] to keep the
// Field entry is a compact array:
// [methodName, hasLevel, isBool, storageValueType, parameterType].
// The Java reader still accepts the old 3-element shape for compatibility.
// file compact.
// ---------------------------------------------------------------------------

type FieldEntry = [string, boolean, boolean]; // [methodName, hasLevel, isBool]
type FieldEntry = [string, boolean, boolean, string, string];

function generateJson(
abilityIdMap: Map<string, string>,
Expand Down Expand Up @@ -335,7 +360,7 @@ function generateJson(
for (const m of cls.methods) {
const key = `${m.fieldId}:${m.dataPtr}`;
if (!(key in own)) {
own[key] = [m.methodName, m.hasLevel, m.wurstCallType === "Boolean"];
own[key] = [m.methodName, m.hasLevel, m.wurstCallType === "Boolean", m.wurstCallType, m.valueParamType];
}
}
// sort by key for deterministic output
Expand All @@ -356,7 +381,7 @@ function generateJson(
for (const m of all) {
const key = `${m.fieldId}:${m.dataPtr}`;
if (!(key in result)) {
result[key] = [m.methodName, m.hasLevel, m.wurstCallType === "Boolean"];
result[key] = [m.methodName, m.hasLevel, m.wurstCallType === "Boolean", m.wurstCallType, m.valueParamType];
}
}
return Object.fromEntries(Object.entries(result).sort(([a], [b]) => a.localeCompare(b)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.jetbrains.annotations.NotNull;

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

for (ObjMod.Obj.Mod m : mods) {
StdlibObjectMappings.FieldMethodInfo info = fieldMethods.get(fieldKey(m, fileType));
if (info != null && !canUseWrapperForMod(m, info)) {
return false;
}
}

out.append("@compiletime function create_").append(fileType.getExt()).append("_").append(newId)
.append("()\n");
out.append("\tnew ").append(wrapperClass).append("(").append(constructorArgs).append(")\n");
Expand All @@ -686,7 +695,7 @@ private static boolean tryExportWithWrapper(Appendable out, ObjectFileType fileT
if (info.hasLevel() && m instanceof ObjMod.Obj.ExtendedMod) {
out.append(String.valueOf(((ObjMod.Obj.ExtendedMod) m).getLevel())).append(", ");
}
out.append(formatModValue(m, info.isBoolField())).append(")\n");
out.append(formatWrapperValue(m, info)).append(")\n");
} else {
// No wrapper method for this field — emit as a commented raw call so the
// user can see what needs to be handled and add it manually.
Expand Down Expand Up @@ -714,6 +723,36 @@ static String fieldKey(ObjMod.Obj.Mod m, ObjectFileType fileType) {
return m.toString() + ":0";
}

private static boolean canUseWrapperForMod(ObjMod.Obj.Mod m, StdlibObjectMappings.FieldMethodInfo info) {
if (!info.parameterType().isEmpty() && !isPrimitiveParameter(info.parameterType())
&& !isEnumParameter(info.parameterType())) {
return false;
}
if (isEnumParameter(info.parameterType())) {
return enumConstantForObjectString(info.parameterType(), m.getVal().toString()) != null;
}
return true;
}

private static boolean isEnumParameter(String parameterType) {
return ENUM_OBJECT_STRING_TO_CONSTANT.containsKey(parameterType);
}

private static boolean isPrimitiveParameter(String parameterType) {
return switch (parameterType) {
case "int", "string", "real", "bool", "boolean" -> true;
default -> false;
};
}

private static String formatWrapperValue(ObjMod.Obj.Mod m, StdlibObjectMappings.FieldMethodInfo info) {
String enumConstant = enumConstantForObjectString(info.parameterType(), m.getVal().toString());
if (enumConstant != null) {
return enumConstant;
}
return formatModValue(m, info.isBoolField());
}

/** Formats a mod value for use in generated Wurst source. */
static String formatModValue(ObjMod.Obj.Mod m, boolean isBoolField) {
if (isBoolField) {
Expand All @@ -722,9 +761,117 @@ static String formatModValue(ObjMod.Obj.Mod m, boolean isBoolField) {
if (m.getValType() == ObjMod.ValType.STRING) {
return Utils.escapeString(m.getVal().toString());
}
if (m.getValType() == ObjMod.ValType.REAL || m.getValType() == ObjMod.ValType.UNREAL) {
return formatRealLiteral(m.getVal().toString());
}
return m.getVal().toString();
}

private static String formatRealLiteral(String value) {
try {
String plain = new BigDecimal(value).toPlainString();
return plain.contains(".") ? plain : plain + ".0";
} catch (NumberFormatException e) {
return value;
}
}

private static @Nullable String enumConstantForObjectString(String parameterType, String value) {
Map<String, String> values = ENUM_OBJECT_STRING_TO_CONSTANT.get(parameterType);
if (values == null) {
return null;
}
String constant = values.get(value);
return constant == null ? null : parameterType + "." + constant;
}

private static Map<String, String> enumConstants(String... valueConstantPairs) {
Map<String, String> result = new LinkedHashMap<>();
for (int i = 0; i < valueConstantPairs.length; i += 2) {
result.put(valueConstantPairs[i], valueConstantPairs[i + 1]);
}
return Collections.unmodifiableMap(result);
}

private static final Map<String, Map<String, String>> ENUM_OBJECT_STRING_TO_CONSTANT = Map.of(
"Race", enumConstants(
"commoner", "Commoner",
"creeps", "Creeps",
"critters", "Critters",
"demon", "Demon",
"human", "Human",
"naga", "Naga",
"nightelf", "Nightelf",
"orc", "Orc",
"other", "Other",
"undead", "Undead",
"unknown", "Unknown"
),
"MovementType", enumConstants(
"", "None",
"foot", "Foot",
"horse", "Horse",
"fly", "Fly",
"hover", "Hover",
"float", "Float",
"amph", "Amphipic"
),
"ArmorType", enumConstants(
"small", "Small",
"medium", "Medium",
"large", "Large",
"fort", "Fortified",
"normal", "Normal",
"hero", "Hero",
"divine", "Divine",
"none", "Unarmored"
),
"AttackType", enumConstants(
"unknown", "Unknown",
"normal", "Normal",
"pierce", "Pierce",
"siege", "Siege",
"spells", "Spells",
"chaos", "Chaos",
"magic", "Magic",
"hero", "Hero"
),
"WeaponType", enumConstants(
"_", "None",
"normal", "Normal",
"instant", "Instant",
"artillery", "Artillery",
"aline", "ArtilleryLine",
"missile", "Missile",
"msplash", "MissileSplash",
"mbounce", "MissileBounce",
"mline", "MissileLine"
),
"WeaponSound", enumConstants(
"Nothing", "Nothing",
"AxeMediumChop", "AxeMediumChop",
"MetalHeavyBash", "MetalHeavyBash",
"MetalHeavyChop", "MetalHeavyChop",
"MetalHeavySlice", "MetalHeavySlice",
"MetalLightChop", "MetalLightChop",
"MetalLightSlice", "MetalLightSlice",
"MetalMediumBash", "MetalMediumBash",
"MetalMediumChop", "MetalMediumChop",
"MetalMediumSlice", "MetalMediumSlice",
"RockHeavyBash", "RockHeavyBash",
"WoodHeavyBash", "WoodHeavyBash",
"WoodLightBash", "WoodLightBash",
"WoodMediumBash", "WoodMediumBash"
),
"ArmorSoundType", enumConstants(
"Ethereal", "Ethereal",
"Flesh", "Flesh",
"Wood", "Wood",
"Stone", "Stone",
"Metal", "Metal"
)
);

/** Appends a single raw field-setter call (e.g. ..setLvlDataUnreal(...)) to {@code out}. */
private static void appendRawMod(Appendable out, ObjMod.Obj.Mod m, ObjectFileType fileType) throws IOException {
if (m instanceof ObjMod.Obj.ExtendedMod ext) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,14 @@ private StdlibObjectMappings() {}
/**
* Info about a wrapper method that sets a specific object field.
*
* @param methodName the Wurst method name, e.g. {@code "setCooldown"}
* @param hasLevel true if the method signature is {@code setXxx(int level, T value)}
* @param isBoolField true if the Wurst method accepts a {@code bool} (stored as int 0/1)
* @param methodName the Wurst method name, e.g. {@code "setCooldown"}
* @param hasLevel true if the method signature is {@code setXxx(int level, T value)}
* @param isBoolField true if the Wurst method accepts a {@code bool} (stored as int 0/1)
* @param storageValueType the underlying {@code def.set*} value type, e.g. {@code "String"}
* @param parameterType the public setter value parameter type, e.g. {@code "ArmorType"}
*/
public record FieldMethodInfo(String methodName, boolean hasLevel, boolean isBoolField) {}
public record FieldMethodInfo(String methodName, boolean hasLevel, boolean isBoolField,
String storageValueType, String parameterType) {}

/**
* Maps base ability ID (4-char, e.g. {@code "Aslo"}) to the stdlib wrapper class name
Expand Down Expand Up @@ -179,10 +182,14 @@ private static Map<String, FieldMethodInfo> parseFlatFieldMethods(JsonObject obj
Map<String, FieldMethodInfo> result = new LinkedHashMap<>(obj.size() * 2);
for (Map.Entry<String, JsonElement> e : obj.entrySet()) {
var arr = e.getValue().getAsJsonArray();
String storageValueType = arr.size() > 3 ? arr.get(3).getAsString() : "";
String parameterType = arr.size() > 4 ? arr.get(4).getAsString() : "";
result.put(e.getKey(), new FieldMethodInfo(
arr.get(0).getAsString(),
arr.get(1).getAsBoolean(),
arr.get(2).getAsBoolean()
arr.get(2).getAsBoolean(),
storageValueType,
parameterType
));
}
return result;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package de.peeeq.wurstscript.gui;

import de.peeeq.wurstscript.attributes.CompileError;
import de.peeeq.wurstscript.attributes.CompileError.ErrorType;

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

@Override
public void sendError(CompileError err) {
if (compactOutput && isGeneratedJassNameResolutionWarning(err)) {
return;
}
super.sendError(err);
}

private boolean isGeneratedJassNameResolutionWarning(CompileError err) {
if (err.getErrorType() != ErrorType.WARNING) {
return false;
}
String message = err.getMessage();
if (!message.contains("Could not find variable") && !message.contains("Could not find a function")) {
return false;
}
String source = err.getSource().getFile().replace('\\', '/').toLowerCase();
return source.endsWith("war3map.j")
|| source.endsWith("output.j")
|| source.contains("/_build/")
|| source.startsWith("_build/");
}

@Override
public void sendProgress(String msg) {
}
Expand Down
Loading
Loading