diff --git a/build.gradle b/build.gradle index 1bec612d2..9a581ceaa 100644 --- a/build.gradle +++ b/build.gradle @@ -76,6 +76,13 @@ project(":core") { api "org.apache.commons:commons-compress:1.20" api "net.nikr:dds:1.0.0" api files(fileTree(dir:'../jars', includes: ['*.jar'])) + + testImplementation "org.junit.jupiter:junit-jupiter-api:5.10.2" + testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.10.2" + } + + test { + useJUnitPlatform() } } diff --git a/core/build.gradle b/core/build.gradle index 125bf13eb..5b7ad8f9a 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -2,8 +2,15 @@ sourceCompatibility = 1.17 -sourceSets.main.java.srcDirs = [ "src/" ] - +sourceSets { + main { + java.srcDirs = [ "src/" ] + } + test { + java.srcDirs = [ "test/" ] + resources.srcDirs = [ "test/resources" ] + } +} eclipse.project { name = appName + "-core" diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java index 32b30ac54..e2fb96f7d 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java @@ -2,6 +2,7 @@ import java.util.EnumMap; +import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; @@ -399,4 +400,8 @@ public String getName() { public Rectangle getRenderBounds() { return this.renderBounds; } + + public static Color createColor8888(int r, int g, int b, int a) { + return new Color(r / 255f, g / 255f, b / 255f, a / 255f); + } } diff --git a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java index ab6569e48..fbb07d75d 100644 --- a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java +++ b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java @@ -59,6 +59,7 @@ import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; +import com.etheller.warsmash.parsers.fdf.frames.AbstractRenderableFrame; import com.etheller.warsmash.parsers.fdf.frames.SetPoint; import com.etheller.warsmash.parsers.fdf.frames.StringFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; @@ -199,6 +200,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.vision.CFogModifierJassMulti; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.vision.CFogModifierJassSingle; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.vision.CRectFogModifier; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.rect.CRect; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.region.CRegion; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.region.CRegionTriggerEnter; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.region.CRegionTriggerLeave; @@ -251,6 +253,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.dialog.CScriptDialog; import com.etheller.warsmash.viewer5.handlers.w3x.ui.dialog.CScriptDialogButton; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.dialog.CTimerDialog; import net.warsmash.parsers.jass.SmashJassParser; public class Jass2 { @@ -793,10 +796,25 @@ private CommonEnvironment(final JassProgram jassProgramVisitor, final DataSource jassProgramVisitor.getJassNativeManager().createNative("GetUnitName", (arguments, globalScope, triggerScope) -> { - final CUnit whichWidget = nullable(arguments, 0, ObjectJassValueVisitor.getInstance()); + final CUnit whichWidget = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); return whichWidget == null ? JassType.STRING.getNullValue() - : new StringJassValue(whichWidget.getUnitType().getName()); + : new StringJassValue(whichWidget.getName()); + }); + jassProgramVisitor.getJassNativeManager().createNative("BlzSetUnitName", + (arguments, globalScope, triggerScope) -> { + final CUnit whichWidget = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); + if (whichWidget != null) { + final String name = nullable(arguments, 1, ObjectJassValueVisitor.getInstance()); + if (name != null) { + whichWidget.setName(name); + } else { + whichWidget.setName(""); // tdauth: Avoid null to avoid exceptions in UI. + } + } + + return null; }); + registerConversionAndStringNatives(jassProgramVisitor, war3MapViewer.getGameUI()); final War3MapConfig mapConfig = war3MapViewer.getMapConfig(); registerConfigNatives(jassProgramVisitor, mapConfig, startlocprioType, gametypeType, placementType, @@ -892,9 +910,9 @@ public JassValue call(final List arguments, final GlobalScope globalS }); jassProgramVisitor.getJassNativeManager().createNative("GroupAddUnit", (arguments, globalScope, triggerScope) -> { - final List group = nullable(arguments, 0, - ObjectJassValueVisitor.>getInstance()); - final CUnit whichUnit = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + final List group = nullableWithWarning(arguments, 0, + ObjectJassValueVisitor.>getInstance(), globalScope, triggerScope); + final CUnit whichUnit = nullableWithWarning(arguments, 1, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); if (group != null && whichUnit != null) { if (!group.contains(whichUnit)) { group.add(whichUnit); @@ -904,10 +922,11 @@ public JassValue call(final List arguments, final GlobalScope globalS }); jassProgramVisitor.getJassNativeManager().createNative("GroupRemoveUnit", (arguments, globalScope, triggerScope) -> { - final List group = arguments.get(0) - .visit(ObjectJassValueVisitor.>getInstance()); - final CUnit whichUnit = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); - group.remove(whichUnit); + final List group = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.>getInstance(), globalScope, triggerScope); + final CUnit whichUnit = nullableWithWarning(arguments, 1, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); + if (group != null && whichUnit != null) { + group.remove(whichUnit); + } return null; }); final JassFunction groupAddGroupFast = (arguments, globalScope, triggerScope) -> { @@ -1506,7 +1525,9 @@ public boolean call(final CUnit unit) { if (group != null) { final CodeJassValue callback = arguments.get(1).visit(CodeJassValueVisitor.getInstance()); try { - for (final CUnit unit : group) { + // tdauth: Do never use a foreach loop here since the group size could be modified by the callback which would lead to a ConcurrentModificationException. + for (int i = 0; i < group.size(); i++) { + final CUnit unit = group.get(i); globalScope.runThreadUntilCompletion(globalScope.createThread(callback, CommonTriggerExecutionScope.enumScope(triggerScope, unit))); } @@ -1664,7 +1685,7 @@ public boolean call(final CUnit unit) { final float miny = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); final float maxx = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); final float maxy = arguments.get(3).visit(RealJassValueVisitor.getInstance()).floatValue(); - return new HandleJassValue(rectType, new Rectangle(minx, miny, maxx - minx, maxy - miny)); + return new HandleJassValue(rectType, new CRect(this.simulation.getHandleIdAllocator().createId(), minx, miny, maxx - minx, maxy - miny)); }); jassProgramVisitor.getJassNativeManager().createNative("RectFromLoc", (arguments, globalScope, triggerScope) -> { @@ -1676,55 +1697,63 @@ public boolean call(final CUnit unit) { final float miny = min.y; final float maxx = max.x; final float maxy = max.y; - return new HandleJassValue(rectType, new Rectangle(minx, miny, maxx - minx, maxy - miny)); + return new HandleJassValue(rectType, new CRect(this.simulation.getHandleIdAllocator().createId(), minx, miny, maxx - minx, maxy - miny)); }); jassProgramVisitor.getJassNativeManager().createNative("RemoveRect", (arguments, globalScope, triggerScope) -> { - final Rectangle rect = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final CRect rect = nullable(arguments, 0, ObjectJassValueVisitor.getInstance()); System.err.println( "RemoveRect called but in Java we don't have a destructor, so we need to unregister later when that is implemented"); return null; }); jassProgramVisitor.getJassNativeManager().createNative("SetRect", (arguments, globalScope, triggerScope) -> { - final Rectangle rect = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - final float minx = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); - final float miny = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); - final float maxx = arguments.get(3).visit(RealJassValueVisitor.getInstance()).floatValue(); - final float maxy = arguments.get(4).visit(RealJassValueVisitor.getInstance()).floatValue(); - rect.set(minx, miny, maxx - minx, maxy - miny); + final CRect rect = nullable(arguments, 0, ObjectJassValueVisitor.getInstance()); + if (rect != null) { + final float minx = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float miny = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float maxx = arguments.get(3).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float maxy = arguments.get(4).visit(RealJassValueVisitor.getInstance()).floatValue(); + rect.set(minx, miny, maxx - minx, maxy - miny); + } return null; }); jassProgramVisitor.getJassNativeManager().createNative("SetRectFromLoc", (arguments, globalScope, triggerScope) -> { - final Rectangle rect = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - final AbilityPointTarget min = arguments.get(1) - .visit(ObjectJassValueVisitor.getInstance()); - final AbilityPointTarget max = arguments.get(2) - .visit(ObjectJassValueVisitor.getInstance()); - final float minx = min.x; - final float miny = min.y; - final float maxx = max.x; - final float maxy = max.y; - rect.set(minx, miny, maxx - minx, maxy - miny); + final CRect rect = nullable(arguments, 0, ObjectJassValueVisitor.getInstance()); + if (rect != null) { + final AbilityPointTarget min = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + final AbilityPointTarget max = arguments.get(2) + .visit(ObjectJassValueVisitor.getInstance()); + final float minx = min.x; + final float miny = min.y; + final float maxx = max.x; + final float maxy = max.y; + rect.set(minx, miny, maxx - minx, maxy - miny); + } return null; }); jassProgramVisitor.getJassNativeManager().createNative("MoveRectTo", (arguments, globalScope, triggerScope) -> { - final Rectangle rect = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - final float newCenterX = arguments.get(1).visit(RealJassValueVisitor.getInstance()) - .floatValue(); - final float newCenterY = arguments.get(2).visit(RealJassValueVisitor.getInstance()) - .floatValue(); - rect.setCenter(newCenterX, newCenterY); + final CRect rect = nullable(arguments, 0, ObjectJassValueVisitor.getInstance()); + if (rect != null) { + final float newCenterX = arguments.get(1).visit(RealJassValueVisitor.getInstance()) + .floatValue(); + final float newCenterY = arguments.get(2).visit(RealJassValueVisitor.getInstance()) + .floatValue(); + rect.setCenter(newCenterX, newCenterY); + } return null; }); jassProgramVisitor.getJassNativeManager().createNative("MoveRectToLoc", (arguments, globalScope, triggerScope) -> { - final Rectangle rect = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - final AbilityPointTarget newCenterLoc = arguments.get(1) - .visit(ObjectJassValueVisitor.getInstance()); - rect.setCenter(newCenterLoc.x, newCenterLoc.y); + final CRect rect = nullable(arguments, 0, ObjectJassValueVisitor.getInstance()); + if (rect != null) { + final AbilityPointTarget newCenterLoc = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + rect.setCenter(newCenterLoc.x, newCenterLoc.y); + } return null; }); jassProgramVisitor.getJassNativeManager().createNative("GetRectCenterX", new JassFunction() { @@ -1733,7 +1762,7 @@ public boolean call(final CUnit unit) { @Override public JassValue call(final List arguments, final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { - final Rectangle rect = nullable(arguments, 0, ObjectJassValueVisitor.getInstance()); + final CRect rect = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); if (rect == null) { return RealJassValue.ZERO; } @@ -1746,7 +1775,7 @@ public JassValue call(final List arguments, final GlobalScope globalS @Override public JassValue call(final List arguments, final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { - final Rectangle rect = nullable(arguments, 0, ObjectJassValueVisitor.getInstance()); + final CRect rect = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); if (rect == null) { return RealJassValue.ZERO; } @@ -1755,27 +1784,39 @@ public JassValue call(final List arguments, final GlobalScope globalS }); jassProgramVisitor.getJassNativeManager().createNative("GetRectMinX", (arguments, globalScope, triggerScope) -> { - final Rectangle rect = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - return RealJassValue.of(rect.getX()); + final CRect rect = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); + if (rect != null) { + return RealJassValue.of(rect.getX()); + } + return RealJassValue.ZERO; }); jassProgramVisitor.getJassNativeManager().createNative("GetRectMinY", (arguments, globalScope, triggerScope) -> { - final Rectangle rect = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - return RealJassValue.of(rect.getY()); + final CRect rect = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); + if (rect != null) { + return RealJassValue.of(rect.getY()); + } + return RealJassValue.ZERO; }); jassProgramVisitor.getJassNativeManager().createNative("GetRectMaxX", (arguments, globalScope, triggerScope) -> { - final Rectangle rect = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - return RealJassValue.of(rect.getX() + rect.getWidth()); + final CRect rect = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); + if (rect != null) { + return RealJassValue.of(rect.getX() + rect.getWidth()); + } + return RealJassValue.ZERO; }); jassProgramVisitor.getJassNativeManager().createNative("GetRectMaxY", (arguments, globalScope, triggerScope) -> { - final Rectangle rect = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - return RealJassValue.of(rect.getY() + rect.getHeight()); + final CRect rect = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); + if (rect != null) { + return RealJassValue.of(rect.getY() + rect.getHeight()); + } + return RealJassValue.ZERO; }); jassProgramVisitor.getJassNativeManager().createNative("CreateRegion", (arguments, globalScope, triggerScope) -> { - return new HandleJassValue(regionType, new CRegion()); + return new HandleJassValue(regionType, new CRegion(CommonEnvironment.this.simulation.getHandleIdAllocator().createId())); }); jassProgramVisitor.getJassNativeManager().createNative("RemoveRegion", (arguments, globalScope, triggerScope) -> { @@ -1785,16 +1826,20 @@ public JassValue call(final List arguments, final GlobalScope globalS }); jassProgramVisitor.getJassNativeManager().createNative("RegionAddRect", (arguments, globalScope, triggerScope) -> { - final CRegion region = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - final Rectangle rect = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); - region.addRect(rect, CommonEnvironment.this.simulation.getRegionManager()); + final CRegion region = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); + final CRect rect = nullableWithWarning(arguments, 1, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); + if (region != null && rect != null) { + region.addRect(rect, CommonEnvironment.this.simulation.getRegionManager()); + } return null; }); jassProgramVisitor.getJassNativeManager().createNative("RegionClearRect", (arguments, globalScope, triggerScope) -> { - final CRegion region = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - final Rectangle rect = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); - region.clearRect(rect, CommonEnvironment.this.simulation.getRegionManager()); + final CRegion region = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); + final CRect rect = nullableWithWarning(arguments, 1, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); + if (region != null && rect != null) { + region.clearRect(rect, CommonEnvironment.this.simulation.getRegionManager()); + } return null; }); jassProgramVisitor.getJassNativeManager().createNative("RegionAddCell", @@ -1915,7 +1960,7 @@ public JassValue call(final List arguments, final GlobalScope globalS final float worldMaxY = CommonEnvironment.this.simulation.getPathingGrid() .getWorldY(CommonEnvironment.this.simulation.getPathingGrid().getHeight() - 1) + 16f; return new HandleJassValue(rectType, - new Rectangle(worldMinX, worldMinY, worldMaxX - worldMinX, worldMaxY - worldMinY)); + new CRect(this.simulation.getHandleIdAllocator().createId(), worldMinX, worldMinY, worldMaxX - worldMinX, worldMaxY - worldMinY)); }); // ============================================================================ // Native trigger interface @@ -2093,12 +2138,16 @@ public void remove() { }); jassProgramVisitor.getJassNativeManager().createNative("TriggerRegisterEnterRegion", (arguments, globalScope, triggerScope) -> { - final Trigger trigger = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - final CRegion region = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + final Trigger trigger = nullable(arguments, 0, ObjectJassValueVisitor.getInstance()); + final CRegion region = nullable(arguments, 1, ObjectJassValueVisitor.getInstance()); final TriggerBooleanExpression boolexpr = nullable(arguments, 2, ObjectJassValueVisitor.getInstance()); - return new HandleJassValue(eventType, - region.add(new CRegionTriggerEnter(globalScope, trigger, boolexpr))); + if (trigger != null && region != null) { + return new HandleJassValue(eventType, + region.add(new CRegionTriggerEnter(globalScope, trigger, boolexpr))); + } + + return null; }); jassProgramVisitor.getJassNativeManager().createNative("GetTriggeringRegion", (arguments, globalScope, triggerScope) -> { @@ -2833,6 +2882,17 @@ public void remove() { } return null; }); + jassProgramVisitor.getJassNativeManager().createNative("UnitInventorySize", (arguments, globalScope, triggerScope) -> { + final CUnit unit = nullable(arguments, 0, ObjectJassValueVisitor.getInstance()); + + if (unit != null) { + final CAbilityInventory inventoryData = unit.getInventoryData(); + if (inventoryData != null) { + return IntegerJassValue.of(inventoryData.getItemCapacity()); + } + } + return IntegerJassValue.of(0); + }); jassProgramVisitor.getJassNativeManager().createNative("UnitAddItemById", (arguments, globalScope, triggerScope) -> { final CUnit unit = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); @@ -2905,6 +2965,25 @@ public void remove() { return new HandleJassValue(destructableType, CommonEnvironment.this.simulation .createDestructable(new War3ID(rawcode), x, y, facing, scale, variation)); }); + jassProgramVisitor.getJassNativeManager().createNative("BlzCreateDestructableZWithSkin", + (arguments, globalScope, triggerScope) -> { + int rawcode = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + final float x = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float y = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float z = arguments.get(3).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float facing = arguments.get(4).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float scale = arguments.get(5).visit(RealJassValueVisitor.getInstance()).floatValue(); + final int variation = arguments.get(6).visit(IntegerJassValueVisitor.getInstance()); + final int skinId = arguments.get(7).visit(IntegerJassValueVisitor.getInstance()); + if (skinId != rawcode) { + // throw new IllegalStateException("Our engine does not support + // DestructableSkinID != DestructableID (skinId="+ new War3ID(skinId) + ", + // destId=" + new War3ID(rawcode) + ")"); + rawcode = skinId; + } + return new HandleJassValue(destructableType, CommonEnvironment.this.simulation + .createDestructableZ(new War3ID(rawcode), x, y, z, facing, scale, variation)); + }); jassProgramVisitor.getJassNativeManager().createNative("CreateDestructableZ", (arguments, globalScope, triggerScope) -> { final int rawcode = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); @@ -2919,8 +2998,10 @@ public void remove() { }); jassProgramVisitor.getJassNativeManager().createNative("KillDestructable", (arguments, globalScope, triggerScope) -> { - final CDestructable dest = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - dest.setLife(CommonEnvironment.this.simulation, 0f); + final CDestructable dest = nullable(arguments, 0, ObjectJassValueVisitor.getInstance()); + if (dest != null) { + dest.setLife(CommonEnvironment.this.simulation, 0f); + } return null; }); jassProgramVisitor.getJassNativeManager().createNative("RemoveDestructable", @@ -3499,24 +3580,74 @@ public void remove() { } return null; }); + jassProgramVisitor.getJassNativeManager().createNative("GetHeroLevel", + (arguments, globalScope, triggerScope) -> { + final CUnit whichUnit = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); + + if (whichUnit != null) { + final CAbilityHero heroData = whichUnit.getHeroData(); + if (heroData != null) { + return IntegerJassValue + .of(heroData.getHeroLevel()); + } + } + + return IntegerJassValue.ZERO; + }); jassProgramVisitor.getJassNativeManager().createNative("SetHeroLevel", (arguments, globalScope, triggerScope) -> { - final CUnit whichUnit = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - final int level = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); - final boolean fx = arguments.get(2).visit(BooleanJassValueVisitor.getInstance()); - final CAbilityHero heroData = whichUnit.getHeroData(); - if (heroData != null) { - heroData.setHeroLevel(CommonEnvironment.this.simulation, whichUnit, level, fx); + final CUnit whichUnit = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); + if (whichUnit != null) { + final int level = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + final boolean fx = arguments.get(2).visit(BooleanJassValueVisitor.getInstance()); + final CAbilityHero heroData = whichUnit.getHeroData(); + if (heroData != null) { + heroData.setHeroLevel(CommonEnvironment.this.simulation, whichUnit, level, fx); + } + } + return null; + }); + jassProgramVisitor.getJassNativeManager().createNative("GetHeroProperName", + (arguments, globalScope, triggerScope) -> { + final CUnit whichUnit = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); + + if (whichUnit != null) { + final CAbilityHero heroData = whichUnit.getHeroData(); + if (heroData != null) { + return StringJassValue + .of(heroData.getProperName()); + } + } + + return StringJassValue.EMPTY_STRING; + }); + jassProgramVisitor.getJassNativeManager().createNative("BlzSetHeroProperName", + (arguments, globalScope, triggerScope) -> { + final CUnit whichUnit = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); + String heroProperName = nullable(arguments, 1, ObjectJassValueVisitor.getInstance()); + + if (heroProperName == null) { + heroProperName = ""; + } + + if (whichUnit != null) { + final CAbilityHero heroData = whichUnit.getHeroData(); + if (heroData != null) { + heroData.setProperName(heroProperName); + } } + return null; }); jassProgramVisitor.getJassNativeManager().createNative("SelectHeroSkill", (arguments, globalScope, triggerScope) -> { - final CUnit whichUnit = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - final int skill = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); - final CAbilityHero heroData = whichUnit.getHeroData(); - if (heroData != null) { - heroData.selectHeroSkill(this.simulation, whichUnit, new War3ID(skill)); + final CUnit whichUnit = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); + if (whichUnit != null) { + final int skill = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + final CAbilityHero heroData = whichUnit.getHeroData(); + if (heroData != null) { + heroData.selectHeroSkill(this.simulation, whichUnit, new War3ID(skill)); + } } return null; }); @@ -4125,9 +4256,15 @@ public JassValue call(final List arguments, final GlobalScope globalS }); jassProgramVisitor.getJassNativeManager().createNative("Preloader", (arguments, globalScope, triggerScope) -> { - final String filename = arguments.get(0).visit(StringJassValueVisitor.getInstance()); - doPreloadScript(dataSource, uiViewport, uiScene, war3MapViewer, filename, meleeUI, - originalFiles, jassProgramVisitor, "PreloadFiles"); + final String filename = nullableWithWarning(arguments, 0, StringJassValueVisitor.getInstance(), globalScope, triggerScope); + if (filename != null) { + if (dataSource.has(filename)) { + doPreloadScript(dataSource, uiViewport, uiScene, war3MapViewer, filename, meleeUI, + originalFiles, jassProgramVisitor, "PreloadFiles"); + } else { + System.err.println("Missing file " + filename + " to be used in Preloader."); + } + } return null; }); jassProgramVisitor.getJassNativeManager().createNative("CreateTimerDialog", @@ -4135,6 +4272,56 @@ public JassValue call(final List arguments, final GlobalScope globalS final CTimer timer = nullable(arguments, 0, ObjectJassValueVisitor.getInstance()); return new HandleJassValue(timerdialogType, meleeUI.createTimerDialog(timer)); }); + jassProgramVisitor.getJassNativeManager().createNative("TimerDialogSetTitle", + (arguments, globalScope, triggerScope) -> { + final CTimerDialog timerDialog = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); + final String title = arguments.get(1).visit(StringJassValueVisitor.getInstance()); + if (timerDialog != null) { + meleeUI.setTimerDialogTitle(timerDialog, title); + } + return null; + }); + jassProgramVisitor.getJassNativeManager().createNative("TimerDialogSetTitleColor", + (arguments, globalScope, triggerScope) -> { + final CTimerDialog timerDialog = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); + final int red = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + final int green = arguments.get(2).visit(IntegerJassValueVisitor.getInstance()); + final int blue = arguments.get(3).visit(IntegerJassValueVisitor.getInstance()); + final int alpha = arguments.get(4).visit(IntegerJassValueVisitor.getInstance()); + if (timerDialog != null) { + timerDialog.setTitleColor(AbstractRenderableFrame.createColor8888(red, green, blue, alpha)); + } + return null; + }); + jassProgramVisitor.getJassNativeManager().createNative("TimerDialogSetTimeColor", + (arguments, globalScope, triggerScope) -> { + final CTimerDialog timerDialog = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); + final int red = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + final int green = arguments.get(2).visit(IntegerJassValueVisitor.getInstance()); + final int blue = arguments.get(3).visit(IntegerJassValueVisitor.getInstance()); + final int alpha = arguments.get(4).visit(IntegerJassValueVisitor.getInstance()); + if (timerDialog != null) { + timerDialog.setValueColor(AbstractRenderableFrame.createColor8888(red, green, blue, alpha)); + } + return null; + }); + jassProgramVisitor.getJassNativeManager().createNative("TimerDialogDisplay", + (arguments, globalScope, triggerScope) -> { + final CTimerDialog timerDialog = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); + final boolean display = arguments.get(1).visit(BooleanJassValueVisitor.getInstance()); + if (timerDialog != null) { + timerDialog.setVisible(display); + } + return null; + }); + jassProgramVisitor.getJassNativeManager().createNative("IsTimerDialogDisplayed", + (arguments, globalScope, triggerScope) -> { + final CTimerDialog timerDialog = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); + if (timerDialog != null) { + return BooleanJassValue.of(timerDialog.isVisible()); + } + return BooleanJassValue.FALSE; + }); jassProgramVisitor.getJassNativeManager().createNative("IsPlayerObserver", (arguments, globalScope, triggerScope) -> { final CPlayer player = nullable(arguments, 0, ObjectJassValueVisitor.getInstance()); @@ -4246,6 +4433,15 @@ public JassValue call(final List arguments, final GlobalScope globalS final CWidget whichWidget = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); return RealJassValue.of(whichWidget.getY()); }); + jassProgramVisitor.getJassNativeManager().createNative("GetDestructableTypeId", + (arguments, globalScope, triggerScope) -> { + final CDestructable d = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); + if (d != null && d.getDestType() != null) { + return IntegerJassValue.of(d.getDestType().getTypeId().getValue()); + } + + return IntegerJassValue.ZERO; + }); jassProgramVisitor.getJassNativeManager().createNative("GetDestructableX", (arguments, globalScope, triggerScope) -> { final CWidget whichWidget = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); @@ -4358,11 +4554,12 @@ public JassValue call(final List arguments, final GlobalScope globalS }); jassProgramVisitor.getJassNativeManager().createNative("GetHandleId", (arguments, globalScope, triggerScope) -> { - final CHandle whichHandle = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - if (whichHandle == null) { - return IntegerJassValue.ZERO; + final Object arg0 = nullable(arguments, 0, ObjectJassValueVisitor.getInstance()); + // Many of the JASS types do not implement CHandle yet. + if (arg0 instanceof CHandle) { + return IntegerJassValue.of(CHandle.class.cast(arg0).getHandleId()); } - return IntegerJassValue.of(whichHandle.getHandleId()); + return IntegerJassValue.ZERO; }); jassProgramVisitor.getJassNativeManager().createNative("TriggerSleepAction", (arguments, globalScope, triggerScope) -> { @@ -4883,7 +5080,7 @@ public JassValue call(final List arguments, final GlobalScope globalS }); jassProgramVisitor.getJassNativeManager().createNative("GetUnitUserData", (arguments, globalScope, triggerScope) -> { - final CUnit whichUnit = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final CUnit whichUnit = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); if (whichUnit == null) { return IntegerJassValue.ZERO; } @@ -4891,7 +5088,7 @@ public JassValue call(final List arguments, final GlobalScope globalS }); jassProgramVisitor.getJassNativeManager().createNative("SetUnitUserData", (arguments, globalScope, triggerScope) -> { - final CUnit whichUnit = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final CUnit whichUnit = nullableWithWarning(arguments, 0, ObjectJassValueVisitor.getInstance(), globalScope, triggerScope); final int data = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); if (whichUnit != null) { whichUnit.setTriggerEditorCustomValue(data); @@ -5365,7 +5562,7 @@ public HandleJassValue accept(final CItem target) { // }); jassProgramVisitor.getJassNativeManager().createNative("InitHashtable", (arguments, globalScope, triggerScope) -> { - return new HandleJassValue(hashtableType, new CHashtable()); + return new HandleJassValue(hashtableType, new CHashtable(simulation.getHandleIdAllocator().createId())); }); jassProgramVisitor.getJassNativeManager().createNative("SaveInteger", new SaveHashtableValueFunc()); jassProgramVisitor.getJassNativeManager().createNative("SaveReal", new SaveHashtableValueFunc()); @@ -6332,8 +6529,12 @@ public HandleJassValue accept(final CItem target) { } } if (filePath != null) { - doPreloadScript(dataSource, uiViewport, uiScene, war3MapViewer, filePath, meleeUI, - originalFiles, jassProgramVisitor, funcToCall); + if (dataSource.has(filePath)) { + doPreloadScript(dataSource, uiViewport, uiScene, war3MapViewer, filePath, meleeUI, + originalFiles, jassProgramVisitor, funcToCall); + } else { + System.err.println("Missing file " + filePath + " to be used in LoadScriptFile call."); + } } return null; }); @@ -9200,16 +9401,6 @@ private static void registerAbilityUserDataHandleNatives(final JassProgram jassP public void main() { this.simulation.setGlobalScope(this.jassProgramVisitor.getGlobals()); - try { - final JassThread abilitiesThread = this.jassProgramVisitor.getGlobals().createThread("abilities_main", - Collections.emptyList(), TriggerExecutionScope.EMPTY); - this.jassProgramVisitor.getGlobals().queueThread(abilitiesThread); - } - catch (final Exception exc) { - new JassException(this.jassProgramVisitor.getGlobals(), - "Exception on Line " + this.jassProgramVisitor.getGlobals().getLineNumber(), exc) - .printStackTrace(); - } try { final JassThread mainThread = this.jassProgramVisitor.getGlobals().createThread("main", Collections.emptyList(), TriggerExecutionScope.EMPTY); @@ -9612,6 +9803,17 @@ private static T nullable(final List arguments, final int index, return arg.visit(visitor); } + private static T nullableWithWarning(final List arguments, final int index, + final JassValueVisitor visitor, + final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final T arg = nullable(arguments, index, visitor); + if (arg == null) { + System.err.println(JassException.message(globalScope, "Warning: JASS function argument " + index + " is null.")); + } + return arg; + } + private static void doPreloadScript(final DataSource dataSource, final Viewport uiViewport, final Scene uiScene, final War3MapViewer war3MapViewer, final String filename, final WarsmashUI meleeUI, final String[] originalFiles, final JassProgram jassProgramVisitor, final String mainFunction) { @@ -10026,15 +10228,20 @@ public static void registerConfigNatives(final JassProgram jassProgramVisitor, f }); jassProgramVisitor.getJassNativeManager().createNative("GetPlayerController", (arguments, globalScope, triggerScope) -> { - final CPlayerJass player = arguments.get(0) - .visit(ObjectJassValueVisitor.getInstance()); - return new HandleJassValue(mapcontrolType, player.getController()); + final CPlayerJass player = nullable(arguments, 0, ObjectJassValueVisitor.getInstance()); + if (player != null) { + return new HandleJassValue(mapcontrolType, player.getController()); + } + return new HandleJassValue(mapcontrolType, null); }); jassProgramVisitor.getJassNativeManager().createNative("GetPlayerSlotState", (arguments, globalScope, triggerScope) -> { - final CPlayerJass player = arguments.get(0) - .visit(ObjectJassValueVisitor.getInstance()); - return new HandleJassValue(playerslotstateType, player.getSlotState()); + final CPlayerJass player = nullable(arguments, 0, ObjectJassValueVisitor.getInstance()); + if (player != null) { + return new HandleJassValue(playerslotstateType, player.getSlotState()); + } + + return new HandleJassValue(playerslotstateType, null); }); jassProgramVisitor.getJassNativeManager().createNative("GetPlayerTaxRate", (arguments, globalScope, triggerScope) -> { @@ -10063,7 +10270,10 @@ public static void registerConfigNatives(final JassProgram jassProgramVisitor, f jassProgramVisitor.getJassNativeManager().createNative("Player", (arguments, globalScope, triggerScope) -> { final int playerIndex = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); - return new HandleJassValue(playerType, playerAPI.getPlayer(playerIndex)); + if (playerIndex >= 0 && playerIndex < playerAPI.getMaxPlayers()) { + return new HandleJassValue(playerType, playerAPI.getPlayer(playerIndex)); + } + return playerType.getNullValue(); }); jassProgramVisitor.getJassNativeManager().createNative("GetPlayerId", (arguments, globalScope, triggerScope) -> { diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java b/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java index a8c5ae40d..fd609ae87 100644 --- a/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java @@ -8,6 +8,7 @@ /** * A tile corner. + * https://github.com/ChiefOfGxBxL/WC3MapSpecification/blob/master/Terrain/12.md */ public class Corner { private float groundHeight; @@ -42,21 +43,32 @@ public Corner(final Corner other) { this.layerHeight = other.layerHeight; } - public void load(final LittleEndianDataInputStream stream) throws IOException { + public void load(final LittleEndianDataInputStream stream, int version) throws IOException { this.groundHeight = (stream.readShort() - 8192) / (float) 512; final short waterAndEdge = stream.readShort(); this.waterHeight = ((waterAndEdge & 0x3FFF) - 8192) / (float) 512; this.mapEdge = waterAndEdge & 0x4000; - final short textureAndFlags = ParseUtils.readUInt8(stream); + if (version >= 12) { + final int textureAndFlags = ParseUtils.readUInt16(stream); - this.ramp = textureAndFlags & 0b00010000; - this.blight = textureAndFlags & 0b00100000; - this.water = textureAndFlags & 0b01000000; - this.boundary = textureAndFlags & 0b10000000; + this.ramp = textureAndFlags & 0b00000000_01000000; + this.blight = textureAndFlags & 0b00000000_10000000; + this.water = textureAndFlags & 0b00000001_00000000; + this.boundary = textureAndFlags & 0b00000010_00000000; - this.groundTexture = textureAndFlags & 0b00001111; + this.groundTexture = textureAndFlags & 0b00111111; + } else { + final short textureAndFlags = ParseUtils.readUInt8(stream); + + this.ramp = textureAndFlags & 0b00010000; + this.blight = textureAndFlags & 0b00100000; + this.water = textureAndFlags & 0b01000000; + this.boundary = textureAndFlags & 0b10000000; + + this.groundTexture = textureAndFlags & 0b00001111; + } final short variation = ParseUtils.readUInt8(stream); @@ -70,16 +82,26 @@ public void load(final LittleEndianDataInputStream stream) throws IOException { } - public void save(final LittleEndianDataOutputStream stream) throws IOException { + public void save(final LittleEndianDataOutputStream stream, int version) throws IOException { stream.writeShort((short) ((this.groundHeight * 512f) + 8192f)); final int mapEdgeWrite = (this.mapEdge != 0) ? 0x4000 : 0; stream.writeShort((short) ((int) ((this.waterHeight * 512f) + 8192f) | (mapEdgeWrite))); - final int rampWrite = (this.ramp != 0) ? 0b00010000 : 0; - final int blightWrite = (this.blight != 0) ? 0b00100000 : 0; - final int waterWrite = (this.water != 0) ? 0b01000000 : 0; - final int boundaryWrite = (this.boundary != 0) ? 0b10000000 : 0; - ParseUtils.writeUInt8(stream, - (short) ((rampWrite) | (blightWrite) | (waterWrite) | (boundaryWrite) | this.groundTexture)); + int rampWrite = (this.ramp != 0) ? 0b00010000 : 0; + int blightWrite = (this.blight != 0) ? 0b00100000 : 0; + int waterWrite = (this.water != 0) ? 0b01000000 : 0; + int boundaryWrite = (this.boundary != 0) ? 0b10000000 : 0; + if (version >= 12) { + rampWrite <<= 2; + blightWrite <<= 2; + waterWrite <<= 2; + boundaryWrite <<= 2; + + ParseUtils.writeUInt16(stream, + (int) ((rampWrite) | (blightWrite) | (waterWrite) | (boundaryWrite) | this.groundTexture)); + } else { + ParseUtils.writeUInt8(stream, + (short) ((rampWrite) | (blightWrite) | (waterWrite) | (boundaryWrite) | this.groundTexture)); + } ParseUtils.writeUInt8(stream, (short) ((this.cliffVariation << 5) | this.groundVariation)); ParseUtils.writeUInt8(stream, (short) ((this.cliffTexture << 4) + this.layerHeight)); } diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3e/War3MapW3e.java b/core/src/com/etheller/warsmash/parsers/w3x/w3e/War3MapW3e.java index 8494df752..99945375f 100644 --- a/core/src/com/etheller/warsmash/parsers/w3x/w3e/War3MapW3e.java +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3e/War3MapW3e.java @@ -11,6 +11,7 @@ /** * war3map.w3e - the environment file. + * https://github.com/ChiefOfGxBxL/WC3MapSpecification/blob/master/Terrain/12.md */ public class War3MapW3e { private static final War3ID MAGIC_NUMBER = War3ID.fromString("W3E!"); @@ -57,7 +58,7 @@ private boolean load(final LittleEndianDataInputStream stream) throws IOExceptio for (int column = 0, columns = this.mapSize[0]; column < columns; column++) { final Corner corner = new Corner(); - corner.load(stream); + corner.load(stream, this.version); this.corners[row][column] = corner; } @@ -88,16 +89,11 @@ public void save(final LittleEndianDataOutputStream stream) throws IOException { for (final Corner[] row : this.corners) { for (final Corner corner : row) { - corner.save(stream); + corner.save(stream, this.version); } } } - public int getByteLength() { - return 37 + (this.groundTiles.size() * 4) + (this.cliffTiles.size() * 4) - + (this.mapSize[0] * this.mapSize[1] * 7); - } - public int getVersion() { return this.version; } diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3i/Player.java b/core/src/com/etheller/warsmash/parsers/w3x/w3i/Player.java index 5ab3d0afd..bffa29a3a 100644 --- a/core/src/com/etheller/warsmash/parsers/w3x/w3i/Player.java +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3i/Player.java @@ -30,13 +30,13 @@ public void load(final LittleEndianDataInputStream stream, final int version) th ParseUtils.readFloatArray(stream, this.startLocation); this.allyLowPriorities = ParseUtils.readUInt32(stream); this.allyHighPriorities = ParseUtils.readUInt32(stream); - if (version > 30) { + if (version >= 31) { this.enemyLowPrioritiesFlags = ParseUtils.readUInt32(stream); this.enemyHighPrioritiesFlags = ParseUtils.readUInt32(stream); } } - public void save(final LittleEndianDataOutputStream stream) throws IOException { + public void save(final LittleEndianDataOutputStream stream, int version) throws IOException { ParseUtils.writeUInt32(stream, this.id); stream.writeInt(this.type); stream.writeInt(this.race); @@ -45,10 +45,10 @@ public void save(final LittleEndianDataOutputStream stream) throws IOException { ParseUtils.writeFloatArray(stream, this.startLocation); ParseUtils.writeUInt32(stream, this.allyLowPriorities); ParseUtils.writeUInt32(stream, this.allyHighPriorities); - } - - public int getByteLength() { - return 33 + this.name.length(); + if (version >= 31) { + ParseUtils.writeUInt32(stream, this.enemyLowPrioritiesFlags); + ParseUtils.writeUInt32(stream, this.enemyHighPrioritiesFlags); + } } public int getId() { diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3i/War3MapW3i.java b/core/src/com/etheller/warsmash/parsers/w3x/w3i/War3MapW3i.java index 1be2f4eaa..4d1c67012 100644 --- a/core/src/com/etheller/warsmash/parsers/w3x/w3i/War3MapW3i.java +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3i/War3MapW3i.java @@ -5,11 +5,13 @@ import java.util.List; import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; import com.google.common.io.LittleEndianDataInputStream; import com.google.common.io.LittleEndianDataOutputStream; /** * war3map.w3i - the general map information file. + * https://github.com/ChiefOfGxBxL/WC3MapSpecification/blob/master/Info/0-33.md */ public class War3MapW3i { private int version; @@ -33,7 +35,7 @@ public class War3MapW3i { private String loadingScreenText; private String loadingScreenTitle; private String loadingScreenSubtitle; - private int gameDataSet; + private int gameDataSet = 0; private String prologueScreenModel; private String prologueScreenText; private String prologueScreenTitle; @@ -42,13 +44,16 @@ public class War3MapW3i { private final float[] fogHeight = new float[2]; private float fogDensity; private final short[] fogColor = new short[4]; - private int globalWeather; + private War3ID globalWeather; private String soundEnvironment; private char lightEnvironmentTileset; private final short[] waterVertexColor = new short[4]; - private final short[] unknown2ProbablyLua = new short[4]; - private long supportedModes; - private long gameDataVersion; + private int scriptLanguage = 0; // 0 = JASS, 1 = Lua + private long supportedModes; // 1=SD, 2=HD, 3=SD+HD + private long gameDataVersion = 0; // 0=ROC, 1=TFT + private int forceDefaultCameraZoom; + private int forceMaxCameraZoom; + private int forceMinCameraZoom; private final List players = new ArrayList<>(); private final List forces = new ArrayList<>(); private final List upgradeAvailabilityChanges = new ArrayList<>(); @@ -83,45 +88,78 @@ private void load(final LittleEndianDataInputStream stream) throws IOException { ParseUtils.readInt32Array(stream, this.playableSize); this.flags = ParseUtils.readUInt32(stream); this.tileset = (char) stream.read(); - this.campaignBackground = stream.readInt(); - if (this.version > 24) { + if (this.version >= 17) { + this.campaignBackground = stream.readInt(); + } + + if (this.version >= 10 && this.version != 18 && this.version != 19) { this.loadingScreenModel = ParseUtils.readUntilNull(stream); } - this.loadingScreenText = ParseUtils.readUntilNull(stream); - this.loadingScreenTitle = ParseUtils.readUntilNull(stream); - this.loadingScreenSubtitle = ParseUtils.readUntilNull(stream); - this.gameDataSet = stream.readInt(); + if (this.version >= 10) { + this.loadingScreenText = ParseUtils.readUntilNull(stream); + } - if (this.version > 24) { + if (this.version >= 11) { + this.loadingScreenTitle = ParseUtils.readUntilNull(stream); + this.loadingScreenSubtitle = ParseUtils.readUntilNull(stream); + } + + if (this.version >= 17) { + this.gameDataSet = stream.readInt(); + } + + if (this.version >= 13 && this.version != 18 && this.version != 19) { this.prologueScreenModel = ParseUtils.readUntilNull(stream); } - this.prologueScreenText = ParseUtils.readUntilNull(stream); - this.prologueScreenTitle = ParseUtils.readUntilNull(stream); - this.prologueScreenSubtitle = ParseUtils.readUntilNull(stream); + if (this.version >= 13) { + this.prologueScreenText = ParseUtils.readUntilNull(stream); + this.prologueScreenTitle = ParseUtils.readUntilNull(stream); + this.prologueScreenSubtitle = ParseUtils.readUntilNull(stream); + } - if (this.version > 24) { + if (this.version >= 19) { this.useTerrainFog = stream.readInt(); ParseUtils.readFloatArray(stream, this.fogHeight); this.fogDensity = stream.readFloat(); ParseUtils.readUInt8Array(stream, this.fogColor); - this.globalWeather = stream.readInt(); // TODO probably war3id, right? + } + + if (this.version >= 21) { + this.globalWeather = ParseUtils.readWar3ID(stream); + } + + if (this.version >= 22) { this.soundEnvironment = ParseUtils.readUntilNull(stream); + } + + if (this.version >= 23) { this.lightEnvironmentTileset = (char) stream.read(); + } + + if (this.version >= 25) { ParseUtils.readUInt8Array(stream, this.waterVertexColor); } - if (this.version > 27) { - ParseUtils.readUInt8Array(stream, this.unknown2ProbablyLua); + if (this.version >= 28) { + this.scriptLanguage = stream.readInt(); } - if (this.version > 30) { + if (this.version >= 29) { this.supportedModes = ParseUtils.readUInt32(stream); + } + if (this.version >= 30) { this.gameDataVersion = ParseUtils.readUInt32(stream); } - else { - this.gameDataVersion = -1; // indicate to the outside that this was unspecified + + if (this.version >= 32) { + this.forceDefaultCameraZoom = stream.readInt(); + this.forceMaxCameraZoom = stream.readInt(); + } + + if (this.version >= 33) { + this.forceMinCameraZoom = stream.readInt(); } for (int i = 0, l = stream.readInt(); i < l; i++) { @@ -144,6 +182,7 @@ private void load(final LittleEndianDataInputStream stream) throws IOException { // some kind of really stupid protected map??? return; } + if (stream.available() > 0) { for (int i = 0, l = stream.readInt(); i < l; i++) { final UpgradeAvailabilityChange upgradeAvailabilityChange = new UpgradeAvailabilityChange(); @@ -208,49 +247,86 @@ public void save(final LittleEndianDataOutputStream stream) throws IOException { ParseUtils.writeInt32Array(stream, this.playableSize); ParseUtils.writeUInt32(stream, this.flags); stream.write((byte) this.tileset); - stream.writeInt(this.campaignBackground); - if (this.version > 24) { + if (this.version >= 17) { + stream.writeInt(this.campaignBackground); + } + + if (this.version >= 10 && this.version != 18 && this.version != 19) { ParseUtils.writeWithNullTerminator(stream, this.loadingScreenModel); } - ParseUtils.writeWithNullTerminator(stream, this.loadingScreenText); - ParseUtils.writeWithNullTerminator(stream, this.loadingScreenTitle); - ParseUtils.writeWithNullTerminator(stream, this.loadingScreenSubtitle); - stream.writeInt(this.gameDataSet); + if (this.version >= 10) { + ParseUtils.writeWithNullTerminator(stream, this.loadingScreenText); + } - if (this.version > 24) { + if (this.version >= 11) { + ParseUtils.writeWithNullTerminator(stream, this.loadingScreenTitle); + ParseUtils.writeWithNullTerminator(stream, this.loadingScreenSubtitle); + } + + if (this.version >= 17) { + stream.writeInt(this.gameDataSet); + } + + if (this.version >= 13 && this.version != 18 && this.version != 19) { ParseUtils.writeWithNullTerminator(stream, this.prologueScreenModel); } - ParseUtils.writeWithNullTerminator(stream, this.prologueScreenText); - ParseUtils.writeWithNullTerminator(stream, this.prologueScreenTitle); - ParseUtils.writeWithNullTerminator(stream, this.prologueScreenSubtitle); + if (this.version >= 13) { + ParseUtils.writeWithNullTerminator(stream, this.prologueScreenText); + ParseUtils.writeWithNullTerminator(stream, this.prologueScreenTitle); + ParseUtils.writeWithNullTerminator(stream, this.prologueScreenSubtitle); + } - if (this.version > 24) { + if (this.version >= 19) { stream.writeInt(this.useTerrainFog); ParseUtils.writeFloatArray(stream, this.fogHeight); stream.writeFloat(this.fogDensity); ParseUtils.writeUInt8Array(stream, this.fogColor); - stream.writeInt(this.globalWeather); // TODO War3ID??? + } + + if (this.version >= 21) { + ParseUtils.writeWar3ID(stream, this.globalWeather); + } + + if (this.version >= 22) { ParseUtils.writeWithNullTerminator(stream, this.soundEnvironment); + } + + if (this.version >= 23) { stream.write((byte) this.lightEnvironmentTileset); + } + + if (this.version >= 25) { ParseUtils.writeUInt8Array(stream, this.waterVertexColor); } - if (this.version > 27) { - ParseUtils.writeUInt8Array(stream, this.unknown2ProbablyLua); + if (this.version >= 28) { + ParseUtils.writeUInt32(stream, this.scriptLanguage); } - if (this.version > 30) { + if (this.version >= 29) { ParseUtils.writeUInt32(stream, this.supportedModes); + } + + if (this.version >= 30) { ParseUtils.writeUInt32(stream, this.gameDataVersion); } + if (this.version >= 32) { + ParseUtils.writeUInt32(stream, this.forceDefaultCameraZoom); + ParseUtils.writeUInt32(stream, this.forceMaxCameraZoom); + } + + if (this.version >= 33) { + ParseUtils.writeUInt32(stream, this.forceMinCameraZoom); + } + ParseUtils.writeUInt32(stream, this.players.size()); for (final Player player : this.players) { - player.save(stream); + player.save(stream, this.version); } ParseUtils.writeUInt32(stream, this.forces.size()); @@ -284,41 +360,6 @@ public void save(final LittleEndianDataOutputStream stream) throws IOException { table.save(stream); } } - - } - - public int getByteLength() { - int size = 111 + this.name.length() + this.author.length() + this.description.length() - + this.recommendedPlayers.length() + this.loadingScreenText.length() + this.loadingScreenTitle.length() - + this.loadingScreenSubtitle.length() + this.prologueScreenText.length() - + this.prologueScreenTitle.length() + this.prologueScreenSubtitle.length(); - - for (final Player player : this.players) { - size += player.getByteLength(); - } - - for (final Force force : this.forces) { - size += force.getByteLength(); - } - - size += this.upgradeAvailabilityChanges.size() * 16; - - size += this.techAvailabilityChanges.size() * 8; - - for (final RandomUnitTable table : this.randomUnitTables) { - size += table.getByteLength(); - } - - if (this.version > 24) { - size += 36 + this.loadingScreenModel.length() + this.prologueScreenModel.length() - + this.soundEnvironment.length(); - - for (final RandomItemTable table : this.randomItemTables) { - size += table.getByteLength(); - } - } - - return size; } public int getVersion() { @@ -429,7 +470,7 @@ public short[] getFogColor() { return this.fogColor; } - public int getGlobalWeather() { + public War3ID getGlobalWeather() { return this.globalWeather; } @@ -445,8 +486,8 @@ public short[] getWaterVertexColor() { return this.waterVertexColor; } - public short[] getUnknown2() { - return this.unknown2ProbablyLua; + public int getScriptLanguage() { + return this.scriptLanguage; } public long getSupportedModes() { diff --git a/core/src/com/etheller/warsmash/units/custom/War3ObjectDataChangeset.java b/core/src/com/etheller/warsmash/units/custom/War3ObjectDataChangeset.java index ebc737c93..63a091c30 100644 --- a/core/src/com/etheller/warsmash/units/custom/War3ObjectDataChangeset.java +++ b/core/src/com/etheller/warsmash/units/custom/War3ObjectDataChangeset.java @@ -8,6 +8,7 @@ import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.nio.charset.CodingErrorAction; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -28,6 +29,7 @@ * * @author Eric * + * Reforged spec: https://github.com/stijnherfst/HiveWE/wiki/war3map(skin).w3*-Modifications */ public final class War3ObjectDataChangeset { public static final int VAR_TYPE_INT = 0; @@ -480,10 +482,8 @@ public boolean loadtable(final LittleEndianDataInputStream stream, final ObjectM final WTS wts, final boolean inlineWTS) throws IOException { final War3ID noid = new War3ID(0); final StringBuilder stringBuilder = new StringBuilder(); - int ptr; final int count = stream.readInt(); for (int i = 0; i < count; i++) { - final long nanoTime = System.nanoTime(); War3ID origid; War3ID newid = null; origid = readWar3ID(stream); @@ -497,8 +497,7 @@ public boolean loadtable(final LittleEndianDataInputStream stream, final ObjectM existingObject = new ObjectDataChangeEntry(origid, noid); } existingObject.setNewId(readWar3ID(stream)); - } - else { + } else { newid = readWar3ID(stream); if (noid.equals(origid) || noid.equals(newid)) { throw new IOException("the input stream might be screwed"); @@ -508,118 +507,134 @@ public boolean loadtable(final LittleEndianDataInputStream stream, final ObjectM existingObject = new ObjectDataChangeEntry(origid, newid); } } + + int setsCount = 1; + if (this.version >= 3) { - final int reforged133JunkCount = stream.readInt(); - for (int reforged133JunkIndex = 0; reforged133JunkIndex < reforged133JunkCount; reforged133JunkIndex++) { - final int reforgedJunk = stream.readInt(); - } - } - final int ccount = stream.readInt();// Retera: I assume this is change count? - if ((ccount == 0) && isOriginal) { - // throw new IOException("we seem to have reached the end of the stream and get - // zeroes"); - System.err.println("we seem to have reached the end of the stream and get zeroes"); - } - if (isOriginal) { - debugprint("StandardUnit \"" + origid + "\" " + ccount + " {"); - } - else { - debugprint("CustomUnit \"" + origid + ":" + newid + "\" " + ccount + " {"); + setsCount = stream.readInt(); } - for (int j = 0; j < ccount; j++) { - final War3ID chid = readWar3ID(stream); - if (noid.equals(chid)) { - throw new IOException("the input stream might be screwed"); - } - if (!this.detected) { - this.detected = detectKind(chid); + + debugprint("setsCount " + setsCount); + + for (int setIndex = 0; setIndex < setsCount; setIndex++) { + + if (this.version >= 3) { + final int setFlag = stream.readInt(); + + debugprint("setFlag " + setFlag); } - final Change newlyReadChange = new Change(); - newlyReadChange.setId(chid); - newlyReadChange.setVartype(stream.readInt()); - debugprint("\t\"" + chid + "\" {"); - debugprint("\t\tType " + newlyReadChange.getVartype() + ","); - if (extended()) { - newlyReadChange.setLevel(stream.readInt()); - newlyReadChange.setDataptr(stream.readInt()); - debugprint("\t\tLevel " + newlyReadChange.getLevel() + ","); - debugprint("\t\tData " + newlyReadChange.getDataptr() + ","); + final int ccount = stream.readInt();// Retera: I assume this is change count? + + debugprint("ccount " + ccount); + + if ((ccount == 0) && isOriginal) { + // tdauth: This seems to happen if object data has been reset in the World Editor but still is stored with zero modifications in the object data. + // throw new IOException("we seem to have reached the end of the stream and get + // zeroes"); + //System.err.println("we seem to have reached the end of the stream and get zeroes"); + } + if (isOriginal) { + debugprint("(" + i + "/" + count + ") StandardObject \"" + origid + "\" " + ccount + " {"); + } else { + debugprint("(" + i + "/" + count + ") CustomObject \"" + origid + ":" + newid + "\" " + ccount + " {"); } + for (int j = 0; j < ccount; j++) { + final War3ID chid = readWar3ID(stream); + if (noid.equals(chid)) { + throw new IOException("the input stream might be screwed"); + } + if (!this.detected) { + this.detected = detectKind(chid); + } - switch (newlyReadChange.getVartype()) { - case 0: - newlyReadChange.setLongval(stream.readInt()); - debugprint("\t\tValue " + newlyReadChange.getLongval() + ","); - break; - case 3: - ptr = 0; - stringBuilder.setLength(0); - int charRead; - while ((charRead = stream.read()) != 0) { - stringBuilder.append((char) charRead); + final Change newlyReadChange = new Change(); + newlyReadChange.setId(chid); + newlyReadChange.setVartype(stream.readInt()); + debugprint("\t\"" + chid + "\" {"); + debugprint("\t\tType " + newlyReadChange.getVartype() + ","); + if (extended()) { + newlyReadChange.setLevel(stream.readInt()); + newlyReadChange.setDataptr(stream.readInt()); + debugprint("\t\tLevel " + newlyReadChange.getLevel() + ","); + debugprint("\t\tData " + newlyReadChange.getDataptr() + ","); } - newlyReadChange.setStrval(stringBuilder.toString()); - if (inlineWTS && (newlyReadChange.getStrval().length() > 8) - && "TRIGSTR_".equals(newlyReadChange.getStrval().substring(0, 8))) { - final int key = getWTSValue(newlyReadChange); - newlyReadChange.setStrval(wts.get(key)); - if ((newlyReadChange.getStrval() != null) - && (newlyReadChange.getStrval().length() > MAX_STR_LEN)) { - newlyReadChange.setStrval(newlyReadChange.getStrval().substring(0, MAX_STR_LEN - 1)); + + switch (newlyReadChange.getVartype()) { + case 0: { + newlyReadChange.setLongval(stream.readInt()); + debugprint("\t\tValue " + newlyReadChange.getLongval() + ","); + break; + } + case 3: { + stringBuilder.setLength(0); + int charRead; + while ((charRead = stream.read()) != 0) { + stringBuilder.append((char) charRead); + } + newlyReadChange.setStrval(stringBuilder.toString()); + if (inlineWTS && (newlyReadChange.getStrval().length() > 8) + && "TRIGSTR_".equals(newlyReadChange.getStrval().substring(0, 8))) { + final int key = getWTSValue(newlyReadChange); + newlyReadChange.setStrval(wts.get(key)); + if ((newlyReadChange.getStrval() != null) + && (newlyReadChange.getStrval().length() > MAX_STR_LEN)) { + newlyReadChange.setStrval(newlyReadChange.getStrval().substring(0, MAX_STR_LEN - 1)); + } + } + debugprint("\t\tValue \"" + newlyReadChange.getStrval() + "\","); + break; + } + case 4: { + newlyReadChange.setBoolval(stream.readInt() == 1); + debugprint("\t\tValue " + newlyReadChange.isBoolval() + ","); + break; + } + default: { + newlyReadChange.setRealval(stream.readFloat()); + debugprint("\t\tValue " + newlyReadChange.getRealval() + ","); + break; } } - debugprint("\t\tValue \"" + newlyReadChange.getStrval() + "\","); - break; - case 4: - newlyReadChange.setBoolval(stream.readInt() == 1); - debugprint("\t\tValue " + newlyReadChange.isBoolval() + ","); - break; - default: - newlyReadChange.setRealval(stream.readFloat()); - debugprint("\t\tValue " + newlyReadChange.getRealval() + ","); - break; - } - final War3ID crap = readWar3ID(stream); - debugprint("\t\tExtra \"" + crap + "\","); - newlyReadChange.setJunkDNA(crap); - List existingChanges = existingObject.getChanges().get(chid); - if (existingChanges == null) { - existingChanges = new ArrayList<>(); - } - Change bestTargetChange = null; - for (final Change targetChange : existingChanges) { - if (targetChange.getLevel() == newlyReadChange.getLevel()) { - bestTargetChange = targetChange; - break; + final War3ID crap = readWar3ID(stream); + debugprint("\t\tExtra \"" + crap + "\","); + newlyReadChange.setJunkDNA(crap); + List existingChanges = existingObject.getChanges().get(chid); + if (existingChanges == null) { + existingChanges = new ArrayList<>(); } - } - if (bestTargetChange != null) { - bestTargetChange.copyFrom(newlyReadChange); - } - else { - existingChanges.add(newlyReadChange.clone()); - if (existingChanges.size() == 1) { - existingObject.getChanges().add(chid, existingChanges); + Change bestTargetChange = null; + for (final Change targetChange : existingChanges) { + if (targetChange.getLevel() == newlyReadChange.getLevel()) { + bestTargetChange = targetChange; + break; + } } - } - if (!crap.equals(existingObject.getOldId()) && !crap.equals(existingObject.getNewId()) - && !crap.equals(noid)) { - for (int charIndex = 0; charIndex < 4; charIndex++) { - if ((crap.charAt(charIndex) < 32) || (crap.charAt(charIndex) > 126)) { - return false; + if (bestTargetChange != null) { + bestTargetChange.copyFrom(newlyReadChange); + } else { + existingChanges.add(newlyReadChange.clone()); + if (existingChanges.size() == 1) { + existingObject.getChanges().add(chid, existingChanges); } } + if (!crap.equals(existingObject.getOldId()) && !crap.equals(existingObject.getNewId()) + && !crap.equals(noid)) { + for (int charIndex = 0; charIndex < 4; charIndex++) { + if ((crap.charAt(charIndex) < 32) || (crap.charAt(charIndex) > 126)) { + return false; + } + } + } + debugprint("\t}"); } - debugprint("\t}"); } + debugprint("}"); if ((newid == null) && !isOriginal) { throw new IllegalStateException("custom unit has no ID!"); } map.put(isOriginal ? origid : newid, existingObject); - final long endNanoTime = System.nanoTime(); - final long deltaNanoTime = endNanoTime - nanoTime; } return true; } @@ -710,8 +725,20 @@ public boolean saveTable(final LittleEndianDataOutputStream outputStream, final final CharBuffer charBuffer = CharBuffer.allocate(1024); final ByteBuffer byteBuffer = ByteBuffer.allocate(1024); final War3ID noid = new War3ID(0); - int count; - count = map.size(); + int count = 0; + + for (final Map.Entry entry : map) { + final ObjectDataChangeEntry cl = entry.getValue(); + int totalSize = 0; + for (final Map.Entry> changeEntry : cl.getChanges()) { + totalSize += changeEntry.getValue().size(); + } + + if ((totalSize > 0) || !isOriginal) { + count++; + } + } + outputStream.writeInt(count); for (final Map.Entry entry : map) { final ObjectDataChangeEntry cl = entry.getValue(); @@ -723,12 +750,11 @@ public boolean saveTable(final LittleEndianDataOutputStream outputStream, final ParseUtils.writeWar3ID(outputStream, cl.getOldId()); ParseUtils.writeWar3ID(outputStream, cl.getNewId()); if (this.version >= 3) { - outputStream.writeInt(1); - outputStream.writeInt(0); + outputStream.writeInt(1); // set count + outputStream.writeInt(0); // set flag for the first set } - count = totalSize;// cl.getChanges().size(); - outputStream.writeInt(count); - for (final Map.Entry> changes : entry.getValue().getChanges()) { + outputStream.writeInt(totalSize); + for (final Map.Entry> changes : cl.getChanges()) { for (final Change change : changes.getValue()) { ParseUtils.writeWar3ID(outputStream, change.getId()); outputStream.writeInt(change.getVartype()); @@ -737,30 +763,34 @@ public boolean saveTable(final LittleEndianDataOutputStream outputStream, final outputStream.writeInt(change.getDataptr()); } switch (change.getVartype()) { - case 0: - outputStream.writeInt(change.getLongval()); - break; - case 3: - charBuffer.clear(); - byteBuffer.clear(); - charBuffer.put(change.getStrval()); - charBuffer.flip(); - encoder.encode(charBuffer, byteBuffer, false); - byteBuffer.flip(); - final byte[] stringBytes = new byte[byteBuffer.remaining() + 1]; - int i = 0; - while (byteBuffer.hasRemaining()) { - stringBytes[i++] = byteBuffer.get(); + case 0: { + outputStream.writeInt(change.getLongval()); + break; + } + case 3: { + charBuffer.clear(); + byteBuffer.clear(); + charBuffer.put(change.getStrval()); + charBuffer.flip(); + encoder.encode(charBuffer, byteBuffer, false); + byteBuffer.flip(); + final byte[] stringBytes = new byte[byteBuffer.remaining() + 1]; + int i = 0; + while (byteBuffer.hasRemaining()) { + stringBytes[i++] = byteBuffer.get(); + } + stringBytes[i] = 0; + outputStream.write(stringBytes); + break; + } + case 4: { + outputStream.writeInt(change.isBoolval() ? 1 : 0); + break; + } + default: { + outputStream.writeFloat(change.getRealval()); + break; } - stringBytes[i] = 0; - outputStream.write(stringBytes); - break; - case 4: - outputStream.writeInt(change.isBoolval() ? 1 : 0); - break; - default: - outputStream.writeFloat(change.getRealval()); - break; } // if (change.getJunkDNA() == null) { // saveWriteChars(outputStream, cl.getNewId().asStringValue().toCharArray()); @@ -801,6 +831,6 @@ public ObjectMap getCustom() { } private static void debugprint(final String s) { - + //System.out.println(s); } } diff --git a/core/src/com/etheller/warsmash/util/ImageUtils.java b/core/src/com/etheller/warsmash/util/ImageUtils.java index a4e0dff68..ef33d77ba 100644 --- a/core/src/com/etheller/warsmash/util/ImageUtils.java +++ b/core/src/com/etheller/warsmash/util/ImageUtils.java @@ -31,16 +31,19 @@ public final class ImageUtils { public static final String DEFAULT_ICON_PATH = "ReplaceableTextures\\CommandButtons\\BTNTemp.blp"; public static Texture getAnyExtensionTexture(final DataSource dataSource, final String path) { - BufferedImage image; - try { - final AnyExtensionImage imageInfo = getAnyExtensionImageFixRGB(dataSource, path, "texture"); - image = imageInfo.getImageData(); - if (image != null) { - return ImageUtils.getTexture(image, imageInfo.isNeedsSRGBFix()); + if (dataSource.has(path)) { + BufferedImage image; + try { + final AnyExtensionImage imageInfo = getAnyExtensionImageFixRGB(dataSource, path, "texture"); + image = imageInfo.getImageData(); + if (image != null) { + return ImageUtils.getTexture(image, imageInfo.isNeedsSRGBFix()); + } + } catch (final IOException e) { + return null; } - } - catch (final IOException e) { - return null; + } else { + System.err.println("Missing texture " + path); } return null; } diff --git a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java index d1d2fd53b..efc198ffd 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java @@ -140,8 +140,6 @@ public Object[] findHandler(final String ext) { public Resource load(final String src, final PathSolver pathSolver, final Object solverParams) { String finalSrc = src; - String extension = ""; - boolean isFetch = false; // If a given path solver, resolve. if (pathSolver != null) { @@ -149,16 +147,22 @@ public Resource load(final String src, final PathSolver pathSolver, final Object finalSrc = solved.getFinalSrc(); if (!this.dataSource.has(finalSrc)) { - final String ddsPath = finalSrc.substring(0, finalSrc.lastIndexOf('.')) + ".dds"; - if (this.dataSource.has(ddsPath)) { - finalSrc = ddsPath; - } - else { + final int lastIndex = finalSrc.lastIndexOf('.'); + if (lastIndex != -1) { + final String ddsPath = finalSrc.substring(0, lastIndex) + ".dds"; + if (this.dataSource.has(ddsPath)) { + finalSrc = ddsPath; + } + else { + System.err.println("Attempting to load non-existant file: " + finalSrc); + } + } else { System.err.println("Attempting to load non-existant file: " + finalSrc); } } - extension = solved.getExtension(); - isFetch = solved.isFetch(); + + String extension = solved.getExtension(); + boolean isFetch = solved.isFetch(); if (!(extension instanceof String)) { throw new IllegalStateException("The path solver did not return an extension!"); @@ -194,7 +198,11 @@ public Resource load(final String src, final PathSolver pathSolver, final Object // TODO this is a synchronous hack, skipped some Ghostwolf code try { - resource.loadData(this.dataSource.getResourceAsStream(finalSrc), null); + if (this.dataSource.has(finalSrc)) { + resource.loadData(this.dataSource.getResourceAsStream(finalSrc), null); + } else { + System.err.println("Attempting to load non-existant file: " + finalSrc); + } } catch (final Exception e) { throw new IllegalStateException("Unable to load data: " + finalSrc, e); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java index bacdd1c2b..e4e39ef79 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java @@ -86,17 +86,12 @@ public ModelInstance createInstance(final int type) { } } - public void load(final Object bufferOrParser) throws IOException { - MdlxModel parser; - - if (bufferOrParser instanceof MdlxModel) { - parser = (MdlxModel) bufferOrParser; - } - else { - System.err.println("Wasting memory with conversion from InputStream to buffer in MdxModel"); - parser = new MdlxModel(ByteBuffer.wrap(IOUtils.toByteArray((InputStream) bufferOrParser))); - } + public void load(final InputStream stream) throws IOException { + System.err.println("Wasting memory with conversion from InputStream to buffer in MdxModel"); + load(new MdlxModel(ByteBuffer.wrap(IOUtils.toByteArray(stream)))); + } + public void load(final MdlxModel parser) { final ModelViewer viewer = this.viewer; final PathSolver pathSolver = this.pathSolver; final SolverParams solverParams = this.solverParams; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 0b44d8f26..93c200bc0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -2115,7 +2115,7 @@ public RenderSpellEffect addSpecialEffectTarget(final String modelName, final CW final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.get(targetWidget); if (renderUnit == null) { final NullPointerException nullPointerException = new NullPointerException( - "renderUnit is null! targetWidget is \"" + ((CUnit) targetWidget).getUnitType().getName() + "renderUnit is null! targetWidget is \"" + ((CUnit) targetWidget).getName() + "\", attachPointName=\"" + attachPointNames + "\""); if (WarsmashConstants.ENABLE_DEBUG) { throw nullPointerException; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItem.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItem.java index b0c4eaa1c..d2ba96c7e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItem.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItem.java @@ -30,25 +30,27 @@ public RenderItem(final War3MapViewer map, RenderItemType itemType, final float final float angle, final CItem simulationItem) { this.portraitModel = itemType.getPortraitModel(); this.simulationItem = simulationItem; - final MdxComplexInstance instance = (MdxComplexInstance) itemType.getModel().addInstance(); - this.location[0] = x; this.location[1] = y; this.location[2] = z; - instance.move(this.location); -// instance.localRotation.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle); - instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle)); - instance.setScene(map.worldScene); + this.radius = 1 * 36; + + if (itemType.getModel() != null) { + final MdxComplexInstance instance = (MdxComplexInstance) itemType.getModel().addInstance(); + + instance.move(this.location); + // instance.localRotation.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle); + instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle)); + instance.setScene(map.worldScene); + this.instance = instance; - if (itemType != null) { final Vector3 tintingColor = itemType.getTintingColor(); instance.setVertexColor(new float[] { tintingColor.x, tintingColor.y, tintingColor.z }); instance.uniformScale(itemType.getModelScale()); - - this.radius = 1 * 36; + } else { + this.instance = null; + System.err.println("Warning: Item type " + simulationItem.getTypeId() + " has no model."); } - - this.instance = instance; } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItemTypeData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItemTypeData.java index 2d6bfcfaa..e302e073c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItemTypeData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItemTypeData.java @@ -29,7 +29,7 @@ public RenderItemTypeData(ObjectData itemObjectData, DataSource dataSource, War3 protected RenderItemType createTypeData(War3ID key, GameObject row) { String path = row.getFieldAsString(ITEM_FILE, 0); - if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { + if (path.length() >= 4 && (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx"))) { path = path.substring(0, path.length() - 4); } final MdxModel model = this.mapViewer.loadModelMdx(path); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidgetTypeData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidgetTypeData.java index 47f58f921..13a607cf0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidgetTypeData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidgetTypeData.java @@ -44,13 +44,12 @@ protected final String getShadowTexture(String unitShadow) { } protected final MdxModel getPortraitModel(final String path, final MdxModel model) { - MdxModel portraitModel; - final String portraitPath = path.substring(0, path.length() - 4) + "_portrait.mdx"; - if (this.dataSource.has(portraitPath)) { - portraitModel = this.mapViewer.loadModelMdx(portraitPath); - } - else { - portraitModel = model; + MdxModel portraitModel = model; + if (path.length() >= 4) { + final String portraitPath = path.substring(0, path.length() - 4) + "_portrait.mdx"; + if (this.dataSource.has(portraitPath)) { + portraitModel = this.mapViewer.loadModelMdx(portraitPath); + } } return portraitModel; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java index c9a705606..5a2becc81 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java @@ -192,10 +192,14 @@ public AbilityDataUI(final Warcraft3MapRuntimeObjectData allObjectData, final Ga final int targetAttachmentIndexMax = Math.min(targetAttachmentCount - 1, targetArtPaths.size() - 1); final int targetIteratorCount = Math.max(targetAttachmentCount, targetArtPaths.size()); for (int i = 0; i < targetIteratorCount; i++) { - final String modelPath = targetArtPaths.get(Math.max(0, Math.min(i, targetAttachmentIndexMax))); - final String attachmentPointKey = tryGet(TARGET_ART_ATTACHMENT_POINT, i); - final List attachmentPoints = abilityTypeData.getFieldAsList(attachmentPointKey); - targetArt.add(new EffectAttachmentUI(modelPath, attachmentPoints)); + final int index = Math.max(0, Math.min(i, targetAttachmentIndexMax)); + + if (index >= 0 && index < targetArtPaths.size()) { + final String modelPath = targetArtPaths.get(index); + final String attachmentPointKey = tryGet(TARGET_ART_ATTACHMENT_POINT, i); + final List attachmentPoints = abilityTypeData.getFieldAsList(attachmentPointKey); + targetArt.add(new EffectAttachmentUI(modelPath, attachmentPoints)); + } } final List specialArt = new ArrayList<>(); final List specialArtPaths = Arrays diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructableType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructableType.java index 7fbec7827..4733bffec 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructableType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructableType.java @@ -3,10 +3,12 @@ import java.awt.image.BufferedImage; import java.util.EnumSet; +import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; public class CDestructableType { + private final War3ID typeId; private final String name; private final float maxLife; private final EnumSet targetedAs; @@ -20,10 +22,11 @@ public class CDestructableType { private final int lumberRepairCost; private final int repairTime; - public CDestructableType(final String name, final float maxLife, final EnumSet targetedAs, - final String armorType, final int buildTime, final int goldRepairCost, final int lumberRepairCost, - final int repairTime, final float occlusionHeight, final BufferedImage pathingPixelMap, - final BufferedImage pathingDeathPixelMap) { + public CDestructableType(final War3ID typeId, final String name, final float maxLife, final EnumSet targetedAs, + final String armorType, final int buildTime, final int goldRepairCost, final int lumberRepairCost, + final int repairTime, final float occlusionHeight, final BufferedImage pathingPixelMap, + final BufferedImage pathingDeathPixelMap) { + this.typeId = typeId; this.name = name; this.maxLife = maxLife; this.targetedAs = targetedAs; @@ -37,6 +40,8 @@ public CDestructableType(final String name, final float maxLife, final EnumSet enterTriggers = new ArrayList<>(); private final List leaveTriggers = new ArrayList<>(); + public CRegion(int handleId) { + this.handleId = handleId; + } + + @Override + public int getHandleId() { + return handleId; + } + public void addRect(final Rectangle rect, final CRegionManager regionManager) { if (this.currentBounds == null) { this.currentBounds = new Rectangle(rect); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/CHashtable.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/CHashtable.java index d82b5ea99..db637e3ac 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/CHashtable.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/CHashtable.java @@ -1,11 +1,23 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; +import com.etheller.interpreter.ast.util.CHandle; + import java.util.HashMap; import java.util.Map; -public class CHashtable { +public class CHashtable implements CHandle { + private final int handleId; private final Map> parentKeyToChildTable = new HashMap<>(); + public CHashtable(final int handleId) { + this.handleId = handleId; + } + + @Override + public int getHandleId() { + return handleId; + } + public void save(final Integer parentKey, final Integer childKey, final Object object) { Map childTable = this.parentKeyToChildTable.get(parentKey); if (childTable == null) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 8ac39d149..3dfb883de 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -1893,7 +1893,7 @@ private String getWorldFrameHoverTipText(final CSimulation game, final RenderWid if (!returnValue.isEmpty()) { returnValue += "|n"; } - returnValue += simulationUnit.getUnitType().getName(); + returnValue += simulationUnit.getName(); if (goldMineData != null) { final String colonGold = this.rootFrame.getTemplates().getDecoratedString("COLON_GOLD"); returnValue += "|n" + colonGold + " " + goldMineData.getGold(); @@ -3223,7 +3223,7 @@ private void reloadSelectedUnitUI(final RenderUnit unit) { this.simpleInfoPanelDestructableDetail.setVisible(false); this.simpleBuildQueueBackdrop.setVisible(true); this.simpleInfoPanelUnitDetail.setVisible(false); - this.rootFrame.setText(this.simpleBuildingNameValue, simulationUnit.getUnitType().getName()); + this.rootFrame.setText(this.simpleBuildingNameValue, simulationUnit.getName()); this.rootFrame.setText(this.simpleBuildingDescriptionValue, ""); this.simpleBuildingBuildTimeIndicator.setVisible(true); @@ -3318,7 +3318,7 @@ else if (multiSelect) { this.smashBuffStatusBar.setVisible(!multiSelect && !simulationUnit.isBuilding() && !constructing); final CAbilityCargoHold cargoData = simulationUnit.getCargoData(); if ((cargoData != null) && !cargoData.isEmpty() && !multiSelect && !constructing) { - final String unitTypeName = simulationUnit.getUnitType().getName(); + final String unitTypeName = simulationUnit.getName(); this.attack1Icon.setVisible(false); this.attack2Icon.setVisible(false); this.armorIcon.setVisible(false); @@ -3348,7 +3348,7 @@ else if (multiSelect) { .setUberTip("Level " + cargoContainedUnit.getHeroData().getHeroLevel()); } else { - this.cargoUnitFrames[i].setToolTip(cargoContainedUnit.getUnitType().getName()); + this.cargoUnitFrames[i].setToolTip(cargoContainedUnit.getName()); this.cargoUnitFrames[i].setUberTip(unitUI.getUberTip()); } this.cargoUnitFrames[i].setLifeRatioRemaining( @@ -3371,7 +3371,7 @@ else if (multiSelect) { this.cargoUnitFrames[i].setVisible(false); } final CUnitType unitType = simulationUnit.getUnitType(); - final String unitTypeName = unitType.getName(); + final String unitName = simulationUnit.getName(); final boolean anyAttacks = simulationUnit.getCurrentAttacks().size() > 0; final UIFrame localArmorIcon = this.armorIcon; @@ -3475,7 +3475,7 @@ else if (multiSelect) { final int heroLevel = heroData.getHeroLevel(); this.simpleClassValue.setVisible(true); this.rootFrame.setText(this.simpleClassValue, - String.format(infopanelLevelClass, heroLevel, unitTypeName)); + String.format(infopanelLevelClass, heroLevel, unitName)); this.rootFrame.setText(this.simpleNameValue, heroData.getProperName()); this.simpleHeroLevelBar.setVisible(true); final CGameplayConstants gameplayConstants = this.war3MapViewer.simulation.getGameplayConstants(); @@ -3485,7 +3485,7 @@ else if (multiSelect) { } else { this.simpleClassValue.setVisible(!simulationUnit.isBuilding()); - this.rootFrame.setText(this.simpleNameValue, unitTypeName); + this.rootFrame.setText(this.simpleNameValue, unitName); String classText = null; for (final CUnitClassification classification : simulationUnit.getClassifications()) { if (classification.getDisplayName() != null) { @@ -5153,6 +5153,11 @@ public CTimerDialog createTimerDialog(final CTimer timer) { return new CTimerDialog(timer, timerDialog, valueFrame, titleFrame); } + @Override + public void setTimerDialogTitle(CTimerDialog timerDialog, String title) { + timerDialog.setTitle(this.rootFrame, title); + } + @Override public void displayTimedText(final float x, final float y, final float duration, final String message) { showGameMessage(message, duration); // TODO x y diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/WarsmashUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/WarsmashUI.java index 2665b49b6..80c423d80 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/WarsmashUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/WarsmashUI.java @@ -45,6 +45,8 @@ public interface WarsmashUI extends CommandErrorListener, WarsmashBaseUI { CTimerDialog createTimerDialog(CTimer timer); + void setTimerDialogTitle(CTimerDialog timerDialog, String title); + void removedUnit(CUnit whichUnit); void removedItem(CItem whichItem); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/dialog/CTimerDialog.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/dialog/CTimerDialog.java index 94ae0b6a6..87cd9362f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/dialog/CTimerDialog.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/dialog/CTimerDialog.java @@ -5,6 +5,7 @@ import com.etheller.warsmash.parsers.fdf.frames.UIFrame; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.timers.CTimer; +import com.badlogic.gdx.graphics.Color; public class CTimerDialog { private final CTimer timer; @@ -24,14 +25,26 @@ public void setTitle(final GameUI rootFrame, final String title) { rootFrame.setText(this.titleFrame, title); } + public void setTitleColor(final Color color) { + this.titleFrame.setColor(color); + } + public void setValue(final GameUI rootFrame, final String value) { rootFrame.setText(this.valueFrame, value); } + public void setValueColor(final Color color) { + this.valueFrame.setColor(color); + } + public void setVisible(final boolean visible) { this.timerDialogFrame.setVisible(visible); } + public boolean isVisible() { + return this.timerDialogFrame.isVisible(); + } + public void update(final GameUI rootFrame, final CSimulation simulation) { if (this.timerDialogFrame.isVisible() && (this.timer != null)) { final float remaining = this.timer.getRemaining(simulation); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/toggle/MeleeToggleUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/toggle/MeleeToggleUI.java index b5946f204..4cf520dcd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/toggle/MeleeToggleUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/toggle/MeleeToggleUI.java @@ -219,6 +219,11 @@ public CTimerDialog createTimerDialog(final CTimer timer) { return this.meleeUI.createTimerDialog(timer); } + @Override + public void setTimerDialogTitle(CTimerDialog timerDialog, String title) { + this.meleeUI.setTimerDialogTitle(timerDialog, title); + } + @Override public void removedUnit(final CUnit whichUnit) { this.meleeUI.removedUnit(whichUnit); diff --git a/core/src/com/hiveworkshop/blizzard/casc/io/WarcraftIIICASC.java b/core/src/com/hiveworkshop/blizzard/casc/io/WarcraftIIICASC.java index 2dbd8a549..28158d7b5 100644 --- a/core/src/com/hiveworkshop/blizzard/casc/io/WarcraftIIICASC.java +++ b/core/src/com/hiveworkshop/blizzard/casc/io/WarcraftIIICASC.java @@ -5,8 +5,11 @@ import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import com.hiveworkshop.blizzard.casc.ConfigurationFile; import com.hiveworkshop.blizzard.casc.info.Info; @@ -288,4 +291,37 @@ public Info getBuildInfo() { public FileSystem getRootFileSystem() { return new FileSystem(); } + + public static void main(String[] args) throws IOException { + // Extract Warcraft III: Reforged into a local directory. + final String homeDir = System.getProperty("user.home"); + final Path wc3ReforgedDir = Paths.get(homeDir, "snap/steam/common/.local/share/Steam/steamapps/compatdata/2243394608/pfx/drive_c/Program Files (x86)/Warcraft III"); + final Path targetDir = Path.of(homeDir, "Dokumente/Warcraft III/Reforged"); + try (WarcraftIIICASC casc = new WarcraftIIICASC(wc3ReforgedDir, true)) { + for (var file : casc.getRootFileSystem().enumerateFiles()) { + try { + final Path targetFilePath = targetDir.resolve(file.replace('\\', '/')); + + if (!Files.exists(targetFilePath)) { + final Path parentDir = targetFilePath.getParent(); + System.out.println(file + ": " + targetFilePath + " -> " + parentDir); + Files.createDirectories(parentDir); + final ByteBuffer buffer = casc.getRootFileSystem().readFileData(file); + + if (buffer != null && buffer.remaining() > 0) { + try (var channel = Files.newByteChannel(targetFilePath, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) { + channel.write(buffer); + } + } + } else { + System.out.println(file + ": Ignoring existing file/directory"); + } + } catch (final Exception e) { + e.printStackTrace(); + } + } + } catch (final IOException e) { + e.printStackTrace(); + } + } } diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxLayer.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxLayer.java index 0f54476a9..442820fde 100644 --- a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxLayer.java +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxLayer.java @@ -25,7 +25,11 @@ public enum FilterMode { } public static FilterMode fromId(final int id) { - return values()[id]; + if (id >= 0 && id < FilterMode.values().length) { + return values()[id]; + } + + return FilterMode.NONE; } public static int nameToId(final String name) { diff --git a/core/test/com/etheller/warsmash/parsers/w3x/objectdata/War3ObjectDataChangesetTest.java b/core/test/com/etheller/warsmash/parsers/w3x/objectdata/War3ObjectDataChangesetTest.java new file mode 100644 index 000000000..5eac350a9 --- /dev/null +++ b/core/test/com/etheller/warsmash/parsers/w3x/objectdata/War3ObjectDataChangesetTest.java @@ -0,0 +1,67 @@ +package com.etheller.warsmash.parsers.w3x.objectdata; + +import com.etheller.warsmash.units.custom.War3ObjectDataChangeset; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class War3ObjectDataChangesetTest { + + @Test + void testUnitsWoWReforged() throws IOException { + final War3ObjectDataChangeset changeset = new War3ObjectDataChangeset('u'); + try (LittleEndianDataInputStream stream = new LittleEndianDataInputStream( + getClass().getClassLoader().getResourceAsStream("wowr_w3x/war3map.w3u"))) { + changeset.load(stream, null, false); + } + + assertEquals(732, changeset.getOriginal().size()); + assertEquals(2199, changeset.getCustom().size()); + + java.io.File testFile = java.io.File.createTempFile("war3map", ".w3u"); + testFile.deleteOnExit(); + + try (LittleEndianDataOutputStream out = new LittleEndianDataOutputStream(new java.io.FileOutputStream(testFile))) { + changeset.save(out, false); + } + + final War3ObjectDataChangeset changeset2 = new War3ObjectDataChangeset('u'); + try (LittleEndianDataInputStream stream = new LittleEndianDataInputStream(new java.io.FileInputStream(testFile))) { + changeset2.load(stream, null, false); + } + + assertEquals(721, changeset2.getOriginal().size(), "Expected several objects without actual modifications to be removed."); + assertEquals(changeset.getCustom().size(), changeset2.getCustom().size()); + } + + @Test + void testAbilitiesWoWReforged() throws IOException { + final War3ObjectDataChangeset changeset = new War3ObjectDataChangeset('a'); + try (LittleEndianDataInputStream stream = new LittleEndianDataInputStream( + getClass().getClassLoader().getResourceAsStream("wowr_w3x/war3map.w3a"))) { + changeset.load(stream, null, false); + } + + assertEquals(62, changeset.getOriginal().size()); + assertEquals(2148, changeset.getCustom().size()); + + java.io.File testFile = java.io.File.createTempFile("war3map", ".w3a"); + testFile.deleteOnExit(); + + try (LittleEndianDataOutputStream out = new LittleEndianDataOutputStream(new java.io.FileOutputStream(testFile))) { + changeset.save(out, false); + } + + final War3ObjectDataChangeset changeset2 = new War3ObjectDataChangeset('a'); + try (LittleEndianDataInputStream stream = new LittleEndianDataInputStream(new java.io.FileInputStream(testFile))) { + changeset2.load(stream, null, false); + } + + assertEquals(21, changeset2.getOriginal().size(), "Expected several objects without actual modifications to be removed."); + assertEquals(changeset.getCustom().size(), changeset2.getCustom().size()); + } +} diff --git a/core/test/com/etheller/warsmash/parsers/w3x/w3e/War3MapW3eTest.java b/core/test/com/etheller/warsmash/parsers/w3x/w3e/War3MapW3eTest.java new file mode 100644 index 000000000..1aa6c4d73 --- /dev/null +++ b/core/test/com/etheller/warsmash/parsers/w3x/w3e/War3MapW3eTest.java @@ -0,0 +1,41 @@ +package com.etheller.warsmash.parsers.w3x.w3e; + +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class War3MapW3eTest { + + @Test + void testWoWReforged() throws IOException { + War3MapW3e mapInfo; + try (LittleEndianDataInputStream stream = new LittleEndianDataInputStream( + getClass().getClassLoader().getResourceAsStream("wowr_w3x/war3map.w3e"))) { + mapInfo = new War3MapW3e(stream); + } + + assertEquals(12, mapInfo.getVersion()); + assertEquals(50, mapInfo.getGroundTiles().size()); + assertEquals(4, mapInfo.getCliffTiles().size()); + + java.io.File testFile = java.io.File.createTempFile("war3map", ".w3e"); + testFile.deleteOnExit(); + + try (LittleEndianDataOutputStream stream = new LittleEndianDataOutputStream(new java.io.FileOutputStream(testFile))) { + mapInfo.save(stream); + } + + War3MapW3e mapInfo2; + try (LittleEndianDataInputStream stream = new LittleEndianDataInputStream(new java.io.FileInputStream(testFile))) { + mapInfo2 = new War3MapW3e(stream); + } + + assertEquals(mapInfo.getVersion(), mapInfo2.getVersion()); + assertEquals(mapInfo.getGroundTiles(), mapInfo2.getGroundTiles()); + assertEquals(mapInfo.getCliffTiles(), mapInfo2.getCliffTiles()); + } +} diff --git a/core/test/com/etheller/warsmash/parsers/w3x/w3i/War3MapW3iTest.java b/core/test/com/etheller/warsmash/parsers/w3x/w3i/War3MapW3iTest.java new file mode 100644 index 000000000..5816bdbd8 --- /dev/null +++ b/core/test/com/etheller/warsmash/parsers/w3x/w3i/War3MapW3iTest.java @@ -0,0 +1,42 @@ +package com.etheller.warsmash.util; + +import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; + +class War3MapW3iTest { + + @Test + void testWoWReforged() throws IOException { + War3MapW3i mapInfo; + try (LittleEndianDataInputStream stream = new LittleEndianDataInputStream( + getClass().getClassLoader().getResourceAsStream("wowr_w3x/war3map.w3i"))) { + mapInfo = new War3MapW3i(stream); + } + + assertEquals(33, mapInfo.getVersion()); + assertEquals("TRIGSTR_004", mapInfo.getAuthor()); + assertEquals(12, mapInfo.getPlayers().size()); + assertEquals(3, mapInfo.getForces().size()); + + java.io.File testFile = java.io.File.createTempFile("war3map_", ".w3i"); + testFile.deleteOnExit(); + + try (LittleEndianDataOutputStream stream = new LittleEndianDataOutputStream(new java.io.FileOutputStream(testFile))) { + mapInfo.save(stream); + } + + War3MapW3i mapInfo2; + try (LittleEndianDataInputStream stream = new LittleEndianDataInputStream(new java.io.FileInputStream(testFile))) { + mapInfo2 = new War3MapW3i(stream); + } + + assertEquals(mapInfo.getVersion(), mapInfo2.getVersion()); + assertEquals(mapInfo.getAuthor(), mapInfo2.getAuthor()); + assertEquals(mapInfo.getPlayers().size(), mapInfo2.getPlayers().size()); + } +} diff --git a/core/test/resources/wowr_w3x/war3map.w3a b/core/test/resources/wowr_w3x/war3map.w3a new file mode 100644 index 000000000..4211f5573 Binary files /dev/null and b/core/test/resources/wowr_w3x/war3map.w3a differ diff --git a/core/test/resources/wowr_w3x/war3map.w3b b/core/test/resources/wowr_w3x/war3map.w3b new file mode 100644 index 000000000..dd8d636dc Binary files /dev/null and b/core/test/resources/wowr_w3x/war3map.w3b differ diff --git a/core/test/resources/wowr_w3x/war3map.w3d b/core/test/resources/wowr_w3x/war3map.w3d new file mode 100644 index 000000000..48612ac4d Binary files /dev/null and b/core/test/resources/wowr_w3x/war3map.w3d differ diff --git a/core/test/resources/wowr_w3x/war3map.w3e b/core/test/resources/wowr_w3x/war3map.w3e new file mode 100644 index 000000000..3779dd615 Binary files /dev/null and b/core/test/resources/wowr_w3x/war3map.w3e differ diff --git a/core/test/resources/wowr_w3x/war3map.w3h b/core/test/resources/wowr_w3x/war3map.w3h new file mode 100644 index 000000000..8b1203094 Binary files /dev/null and b/core/test/resources/wowr_w3x/war3map.w3h differ diff --git a/core/test/resources/wowr_w3x/war3map.w3i b/core/test/resources/wowr_w3x/war3map.w3i new file mode 100644 index 000000000..28a306ee8 Binary files /dev/null and b/core/test/resources/wowr_w3x/war3map.w3i differ diff --git a/core/test/resources/wowr_w3x/war3map.w3q b/core/test/resources/wowr_w3x/war3map.w3q new file mode 100644 index 000000000..013c0c8e7 Binary files /dev/null and b/core/test/resources/wowr_w3x/war3map.w3q differ diff --git a/core/test/resources/wowr_w3x/war3map.w3t b/core/test/resources/wowr_w3x/war3map.w3t new file mode 100644 index 000000000..fa13b2fcd Binary files /dev/null and b/core/test/resources/wowr_w3x/war3map.w3t differ diff --git a/core/test/resources/wowr_w3x/war3map.w3u b/core/test/resources/wowr_w3x/war3map.w3u new file mode 100644 index 000000000..543924fd8 Binary files /dev/null and b/core/test/resources/wowr_w3x/war3map.w3u differ diff --git a/jassparser/src/com/etheller/interpreter/ast/debug/JassException.java b/jassparser/src/com/etheller/interpreter/ast/debug/JassException.java index e582c0916..81e16def9 100644 --- a/jassparser/src/com/etheller/interpreter/ast/debug/JassException.java +++ b/jassparser/src/com/etheller/interpreter/ast/debug/JassException.java @@ -10,7 +10,7 @@ public JassException(final GlobalScope globalScope, final String message, final super(message(globalScope, message), javaCause); } - private static String message(final GlobalScope globalScope, final String message) { + public static String message(final GlobalScope globalScope, final String message) { final List stackTrace = globalScope.copyJassStack(); final StringBuilder sb = new StringBuilder(message); for (final JassStackElement element : stackTrace) { diff --git a/jassparser/src/com/etheller/interpreter/ast/value/ArrayJassValue.java b/jassparser/src/com/etheller/interpreter/ast/value/ArrayJassValue.java index 7365c3757..458f2f612 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/ArrayJassValue.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/ArrayJassValue.java @@ -52,7 +52,7 @@ public void set(final GlobalScope globalScope, final int index, JassValue value) + (valueType == null ? "null" : valueType.getName())); } if (index >= this.data.length) { - throw new JassException(globalScope, "Max jass array size exceeded", + throw new JassException(globalScope, "Maximum JASS array size " + this.data.length + " exceeded with index " + index, new ArrayIndexOutOfBoundsException(index)); } this.data[index] = value;