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/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..2b4805dba 100644 --- a/core/src/com/etheller/warsmash/units/custom/War3ObjectDataChangeset.java +++ b/core/src/com/etheller/warsmash/units/custom/War3ObjectDataChangeset.java @@ -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,111 +507,118 @@ 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"); + setsCount = stream.readInt(); } - if (isOriginal) { - debugprint("StandardUnit \"" + origid + "\" " + ccount + " {"); - } - else { - debugprint("CustomUnit \"" + 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); + + for (int setIndex = 0; setIndex < setsCount; setIndex++) { + + if (this.version >= 3) { + final int setFlag = stream.readInt(); } - 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? + 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 + " {"); + } + 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: + ptr = 0; + 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!"); 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/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 307ab61de..13f77d823 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -175,7 +175,7 @@ public CSimulation(final War3MapConfig config, final int mapVersion, final DataT for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) { final CBasePlayer configPlayer = config.getPlayer(i); final War3MapConfigStartLoc startLoc = config.getStartLoc(configPlayer.getStartLocationIndex()); - CRace defaultRace = null; + CRace defaultRace = WarsmashConstants.RACE_MANAGER.getRace(1); // Make sure this is not null if nothing matching is found. if (configPlayer.isRacePrefSet(WarsmashConstants.RACE_MANAGER.getRandomRacePreference())) { final CRaceManagerEntry raceEntry = WarsmashConstants.RACE_MANAGER .get(seededRandom.nextInt(WarsmashConstants.RACE_MANAGER.getEntryCount())); diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxLayer.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxLayer.java index 0f54476a9..4bb9f5ef8 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) { @@ -80,7 +84,10 @@ public void readMdx(final BinaryReader reader, final int version) { final int position = reader.position(); final long size = reader.readUInt32(); - this.filterMode = FilterMode.fromId(reader.readInt32()); + final int filterModeIndex = reader.readInt32(); + if (filterModeIndex > 0 && filterModeIndex < FilterMode.values().length) { + this.filterMode = FilterMode.fromId(filterModeIndex); + } this.flags = reader.readInt32(); // UInt32 in JS this.textureId = reader.readInt32(); this.textureAnimationId = reader.readInt32(); 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..3b6eff7b7 --- /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 out = new LittleEndianDataOutputStream(new java.io.FileOutputStream(testFile))) { + mapInfo.save(out); + } + + War3MapW3e mapInfo2; + try (LittleEndianDataInputStream in = new LittleEndianDataInputStream(new java.io.FileInputStream(testFile))) { + mapInfo2 = new War3MapW3e(in); + } + + 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..82899a358 --- /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 out = new LittleEndianDataOutputStream(new java.io.FileOutputStream(testFile))) { + mapInfo.save(out); + } + + War3MapW3i mapInfo2; + try (LittleEndianDataInputStream in = new LittleEndianDataInputStream(new java.io.FileInputStream(testFile))) { + mapInfo2 = new War3MapW3i(in); + } + + 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.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.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