From fb216f8904a2890e2e47eb83328060b009954b21 Mon Sep 17 00:00:00 2001 From: Frotty Date: Wed, 24 Jun 2026 15:01:56 +0200 Subject: [PATCH 1/8] fixes --- .../main/resources/stdlib-obj-mappings.json | 20 ++++++------- .../wurstscript/tests/ExportToWurstTest.java | 28 ++++++++++++++++++- .../java/tests/wurstscript/tests/StdLib.java | 2 +- 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/de.peeeq.wurstscript/src/main/resources/stdlib-obj-mappings.json b/de.peeeq.wurstscript/src/main/resources/stdlib-obj-mappings.json index 6050085fc..62430caa6 100644 --- a/de.peeeq.wurstscript/src/main/resources/stdlib-obj-mappings.json +++ b/de.peeeq.wurstscript/src/main/resources/stdlib-obj-mappings.json @@ -5355,8 +5355,8 @@ "setMorphingFlags", true, false, - "String", - "string" + "Int", + "int" ], "Eme3:3": [ "setAltitudeAdjustmentDuration", @@ -5932,8 +5932,8 @@ "setAttacksPrevented", true, false, - "String", - "string" + "Int", + "int" ], "Nsi2:2": [ "setChanceToMiss", @@ -6052,8 +6052,8 @@ "setStackFlags", true, false, - "String", - "string" + "Int", + "int" ] }, "AbilityDefinitionColdArrowscreep": { @@ -13439,8 +13439,8 @@ "setStackingType", true, false, - "String", - "string" + "Int", + "int" ] }, "AbilityDefinitionPenguinSqueek": { @@ -17348,8 +17348,8 @@ "setStackingType", true, false, - "String", - "string" + "Int", + "int" ] }, "AbilityDefinitionSpawnHydra": { diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ExportToWurstTest.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ExportToWurstTest.java index 28828e238..2f8bc4ab6 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ExportToWurstTest.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ExportToWurstTest.java @@ -476,7 +476,7 @@ public void parasiteEredarInheritsParentFields() throws IOException { W3A.Obj obj = w3a.addObj(ObjId.valueOf("A017"), ObjId.valueOf("ACpa")); addLvlMod(obj, "Npa6", ObjMod.ValType.UNREAL, 1, 0, 0.01); addLvlMod(obj, "Poi1", ObjMod.ValType.UNREAL, 1, 1, 0.0); - addLvlMod(obj, "Poi4", ObjMod.ValType.STRING, 1, 4, "0"); + addLvlMod(obj, "Poi4", ObjMod.ValType.INT, 1, 4, 0); addLvlMod(obj, "ipmu", ObjMod.ValType.STRING, 1, 0, "nfbr"); String out = export(obj, ObjectFileType.ABILITIES); @@ -485,6 +485,32 @@ public void parasiteEredarInheritsParentFields() throws IOException { "ACpa should use wrapper, not raw fallback"); assertFalse(out.contains("// TODO no wrapper"), "All ACpa fields should be mapped via inheritance, got:\n" + out); + assertTrue(out.contains("..setStackingType(1, 0)"), out); + assertExportCompiles(out, "import AbilityObjEditing"); + } + + @Test + public void abilityIntegerLevelFieldsWithWrapperMethodsCompile() throws IOException { + Object[][] cases = { + // baseId, fieldId, dataPtr, wrapper class, setter method, newId + {"ACpa", "Poi4", 4, "AbilityDefinitionParasiteEredar", "setStackingType", "Zp04"}, + {"AIsz", "Spo4", 4, "AbilityDefinitionSlowPoisonItem", "setStackingType", "Zs04"}, + {"Abu5", "Eme2", 2, "AbilityDefinitionBurrowBarbedArachnathid", "setMorphingFlags", "Ze02"}, + {"Acdh", "Nsi1", 1, "AbilityDefinitionChenDrunkenHaze", "setAttacksPrevented", "Zn01"}, + {"AHca", "Hca4", 4, "AbilityDefinitionRangerColdArrows", "setStackFlags", "Zh04"}, + }; + + for (Object[] c : cases) { + W3A w3a = new W3A(); + W3A.Obj obj = w3a.addObj(ObjId.valueOf((String) c[5]), ObjId.valueOf((String) c[0])); + addLvlMod(obj, (String) c[1], ObjMod.ValType.INT, 1, (int) c[2], 0); + + String out = export(obj, ObjectFileType.ABILITIES); + + assertTrue(out.contains("new " + c[3] + "('" + c[5] + "')"), out); + assertTrue(out.contains(".." + c[4] + "(1, 0)"), out); + assertExportCompiles(out, "import AbilityObjEditing"); + } } // ------------------------------------------------------------------------- diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/StdLib.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/StdLib.java index 0f22679d2..663970b0f 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/StdLib.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/StdLib.java @@ -23,7 +23,7 @@ public class StdLib { /** * version to use for the tests */ - private final static String version = "85f9debf4f53207e1ffcc23ce2bf5e759a07ba06"; + private final static String version = "5f8e332421ae4ce34f7c6b1feeccf58e06ed58e1"; /** * flag so that initialization in only done once From ed401258a44040961ee713fa9744c9d7e150cf8b Mon Sep 17 00:00:00 2001 From: Frotty Date: Wed, 24 Jun 2026 16:14:23 +0200 Subject: [PATCH 2/8] add some missing aliases --- .../interpreter/ProgramStateIO.java | 10 +- .../main/resources/stdlib-obj-mappings.json | 111 ++++++++++++++++++ .../wurstscript/tests/ExportToWurstTest.java | 56 ++++++++- .../java/tests/wurstscript/tests/StdLib.java | 2 +- 4 files changed, 167 insertions(+), 12 deletions(-) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java index 45c632e04..e11bb50f6 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java @@ -677,20 +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"); for (ObjMod.Obj.Mod m : mods) { StdlibObjectMappings.FieldMethodInfo info = fieldMethods.get(fieldKey(m, fileType)); - if (info != null) { + if (info != null && canUseWrapperForMod(m, info)) { out.append("\t..").append(info.methodName()).append("("); if (info.hasLevel() && m instanceof ObjMod.Obj.ExtendedMod) { out.append(String.valueOf(((ObjMod.Obj.ExtendedMod) m).getLevel())).append(", "); @@ -848,6 +841,7 @@ private static Map enumConstants(String... valueConstantPairs) { "mline", "MissileLine" ), "WeaponSound", enumConstants( + "", "Nothing", "Nothing", "Nothing", "AxeMediumChop", "AxeMediumChop", "MetalHeavyBash", "MetalHeavyBash", diff --git a/de.peeeq.wurstscript/src/main/resources/stdlib-obj-mappings.json b/de.peeeq.wurstscript/src/main/resources/stdlib-obj-mappings.json index 62430caa6..5c64bb0d5 100644 --- a/de.peeeq.wurstscript/src/main/resources/stdlib-obj-mappings.json +++ b/de.peeeq.wurstscript/src/main/resources/stdlib-obj-mappings.json @@ -41,6 +41,7 @@ "Abur": "AbilityDefinitionBurrow", "ACac": "AbilityDefinitionAuraCommandCreep", "ACad": "AbilityDefinitionAnimateDeadcreep", + "ACah": "AbilityDefinitionThornsAuraCreep", "ACam": "AbilityDefinitionAntimagicShieldcreep", "Acan": "AbilityDefinitionCannibalize", "ACat": "AbilityDefinitionAuraTrueshotCreep", @@ -58,6 +59,7 @@ "ACc3": "AbilityDefinitionCrushingWaveLesser", "ACca": "AbilityDefinitionCarrionSwarmcreep", "ACcb": "AbilityDefinitionFrostBolt", + "ACce": "AbilityDefinitionCleavingAttackCreep", "ACch": "AbilityDefinitionCharm", "ACcl": "AbilityDefinitionChainLightningcreep", "ACcn": "AbilityDefinitionCannibalizecreep", @@ -74,6 +76,7 @@ "Acdh": "AbilityDefinitionChenDrunkenHaze", "ACdm": "AbilityDefinitionAbolishMagicCreep", "ACdr": "AbilityDefinitionDrainLifeCreep", + "ACds": "AbilityDefinitionDivineShieldCreep", "ACdv": "AbilityDefinitionDevourCreep", "Acef": "AbilityDefinitionChenStormEarthAndFire", "ACen": "AbilityDefinitionEnsnareCreep", @@ -364,6 +367,7 @@ "AIhb": "AbilityDefinitionItemHealAoeGreater", "AIhe": "AbilityDefinitionAIhe", "AIhl": "AbilityDefinitionHolyLightItem", + "AIhm": "AbilityDefinitionShadowMeldItem", "Aihn": "AbilityDefinitionInventory2SlotUnitHuman", "AIhw": "AbilityDefinitionHealingWardAIhw", "AIhx": "AbilityDefinitionItemHealLeastest", @@ -524,6 +528,7 @@ "Amov": "AbilityDefinitionMove", "Amrf": "AbilityDefinitionRavenFormMedivh", "ANab": "AbilityDefinitionAlchemistAcidBomb", + "ANak": "AbilityDefinitionOrbOfAnnihilationQuillSpray", "ANav": "AbilityDefinitionAvatarGarithos", "ANb2": "AbilityDefinitionBashmaulSPBearlevel3", "ANba": "AbilityDefinitionDarkRangerBlackArrow", @@ -1001,6 +1006,7 @@ "AbilityDefinitionChenDrunkenBrawler": "AbilityDefinition", "AbilityDefinitionChenDrunkenHaze": "AbilityDefinition", "AbilityDefinitionChenStormEarthAndFire": "AbilityDefinition", + "AbilityDefinitionCleavingAttackCreep": "AbilityDefinition", "AbilityDefinitionCloudofFog": "AbilityDefinition", "AbilityDefinitionCloudofFogItem": "AbilityDefinition", "AbilityDefinitionColdArrows": "AbilityDefinition", @@ -1080,6 +1086,7 @@ "AbilityDefinitionDisenchantold": "AbilityDefinition", "AbilityDefinitionDispelMagic": "AbilityDefinition", "AbilityDefinitionDispelMagiccreep": "AbilityDefinition", + "AbilityDefinitionDivineShieldCreep": "AbilityDefinition", "AbilityDefinitionDivineShieldItem": "AbilityDefinition", "AbilityDefinitionDrainLifeCreep": "AbilityDefinition", "AbilityDefinitionDreadlordCarrionSwarm": "AbilityDefinition", @@ -1355,6 +1362,7 @@ "AbilityDefinitionOnFireOrc": "AbilityDefinition", "AbilityDefinitionOnFireUndead": "AbilityDefinition", "AbilityDefinitionOrbofAnnihilation": "AbilityDefinition", + "AbilityDefinitionOrbOfAnnihilationQuillSpray": "AbilityDefinition", "AbilityDefinitionOrbofCorruption": "AbilityDefinition", "AbilityDefinitionOrbofDarkness": "AbilityDefinition", "AbilityDefinitionOrbofDarknessBlackArrow": "AbilityDefinition", @@ -1521,6 +1529,7 @@ "AbilityDefinitionShadowMeld": "AbilityDefinition", "AbilityDefinitionShadowMeldAkama": "AbilityDefinition", "AbilityDefinitionShadowMeldInstant": "AbilityDefinition", + "AbilityDefinitionShadowMeldItem": "AbilityDefinition", "AbilityDefinitionShadowOrbAbility": "AbilityDefinition", "AbilityDefinitionShadowSight": "AbilityDefinition", "AbilityDefinitionShadowStrikeCreep": "AbilityDefinition", @@ -1590,6 +1599,7 @@ "AbilityDefinitionTaurenChieftainReincarnation": "AbilityDefinition", "AbilityDefinitionTaurenChieftainShockWave": "AbilityDefinition", "AbilityDefinitionTaurenChieftainWarStomp": "AbilityDefinition", + "AbilityDefinitionThornsAuraCreep": "AbilityDefinition", "AbilityDefinitionThornyShieldCreep": "AbilityDefinition", "AbilityDefinitionThornyShieldDragonTurtle": "AbilityDefinition", "AbilityDefinitionThunderBoltCreep": "AbilityDefinition", @@ -5966,6 +5976,15 @@ "string" ] }, + "AbilityDefinitionCleavingAttackCreep": { + "nca1:1": [ + "setDistributedDamageFactor", + true, + false, + "Unreal", + "real" + ] + }, "AbilityDefinitionCloudofFog": { "Nsi1:1": [ "setAttacksPrevented", @@ -7734,6 +7753,15 @@ "real" ] }, + "AbilityDefinitionDivineShieldCreep": { + "Hds1:1": [ + "setCanDeactivate", + true, + true, + "Boolean", + "bool" + ] + }, "AbilityDefinitionDivineShieldItem": { "Hds1:1": [ "setCanDeactivate", @@ -13037,6 +13065,43 @@ "real" ] }, + "AbilityDefinitionOrbOfAnnihilationQuillSpray": { + "fak1:1": [ + "setDamageBonus", + true, + false, + "Unreal", + "real" + ], + "fak2:2": [ + "setMediumDamageFactor", + true, + false, + "Unreal", + "real" + ], + "fak3:3": [ + "setSmallDamageFactor", + true, + false, + "Unreal", + "real" + ], + "fak4:4": [ + "setFullDamageRadius", + true, + false, + "Unreal", + "real" + ], + "fak5:5": [ + "setHalfDamageRadius", + true, + false, + "Unreal", + "real" + ] + }, "AbilityDefinitionOrbofCorruption": { "Iarp:2": [ "setArmorPenalty", @@ -16854,6 +16919,36 @@ "bool" ] }, + "AbilityDefinitionShadowMeldItem": { + "Shm1:1": [ + "setFadeDuration", + true, + false, + "Unreal", + "real" + ], + "Shm2:2": [ + "setDayNightDuration", + true, + false, + "Unreal", + "real" + ], + "Shm3:3": [ + "setActionDuration", + true, + false, + "Unreal", + "real" + ], + "Shm4:4": [ + "setPermanentCloak", + true, + true, + "Boolean", + "bool" + ] + }, "AbilityDefinitionShadowOrbAbility": { "Idam:1": [ "setDamageBonus", @@ -18210,6 +18305,22 @@ "real" ] }, + "AbilityDefinitionThornsAuraCreep": { + "Eah1:1": [ + "setDamageDealttoAttackers", + true, + false, + "Unreal", + "real" + ], + "Eah2:2": [ + "setDamageisPercentReceived", + true, + true, + "Boolean", + "bool" + ] + }, "AbilityDefinitionThornyShieldCreep": { "Uts1:1": [ "setReturnedDamageFactor", diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ExportToWurstTest.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ExportToWurstTest.java index 2f8bc4ab6..feac75860 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ExportToWurstTest.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ExportToWurstTest.java @@ -117,6 +117,40 @@ public void abilityWithKnownBaseIdUsesWrapperClass() throws IOException { "Should not fall back to raw format for known base ID"); } + @Test + public void abilityAliasBaseIdsUseAliasSpecificWrapperClasses() throws IOException { + Object[][] cases = { + {"A9C1", "ACce", "AbilityDefinitionCleavingAttackCreep", "AbilityDefinitionPitLordCleavingAttack", + "nca1", ObjMod.ValType.UNREAL, 1, 1, 0.25, "..setDistributedDamageFactor(1, 0.25)"}, + {"A9C2", "ACds", "AbilityDefinitionDivineShieldCreep", "AbilityDefinitionPaladinDivineShield", + "Hds1", ObjMod.ValType.INT, 1, 1, 1, "..setCanDeactivate(1, true)"}, + {"A9C3", "ANak", "AbilityDefinitionOrbOfAnnihilationQuillSpray", "AbilityDefinitionOrbofAnnihilation", + "fak1", ObjMod.ValType.UNREAL, 1, 1, 15.0, "..setDamageBonus(1, 15.0)"}, + {"A9C4", "AIhm", "AbilityDefinitionShadowMeldItem", "AbilityDefinitionShadowMeld", + "Shm1", ObjMod.ValType.UNREAL, 1, 1, 1.5, "..setFadeDuration(1, 1.5)"}, + {"A9C5", "ACah", "AbilityDefinitionThornsAuraCreep", "AbilityDefinitionKeeperoftheGroveThornsAura", + "Eah1", ObjMod.ValType.UNREAL, 1, 1, 0.1, "..setDamageDealttoAttackers(1, 0.1)"} + }; + + for (Object[] c : cases) { + W3A w3a = new W3A(); + String newId = (String) c[0]; + String baseId = (String) c[1]; + String expectedClass = (String) c[2]; + String implementationCodeClass = (String) c[3]; + W3A.Obj obj = w3a.addObj(ObjId.valueOf(newId), ObjId.valueOf(baseId)); + addLvlMod(obj, (String) c[4], (ObjMod.ValType) c[5], (int) c[6], (int) c[7], c[8]); + + String out = export(obj, ObjectFileType.ABILITIES); + + assertTrue(out.contains("new " + expectedClass + "('" + newId + "')"), baseId + " export:\n" + out); + assertTrue(out.contains((String) c[9]), baseId + " export:\n" + out); + assertFalse(out.contains("new " + implementationCodeClass + "("), baseId + " must not export via implementation-code class:\n" + out); + assertFalse(out.contains("createObjectDefinition"), baseId + " must not fall back to raw export:\n" + out); + assertExportCompiles(out, "import AbilityObjEditing"); + } + } + @Test public void abilityWrapperIncludesAllKnownFields() throws IOException { W3A w3a = new W3A(); @@ -225,6 +259,21 @@ public void abilityEnumFieldEmitsEnumConstantAndCompiles() throws IOException { assertExportCompiles(out, "import AbilityObjEditing"); } + @Test + public void abilityUnknownEnumFieldDoesNotForceRawObjectFallback() throws IOException { + W3A w3a = new W3A(); + W3A.Obj obj = w3a.addObj(ObjId.valueOf("A01O"), ObjId.valueOf("Aslo")); + addLvlMod(obj, "arac", ObjMod.ValType.STRING, 0, 0, "not-a-race"); + + String out = export(obj, ObjectFileType.ABILITIES); + + assertTrue(out.contains("new AbilityDefinitionSlow('A01O')"), out); + assertFalse(out.contains("createObjectDefinition"), out); + assertTrue(out.contains("// TODO no wrapper:"), out); + assertTrue(out.contains("arac"), out); + assertExportCompiles(out, "import AbilityObjEditing"); + } + // ------------------------------------------------------------------------- // Unit (w3u) // ------------------------------------------------------------------------- @@ -291,15 +340,16 @@ public void unitEnumFieldsEmitEnumConstantsAndCompile() throws IOException { } @Test - public void emptyEnumValueFallsBackToRawExportAndCompiles() throws IOException { + public void emptyWeaponSoundEnumValueKeepsTypedExportAndCompiles() throws IOException { W3U w3u = new W3U(); W3U.Obj obj = w3u.addObj(ObjId.valueOf("n011"), ObjId.valueOf("hfoo")); addMod(obj, "ucs1", ObjMod.ValType.STRING, ""); String out = export(obj, ObjectFileType.UNITS); - assertTrue(out.contains("createObjectDefinition(\"w3u\", 'n011', 'hfoo')"), out); - assertTrue(out.contains("..setString(\"ucs1\", \"\")"), out); + assertTrue(out.contains("new UnitDefinition('n011', 'hfoo')"), out); + assertTrue(out.contains("..setAttack1WeaponSound(WeaponSound.Nothing)"), out); + assertFalse(out.contains("createObjectDefinition"), out); assertExportCompiles(out, "import UnitObjEditing"); } diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/StdLib.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/StdLib.java index 663970b0f..6a4dc9b1d 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/StdLib.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/StdLib.java @@ -23,7 +23,7 @@ public class StdLib { /** * version to use for the tests */ - private final static String version = "5f8e332421ae4ce34f7c6b1feeccf58e06ed58e1"; + private final static String version = "16729fa07197bdc331dc5e30eb0f91d444eece85"; /** * flag so that initialization in only done once From 14aaf6f5352f0b5760d3744410309d1a3f912525 Mon Sep 17 00:00:00 2001 From: Frotty Date: Thu, 25 Jun 2026 09:26:15 +0200 Subject: [PATCH 3/8] more game data fixes --- .../main/resources/stdlib-obj-mappings.json | 205 +++++++++++++++++- .../wurstscript/tests/ExportToWurstTest.java | 114 ++++++++++ 2 files changed, 317 insertions(+), 2 deletions(-) diff --git a/de.peeeq.wurstscript/src/main/resources/stdlib-obj-mappings.json b/de.peeeq.wurstscript/src/main/resources/stdlib-obj-mappings.json index 5c64bb0d5..2d8539f2e 100644 --- a/de.peeeq.wurstscript/src/main/resources/stdlib-obj-mappings.json +++ b/de.peeeq.wurstscript/src/main/resources/stdlib-obj-mappings.json @@ -3,6 +3,7 @@ "Aabr": "AbilityDefinitionAuraRegenerationStatue", "Aabs": "AbilityDefinitionAbsorbMana", "Aadm": "AbilityDefinitionAbolishMagic", + "Aaha": "AbilityDefinitionAcolyteHarvest", "Aakb": "AbilityDefinitionAuraWarDrumsKodobeast", "Aall": "AbilityDefinitionShopSharing", "Aalr": "AbilityDefinitionAlarm", @@ -22,6 +23,7 @@ "Aatk": "AbilityDefinitionAttack", "Aatp": "AbilityDefinitionAttackTargetPriority", "Aave": "AbilityDefinitionAvengerForm", + "Aawa": "AbilityDefinitionAwaken", "Abdl": "AbilityDefinitionBlightDispelLarge", "Abds": "AbilityDefinitionBlightDispelSmall", "Abdt": "AbilityDefinitionBurrowDetectionFlyers", @@ -133,7 +135,7 @@ "ACrg": "AbilityDefinitionRainOfFireCreepGreater", "Acri": "AbilityDefinitionCripple", "ACrj": "AbilityDefinitionRejuvinationcreep", - "ACrk": "AbilityDefinitionResistantSkin", + "ACrk": "AbilityDefinitionResistantSkinCreep", "ACrn": "AbilityDefinitionReincarnationcreep", "ACro": "AbilityDefinitionRoarcreep", "Acrs": "AbilityDefinitionCurse", @@ -147,6 +149,7 @@ "ACsk": "AbilityDefinitionResistantSkin31PosCreep", "ACsl": "AbilityDefinitionSleepcreep", "ACsm": "AbilityDefinitionSiphonManaCreep", + "ACsp": "AbilityDefinitionCreepSleep", "ACss": "AbilityDefinitionShadowStrikeCreep", "ACst": "AbilityDefinitionShockwaveTrap", "ACsw": "AbilityDefinitionSlowCreep", @@ -169,6 +172,8 @@ "Adet": "AbilityDefinitionAdet", "Adev": "AbilityDefinitionDevour", "Adis": "AbilityDefinitionDispelMagic", + "Adri": "AbilityDefinitionDropInstant", + "Adro": "AbilityDefinitionDrop", "Adsm": "AbilityDefinitionDispelMagiccreep", "Adt1": "AbilityDefinitionDetectSentryWard", "Adtg": "AbilityDefinitionDetectgeneral", @@ -180,6 +185,7 @@ "AEar": "AbilityDefinitionMoonPriestessTrueshotAura", "Aeat": "AbilityDefinitionEatTree", "AEbl": "AbilityDefinitionWardenBlink", + "AEbu": "AbilityDefinitionBuildNightElf", "AEer": "AbilityDefinitionKeeperoftheGroveEntanglingRoots", "AEev": "AbilityDefinitionDemonHunterEvasion", "AEfk": "AbilityDefinitionWardenFanofKnives", @@ -234,6 +240,7 @@ "Afrz": "AbilityDefinitionFreezingBreath", "Afsh": "AbilityDefinitionFragShards", "Afzy": "AbilityDefinitionFrenzy", + "AGbu": "AbilityDefinitionBuildNaga", "Agho": "AbilityDefinitionGhost", "Agld": "AbilityDefinitionGoldMine", "Agra": "AbilityDefinitionGrabTree", @@ -247,6 +254,7 @@ "AHav": "AbilityDefinitionMountainKingAvatar", "AHbh": "AbilityDefinitionMountainKingBash", "AHbn": "AbilityDefinitionBloodMageBanish", + "AHbu": "AbilityDefinitionBuildHuman", "AHbz": "AbilityDefinitionArchMageBlizzard", "AHca": "AbilityDefinitionRangerColdArrows", "AHdr": "AbilityDefinitionBloodMageSiphonMana", @@ -330,7 +338,9 @@ "AIdd": "AbilityDefinitionDefendItem", "AIde": "AbilityDefinitionAIde", "AIdf": "AbilityDefinitionOrbofDarkness", + "AIdg": "AbilityDefinitionItemRitualDaggerInstant", "AIdi": "AbilityDefinitionItemDispelAoe", + "AIdm": "AbilityDefinitionItemDamageAoe", "AIdn": "AbilityDefinitionShadowOrbAbility", "AIdp": "AbilityDefinitionDeathPactItem", "AIds": "AbilityDefinitionItemDispelAoeWithCooldown", @@ -344,9 +354,14 @@ "AIfa": "AbilityDefinitionFlareGun", "AIfb": "AbilityDefinitionFireDamageBonus", "AIfd": "AbilityDefinitionFigurineRedDrake", + "AIfe": "AbilityDefinitionFlagUndead", "AIff": "AbilityDefinitionFigurineFurbolg", "AIfg": "AbilityDefinitionCloudofFogItem", "AIfh": "AbilityDefinitionFigurineFelHound", + "AIfl": "AbilityDefinitionFlag", + "AIfm": "AbilityDefinitionFlagHuman", + "AIfn": "AbilityDefinitionFlagNightElf", + "AIfo": "AbilityDefinitionFlagOrc", "AIfr": "AbilityDefinitionFigurineRockGolem", "AIfs": "AbilityDefinitionFigurineSkeleton", "AIft": "AbilityDefinitionFrostguardFrostMelee", @@ -354,6 +369,7 @@ "AIfw": "AbilityDefinitionSearingBladeFireMelee", "AIfx": "AbilityDefinitionFlagOrcBattleStandard", "AIfz": "AbilityDefinitionFingerOfDeathItem", + "AIg2": "AbilityDefinitionItemRitualDaggerRegen", "AIgd": "AbilityDefinitionOrbOfGuldan", "AIgf": "AbilityDefinitionFortificationGlyph", "AIgm": "AbilityDefinitionAgilityModPlus2", @@ -410,6 +426,7 @@ "AInd": "AbilityDefinitionAnimateDeaditemspecial", "Ainf": "AbilityDefinitionInnerFire", "AInm": "AbilityDefinitionStrengthModPlus2", + "AIno": "AbilityDefinitionSlow2", "AInv": "AbilityDefinitionInventory", "AIob": "AbilityDefinitionFrostDamageBonus", "Aion": "AbilityDefinitionInventory2SlotUnitOrc", @@ -457,6 +474,7 @@ "AIsi": "AbilityDefinitionSightBonus", "AIsl": "AbilityDefinitionScrollofLifeRegen", "AIsm": "AbilityDefinitionStrengthMod", + "AIso": "AbilityDefinitionSoulTrap", "AIsp": "AbilityDefinitionItemSpeed", "AIsr": "AbilityDefinitionRunedBracers", "AIsw": "AbilityDefinitionSentryWardAIsw", @@ -491,6 +509,7 @@ "AIvl": "AbilityDefinitionItemInvulLesser", "AIvm": "AbilityDefinitionReassignableAttributeBonusPlus1", "Aivs": "AbilityDefinitionInvisibility", + "AIvu": "AbilityDefinitionItemInvulNormal", "AIwb": "AbilityDefinitionItemWeb", "AIwd": "AbilityDefinitionItemAuraWarDrums", "AIwm": "AbilityDefinitionWateryMinionItem", @@ -503,6 +522,7 @@ "AIxm": "AbilityDefinitionPermanentAllPlus1", "AIxs": "AbilityDefinitionAntimagicShieldAIxs", "AIzb": "AbilityDefinitionFreezeDamageBonus", + "Alam": "AbilityDefinitionSacrificeAcolyte", "Aliq": "AbilityDefinitionLiquidFire", "Alit": "AbilityDefinitionLightningAttack", "Aloa": "AbilityDefinitionLoad", @@ -514,10 +534,13 @@ "Ambt": "AbilityDefinitionManaBattery", "Amdf": "AbilityDefinitionMagicDefense", "Amec": "AbilityDefinitionMechanicalCritter", + "Amed": "AbilityDefinitionMeatDrop", + "Amel": "AbilityDefinitionMeatLoad", "Amfl": "AbilityDefinitionManaFlare", "Amgi": "AbilityDefinitionBouncingMissileFilter", "Amgl": "AbilityDefinitionMoonGlaive", "Amgr": "AbilityDefinitionMoonGlaiveNoResearch", + "Amic": "AbilityDefinitionMilitiaConversion", "Amil": "AbilityDefinitionMilitia", "Amim": "AbilityDefinitionMagicImmunity", "Amin": "AbilityDefinitionMine", @@ -537,6 +560,7 @@ "ANbl": "AbilityDefinitionBlinkBeastmasterBear", "ANbr": "AbilityDefinitionBattleRoar", "ANbs": "AbilityDefinitionOrbofDarknessBlackArrow", + "ANbu": "AbilityDefinitionBuildNeutral", "ANc1": "AbilityDefinitionTinkererClusterRocketsLevel1", "ANc2": "AbilityDefinitionTinkererClusterRocketsLevel2", "ANc3": "AbilityDefinitionTinkererClusterRocketsLevel3", @@ -629,6 +653,7 @@ "Aoar": "AbilityDefinitionAuraRegenerationHealingWard", "Aobk": "AbilityDefinitionPassiveOrcBerserkersRobk", "Aobs": "AbilityDefinitionPassiveOrcGruntBerserkRobs", + "AObu": "AbilityDefinitionBuildOrc", "AOcl": "AbilityDefinitionFarseerChainLightning", "AOcr": "AbilityDefinitionBladeMasterCriticalStrike", "AOeq": "AbilityDefinitionFarseerEarthquake", @@ -648,6 +673,7 @@ "AOsw": "AbilityDefinitionShadowHunterSerpentWard", "Aoth": "AbilityDefinitionPassiveOrcGhostIconOnlyOrcAethUnused", "Aotr": "AbilityDefinitionPassiveOrcTrollRegenerationRotr", + "AOvd": "AbilityDefinitionShadowHunterVoodooo", "AOw2": "AbilityDefinitionCairneWarStomp", "AOwk": "AbilityDefinitionBladeMasterWindWalk", "AOws": "AbilityDefinitionTaurenChieftainWarStomp", @@ -661,6 +687,7 @@ "APh3": "AbilityDefinitionPowerupHealAoeGreater", "Aphx": "AbilityDefinitionPhoenix", "Apig": "AbilityDefinitionPermanentImmolationgraphic", + "Apit": "AbilityDefinitionPurchaseItem", "Apiv": "AbilityDefinitionPermanentInvisibility", "Aply": "AbilityDefinitionPolymorph", "Apmf": "AbilityDefinitionPermanentImmolationflying", @@ -681,6 +708,7 @@ "Apxf": "AbilityDefinitionPhoenixFire", "Ara2": "AbilityDefinitionRoarAra2", "Arai": "AbilityDefinitionRaiseDead", + "ARal": "AbilityDefinitionRally", "Arav": "AbilityDefinitionRavenFormDruidoftheTalon", "Arbr": "AbilityDefinitionReinforcedBurrows", "Arej": "AbilityDefinitionRejuvination", @@ -688,6 +716,7 @@ "Aren": "AbilityDefinitionRenew", "Arep": "AbilityDefinitionRepairOrc", "Aret": "AbilityDefinitionRetrain", + "Arev": "AbilityDefinitionRevive", "Argd": "AbilityDefinitionReturnGold", "Argl": "AbilityDefinitionReturnGoldLumber", "Arll": "AbilityDefinitionRegenLifeArll", @@ -702,11 +731,13 @@ "Arpl": "AbilityDefinitionReplenishLife", "Arpm": "AbilityDefinitionReplenishMana", "Arsg": "AbilityDefinitionRexxarSummonBear", + "Arsk": "AbilityDefinitionResistantSkin", "Arsp": "AbilityDefinitionRexxarStampede", "Arsq": "AbilityDefinitionRexxarSummonQuilbeast", "Arst": "AbilityDefinitionRestoration", "Arsw": "AbilityDefinitionRokhanSerpentWard", "Artn": "AbilityDefinitionArtn", + "Asac": "AbilityDefinitionSacrificeSacrificialPit", "Asal": "AbilityDefinitionPillage", "Asb1": "AbilityDefinitionSubmergeMyrmidon", "Asb2": "AbilityDefinitionSubmergeRoyalGuard", @@ -715,13 +746,14 @@ "Asd3": "AbilityDefinitionSelfDestruct3ClockwerkGoblins", "Asdg": "AbilityDefinitionSelfDestructClockwerkGoblins", "Asds": "AbilityDefinitionSelfDestruct", - "Asel": "AbilityDefinitionSellUnit", "Ashm": "AbilityDefinitionShadowMeld", "Ashs": "AbilityDefinitionShadowSight", + "Asid": "AbilityDefinitionSellItem", "Asla": "AbilityDefinitionSleepAlways", "Aslo": "AbilityDefinitionSlow", "Aslp": "AbilityDefinitionSummonLobstrokPrawns", "Asod": "AbilityDefinitionSpawnOnDeathskeleton", + "Asou": "AbilityDefinitionSoulPossession", "Asp1": "AbilityDefinitionSphereSoVLevel1", "Asp2": "AbilityDefinitionSphereSoVLevel2", "Asp3": "AbilityDefinitionSphereSoVLevel3", @@ -747,17 +779,21 @@ "Aste": "AbilityDefinitionManaSteal", "Asth": "AbilityDefinitionStormHammers", "Astn": "AbilityDefinitionStoneForm", + "Asud": "AbilityDefinitionSellUnit", "Atau": "AbilityDefinitionTaunt", "Atdg": "AbilityDefinitionTornadoDamage", "Atdp": "AbilityDefinitionDropPilot", "Atlp": "AbilityDefinitionLoadPilot", + "Atol": "AbilityDefinitionTreeOfLifeForAttachingArt", "Atru": "AbilityDefinitionDetectShade", "Atsp": "AbilityDefinitionTornadoSpin", "Attu": "AbilityDefinitionTankTurret", "Atwa": "AbilityDefinitionTornadoWander", + "AUa2": "AbilityDefinitionDeathKnightAnimateDead1", "AUan": "AbilityDefinitionDeathKnightAnimateDead", "AUau": "AbilityDefinitionDeathKnightUnholyAura", "AUav": "AbilityDefinitionDreadlordVampiricAura", + "AUbu": "AbilityDefinitionBuildUndead", "AUcb": "AbilityDefinitionCryptLordCarrionScarabs", "Auco": "AbilityDefinitionUnstableConcoction", "AUcs": "AbilityDefinitionDreadlordCarrionSwarm", @@ -834,6 +870,7 @@ "AbilityDefinitionAbolishMagicNaga": "AbilityDefinition", "AbilityDefinitionAbsorbMana": "AbilityDefinition", "AbilityDefinitionAcha": "AbilityDefinition", + "AbilityDefinitionAcolyteHarvest": "AbilityDefinition", "AbilityDefinitionAdet": "AbilityDefinition", "AbilityDefinitionAerialShackles": "AbilityDefinition", "AbilityDefinitionAgilityBonusPlus1": "AbilityDefinition", @@ -916,6 +953,7 @@ "AbilityDefinitionAuraWarDrumsKodobeast": "AbilityDefinition", "AbilityDefinitionAvatarGarithos": "AbilityDefinition", "AbilityDefinitionAvengerForm": "AbilityDefinition", + "AbilityDefinitionAwaken": "AbilityDefinition", "AbilityDefinitionBallsofFire": "AbilityDefinition", "AbilityDefinitionBanishCreep": "AbilityDefinition", "AbilityDefinitionBashBeastmasterBear": "AbilityDefinition", @@ -962,6 +1000,11 @@ "AbilityDefinitionBrewmasterDrunkenBrawler": "AbilityDefinition", "AbilityDefinitionBrewmasterDrunkenHaze": "AbilityDefinition", "AbilityDefinitionBrewmasterStormEarthandFire": "AbilityDefinition", + "AbilityDefinitionBuildHuman": "AbilityDefinition", + "AbilityDefinitionBuildNaga": "AbilityDefinition", + "AbilityDefinitionBuildNeutral": "AbilityDefinition", + "AbilityDefinitionBuildNightElf": "AbilityDefinition", + "AbilityDefinitionBuildOrc": "AbilityDefinition", "AbilityDefinitionBuildTinyAltar": "AbilityDefinition", "AbilityDefinitionBuildTinyBarracks": "AbilityDefinition", "AbilityDefinitionBuildTinyBlacksmith": "AbilityDefinition", @@ -970,6 +1013,7 @@ "AbilityDefinitionBuildTinyGreatHall": "AbilityDefinition", "AbilityDefinitionBuildTinyLumberMill": "AbilityDefinition", "AbilityDefinitionBuildTinyScoutTower": "AbilityDefinition", + "AbilityDefinitionBuildUndead": "AbilityDefinition", "AbilityDefinitionBurrow": "AbilityDefinition", "AbilityDefinitionBurrowBarbedArachnathid": "AbilityDefinition", "AbilityDefinitionBurrowDetectionFlyers": "AbilityDefinition", @@ -1019,6 +1063,7 @@ "AbilityDefinitionCoupleHippogryph": "AbilityDefinition", "AbilityDefinitionCoupleInstantArcher": "AbilityDefinition", "AbilityDefinitionCoupleInstantHippogryph": "AbilityDefinition", + "AbilityDefinitionCreepSleep": "AbilityDefinition", "AbilityDefinitionCripple": "AbilityDefinition", "AbilityDefinitionCripplecreep": "AbilityDefinition", "AbilityDefinitionCrippleWarlock": "AbilityDefinition", @@ -1052,6 +1097,7 @@ "AbilityDefinitionDeathDamageAOEmineBIG": "AbilityDefinition", "AbilityDefinitionDeathDamageAOEsapper": "AbilityDefinition", "AbilityDefinitionDeathKnightAnimateDead": "AbilityDefinition", + "AbilityDefinitionDeathKnightAnimateDead1": "AbilityDefinition", "AbilityDefinitionDeathKnightDeathCoil": "AbilityDefinition", "AbilityDefinitionDeathKnightDeathPact": "AbilityDefinition", "AbilityDefinitionDeathKnightUnholyAura": "AbilityDefinition", @@ -1093,7 +1139,9 @@ "AbilityDefinitionDreadlordInferno": "AbilityDefinition", "AbilityDefinitionDreadlordSleep": "AbilityDefinition", "AbilityDefinitionDreadlordVampiricAura": "AbilityDefinition", + "AbilityDefinitionDrop": "AbilityDefinition", "AbilityDefinitionDrop1": "AbilityDefinition", + "AbilityDefinitionDropInstant": "AbilityDefinition", "AbilityDefinitionDropPilot": "AbilityDefinition", "AbilityDefinitionDustofAppearance": "AbilityDefinition", "AbilityDefinitionEatTree": "AbilityDefinition", @@ -1152,7 +1200,12 @@ "AbilityDefinitionFirelordSoulBurn": "AbilityDefinition", "AbilityDefinitionFirelordSummonLavaSpawn": "AbilityDefinition", "AbilityDefinitionFirelordVolcano": "AbilityDefinition", + "AbilityDefinitionFlag": "AbilityDefinition", + "AbilityDefinitionFlagHuman": "AbilityDefinition", + "AbilityDefinitionFlagNightElf": "AbilityDefinition", + "AbilityDefinitionFlagOrc": "AbilityDefinition", "AbilityDefinitionFlagOrcBattleStandard": "AbilityDefinition", + "AbilityDefinitionFlagUndead": "AbilityDefinition", "AbilityDefinitionFlakCannon": "AbilityDefinition", "AbilityDefinitionFlameStrikeCreep": "AbilityDefinition", "AbilityDefinitionFlameStrikeImprovedCreep": "AbilityDefinition", @@ -1243,6 +1296,7 @@ "AbilityDefinitionItemChangeTOD": "AbilityDefinition", "AbilityDefinitionItemCloakOfFlames": "AbilityDefinition", "AbilityDefinitionItemCommand": "AbilityDefinition", + "AbilityDefinitionItemDamageAoe": "AbilityDefinition", "AbilityDefinitionItemDefenseAoe": "AbilityDefinition", "AbilityDefinitionItemDefenseAoePlusHealing": "AbilityDefinition", "AbilityDefinitionItemDetectAoe": "AbilityDefinition", @@ -1261,6 +1315,7 @@ "AbilityDefinitionItemInvisLesser": "AbilityDefinition", "AbilityDefinitionItemInvulDivinity": "AbilityDefinition", "AbilityDefinitionItemInvulLesser": "AbilityDefinition", + "AbilityDefinitionItemInvulNormal": "AbilityDefinition", "AbilityDefinitionItemManaRestoreAoe": "AbilityDefinition", "AbilityDefinitionItemManaRestoreGreater": "AbilityDefinition", "AbilityDefinitionItemManaRestoreLesser": "AbilityDefinition", @@ -1275,6 +1330,8 @@ "AbilityDefinitionItemRestore": "AbilityDefinition", "AbilityDefinitionItemRestoreAoe": "AbilityDefinition", "AbilityDefinitionItemRevealMap": "AbilityDefinition", + "AbilityDefinitionItemRitualDaggerInstant": "AbilityDefinition", + "AbilityDefinitionItemRitualDaggerRegen": "AbilityDefinition", "AbilityDefinitionItemSpeed": "AbilityDefinition", "AbilityDefinitionItemSpeedAoe": "AbilityDefinition", "AbilityDefinitionItemTownPortal": "AbilityDefinition", @@ -1330,8 +1387,11 @@ "AbilityDefinitionMaxManaBonusLeastest": "AbilityDefinition", "AbilityDefinitionMaxManaBonusLeastestReally": "AbilityDefinition", "AbilityDefinitionMaxManaBonusMost": "AbilityDefinition", + "AbilityDefinitionMeatDrop": "AbilityDefinition", + "AbilityDefinitionMeatLoad": "AbilityDefinition", "AbilityDefinitionMechanicalCritter": "AbilityDefinition", "AbilityDefinitionMilitia": "AbilityDefinition", + "AbilityDefinitionMilitiaConversion": "AbilityDefinition", "AbilityDefinitionMindRot": "AbilityDefinition", "AbilityDefinitionMine": "AbilityDefinition", "AbilityDefinitionMonsoon": "AbilityDefinition", @@ -1433,6 +1493,7 @@ "AbilityDefinitionPreservation": "AbilityDefinition", "AbilityDefinitionPulverize": "AbilityDefinition", "AbilityDefinitionPulverizecreep": "AbilityDefinition", + "AbilityDefinitionPurchaseItem": "AbilityDefinition", "AbilityDefinitionPurge": "AbilityDefinition", "AbilityDefinitionPurgeApg2": "AbilityDefinition", "AbilityDefinitionPurgeCreep": "AbilityDefinition", @@ -1447,6 +1508,7 @@ "AbilityDefinitionRaiseDead": "AbilityDefinition", "AbilityDefinitionRaiseDeadCreep": "AbilityDefinition", "AbilityDefinitionRaiseDeadItem": "AbilityDefinition", + "AbilityDefinitionRally": "AbilityDefinition", "AbilityDefinitionRangerColdArrows": "AbilityDefinition", "AbilityDefinitionRavenFormDruidoftheTalon": "AbilityDefinition", "AbilityDefinitionRavenFormMedivh": "AbilityDefinition", @@ -1468,6 +1530,7 @@ "AbilityDefinitionReplenishMana": "AbilityDefinition", "AbilityDefinitionResistantSkin": "AbilityDefinition", "AbilityDefinitionResistantSkin31PosCreep": "AbilityDefinition", + "AbilityDefinitionResistantSkinCreep": "AbilityDefinition", "AbilityDefinitionRestoration": "AbilityDefinition", "AbilityDefinitionResurrection": "AbilityDefinition", "AbilityDefinitionResurrectionItem": "AbilityDefinition", @@ -1477,6 +1540,7 @@ "AbilityDefinitionReturnLumber": "AbilityDefinition", "AbilityDefinitionRevealArcaneTower": "AbilityDefinition", "AbilityDefinitionRevenge": "AbilityDefinition", + "AbilityDefinitionRevive": "AbilityDefinition", "AbilityDefinitionRexxarStampede": "AbilityDefinition", "AbilityDefinitionRexxarStormBolt": "AbilityDefinition", "AbilityDefinitionRexxarSummonBear": "AbilityDefinition", @@ -1503,6 +1567,8 @@ "AbilityDefinitionRuneOfTheWatcher": "AbilityDefinition", "AbilityDefinitionRuneRestoreAoe": "AbilityDefinition", "AbilityDefinitionRuneSpeedAoe": "AbilityDefinition", + "AbilityDefinitionSacrificeAcolyte": "AbilityDefinition", + "AbilityDefinitionSacrificeSacrificialPit": "AbilityDefinition", "AbilityDefinitionSanctuary": "AbilityDefinition", "AbilityDefinitionScrollofLifeRegen": "AbilityDefinition", "AbilityDefinitionScrollofRejuvI": "AbilityDefinition", @@ -1517,6 +1583,7 @@ "AbilityDefinitionSelfDestruct2ClockwerkGoblins": "AbilityDefinition", "AbilityDefinitionSelfDestruct3ClockwerkGoblins": "AbilityDefinition", "AbilityDefinitionSelfDestructClockwerkGoblins": "AbilityDefinition", + "AbilityDefinitionSellItem": "AbilityDefinition", "AbilityDefinitionSellUnit": "AbilityDefinition", "AbilityDefinitionSentinel": "AbilityDefinition", "AbilityDefinitionSentinelNoResearch": "AbilityDefinition", @@ -1526,6 +1593,7 @@ "AbilityDefinitionShadowHunterHealingWave": "AbilityDefinition", "AbilityDefinitionShadowHunterHex": "AbilityDefinition", "AbilityDefinitionShadowHunterSerpentWard": "AbilityDefinition", + "AbilityDefinitionShadowHunterVoodooo": "AbilityDefinition", "AbilityDefinitionShadowMeld": "AbilityDefinition", "AbilityDefinitionShadowMeldAkama": "AbilityDefinition", "AbilityDefinitionShadowMeldInstant": "AbilityDefinition", @@ -1545,10 +1613,13 @@ "AbilityDefinitionSleepcreep": "AbilityDefinition", "AbilityDefinitionSlow": "AbilityDefinition", "AbilityDefinitionSlow1": "AbilityDefinition", + "AbilityDefinitionSlow2": "AbilityDefinition", "AbilityDefinitionSlowAIos": "AbilityDefinition", "AbilityDefinitionSlowCreep": "AbilityDefinition", "AbilityDefinitionSlowPoison": "AbilityDefinition", "AbilityDefinitionSlowPoisonItem": "AbilityDefinition", + "AbilityDefinitionSoulPossession": "AbilityDefinition", + "AbilityDefinitionSoulTrap": "AbilityDefinition", "AbilityDefinitionSpawnHydra": "AbilityDefinition", "AbilityDefinitionSpawnHydraHatchling": "AbilityDefinition", "AbilityDefinitionSpawnOnDeathskeleton": "AbilityDefinition", @@ -1627,6 +1698,7 @@ "AbilityDefinitionTornadoDamage": "AbilityDefinition", "AbilityDefinitionTornadoSpin": "AbilityDefinition", "AbilityDefinitionTornadoWander": "AbilityDefinition", + "AbilityDefinitionTreeOfLifeForAttachingArt": "AbilityDefinition", "AbilityDefinitionUltravision": "AbilityDefinition", "AbilityDefinitionUltraVisionGlyph": "AbilityDefinition", "AbilityDefinitionUnholyAuracreep": "AbilityDefinition", @@ -7104,6 +7176,29 @@ "bool" ] }, + "AbilityDefinitionDeathKnightAnimateDead1": { + "Hre2:2": [ + "setRaisedUnitsAreInvulnerable", + true, + true, + "Boolean", + "bool" + ], + "Uan1:1": [ + "setNumberofCorpsesRaised", + true, + false, + "Int", + "int" + ], + "Uan3:3": [ + "setInheritUpgrades", + true, + true, + "Boolean", + "bool" + ] + }, "AbilityDefinitionDeathKnightDeathCoil": { "Udc1:1": [ "setAmountHealedDamaged", @@ -11410,6 +11505,15 @@ "bool" ] }, + "AbilityDefinitionItemInvulNormal": { + "AIvu:1": [ + "setData", + true, + true, + "Boolean", + "bool" + ] + }, "AbilityDefinitionItemManaRestoreAoe": { "Impg:1": [ "setManaPointsGained", @@ -11597,6 +11701,80 @@ "string" ] }, + "AbilityDefinitionItemRitualDaggerInstant": { + "Idg1:2": [ + "setData", + true, + true, + "Boolean", + "bool" + ], + "Idg2:3": [ + "setData1", + true, + true, + "Boolean", + "bool" + ], + "Idg3:8": [ + "setData2", + true, + false, + "String", + "string" + ], + "Ihpg:1": [ + "setHitPointsGained", + true, + false, + "Int", + "int" + ], + "Udp5:5": [ + "setLeaveTargetAlive", + true, + true, + "Boolean", + "bool" + ] + }, + "AbilityDefinitionItemRitualDaggerRegen": { + "Idg1:2": [ + "setData", + true, + true, + "Boolean", + "bool" + ], + "Idg2:3": [ + "setData1", + true, + true, + "Boolean", + "bool" + ], + "Idg3:8": [ + "setData2", + true, + false, + "String", + "string" + ], + "Ihpg:1": [ + "setHitPointsGained", + true, + false, + "Int", + "int" + ], + "Udp5:5": [ + "setLeaveTargetAlive", + true, + true, + "Boolean", + "bool" + ] + }, "AbilityDefinitionItemSpeed": { "Ispi:1": [ "setMovementSpeedIncrease", @@ -17341,6 +17519,29 @@ "bool" ] }, + "AbilityDefinitionSlow2": { + "Slo1:1": [ + "setMovementSpeedFactor", + true, + false, + "Unreal", + "real" + ], + "Slo2:2": [ + "setAttackSpeedFactor", + true, + false, + "Unreal", + "real" + ], + "Slo3:3": [ + "setAlwaysAutocast", + true, + true, + "Boolean", + "bool" + ] + }, "AbilityDefinitionSlowAIos": { "Slo1:1": [ "setMovementSpeedFactor", diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ExportToWurstTest.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ExportToWurstTest.java index feac75860..de7e31307 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ExportToWurstTest.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ExportToWurstTest.java @@ -151,6 +151,120 @@ public void abilityAliasBaseIdsUseAliasSpecificWrapperClasses() throws IOExcepti } } + @Test + public void abilityCoverageGapsFromPristineExportUseTypedWrappers() throws IOException { + W3A w3a = new W3A(); + W3A.Obj resistantSkin = w3a.addObj(ObjId.valueOf("A07B"), ObjId.valueOf("Arsk")); + addLvlMod(resistantSkin, "arac", ObjMod.ValType.STRING, 0, 0, "human"); + addLvlMod(resistantSkin, "areq", ObjMod.ValType.STRING, 0, 0, ""); + + String resistantSkinOut = export(resistantSkin, ObjectFileType.ABILITIES); + + assertTrue(resistantSkinOut.contains("new AbilityDefinitionResistantSkin('A07B')"), resistantSkinOut); + assertTrue(resistantSkinOut.contains("..setRace(Race.Human)"), resistantSkinOut); + assertTrue(resistantSkinOut.contains("..setRequirements(\"\")"), resistantSkinOut); + assertFalse(resistantSkinOut.contains("createObjectDefinition"), resistantSkinOut); + + W3A.Obj undeadBuild = w3a.addObj(ObjId.valueOf("AUbu"), null); + addLvlMod(undeadBuild, "aart", ObjMod.ValType.STRING, 0, 0, + "ReplaceableTextures\\CommandButtons\\BTNAdvStruct.blp"); + + String undeadBuildOut = export(undeadBuild, ObjectFileType.ABILITIES); + + assertTrue(undeadBuildOut.contains("new AbilityDefinitionBuildUndead('AUbu')"), undeadBuildOut); + assertTrue(undeadBuildOut.contains("..setIconNormal(\"ReplaceableTextures\\\\CommandButtons\\\\BTNAdvStruct.blp\")"), undeadBuildOut); + assertFalse(undeadBuildOut.contains("createObjectDefinition"), undeadBuildOut); + + W3A.Obj rally = w3a.addObj(ObjId.valueOf("ARal"), null); + addLvlMod(rally, "anam", ObjMod.ValType.STRING, 0, 0, "Sync"); + addLvlMod(rally, "atp1", ObjMod.ValType.STRING, 0, 0, ""); + + String rallyOut = export(rally, ObjectFileType.ABILITIES); + + assertTrue(rallyOut.contains("new AbilityDefinitionRally('ARal')"), rallyOut); + assertTrue(rallyOut.contains("..setName(\"Sync\")"), rallyOut); + assertTrue(rallyOut.contains("..setTooltipNormal(0, \"\")"), rallyOut); + assertFalse(rallyOut.contains("createObjectDefinition"), rallyOut); + } + + @Test + public void abilityDataBaseIdsHaveWrapperCoverageForPreviouslyMissingRows() throws IOException { + Object[][] cases = { + {"AOvd", "AbilityDefinitionShadowHunterVoodooo"}, + {"Aaha", "AbilityDefinitionAcolyteHarvest"}, + {"Aawa", "AbilityDefinitionAwaken"}, + {"ANbu", "AbilityDefinitionBuildNeutral"}, + {"AHbu", "AbilityDefinitionBuildHuman"}, + {"AObu", "AbilityDefinitionBuildOrc"}, + {"AEbu", "AbilityDefinitionBuildNightElf"}, + {"AGbu", "AbilityDefinitionBuildNaga"}, + {"ACsp", "AbilityDefinitionCreepSleep"}, + {"Adri", "AbilityDefinitionDropInstant"}, + {"Adro", "AbilityDefinitionDrop"}, + {"Amed", "AbilityDefinitionMeatDrop"}, + {"Amel", "AbilityDefinitionMeatLoad"}, + {"Amic", "AbilityDefinitionMilitiaConversion"}, + {"Apit", "AbilityDefinitionPurchaseItem"}, + {"Arev", "AbilityDefinitionRevive"}, + {"Asac", "AbilityDefinitionSacrificeSacrificialPit"}, + {"Alam", "AbilityDefinitionSacrificeAcolyte"}, + {"Asid", "AbilityDefinitionSellItem"}, + {"Asud", "AbilityDefinitionSellUnit"}, + {"Atol", "AbilityDefinitionTreeOfLifeForAttachingArt"}, + {"AIfl", "AbilityDefinitionFlag"}, + {"AIfm", "AbilityDefinitionFlagHuman"}, + {"AIfo", "AbilityDefinitionFlagOrc"}, + {"AIfn", "AbilityDefinitionFlagNightElf"}, + {"AIfe", "AbilityDefinitionFlagUndead"}, + {"AIso", "AbilityDefinitionSoulTrap"}, + {"Asou", "AbilityDefinitionSoulPossession"}, + {"AIdm", "AbilityDefinitionItemDamageAoe"}, + {"AIvu", "AbilityDefinitionItemInvulNormal"}, + {"AIdg", "AbilityDefinitionItemRitualDaggerInstant"}, + {"AIg2", "AbilityDefinitionItemRitualDaggerRegen"}, + {"AIno", "AbilityDefinitionSlow2"}, + {"AUa2", "AbilityDefinitionDeathKnightAnimateDead1"} + }; + + for (int i = 0; i < cases.length; i++) { + W3A w3a = new W3A(); + String baseId = (String) cases[i][0]; + String expectedClass = (String) cases[i][1]; + String newId = String.format("Z%03d", i); + W3A.Obj obj = w3a.addObj(ObjId.valueOf(newId), ObjId.valueOf(baseId)); + + String out = export(obj, ObjectFileType.ABILITIES); + + assertTrue(out.contains("new " + expectedClass + "('" + newId + "')"), baseId + " export:\n" + out); + assertFalse(out.contains("createObjectDefinition"), baseId + " must not fall back to raw export:\n" + out); + } + } + + @Test + public void abilityDataBaseIdsWithSpecificFieldsUseTypedSetters() throws IOException { + Object[][] cases = { + {"AIvu", "AIvu", ObjMod.ValType.INT, 1, 1, 1, "..setData(1, true)"}, + {"AIdg", "Idg1", ObjMod.ValType.INT, 1, 2, 1, "..setData(1, true)"}, + {"AIg2", "Ihpg", ObjMod.ValType.INT, 1, 1, 50, "..setHitPointsGained(1, 50)"}, + {"AIno", "Slo1", ObjMod.ValType.UNREAL, 1, 1, 0.5, "..setMovementSpeedFactor(1, 0.5)"}, + {"AUa2", "Uan1", ObjMod.ValType.INT, 1, 1, 6, "..setNumberofCorpsesRaised(1, 6)"} + }; + + for (int i = 0; i < cases.length; i++) { + W3A w3a = new W3A(); + String baseId = (String) cases[i][0]; + String newId = String.format("Y%03d", i); + W3A.Obj obj = w3a.addObj(ObjId.valueOf(newId), ObjId.valueOf(baseId)); + addLvlMod(obj, (String) cases[i][1], (ObjMod.ValType) cases[i][2], + (int) cases[i][3], (int) cases[i][4], cases[i][5]); + + String out = export(obj, ObjectFileType.ABILITIES); + + assertTrue(out.contains((String) cases[i][6]), baseId + " export:\n" + out); + assertFalse(out.contains("createObjectDefinition"), baseId + " must not fall back to raw export:\n" + out); + } + } + @Test public void abilityWrapperIncludesAllKnownFields() throws IOException { W3A w3a = new W3A(); From c0a34abd1ba422c2f6e3edb07ac49b7b33f169f5 Mon Sep 17 00:00:00 2001 From: Frotty Date: Thu, 25 Jun 2026 10:47:26 +0200 Subject: [PATCH 4/8] fix build --- .github/workflows/build.yml | 1 + .gitignore | 2 ++ .../interpreter/ProgramStateIO.java | 34 ++++++++++++++++--- .../wurstscript/tests/ExportToWurstTest.java | 16 +++++++++ .../tests/LuaTranslationTests.java | 2 -- 5 files changed, 49 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f00af69cd..a407417ca 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,6 +34,7 @@ jobs: matrix: ${{ fromJson(needs.setup.outputs.matrix) }} runs-on: ${{ matrix.os }} permissions: + checks: write contents: read env: JAVA_TOOL_OPTIONS: >- diff --git a/.gitignore b/.gitignore index fb5e25193..7b7bf5ad3 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,5 @@ WurstSetup/proguard.map de.peeeq.wurstscript/output.txt /HelperScripts/gamedata /HelperScripts/.gradle +/.gradle-user-home +/gradle-home-temp diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java index e11bb50f6..c440613d0 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java @@ -616,9 +616,11 @@ public static void exportToWurst(List customObjs, ObjectFi *

Fields that have no known wrapper method are emitted as commented-out raw * calls so the output is still useful even when coverage is incomplete. * - *

Returns {@code false} only when there is no wrapper class at all for this + *

Returns {@code false} when there is no wrapper class at all for this * object type / base ID (e.g. doodads, upgrades, or an unknown ability base ID), - * in which case the caller should fall back to the fully raw format. + * or when a mapped wrapper setter exists but its parameter type is not yet + * supported for source re-emission. In those cases the caller should fall back + * to the fully raw format so no object data is lost on re-export. */ private static boolean tryExportWithWrapper(Appendable out, ObjectFileType fileType, String newId, String oldId, @@ -677,6 +679,10 @@ private static boolean tryExportWithWrapper(Appendable out, ObjectFileType fileT return false; } + if (hasUnsupportedMappedWrapperField(mods, fieldMethods, fileType)) { + 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"); @@ -700,6 +706,21 @@ private static boolean tryExportWithWrapper(Appendable out, ObjectFileType fileT return true; } + private static boolean hasUnsupportedMappedWrapperField(List mods, + Map fieldMethods, + ObjectFileType fileType) { + for (ObjMod.Obj.Mod mod : mods) { + StdlibObjectMappings.FieldMethodInfo info = fieldMethods.get(fieldKey(mod, fileType)); + if (info == null) { + continue; + } + if (!supportsWrapperParameterType(info.parameterType())) { + return true; + } + } + return false; + } + /** * Returns the lookup key used to match a mod to a {@link StdlibObjectMappings.FieldMethodInfo}. * Uses the mod's actual dataPtr when it is an {@link ObjMod.Obj.ExtendedMod}, regardless of @@ -717,8 +738,7 @@ static String fieldKey(ObjMod.Obj.Mod m, ObjectFileType fileType) { } private static boolean canUseWrapperForMod(ObjMod.Obj.Mod m, StdlibObjectMappings.FieldMethodInfo info) { - if (!info.parameterType().isEmpty() && !isPrimitiveParameter(info.parameterType()) - && !isEnumParameter(info.parameterType())) { + if (!supportsWrapperParameterType(info.parameterType())) { return false; } if (isEnumParameter(info.parameterType())) { @@ -727,6 +747,12 @@ private static boolean canUseWrapperForMod(ObjMod.Obj.Mod m, StdlibObjectMapping return true; } + private static boolean supportsWrapperParameterType(String parameterType) { + return parameterType.isEmpty() + || isPrimitiveParameter(parameterType) + || isEnumParameter(parameterType); + } + private static boolean isEnumParameter(String parameterType) { return ENUM_OBJECT_STRING_TO_CONSTANT.containsKey(parameterType); } diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ExportToWurstTest.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ExportToWurstTest.java index de7e31307..fa3346c9b 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ExportToWurstTest.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ExportToWurstTest.java @@ -388,6 +388,22 @@ public void abilityUnknownEnumFieldDoesNotForceRawObjectFallback() throws IOExce assertExportCompiles(out, "import AbilityObjEditing"); } + @Test + public void abilityUnsupportedMappedWrapperParameterFallsBackToRawExport() throws IOException { + W3A w3a = new W3A(); + W3A.Obj obj = w3a.addObj(ObjId.valueOf("A0RJ"), ObjId.valueOf("Arej")); + addLvlMod(obj, "acdn", ObjMod.ValType.UNREAL, 1, 0, 4.0); + addLvlMod(obj, "Rej3", ObjMod.ValType.INT, 1, 3, 1); + + String out = export(obj, ObjectFileType.ABILITIES); + + assertTrue(out.contains("createObjectDefinition(\"w3a\", 'A0RJ', 'Arej')"), out); + assertTrue(out.contains("..setLvlDataUnreal(\"acdn\", 1, 0, 4.0)"), out); + assertTrue(out.contains("..setLvlDataInt(\"Rej3\", 1, 3, 1)"), out); + assertFalse(out.contains("new AbilityDefinitionRejuvination('A0RJ')"), out); + assertFalse(out.contains("// TODO no wrapper:"), out); + } + // ------------------------------------------------------------------------- // Unit (w3u) // ------------------------------------------------------------------------- diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaTranslationTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaTranslationTests.java index f67c1d19e..8933d24f7 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaTranslationTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaTranslationTests.java @@ -186,8 +186,6 @@ public void testStdLib() throws IOException { assertTrue(compiled.contains("MagicFunctions_compiletime")); assertTrue(compiled.contains("function __wurst_InitHashtable(")); assertTrue(compiled.contains("function __wurst_SaveInteger(")); - assertTrue(compiled.contains("function __wurst_LoadInteger(")); - assertFunctionBodyContains(compiled, "__wurst_LoadInteger", "return 0", true); } @Test From effe45b0f7d91ab0d115ee4304ec60d6bc033d90 Mon Sep 17 00:00:00 2001 From: Frotty Date: Thu, 25 Jun 2026 11:08:08 +0200 Subject: [PATCH 5/8] review fix and AppCDS --- de.peeeq.wurstscript/deploy.gradle | 44 ++++++++++++++++++- .../interpreter/ProgramStateIO.java | 2 +- .../languageserver/LanguageWorker.java | 25 ++++++++--- .../wurstscript/tests/ExportToWurstTest.java | 11 +++-- 4 files changed, 68 insertions(+), 14 deletions(-) diff --git a/de.peeeq.wurstscript/deploy.gradle b/de.peeeq.wurstscript/deploy.gradle index bae051470..b7c6806cc 100644 --- a/de.peeeq.wurstscript/deploy.gradle +++ b/de.peeeq.wurstscript/deploy.gradle @@ -42,6 +42,7 @@ def jreImageDir = layout.buildDirectory.dir("jre-wurst-25") def distRoot = layout.buildDirectory.dir("dist/slim-${plat}") def releasesDir = layout.buildDirectory.dir("releases") +def appCdsArchive = distRoot.map { it.file("wurst-runtime/wurst-lsp.jsa") } // ----------------------- Toolchain / tool paths (providers) ------------------------- def toolchainSvc = extensions.getByType(JavaToolchainService) @@ -223,11 +224,52 @@ tasks.named("assembleSlimCompilerDist", Copy) { t -> } } +tasks.register("generateAppCdsArchive", Exec) { + description = "Best-effort AppCDS archive generation for language-server startup." + group = "distribution" + dependsOn("assembleSlimCompilerDist") + + inputs.file(fatJar) + inputs.dir(distRoot) + outputs.file(appCdsArchive) + ignoreExitValue = true + + doFirst { + def distDir = distRoot.get().asFile + def archiveFile = appCdsArchive.get().asFile + def runtimeJava = new File(distDir, "wurst-runtime/bin/java${os.isWindows() ? '.exe' : ''}") + def compilerJar = new File(distDir, "wurst-compiler/${fatJar.get().asFile.name}") + + archiveFile.parentFile.mkdirs() + if (archiveFile.exists()) { + archiveFile.delete() + } + + executable = runtimeJava.absolutePath + args "-Xshare:off", + "-XX:ArchiveClassesAtExit=${archiveFile.absolutePath}", + "-jar", compilerJar.absolutePath, + "-languageServer" + standardInput = new ByteArrayInputStream(new byte[0]) + + logger.lifecycle("[appcds] Training LS startup with ${runtimeJava.absolutePath}") + } + + doLast { + def archiveFile = appCdsArchive.get().asFile + if (executionResult.get().exitValue != 0 || !archiveFile.exists()) { + logger.warn("[appcds] Archive generation did not succeed on ${plat}; continuing without shipped AppCDS archive.") + } else { + logger.lifecycle("[appcds] Wrote ${archiveFile.absolutePath}") + } + } +} + // 4) Package ZIP on all platforms tasks.register("packageSlimCompilerDistZip", Zip) { description = "Packages slim dist as a ZIP archive (all platforms)." group = "distribution" - dependsOn("assembleSlimCompilerDist") + dependsOn("generateAppCdsArchive") from(distRoot) destinationDirectory.set(releasesDir) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java index c440613d0..c6b5d28f7 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java @@ -714,7 +714,7 @@ private static boolean hasUnsupportedMappedWrapperField(List mod if (info == null) { continue; } - if (!supportsWrapperParameterType(info.parameterType())) { + if (!canUseWrapperForMod(mod, info)) { return true; } } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/LanguageWorker.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/LanguageWorker.java index d56813e25..28d601728 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/LanguageWorker.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/LanguageWorker.java @@ -58,6 +58,7 @@ public void setRootPath(WFile rootPath) { lock.notify(); } }); + private boolean initialBuildPending = false; private final BufferManager bufferManager = new BufferManager(); private LanguageClient languageClient; @@ -200,9 +201,6 @@ private Workitem getNextWorkItem() { changesToReconcile = ModelManager.Changes.empty(); reconcileNowRequested = false; return new Workitem("reconcile files (save)", () -> modelManager.reconcile(changes)); - } else if (!userRequests.isEmpty()) { - UserRequest req = userRequests.remove(); - return new Workitem(req.toString(), () -> req.run(modelManager)); } else if (!changes.isEmpty()) { // TODO this can be done more efficiently than doing one at a time PendingChange change = removeFirst(changes); @@ -246,6 +244,12 @@ private Workitem getNextWorkItem() { return new Workitem("reconcile files", () -> { modelManager.reconcile(changes); }); + } else if (!userRequests.isEmpty()) { + UserRequest req = userRequests.remove(); + return new Workitem(req.toString(), () -> req.run(modelManager)); + } else if (initialBuildPending) { + initialBuildPending = false; + return new Workitem("initial full build", () -> doInitialBuild()); } return null; } @@ -271,11 +275,20 @@ private void doInit(WFile rootPath) { log("Handle init " + rootPath); modelManager = new ModelManagerImpl(rootPath.getFile(), bufferManager); modelManager.onCompilationResult(this::onCompilationResult); + initialBuildPending = true; + } catch (Exception e) { + WLogger.severe(e); + } + } - log("Start building " + rootPath); + private void doInitialBuild() { + try { + if (modelManager == null || rootPath == null) { + return; + } + log("Start background full build " + rootPath); modelManager.buildProject(); - - log("Finished building " + rootPath); + log("Finished background full build " + rootPath); } catch (Exception e) { WLogger.severe(e); } diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ExportToWurstTest.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ExportToWurstTest.java index fa3346c9b..5eebb70b4 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ExportToWurstTest.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ExportToWurstTest.java @@ -374,18 +374,17 @@ public void abilityEnumFieldEmitsEnumConstantAndCompiles() throws IOException { } @Test - public void abilityUnknownEnumFieldDoesNotForceRawObjectFallback() throws IOException { + public void abilityUnknownEnumFieldFallsBackToRawObjectExport() throws IOException { W3A w3a = new W3A(); W3A.Obj obj = w3a.addObj(ObjId.valueOf("A01O"), ObjId.valueOf("Aslo")); addLvlMod(obj, "arac", ObjMod.ValType.STRING, 0, 0, "not-a-race"); String out = export(obj, ObjectFileType.ABILITIES); - assertTrue(out.contains("new AbilityDefinitionSlow('A01O')"), out); - assertFalse(out.contains("createObjectDefinition"), out); - assertTrue(out.contains("// TODO no wrapper:"), out); - assertTrue(out.contains("arac"), out); - assertExportCompiles(out, "import AbilityObjEditing"); + assertTrue(out.contains("createObjectDefinition(\"w3a\", 'A01O', 'Aslo')"), out); + assertTrue(out.contains("..setLvlDataString(\"arac\", 0, 0, \"not-a-race\")"), out); + assertFalse(out.contains("new AbilityDefinitionSlow('A01O')"), out); + assertFalse(out.contains("// TODO no wrapper:"), out); } @Test From 1f5945d9e14b6bf6c5f2df80dc9a4390402024e3 Mon Sep 17 00:00:00 2001 From: Frotty Date: Thu, 25 Jun 2026 11:23:48 +0200 Subject: [PATCH 6/8] review fixes --- de.peeeq.wurstscript/deploy.gradle | 3 +- .../src/main/java/de/peeeq/wurstio/Main.java | 5 ++ .../languageserver/LanguageServerStarter.java | 10 ++++ .../languageserver/ModelManagerImpl.java | 46 +++++++++++-------- .../java/de/peeeq/wurstscript/RunArgs.java | 6 +++ 5 files changed, 50 insertions(+), 20 deletions(-) diff --git a/de.peeeq.wurstscript/deploy.gradle b/de.peeeq.wurstscript/deploy.gradle index b7c6806cc..ce63e12a6 100644 --- a/de.peeeq.wurstscript/deploy.gradle +++ b/de.peeeq.wurstscript/deploy.gradle @@ -249,8 +249,7 @@ tasks.register("generateAppCdsArchive", Exec) { args "-Xshare:off", "-XX:ArchiveClassesAtExit=${archiveFile.absolutePath}", "-jar", compilerJar.absolutePath, - "-languageServer" - standardInput = new ByteArrayInputStream(new byte[0]) + "-languageServerAppCdsTrain" logger.lifecycle("[appcds] Training LS startup with ${runtimeJava.absolutePath}") } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/Main.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/Main.java index 0f6bb78ac..161895aff 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/Main.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/Main.java @@ -79,6 +79,11 @@ public static void main(String[] args) { return; } + if (runArgs.isLanguageServerAppCdsTrain()) { + LanguageServerStarter.trainForAppCds(); + return; + } + if (runArgs.isLanguageServer()) { LanguageServerStarter.start(); return; diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/LanguageServerStarter.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/LanguageServerStarter.java index dfb2d07b6..1caffd5ca 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/LanguageServerStarter.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/LanguageServerStarter.java @@ -22,5 +22,15 @@ public static void start() { server.setRemoteEndpoint(launcher.getRemoteEndpoint()); } + public static void trainForAppCds() { + WurstLanguageServer server = new WurstLanguageServer(); + try { + server.getTextDocumentService(); + server.getWorkspaceService(); + } finally { + server.shutdown().join(); + } + } + } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ModelManagerImpl.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ModelManagerImpl.java index 2945229bc..dface9274 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ModelManagerImpl.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ModelManagerImpl.java @@ -516,20 +516,11 @@ private void resolveImports(WurstGui gui) { } private void replaceCompilationUnit(WFile filename) { - File f; try { - f = filename.getFile(); - } catch (FileNotFoundException e) { - WLogger.info("Cannot replaceCompilationUnit for " + filename + "\n" + e); - return; - } - if (!f.exists()) { - removeCompilationUnit(filename); - return; - } - try { - String contents = Files.toString(f, Charsets.UTF_8); - bufferManager.updateFile(WFile.create(f), contents); + String contents = readCompilationUnitContents(filename, true); + if (contents == null) { + return; + } replaceCompilationUnit(filename, contents, true); } catch (IOException e) { WLogger.severe(e); @@ -582,13 +573,10 @@ public Changes syncCompilationUnit(WFile f) { WLogger.debug("syncCompilationUnit File " + f); String contents; try { - File file = f.getFile(); - if (!file.exists()) { - removeCompilationUnit(f); + contents = readCompilationUnitContents(f, true); + if (contents == null) { return Changes.empty(); } - contents = Files.toString(file, Charsets.UTF_8); - bufferManager.updateFile(WFile.create(file), contents); } catch (IOException e) { WLogger.severe(e); throw new ModelManagerException(e); @@ -608,6 +596,28 @@ public Changes syncCompilationUnit(WFile f) { return new Changes(io.vavr.collection.HashSet.of(f), oldPackages); } + private @Nullable String readCompilationUnitContents(WFile filename, boolean preferOpenBuffer) throws IOException { + if (preferOpenBuffer && bufferManager.getTextDocumentVersion(filename) >= 0) { + return bufferManager.getBuffer(filename); + } + File file; + try { + file = filename.getFile(); + } catch (FileNotFoundException e) { + WLogger.info("Cannot read compilation unit for " + filename + "\n" + e); + return null; + } + if (!file.exists()) { + removeCompilationUnit(filename); + return null; + } + String contents = Files.toString(file, Charsets.UTF_8); + if (bufferManager.getTextDocumentVersion(filename) < 0) { + bufferManager.updateFile(WFile.create(file), contents); + } + return contents; + } + private CompilationUnit replaceCompilationUnit(WFile filename, String contents, boolean reportErrors) { if (!isInWurstFolder(filename) && !isAlreadyLoaded(filename)) { return null; diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/RunArgs.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/RunArgs.java index a6fb6b95c..fa5be557c 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/RunArgs.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/RunArgs.java @@ -42,6 +42,7 @@ public class RunArgs { private final RunOption optionExtractImports; private final RunOption optionStartServer; private final RunOption optionLanguageServer; + private final RunOption optionLanguageServerAppCdsTrain; private final RunOption optionNoExtractMapScript; private final RunOption optionFixInstall; private final RunOption optionCopyMap; @@ -136,6 +137,7 @@ public RunArgs(String... args) { optionLanguageServer = addOption("languageServer", "Starts a language server which can be used by editors to get services " + "like code completion, validations, and find declaration. The communication to the language server is via standard input output."); + optionLanguageServerAppCdsTrain = addOption("languageServerAppCdsTrain", "Starts and immediately stops a lightweight language-server startup path for AppCDS training."); optionHelp = addOption("help", "Prints this help message."); optionDisablePjass = addOption("noPJass", "Disables PJass checks for the generated code."); @@ -352,6 +354,10 @@ public boolean isLanguageServer() { return optionLanguageServer.isSet; } + public boolean isLanguageServerAppCdsTrain() { + return optionLanguageServerAppCdsTrain.isSet; + } + public boolean isNoExtractMapScript() { return optionNoExtractMapScript.isSet; } From 5ab98f541b70a390eee6ada758a53a163d1a8d9d Mon Sep 17 00:00:00 2001 From: Frotty Date: Thu, 25 Jun 2026 14:26:39 +0200 Subject: [PATCH 7/8] Update LanguageWorker.java --- .../de/peeeq/wurstio/languageserver/LanguageWorker.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/LanguageWorker.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/LanguageWorker.java index 28d601728..e293319dc 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/LanguageWorker.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/LanguageWorker.java @@ -244,12 +244,12 @@ private Workitem getNextWorkItem() { return new Workitem("reconcile files", () -> { modelManager.reconcile(changes); }); - } else if (!userRequests.isEmpty()) { - UserRequest req = userRequests.remove(); - return new Workitem(req.toString(), () -> req.run(modelManager)); } else if (initialBuildPending) { initialBuildPending = false; return new Workitem("initial full build", () -> doInitialBuild()); + } else if (!userRequests.isEmpty()) { + UserRequest req = userRequests.remove(); + return new Workitem(req.toString(), () -> req.run(modelManager)); } return null; } From 71c5ddd0ed17e94f5a600b4f65e163e81018742e Mon Sep 17 00:00:00 2001 From: Frotty Date: Thu, 25 Jun 2026 14:56:11 +0200 Subject: [PATCH 8/8] Update deploy.gradle --- de.peeeq.wurstscript/deploy.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/de.peeeq.wurstscript/deploy.gradle b/de.peeeq.wurstscript/deploy.gradle index ce63e12a6..1e5d9577e 100644 --- a/de.peeeq.wurstscript/deploy.gradle +++ b/de.peeeq.wurstscript/deploy.gradle @@ -246,7 +246,7 @@ tasks.register("generateAppCdsArchive", Exec) { } executable = runtimeJava.absolutePath - args "-Xshare:off", + args "-Xshare:auto", "-XX:ArchiveClassesAtExit=${archiveFile.absolutePath}", "-jar", compilerJar.absolutePath, "-languageServerAppCdsTrain"