diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 71542ff..1363ca3 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -38,6 +38,7 @@ import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; +import com.etheller.warsmash.units.HashedGameObject; import com.etheller.warsmash.util.DataSourceFileHandle; import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.viewer5.CanvasProvider; @@ -141,7 +142,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv } final Element cameraData = this.viewer.miscData.get("Camera"); - final Element cameraListenerData = this.viewer.miscData.get("Listener"); + Element cameraListenerData = this.viewer.miscData.get("Listener"); + if(cameraListenerData==null) { + cameraListenerData = new Element("Listener", new DataTable(null)); + } final CameraPreset[] cameraPresets = new CameraPreset[6]; for (int i = 0; i < cameraPresets.length; i++) { cameraPresets[i] = new CameraPreset(cameraData.getFieldFloatValue("AOA", i), diff --git a/core/src/com/etheller/warsmash/util/Quadtree.java b/core/src/com/etheller/warsmash/util/Quadtree.java index 939a03f..9489fd8 100644 --- a/core/src/com/etheller/warsmash/util/Quadtree.java +++ b/core/src/com/etheller/warsmash/util/Quadtree.java @@ -75,6 +75,43 @@ public class Quadtree { } } + public boolean intersect(float x, float y, final QuadtreeIntersector intersector) { + if (this.leaf) { + for (int i = 0; i < this.nodes.size; i++) { + final Node node = this.nodes.get(i); + if (node.bounds.contains(x, y)) { + if (intersector.onIntersect(node.object)) { + return true; + } + } + } + return false; + } + else { + if (this.northeast.bounds.contains(x, y)) { + if (this.northeast.intersect(x, y, intersector)) { + return true; + } + } + if (this.northwest.bounds.contains(x, y)) { + if (this.northwest.intersect(x, y, intersector)) { + return true; + } + } + if (this.southwest.bounds.contains(x, y)) { + if (this.southwest.intersect(x, y, intersector)) { + return true; + } + } + if (this.southeast.bounds.contains(x, y)) { + if (this.southeast.intersect(x, y, intersector)) { + return true; + } + } + return false; + } + } + private void add(final Node node, final int depth) { if (this.leaf) { if ((this.nodes.size >= SPLIT_THRESHOLD) && (depth < MAX_DEPTH)) { diff --git a/core/src/com/etheller/warsmash/viewer5/Bounds.java b/core/src/com/etheller/warsmash/viewer5/Bounds.java index 82413c7..153d483 100644 --- a/core/src/com/etheller/warsmash/viewer5/Bounds.java +++ b/core/src/com/etheller/warsmash/viewer5/Bounds.java @@ -31,4 +31,8 @@ public class Bounds { public boolean intersectRayFast(final Ray ray) { return Intersector.intersectRayBoundsFast(ray, this.boundingBox); } + + public BoundingBox getBoundingBox() { + return boundingBox; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java index be44cf7..68d07a5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java @@ -22,343 +22,328 @@ import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.handlers.EmitterObject; public class EventObjectEmitterObject extends GenericObject implements EmitterObject { - private static final class LoadGenericSoundCallback implements LoadGenericCallback { - private final String filename; + private static final class LoadGenericSoundCallback implements LoadGenericCallback { + private final String filename; - public LoadGenericSoundCallback(final String filename) { - this.filename = filename; - } + public LoadGenericSoundCallback(final String filename) { + this.filename = filename; + } - @Override - public Object call(final InputStream data) { - final FileHandle temp = new FileHandle(this.filename) { - @Override - public InputStream read() { - return data; - }; - }; - if (data != null) { - return Gdx.audio.newSound(temp); - } - else { - System.err.println("Warning: missing sound file: " + this.filename); - return null; - } - } - } + @Override + public Object call(final InputStream data) { + final FileHandle temp = new FileHandle(this.filename) { + @Override + public InputStream read() { + return data; + } - private static final LoadGenericCallback mappedDataCallback = new LoadGenericCallback() { + ; + }; + if (data != null) { + return Gdx.audio.newSound(temp); + } else { + System.err.println("Warning: missing sound file: " + this.filename); + return null; + } + } + } - @Override - public Object call(final InputStream data) { - final StringBuilder stringBuilder = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { - String line; - while ((line = reader.readLine()) != null) { - stringBuilder.append(line); - stringBuilder.append("\n"); - } - } - catch (final UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - catch (final IOException e) { - throw new RuntimeException(e); - } - return new MappedData(stringBuilder.toString()); - } - }; + private static final LoadGenericCallback mappedDataCallback = new LoadGenericCallback() { - private int geometryEmitterType = -1; - public final String type; - private final String id; - public final long[] keyFrames; - private long globalSequence = -1; - private final long[] defval = { 1 }; - public MdxModel internalModel; - public Texture internalTexture; - public float[][] colors; - public float[] intervalTimes; - public float scale; - public int columns; - public int rows; - public float lifeSpan; - public int blendSrc; - public int blendDst; - public float[][] intervals; - public float distanceCutoff; - private float maxDistance; - public float minDistance; - private float pitch; - private float pitchVariance; - private float volume; - public List decodedBuffers = new ArrayList<>(); - /** - * If this is an SPL/UBR emitter object, ok will be set to true if the tables - * are loaded. - * - * This is because, like the other geometry emitters, it is fine to use them - * even if the textures don't load. - * - * The particles will simply be black. - */ - private boolean ok = false; + @Override + public Object call(final InputStream data) { + final StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append("\n"); + } + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } catch (final IOException e) { + throw new RuntimeException(e); + } + return new MappedData(stringBuilder.toString()); + } + }; - public EventObjectEmitterObject(final MdxModel model, - final com.etheller.warsmash.parsers.mdlx.EventObject eventObject, final int index) { - super(model, eventObject, index); + private int geometryEmitterType = -1; + public final String type; + private final String id; + public final long[] keyFrames; + private long globalSequence = -1; + private final long[] defval = {1}; + public MdxModel internalModel; + public Texture internalTexture; + public float[][] colors; + public float[] intervalTimes; + public float scale; + public int columns; + public int rows; + public float lifeSpan; + public int blendSrc; + public int blendDst; + public float[][] intervals; + public float distanceCutoff; + private float maxDistance; + public float minDistance; + private float pitch; + private float pitchVariance; + private float volume; + public List decodedBuffers = new ArrayList<>(); + /** + * If this is an SPL/UBR emitter object, ok will be set to true if the tables + * are loaded. + *

+ * This is because, like the other geometry emitters, it is fine to use them + * even if the textures don't load. + *

+ * The particles will simply be black. + */ + private boolean ok = false; - final ModelViewer viewer = model.viewer; - final String name = eventObject.getName(); - String type = name.substring(0, 3); - final String id = name.substring(4); + public EventObjectEmitterObject(final MdxModel model, + final com.etheller.warsmash.parsers.mdlx.EventObject eventObject, final int index) { + super(model, eventObject, index); - // Same thing - if ("FPT".equals(type)) { - type = "SPL"; - } + final ModelViewer viewer = model.viewer; + final String name = eventObject.getName(); + String type = name.substring(0, 3); + final String id = name.substring(4); - if ("SPL".equals(type)) { - this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_SPLAT; - } - else if ("UBR".equals(type)) { - this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_UBERSPLAT; - } - else if ("SPN".equals(type)) { - this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_SPN; - } + // Same thing + if ("FPT".equals(type)) { + type = "SPL"; + } - this.type = type; - this.id = id; - this.keyFrames = eventObject.getKeyFrames(); + if ("SPL".equals(type)) { + this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_SPLAT; + } else if ("UBR".equals(type)) { + this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_UBERSPLAT; + } else if ("SPN".equals(type)) { + this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_SPN; + } - final int globalSequenceId = eventObject.getGlobalSequenceId(); - if (globalSequenceId != -1) { - this.globalSequence = model.getGlobalSequences().get(globalSequenceId); - } + this.type = type; + this.id = id; + this.keyFrames = eventObject.getKeyFrames(); - final List tables = new ArrayList<>(); - final PathSolver pathSolver = model.pathSolver; - final Object solverParams = model.solverParams; + final int globalSequenceId = eventObject.getGlobalSequenceId(); + if (globalSequenceId != -1) { + this.globalSequence = model.getGlobalSequences().get(globalSequenceId); + } - if ("SPN".equals(type)) { - tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\SpawnData.slk", solverParams).finalSrc, - FetchDataTypeName.SLK, mappedDataCallback)); - } - else if ("SPL".equals(type)) { - tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\SplatData.slk", solverParams).finalSrc, - FetchDataTypeName.SLK, mappedDataCallback)); - } - else if ("UBR".equals(type)) { - tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\UberSplatData.slk", solverParams).finalSrc, - FetchDataTypeName.SLK, mappedDataCallback)); - } - else if ("SND".equals(type)) { - if (!model.reforged) { - tables.add(viewer.loadGeneric(pathSolver.solve("UI\\SoundInfo\\AnimLookups.slk", solverParams).finalSrc, - FetchDataTypeName.SLK, mappedDataCallback)); - } - tables.add(viewer.loadGeneric(pathSolver.solve("UI\\SoundInfo\\AnimSounds.slk", solverParams).finalSrc, - FetchDataTypeName.SLK, mappedDataCallback)); - } - else { - // Units\Critters\BlackStagMale\BlackStagMale.mdx has an event object named - // "Point01". - return; - } + final List tables = new ArrayList<>(); + final PathSolver pathSolver = model.pathSolver; + final Object solverParams = model.solverParams; - // TODO I am scrapping some async stuff with promises here from the JS and - // calling load - this.load(tables); - } + if ("SPN".equals(type)) { + tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\SpawnData.slk", solverParams).finalSrc, + FetchDataTypeName.SLK, mappedDataCallback)); + } else if ("SPL".equals(type)) { + tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\SplatData.slk", solverParams).finalSrc, + FetchDataTypeName.SLK, mappedDataCallback)); + } else if ("UBR".equals(type)) { + tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\UberSplatData.slk", solverParams).finalSrc, + FetchDataTypeName.SLK, mappedDataCallback)); + } else if ("SND".equals(type)) { + if (!model.reforged) { + tables.add(viewer.loadGeneric(pathSolver.solve("UI\\SoundInfo\\AnimLookups.slk", solverParams).finalSrc, + FetchDataTypeName.SLK, mappedDataCallback)); + } + tables.add(viewer.loadGeneric(pathSolver.solve("UI\\SoundInfo\\AnimSounds.slk", solverParams).finalSrc, + FetchDataTypeName.SLK, mappedDataCallback)); + } else { + // Units\Critters\BlackStagMale\BlackStagMale.mdx has an event object named + // "Point01". + return; + } - private float getFloat(final MappedDataRow row, final String name) { - final Float x = (Float) row.get(name); - if (x == null) { - return Float.NaN; - } - else { - return x.floatValue(); - } - } + // TODO I am scrapping some async stuff with promises here from the JS and + // calling load + this.load(tables); + } - private int getInt(final MappedDataRow row, final String name) { - return getInt(row, name, Integer.MIN_VALUE); - } + private float getFloat(final MappedDataRow row, final String name) { + final Float x = (Float) row.get(name); + if (x == null) { + return Float.NaN; + } else { + return x.floatValue(); + } + } - private int getInt(final MappedDataRow row, final String name, final int defaultValue) { - final Number x = (Number) row.get(name); - if (x == null) { - return defaultValue; - } - else { - return x.intValue(); - } - } + private int getInt(final MappedDataRow row, final String name) { + return getInt(row, name, Integer.MIN_VALUE); + } - private void load(final List tables) { - final MappedData firstTable = (MappedData) tables.get(0).data; - final MappedDataRow row = firstTable.getRow(this.id.trim()); + private int getInt(final MappedDataRow row, final String name, final int defaultValue) { + final Number x = (Number) row.get(name); + if (x == null) { + return defaultValue; + } else { + return x.intValue(); + } + } - if (row != null) { - final MdxModel model = this.model; - final ModelViewer viewer = model.viewer; - final PathSolver pathSolver = model.pathSolver; + private void load(final List tables) { + final MappedData firstTable = (MappedData) tables.get(0).data; + if (firstTable == null) { + return; + } + final MappedDataRow row = firstTable.getRow(this.id.trim()); - if ("SPN".equals(this.type)) { - this.internalModel = (MdxModel) viewer.load(((String) row.get("Model")).replace(".mdl", ".mdx"), - pathSolver, model.solverParams); + if (row != null) { + final MdxModel model = this.model; + final ModelViewer viewer = model.viewer; + final PathSolver pathSolver = model.pathSolver; - if (this.internalModel != null) { - // TODO javascript async code removed here + if ("SPN".equals(this.type)) { + this.internalModel = (MdxModel) viewer.load(((String) row.get("Model")).replace(".mdl", ".mdx"), + pathSolver, model.solverParams); + + if (this.internalModel != null) { + // TODO javascript async code removed here // this.internalModel.whenLoaded((model) => this.ok = model.ok) - this.ok = this.internalModel.ok; - } - } - else if ("SPL".equals(this.type) || "UBR".equals(this.type)) { - final String texturesExt = model.reforged ? ".dds" : ".blp"; + this.ok = this.internalModel.ok; + } + } else if ("SPL".equals(this.type) || "UBR".equals(this.type)) { + final String texturesExt = model.reforged ? ".dds" : ".blp"; - this.internalTexture = (Texture) viewer.load( - "ReplaceableTextures\\Splats\\" + row.get("file") + texturesExt, pathSolver, - model.solverParams); + this.internalTexture = (Texture) viewer.load( + "ReplaceableTextures\\Splats\\" + row.get("file") + texturesExt, pathSolver, + model.solverParams); - this.scale = getFloat(row, "Scale"); - this.colors = new float[][] { - { getFloat(row, "StartR"), getFloat(row, "StartG"), getFloat(row, "StartB"), - getFloat(row, "StartA") }, - { getFloat(row, "MiddleR"), getFloat(row, "MiddleG"), getFloat(row, "MiddleB"), - getFloat(row, "MiddleA") }, - { getFloat(row, "EndR"), getFloat(row, "EndG"), getFloat(row, "EndB"), - getFloat(row, "EndA") } }; + this.scale = getFloat(row, "Scale"); + this.colors = new float[][]{ + {getFloat(row, "StartR"), getFloat(row, "StartG"), getFloat(row, "StartB"), + getFloat(row, "StartA")}, + {getFloat(row, "MiddleR"), getFloat(row, "MiddleG"), getFloat(row, "MiddleB"), + getFloat(row, "MiddleA")}, + {getFloat(row, "EndR"), getFloat(row, "EndG"), getFloat(row, "EndB"), + getFloat(row, "EndA")}}; - if ("SPL".equals(this.type)) { - this.columns = getInt(row, "Columns"); - this.rows = getInt(row, "Rows"); - this.lifeSpan = getFloat(row, "Lifespan") + getFloat(row, "Decay"); - this.intervalTimes = new float[] { getFloat(row, "Lifespan"), getFloat(row, "Decay") }; - this.intervals = new float[][] { - { getFloat(row, "UVLifespanStart"), getFloat(row, "UVLifespanEnd"), - getFloat(row, "LifespanRepeat") }, - { getFloat(row, "UVDecayStart"), getFloat(row, "UVDecayEnd"), - getFloat(row, "DecayRepeat") }, }; - } - else { - this.columns = 1; - this.rows = 1; - this.lifeSpan = getFloat(row, "BirthTime") + getFloat(row, "PauseTime") + getFloat(row, "Decay"); - this.intervalTimes = new float[] { getFloat(row, "BirthTime"), getFloat(row, "PauseTime"), - getFloat(row, "Decay") }; - } + if ("SPL".equals(this.type)) { + this.columns = getInt(row, "Columns"); + this.rows = getInt(row, "Rows"); + this.lifeSpan = getFloat(row, "Lifespan") + getFloat(row, "Decay"); + this.intervalTimes = new float[]{getFloat(row, "Lifespan"), getFloat(row, "Decay")}; + this.intervals = new float[][]{ + {getFloat(row, "UVLifespanStart"), getFloat(row, "UVLifespanEnd"), + getFloat(row, "LifespanRepeat")}, + {getFloat(row, "UVDecayStart"), getFloat(row, "UVDecayEnd"), + getFloat(row, "DecayRepeat")},}; + } else { + this.columns = 1; + this.rows = 1; + this.lifeSpan = getFloat(row, "BirthTime") + getFloat(row, "PauseTime") + getFloat(row, "Decay"); + this.intervalTimes = new float[]{getFloat(row, "BirthTime"), getFloat(row, "PauseTime"), + getFloat(row, "Decay")}; + } - final int[] blendModes = FilterMode - .emitterFilterMode(com.etheller.warsmash.parsers.mdlx.ParticleEmitter2.FilterMode - .fromId(getInt(row, "BlendMode"))); + final int[] blendModes = FilterMode + .emitterFilterMode(com.etheller.warsmash.parsers.mdlx.ParticleEmitter2.FilterMode + .fromId(getInt(row, "BlendMode"))); - this.blendSrc = blendModes[0]; - this.blendDst = blendModes[1]; + this.blendSrc = blendModes[0]; + this.blendDst = blendModes[1]; - this.ok = true; - } - else if ("SND".equals(this.type)) { - // Only load sounds if audio is enabled. - // This is mostly to save on bandwidth and loading time, especially when loading - // full maps. - if (viewer.audioEnabled) { - final MappedData animSounds = (MappedData) tables.get(1).data; + this.ok = true; + } else if ("SND".equals(this.type)) { + // Only load sounds if audio is enabled. + // This is mostly to save on bandwidth and loading time, especially when loading + // full maps. + if (viewer.audioEnabled) { + final MappedData animSounds = (MappedData) tables.get(1).data; - final MappedDataRow animSoundsRow = animSounds.getRow((String) row.get("SoundLabel")); + final MappedDataRow animSoundsRow = animSounds.getRow((String) row.get("SoundLabel")); - if (animSoundsRow != null) { - this.distanceCutoff = getFloat(animSoundsRow, "DistanceCutoff"); - this.maxDistance = getFloat(animSoundsRow, "MaxDistance"); - this.minDistance = getFloat(animSoundsRow, "MinDistance"); - this.pitch = getFloat(animSoundsRow, "Pitch"); - this.pitchVariance = getFloat(animSoundsRow, "PitchVariance"); - this.volume = getFloat(animSoundsRow, "Volume"); + if (animSoundsRow != null) { + this.distanceCutoff = getFloat(animSoundsRow, "DistanceCutoff"); + this.maxDistance = getFloat(animSoundsRow, "MaxDistance"); + this.minDistance = getFloat(animSoundsRow, "MinDistance"); + this.pitch = getFloat(animSoundsRow, "Pitch"); + this.pitchVariance = getFloat(animSoundsRow, "PitchVariance"); + this.volume = getFloat(animSoundsRow, "Volume"); - final String[] fileNames = ((String) animSoundsRow.get("FileNames")).split(","); - final GenericResource[] resources = new GenericResource[fileNames.length]; - for (int i = 0; i < fileNames.length; i++) { - final String path = ((String) animSoundsRow.get("DirectoryBase")) + fileNames[i]; - try { - final String pathString = pathSolver.solve(path, model.solverParams).finalSrc; - final GenericResource genericResource = viewer.loadGeneric(pathString, - FetchDataTypeName.ARRAY_BUFFER, new LoadGenericSoundCallback(pathString)); - if (genericResource == null) { - System.err.println("Null sound: " + fileNames[i]); - } - resources[i] = genericResource; - } - catch (final Exception exc) { - System.err.println("Failed to load sound: " + path); - exc.printStackTrace(); - } - } + final String[] fileNames = ((String) animSoundsRow.get("FileNames")).split(","); + final GenericResource[] resources = new GenericResource[fileNames.length]; + for (int i = 0; i < fileNames.length; i++) { + final String path = ((String) animSoundsRow.get("DirectoryBase")) + fileNames[i]; + try { + final String pathString = pathSolver.solve(path, model.solverParams).finalSrc; + final GenericResource genericResource = viewer.loadGeneric(pathString, + FetchDataTypeName.ARRAY_BUFFER, new LoadGenericSoundCallback(pathString)); + if (genericResource == null) { + System.err.println("Null sound: " + fileNames[i]); + } + resources[i] = genericResource; + } catch (final Exception exc) { + System.err.println("Failed to load sound: " + path); + exc.printStackTrace(); + } + } - // TODO JS async removed - for (final GenericResource resource : resources) { - if (resource != null) { - this.decodedBuffers.add((Sound) resource.data); - } - } - this.ok = true; - } - } - } - else { - System.err.println("Unknown event object type: " + this.type + this.id); - } - } - else { - System.err.println("Unknown event object ID: " + this.type + this.id); - } - } + // TODO JS async removed + for (final GenericResource resource : resources) { + if (resource != null) { + this.decodedBuffers.add((Sound) resource.data); + } + } + this.ok = true; + } + } + } else { + System.err.println("Unknown event object type: " + this.type + this.id); + } + } else { + System.err.println("Unknown event object ID: " + this.type + this.id); + } + } - public int getValue(final long[] out, final MdxComplexInstance instance) { - if (this.globalSequence != -1) { + public int getValue(final long[] out, final MdxComplexInstance instance) { + if (this.globalSequence != -1) { - return this.getValueAtTime(out, instance.counter % this.globalSequence, 0, this.globalSequence); - } - else if (instance.sequence != -1) { - final long[] interval = this.model.getSequences().get(instance.sequence).getInterval(); + return this.getValueAtTime(out, instance.counter % this.globalSequence, 0, this.globalSequence); + } else if (instance.sequence != -1) { + final long[] interval = this.model.getSequences().get(instance.sequence).getInterval(); - return this.getValueAtTime(out, instance.frame, interval[0], interval[1]); - } - else { - out[0] = this.defval[0]; + return this.getValueAtTime(out, instance.frame, interval[0], interval[1]); + } else { + out[0] = this.defval[0]; - return -1; - } - } + return -1; + } + } - public int getValueAtTime(final long[] out, final long frame, final long start, final long end) { - if ((frame >= start) && (frame <= end)) { - for (int i = this.keyFrames.length - 1; i > -1; i--) { - if (this.keyFrames[i] < start) { - out[0] = 0; + public int getValueAtTime(final long[] out, final long frame, final long start, final long end) { + if ((frame >= start) && (frame <= end)) { + for (int i = this.keyFrames.length - 1; i > -1; i--) { + if (this.keyFrames[i] < start) { + out[0] = 0; - return i; - } - else if (this.keyFrames[i] <= frame) { - out[0] = 1; + return i; + } else if (this.keyFrames[i] <= frame) { + out[0] = 1; - return i; - } - } - } + return i; + } + } + } - out[0] = 0; + out[0] = 0; - return -1; - } + return -1; + } - @Override - public boolean ok() { - return this.ok; - } + @Override + public boolean ok() { + return this.ok; + } - @Override - public int getGeometryEmitterType() { - return this.geometryEmitterType; - } + @Override + public int getGeometryEmitterType() { + return this.geometryEmitterType; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index b9e3b8f..093433a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -26,756 +26,744 @@ import com.etheller.warsmash.viewer5.gl.DataTexture; import com.etheller.warsmash.viewer5.handlers.w3x.DynamicShadowManager; public class MdxComplexInstance extends ModelInstance { - private static final float[] visibilityHeap = new float[1]; - private static final float[] translationHeap = new float[3]; - private static final float[] rotationHeap = new float[4]; - private static final float[] scaleHeap = new float[3]; - private static final float[] colorHeap = new float[3]; - private static final float[] alphaHeap = new float[1]; - private static final long[] textureIdHeap = new long[1]; - - public List lights = new ArrayList<>(); - public List attachments = new ArrayList<>(); - public List particleEmitters = new ArrayList<>(); - public List particleEmitters2 = new ArrayList<>(); - public List ribbonEmitters = new ArrayList<>(); - public List> eventObjectEmitters = new ArrayList<>(); - public MdxNode[] nodes; - public SkeletalNode[] sortedNodes; - public int frame = 0; - public float floatingFrame = 0; - // Global sequences - public int counter = 0; - public int sequence = -1; - public SequenceLoopMode sequenceLoopMode = SequenceLoopMode.NEVER_LOOP; - public boolean sequenceEnded = false; - public float[] vertexColor = { 1, 1, 1, 1 }; - // Particles do not spawn when the sequence is -1, or when the sequence finished - // and it's not repeating - public boolean allowParticleSpawn = false; - // If forced is true, everything will update regardless of variancy. - // Any later non-forced update can then use variancy to skip updating things. - // It is set to true every time the sequence is set with setSequence(). - public boolean forced = true; - public float[][] geosetColors; - public float[] layerAlphas; - public int[] layerTextures; - public float[][] uvAnims; - public Matrix4[] worldMatrices; - public FloatBuffer worldMatricesCopyHeap; - public DataTexture boneTexture; - public Texture[] replaceableTextures = new Texture[WarsmashConstants.REPLACEABLE_TEXTURE_LIMIT]; - private float animationSpeed = 1.0f; - - public MdxComplexInstance(final MdxModel model) { - super(model); - } - - @Override - public void load() { - final MdxModel model = (MdxModel) this.model; - - this.geosetColors = new float[model.geosets.size()][]; - for (int i = 0, l = model.geosets.size(); i < l; i++) { - this.geosetColors[i] = new float[4]; - } - - this.layerAlphas = new float[model.layers.size()]; - this.layerTextures = new int[model.layers.size()]; - this.uvAnims = new float[model.layers.size()][]; - for (int i = 0, l = model.layers.size(); i < l; i++) { - this.layerAlphas[i] = 0; - this.layerTextures[i] = 0; - this.uvAnims[i] = new float[5]; - } - - // Create the needed amount of shared nodes. - final Object[] sharedNodeData = Node.createSkeletalNodes(model.genericObjects.size(), - MdxNodeDescriptor.INSTANCE); - final List nodes = (List) sharedNodeData[0]; - int nodeIndex = 0; - this.nodes = nodes.toArray(new MdxNode[nodes.size()]); - - // A shared typed array for all world matrices of the internal nodes. - this.worldMatrices = ((List) sharedNodeData[1]).toArray(new Matrix4[0]); - this.worldMatricesCopyHeap = ByteBuffer.allocateDirect(16 * this.worldMatrices.length * 4) - .order(ByteOrder.nativeOrder()).asFloatBuffer(); - - // And now initialize all of the nodes and objects - for (final Bone bone : model.bones) { - this.initNode(this.nodes, this.nodes[nodeIndex++], bone); - } - - for (final Light light : model.lights) { - final LightInstance lightInstance = new LightInstance(this, light); - this.lights.add(lightInstance); - this.initNode(this.nodes, this.nodes[nodeIndex++], light, lightInstance); - } - - for (final Helper helper : model.helpers) { - this.initNode(this.nodes, this.nodes[nodeIndex++], helper); - } - - for (final Attachment attachment : model.attachments) { - AttachmentInstance attachmentInstance = null; - - // Attachments may have game models attached to them, such as Undead and - // Nightelf building animations. - if (attachment.internalModel != null) { - attachmentInstance = new AttachmentInstance(this, attachment); - - this.attachments.add(attachmentInstance); - } - - this.initNode(this.nodes, this.nodes[nodeIndex++], attachment, attachmentInstance); - } - - for (final ParticleEmitterObject emitterObject : model.particleEmitters) { - final ParticleEmitter emitter = new ParticleEmitter(this, emitterObject); - - this.particleEmitters.add(emitter); - - this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); - } - - for (final ParticleEmitter2Object emitterObject : model.particleEmitters2) { - final ParticleEmitter2 emitter = new ParticleEmitter2(this, emitterObject); - - this.particleEmitters2.add(emitter); - - this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); - } - - for (final RibbonEmitterObject emitterObject : model.ribbonEmitters) { - final RibbonEmitter emitter = new RibbonEmitter(this, emitterObject); - - this.ribbonEmitters.add(emitter); - - this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); - } - - for (final EventObjectEmitterObject emitterObject : model.eventObjects) { - final String type = emitterObject.type; - EventObjectEmitter emitter; - - if ("SPN".equals(type)) { - emitter = new EventObjectSpnEmitter(this, emitterObject); - } - else if ("SPL".equals(type)) { - emitter = new EventObjectSplEmitter(this, emitterObject); - } - else if ("UBR".equals(type)) { - emitter = new EventObjectUbrEmitter(this, emitterObject); - } - else { - emitter = new EventObjectSndEmitter(this, emitterObject); - } - - this.eventObjectEmitters.add(emitter); - - this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); - } - - for (final CollisionShape collisionShape : model.collisionShapes) { - this.initNode(this.nodes, this.nodes[nodeIndex++], collisionShape); - } - - // Save a sorted array of all of the nodes, such that every child node comes - // after its parent. - // This allows for flat iteration when updating. - final List hierarchy = model.hierarchy; - - this.sortedNodes = new SkeletalNode[nodes.size()]; - for (int i = 0, l = nodes.size(); i < l; i++) { - this.sortedNodes[i] = this.nodes[hierarchy.get(i)]; - } - - // If the sequence was changed before the model was loaded, reset it now that - // the model loaded. - this.setSequence(this.sequence); - - if (model.bones.size() != 0) { - this.boneTexture = new DataTexture(model.viewer.gl, 4, model.bones.size() * 4, 1); - } - } - - /* - * Clear all of the emitted objects that belong to this instance. - */ - @Override - public void clearEmittedObjects() { - for (final ParticleEmitter emitter : this.particleEmitters) { - emitter.clear(); - } - - for (final ParticleEmitter2 emitter : this.particleEmitters2) { - emitter.clear(); - } - - for (final RibbonEmitter emitter : this.ribbonEmitters) { - emitter.clear(); - } - - for (final EventObjectEmitter emitter : this.eventObjectEmitters) { - emitter.clear(); - } - } - - private void initNode(final MdxNode[] nodes, final SkeletalNode node, final GenericObject genericObject) { - initNode(nodes, node, genericObject, null); - } - - /** - * Initialize a skeletal node. - */ - private void initNode(final MdxNode[] nodes, final SkeletalNode node, final GenericObject genericObject, - final UpdatableObject object) { - node.pivot.set(genericObject.pivot); - - if (genericObject.parentId == -1) { - node.parent = this; - } - else { - node.parent = nodes[genericObject.parentId]; - } - - /// TODO: single-axis billboarding - if (genericObject.billboarded != 0) { - node.billboarded = true; - } - else if (genericObject.billboardedX != 0) { - node.billboardedX = true; - } - else if (genericObject.billboardedY != 0) { - node.billboardedY = true; - } - else if (genericObject.billboardedZ != 0) { - node.billboardedZ = true; - } - - if (object != null) { - node.object = object; - } - - } - - /* - * Overriden to hide also attachment models. - */ - @Override - public void hide() { - super.hide(); - - for (final AttachmentInstance attachment : this.attachments) { - attachment.internalInstance.hide(); - } - } - - /** - * Updates all of this instance internal nodes and objects. Nodes that are - * determined to not be visible will not be updated, nor will any of their - * children down the hierarchy. - */ - public void updateNodes(final float dt, final boolean forced) { - if (!this.model.ok) { - return; - } - final int sequence = this.sequence; - final int frame = this.frame; - final int counter = this.counter; - final SkeletalNode[] sortedNodes = this.sortedNodes; - final MdxModel model = (MdxModel) this.model; - final List sortedGenericObjects = model.sortedGenericObjects; - final Scene scene = this.scene; - - // Update the nodes - for (int i = 0, l = sortedNodes.length; i < l; i++) { - final GenericObject genericObject = sortedGenericObjects.get(i); - final SkeletalNode node = sortedNodes[i]; - final GenericNode parent = node.parent; - - genericObject.getVisibility(visibilityHeap, sequence, frame, counter); - - final boolean objectVisible = visibilityHeap[0] > 0; - final boolean nodeVisible = forced || (parent.visible && objectVisible); - - node.visible = nodeVisible; - - // Every node only needs to be updated if this is a forced update, or if both - // the parent node and the generic object corresponding to this node are - // visible. - // Incoming messy code for optimizations! - if (nodeVisible) { - boolean wasDirty = false; - final GenericObject.Variants variants = genericObject.variants; - final Vector3 localLocation = node.localLocation; - final Quaternion localRotation = node.localRotation; - final Vector3 localScale = node.localScale; - - // Only update the local node data if there is a need to - if (forced || variants.generic[sequence]) { - wasDirty = true; - - // Translation - if (forced || variants.translation[sequence]) { - genericObject.getTranslation(translationHeap, sequence, frame, counter); - - localLocation.x = translationHeap[0]; - localLocation.y = translationHeap[1]; - localLocation.z = translationHeap[2]; - } - - // Rotation - if (forced || variants.rotation[sequence]) { - genericObject.getRotation(rotationHeap, sequence, frame, counter); - - localRotation.x = rotationHeap[0]; - localRotation.y = rotationHeap[1]; - localRotation.z = rotationHeap[2]; - localRotation.w = rotationHeap[3]; - } - - // Scale - if (forced || variants.scale[sequence]) { - genericObject.getScale(scaleHeap, sequence, frame, counter); - - localScale.x = scaleHeap[0]; - localScale.y = scaleHeap[1]; - localScale.z = scaleHeap[2]; - } - } - - final boolean wasReallyDirty = forced || wasDirty || parent.wasDirty || genericObject.anyBillboarding; - - node.wasDirty = wasReallyDirty; - - // If this is a forced update, or this node's local data was updated, or the - // parent node was updated, do a full world update. - if (wasReallyDirty) { - node.recalculateTransformation(scene); - } - - // If there is an instance object associated with this node, and the node is - // visible (which might not be the case for a forced update!), update the - // object. - // This includes attachments and emitters. - final UpdatableObject object = node.object; - - if (object != null) { - object.update(dt, objectVisible); - } - - // Update all of the node's non-skeletal children, which will update their - // children, and so on. - node.updateChildren(dt, scene); - } - } - } - - /** - * Update the batch data. - */ - public void updateBatches(final boolean forced) { - final int sequence = this.sequence; - final int frame = this.frame; - final int counter = this.counter; - final MdxModel model = (MdxModel) this.model; - if (!model.ok) { - return; - } - final List geosets = model.geosets; - final List layers = model.layers; - final float[][] geosetColors = this.geosetColors; - final float[] layerAlphas = this.layerAlphas; - final int[] layerTextures = this.layerTextures; - final float[][] uvAnims = this.uvAnims; - - // Geoset - for (int i = 0, l = geosets.size(); i < l; i++) { - final Geoset geoset = geosets.get(i); - final GeosetAnimation geosetAnimation = geoset.geosetAnimation; - final float[] geosetColor = geosetColors[i]; - - if (geosetAnimation != null) { - // Color - if (forced || (geosetAnimation.variants.get("color")[sequence] != 0)) { - geosetAnimation.getColor(colorHeap, sequence, frame, counter); - - geosetColor[0] = colorHeap[0]; - geosetColor[1] = colorHeap[1]; - geosetColor[2] = colorHeap[2]; - } - - // Alpha - if (forced || (geosetAnimation.variants.get("alpha")[sequence] != 0)) { - geosetAnimation.getAlpha(alphaHeap, sequence, frame, counter); - - geosetColor[3] = alphaHeap[0]; - } - } - else if (forced) { - geosetColor[0] = 1; - geosetColor[1] = 1; - geosetColor[2] = 1; - geosetColor[3] = 1; - } - } - - // Layers - for (int i = 0, l = layers.size(); i < l; i++) { - final Layer layer = layers.get(i); - final TextureAnimation textureAnimation = layer.textureAnimation; - final float[] uvAnim = uvAnims[i]; - - // Alpha - if (forced || (layer.variants.get("alpha")[sequence] != 0)) { - layer.getAlpha(alphaHeap, sequence, frame, counter); - - layerAlphas[i] = alphaHeap[0]; - } - - // Sprite animation - if (forced || (layer.variants.get("textureId")[sequence] != 0)) { - layer.getTextureId(textureIdHeap, sequence, frame, counter); - - layerTextures[i] = (int) textureIdHeap[0]; - } - - if (textureAnimation != null) { - // UV translation animation - if (forced || (textureAnimation.variants.get("translation")[sequence] != 0)) { - textureAnimation.getTranslation(translationHeap, sequence, frame, counter); - - uvAnim[0] = translationHeap[0]; - uvAnim[1] = translationHeap[1]; - } - - // UV rotation animation - if (forced || (textureAnimation.variants.get("rotation")[sequence] != 0)) { - textureAnimation.getRotation(rotationHeap, sequence, frame, counter); - - uvAnim[2] = rotationHeap[2]; - uvAnim[3] = rotationHeap[3]; - } - - // UV scale animation - if (forced || (textureAnimation.variants.get("scale")[sequence] != 0)) { - textureAnimation.getScale(scaleHeap, sequence, frame, counter); - - uvAnim[4] = scaleHeap[0]; - } - } - else if (forced) { - uvAnim[0] = 0; - uvAnim[1] = 0; - uvAnim[2] = 0; - uvAnim[3] = 1; - uvAnim[4] = 1; - } - } - } - - public void updateBoneTexture() { - if (this.boneTexture != null) { - this.worldMatricesCopyHeap.clear(); - for (int i = 0, l = this.worldMatrices.length; i < l; i++) { - final Matrix4 worldMatrix = this.worldMatrices[i]; - this.worldMatricesCopyHeap.put((i * 16) + 0, worldMatrix.val[Matrix4.M00]); - this.worldMatricesCopyHeap.put((i * 16) + 1, worldMatrix.val[Matrix4.M10]); - this.worldMatricesCopyHeap.put((i * 16) + 2, worldMatrix.val[Matrix4.M20]); - this.worldMatricesCopyHeap.put((i * 16) + 3, worldMatrix.val[Matrix4.M30]); - this.worldMatricesCopyHeap.put((i * 16) + 4, worldMatrix.val[Matrix4.M01]); - this.worldMatricesCopyHeap.put((i * 16) + 5, worldMatrix.val[Matrix4.M11]); - this.worldMatricesCopyHeap.put((i * 16) + 6, worldMatrix.val[Matrix4.M21]); - this.worldMatricesCopyHeap.put((i * 16) + 7, worldMatrix.val[Matrix4.M31]); - this.worldMatricesCopyHeap.put((i * 16) + 8, worldMatrix.val[Matrix4.M02]); - this.worldMatricesCopyHeap.put((i * 16) + 9, worldMatrix.val[Matrix4.M12]); - this.worldMatricesCopyHeap.put((i * 16) + 10, worldMatrix.val[Matrix4.M22]); - this.worldMatricesCopyHeap.put((i * 16) + 11, worldMatrix.val[Matrix4.M32]); - this.worldMatricesCopyHeap.put((i * 16) + 12, worldMatrix.val[Matrix4.M03]); - this.worldMatricesCopyHeap.put((i * 16) + 13, worldMatrix.val[Matrix4.M13]); - this.worldMatricesCopyHeap.put((i * 16) + 14, worldMatrix.val[Matrix4.M23]); - this.worldMatricesCopyHeap.put((i * 16) + 15, worldMatrix.val[Matrix4.M33]); - } - this.boneTexture.bindAndUpdate(this.worldMatricesCopyHeap); - } - } - - @Override - public void renderOpaque(final Matrix4 mvp) { - final MdxModel model = (MdxModel) this.model; - - for (final GenericGroup group : model.opaqueGroups) { - group.render(this, mvp); - } - } - - @Override - public void renderTranslucent() { - if (DynamicShadowManager.IS_SHADOW_MAPPING) { - return; - } - final MdxModel model = (MdxModel) this.model; - - for (final GenericGroup group : model.translucentGroups) { - group.render(this, this.scene.camera.viewProjectionMatrix); - } - } - - @Override - public void updateAnimations(final float dt) { - final MdxModel model = (MdxModel) this.model; - final int sequenceId = this.sequence; - - if (sequenceId != -1) { - final Sequence sequence = model.sequences.get(sequenceId); - final long[] interval = sequence.getInterval(); - final float frameTime = (dt * 1000 * this.animationSpeed); - - final int lastIntegerFrame = this.frame; - this.floatingFrame += frameTime; - this.frame = (int) this.floatingFrame; - final int integerFrameTime = this.frame - lastIntegerFrame; - this.counter += integerFrameTime; - this.allowParticleSpawn = true; - - if (this.floatingFrame >= interval[1]) { - if ((this.sequenceLoopMode == SequenceLoopMode.ALWAYS_LOOP) - || ((this.sequenceLoopMode == SequenceLoopMode.MODEL_LOOP) && (sequence.getFlags() == 0))) { - this.floatingFrame = this.frame = (int) interval[0]; // TODO not cast - - this.resetEventEmitters(); - } - else if (this.sequenceLoopMode == SequenceLoopMode.LOOP_TO_NEXT_ANIMATION) { // faux queued animation - // mode - final float framesPast = this.floatingFrame - interval[1]; - - final List sequences = model.sequences; - this.sequence = (this.sequence + 1) % sequences.size(); - this.floatingFrame = sequences.get(this.sequence).getInterval()[0] + framesPast; // TODO not cast - this.frame = (int) this.floatingFrame; - this.sequenceEnded = false; - this.resetEventEmitters(); - this.forced = true; - } - else { - this.floatingFrame = this.frame = (int) interval[1]; // TODO not cast - this.counter -= integerFrameTime; - this.allowParticleSpawn = false; - } - if (this.sequenceLoopMode == SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE) { - hide(); - } - - this.sequenceEnded = true; - } - else { - this.sequenceEnded = false; - } - } - - final boolean forced = this.forced; - - if (sequenceId == -1) { - if (forced) { - // Update the nodes - this.updateNodes(dt, forced); - - this.updateBoneTexture(); - - // Update the batches - this.updateBatches(forced); - } - } - else { - // let variants = model.variants; - - // if (forced || variants.nodes[sequenceId]) { - // Update the nodes - this.updateNodes(dt, forced); - - this.updateBoneTexture(); - // } - - // if (forced || variants.batches[sequenceId]) { - // Update the batches - this.updateBatches(forced); - // } - } - - this.forced = false; - - } - - @Override - protected void updateLights(final Scene scene) { - for (final LightInstance light : this.lights) { - light.update(scene); - } - } - - @Override - protected void removeLights(final Scene scene2) { - for (final LightInstance light : this.lights) { - light.remove(this.scene); - } - } - - /** - * Set the team color of this instance. - */ - public MdxComplexInstance setTeamColor(final int id) { - this.replaceableTextures[1] = (Texture) this.model.viewer.load( - "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(id) + ".blp", - PathSolver.DEFAULT, null); - this.replaceableTextures[2] = (Texture) this.model.viewer.load( - "ReplaceableTextures\\" + ReplaceableIds.getPathString(2) + ReplaceableIds.getIdString(id) + ".blp", - PathSolver.DEFAULT, null); - return this; - } - - @Override - public void setReplaceableTexture(final int replaceableTextureId, final String replaceableTextureFile) { - this.replaceableTextures[replaceableTextureId] = (Texture) this.model.viewer.load(replaceableTextureFile, - PathSolver.DEFAULT, null); - } - - /** - * Set the vertex color of this instance. - */ - public MdxComplexInstance setVertexColor(final float[] color) { - System.arraycopy(color, 0, this.vertexColor, 0, color.length); - - return this; - } - - /** - * Set the sequence of this instance. - */ - public MdxComplexInstance setSequence(final int id) { - final MdxModel model = (MdxModel) this.model; - - this.sequence = id; - - if (model.ok) { - final List sequences = model.sequences; - - if ((id < 0) || (id > (sequences.size() - 1))) { - this.sequence = -1; - this.frame = 0; - this.floatingFrame = 0; - this.allowParticleSpawn = false; - } - else { - this.frame = (int) sequences.get(id).getInterval()[0]; // TODO not cast - this.floatingFrame = this.frame; - this.sequenceEnded = false; - } - - this.resetEventEmitters(); - - this.forced = true; - } - - return this; - } - - /** - * Set the seuqnece loop mode. 0 to never loop, 1 to loop based on the model, - * and 2 to always loop. 3 was added by Retera as "hide after done" for gameplay - * spawned effects - */ - public MdxComplexInstance setSequenceLoopMode(final SequenceLoopMode mode) { - this.sequenceLoopMode = mode; - - return this; - } - - /** - * Get an attachment node. - */ - public MdxNode getAttachment(final int id) { - final MdxModel model = (MdxModel) this.model; - final Attachment attachment = model.attachments.get(id); - - if (attachment != null) { - return this.nodes[attachment.index]; - } - - return null; - } - - /** - * Event emitters depend on keyframe index changes to emit, rather than only - * values. To work, they need to check what the last keyframe was, and only if - * it's a different one, do something. When changing sequences, these states - * need to be reset, so they can immediately emit things if needed. - */ - private void resetEventEmitters() { - /// TODO: Update this. Said Ghostwolf. - for (final EventObjectEmitter eventObjectEmitter : this.eventObjectEmitters) { - eventObjectEmitter.reset(); - } - } - - @Override - protected RenderBatch getBatch(final TextureMapper textureMapper2) { - throw new UnsupportedOperationException("NOT API"); - } - - public void intersectRayBounds(final Ray ray, final Vector3 intersection) { - this.model.bounds.intersectRay(ray, intersection); - } - - /** - * Intersects a world ray with the model's CollisionShapes. Only ever call this - * function on the Gdx thread because it uses static variables to hold state - * while processing. - * - * @param ray - */ - public boolean intersectRayWithCollision(final Ray ray, final Vector3 intersection, final boolean alwaysUseMesh) { - final MdxModel mdxModel = (MdxModel) this.model; - final List collisionShapes = mdxModel.collisionShapes; - for (final CollisionShape collisionShape : collisionShapes) { - final MdxNode mdxNode = this.nodes[collisionShape.index]; - if (collisionShape.checkIntersect(ray, mdxNode, intersection)) { - return true; - } - } - if (collisionShapes.isEmpty() || alwaysUseMesh) { - for (final Geoset geoset : mdxModel.geosets) { - if (!geoset.unselectable) { - geoset.getAlpha(alphaHeap, this.sequence, this.frame, this.counter); - if (alphaHeap[0] > 0) { - final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset = geoset.mdlxGeoset; - if (CollisionShape.intersectRayTriangles(ray, this, mdlxGeoset.getVertices(), - mdlxGeoset.getFaces(), 3, intersection)) { - return true; - } - } - } - } - } - return false; - } - - public void setAnimationSpeed(final float speedRatio) { - this.animationSpeed = speedRatio; - } - - public void setFrame(final int frame) { - this.frame = frame; - this.floatingFrame = frame; - } - - public void setFrameByRatio(final float ratioOfAnimationCompleted) { - if (this.sequence != -1) { - final Sequence currentlyPlayingSequence = ((MdxModel) this.model).sequences.get(this.sequence); - this.floatingFrame = currentlyPlayingSequence.getInterval()[0] - + ((currentlyPlayingSequence.getInterval()[1] - currentlyPlayingSequence.getInterval()[0]) - * ratioOfAnimationCompleted); - this.frame = (int) this.floatingFrame; - } - } + private static final float[] visibilityHeap = new float[1]; + private static final float[] translationHeap = new float[3]; + private static final float[] rotationHeap = new float[4]; + private static final float[] scaleHeap = new float[3]; + private static final float[] colorHeap = new float[3]; + private static final float[] alphaHeap = new float[1]; + private static final long[] textureIdHeap = new long[1]; + + public List lights = new ArrayList<>(); + public List attachments = new ArrayList<>(); + public List particleEmitters = new ArrayList<>(); + public List particleEmitters2 = new ArrayList<>(); + public List ribbonEmitters = new ArrayList<>(); + public List> eventObjectEmitters = new ArrayList<>(); + public MdxNode[] nodes; + public SkeletalNode[] sortedNodes; + public int frame = 0; + public float floatingFrame = 0; + // Global sequences + public int counter = 0; + public int sequence = -1; + public SequenceLoopMode sequenceLoopMode = SequenceLoopMode.NEVER_LOOP; + public boolean sequenceEnded = false; + public float[] vertexColor = {1, 1, 1, 1}; + // Particles do not spawn when the sequence is -1, or when the sequence finished + // and it's not repeating + public boolean allowParticleSpawn = false; + // If forced is true, everything will update regardless of variancy. + // Any later non-forced update can then use variancy to skip updating things. + // It is set to true every time the sequence is set with setSequence(). + public boolean forced = true; + public float[][] geosetColors; + public float[] layerAlphas; + public int[] layerTextures; + public float[][] uvAnims; + public Matrix4[] worldMatrices; + public FloatBuffer worldMatricesCopyHeap; + public DataTexture boneTexture; + public Texture[] replaceableTextures = new Texture[WarsmashConstants.REPLACEABLE_TEXTURE_LIMIT]; + private float animationSpeed = 1.0f; + + public MdxComplexInstance(final MdxModel model) { + super(model); + } + + @Override + public void load() { + final MdxModel model = (MdxModel) this.model; + + this.geosetColors = new float[model.geosets.size()][]; + for (int i = 0, l = model.geosets.size(); i < l; i++) { + this.geosetColors[i] = new float[4]; + } + + this.layerAlphas = new float[model.layers.size()]; + this.layerTextures = new int[model.layers.size()]; + this.uvAnims = new float[model.layers.size()][]; + for (int i = 0, l = model.layers.size(); i < l; i++) { + this.layerAlphas[i] = 0; + this.layerTextures[i] = 0; + this.uvAnims[i] = new float[5]; + } + + // Create the needed amount of shared nodes. + final Object[] sharedNodeData = Node.createSkeletalNodes(model.genericObjects.size(), + MdxNodeDescriptor.INSTANCE); + final List nodes = (List) sharedNodeData[0]; + int nodeIndex = 0; + this.nodes = nodes.toArray(new MdxNode[nodes.size()]); + + // A shared typed array for all world matrices of the internal nodes. + this.worldMatrices = ((List) sharedNodeData[1]).toArray(new Matrix4[0]); + this.worldMatricesCopyHeap = ByteBuffer.allocateDirect(16 * this.worldMatrices.length * 4) + .order(ByteOrder.nativeOrder()).asFloatBuffer(); + + // And now initialize all of the nodes and objects + for (final Bone bone : model.bones) { + this.initNode(this.nodes, this.nodes[nodeIndex++], bone); + } + + for (final Light light : model.lights) { + final LightInstance lightInstance = new LightInstance(this, light); + this.lights.add(lightInstance); + this.initNode(this.nodes, this.nodes[nodeIndex++], light, lightInstance); + } + + for (final Helper helper : model.helpers) { + this.initNode(this.nodes, this.nodes[nodeIndex++], helper); + } + + for (final Attachment attachment : model.attachments) { + AttachmentInstance attachmentInstance = null; + + // Attachments may have game models attached to them, such as Undead and + // Nightelf building animations. + if (attachment.internalModel != null) { + attachmentInstance = new AttachmentInstance(this, attachment); + + this.attachments.add(attachmentInstance); + } + + this.initNode(this.nodes, this.nodes[nodeIndex++], attachment, attachmentInstance); + } + + for (final ParticleEmitterObject emitterObject : model.particleEmitters) { + final ParticleEmitter emitter = new ParticleEmitter(this, emitterObject); + + this.particleEmitters.add(emitter); + + this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); + } + + for (final ParticleEmitter2Object emitterObject : model.particleEmitters2) { + final ParticleEmitter2 emitter = new ParticleEmitter2(this, emitterObject); + + this.particleEmitters2.add(emitter); + + this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); + } + + for (final RibbonEmitterObject emitterObject : model.ribbonEmitters) { + final RibbonEmitter emitter = new RibbonEmitter(this, emitterObject); + + this.ribbonEmitters.add(emitter); + + this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); + } + + for (final EventObjectEmitterObject emitterObject : model.eventObjects) { + final String type = emitterObject.type; + EventObjectEmitter emitter; + + if ("SPN".equals(type)) { + emitter = new EventObjectSpnEmitter(this, emitterObject); + } else if ("SPL".equals(type)) { + emitter = new EventObjectSplEmitter(this, emitterObject); + } else if ("UBR".equals(type)) { + emitter = new EventObjectUbrEmitter(this, emitterObject); + } else { + emitter = new EventObjectSndEmitter(this, emitterObject); + } + + this.eventObjectEmitters.add(emitter); + + this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); + } + + for (final CollisionShape collisionShape : model.collisionShapes) { + this.initNode(this.nodes, this.nodes[nodeIndex++], collisionShape); + } + + // Save a sorted array of all of the nodes, such that every child node comes + // after its parent. + // This allows for flat iteration when updating. + final List hierarchy = model.hierarchy; + + this.sortedNodes = new SkeletalNode[nodes.size()]; + for (int i = 0, l = nodes.size(); i < l; i++) { + this.sortedNodes[i] = this.nodes[hierarchy.get(i)]; + } + + // If the sequence was changed before the model was loaded, reset it now that + // the model loaded. + this.setSequence(this.sequence); + + if (model.bones.size() != 0) { + this.boneTexture = new DataTexture(model.viewer.gl, 4, model.bones.size() * 4, 1); + } + } + + /* + * Clear all of the emitted objects that belong to this instance. + */ + @Override + public void clearEmittedObjects() { + for (final ParticleEmitter emitter : this.particleEmitters) { + emitter.clear(); + } + + for (final ParticleEmitter2 emitter : this.particleEmitters2) { + emitter.clear(); + } + + for (final RibbonEmitter emitter : this.ribbonEmitters) { + emitter.clear(); + } + + for (final EventObjectEmitter emitter : this.eventObjectEmitters) { + emitter.clear(); + } + } + + private void initNode(final MdxNode[] nodes, final SkeletalNode node, final GenericObject genericObject) { + initNode(nodes, node, genericObject, null); + } + + /** + * Initialize a skeletal node. + */ + private void initNode(final MdxNode[] nodes, final SkeletalNode node, final GenericObject genericObject, + final UpdatableObject object) { + node.pivot.set(genericObject.pivot); + + if (genericObject.parentId == -1) { + node.parent = this; + } else { + node.parent = nodes[genericObject.parentId]; + } + + /// TODO: single-axis billboarding + if (genericObject.billboarded != 0) { + node.billboarded = true; + } else if (genericObject.billboardedX != 0) { + node.billboardedX = true; + } else if (genericObject.billboardedY != 0) { + node.billboardedY = true; + } else if (genericObject.billboardedZ != 0) { + node.billboardedZ = true; + } + + if (object != null) { + node.object = object; + } + + } + + /* + * Overriden to hide also attachment models. + */ + @Override + public void hide() { + super.hide(); + + for (final AttachmentInstance attachment : this.attachments) { + attachment.internalInstance.hide(); + } + } + + /** + * Updates all of this instance internal nodes and objects. Nodes that are + * determined to not be visible will not be updated, nor will any of their + * children down the hierarchy. + */ + public void updateNodes(final float dt, final boolean forced) { + if (!this.model.ok) { + return; + } + final int sequence = this.sequence; + final int frame = this.frame; + final int counter = this.counter; + final SkeletalNode[] sortedNodes = this.sortedNodes; + final MdxModel model = (MdxModel) this.model; + final List sortedGenericObjects = model.sortedGenericObjects; + final Scene scene = this.scene; + + // Update the nodes + for (int i = 0, l = sortedNodes.length; i < l; i++) { + final GenericObject genericObject = sortedGenericObjects.get(i); + final SkeletalNode node = sortedNodes[i]; + final GenericNode parent = node.parent; + + genericObject.getVisibility(visibilityHeap, sequence, frame, counter); + + final boolean objectVisible = visibilityHeap[0] > 0; + final boolean nodeVisible = forced || (parent.visible && objectVisible); + + node.visible = nodeVisible; + + // Every node only needs to be updated if this is a forced update, or if both + // the parent node and the generic object corresponding to this node are + // visible. + // Incoming messy code for optimizations! + if (nodeVisible) { + boolean wasDirty = false; + final GenericObject.Variants variants = genericObject.variants; + final Vector3 localLocation = node.localLocation; + final Quaternion localRotation = node.localRotation; + final Vector3 localScale = node.localScale; + + // Only update the local node data if there is a need to + if (forced || variants.generic[sequence]) { + wasDirty = true; + + // Translation + if (forced || variants.translation[sequence]) { + genericObject.getTranslation(translationHeap, sequence, frame, counter); + + localLocation.x = translationHeap[0]; + localLocation.y = translationHeap[1]; + localLocation.z = translationHeap[2]; + } + + // Rotation + if (forced || variants.rotation[sequence]) { + genericObject.getRotation(rotationHeap, sequence, frame, counter); + + localRotation.x = rotationHeap[0]; + localRotation.y = rotationHeap[1]; + localRotation.z = rotationHeap[2]; + localRotation.w = rotationHeap[3]; + } + + // Scale + if (forced || variants.scale[sequence]) { + genericObject.getScale(scaleHeap, sequence, frame, counter); + + localScale.x = scaleHeap[0]; + localScale.y = scaleHeap[1]; + localScale.z = scaleHeap[2]; + } + } + + final boolean wasReallyDirty = forced || wasDirty || parent.wasDirty || genericObject.anyBillboarding; + + node.wasDirty = wasReallyDirty; + + // If this is a forced update, or this node's local data was updated, or the + // parent node was updated, do a full world update. + if (wasReallyDirty) { + node.recalculateTransformation(scene); + } + + // If there is an instance object associated with this node, and the node is + // visible (which might not be the case for a forced update!), update the + // object. + // This includes attachments and emitters. + final UpdatableObject object = node.object; + + if (object != null) { + object.update(dt, objectVisible); + } + + // Update all of the node's non-skeletal children, which will update their + // children, and so on. + node.updateChildren(dt, scene); + } + } + } + + /** + * Update the batch data. + */ + public void updateBatches(final boolean forced) { + final int sequence = this.sequence; + final int frame = this.frame; + final int counter = this.counter; + final MdxModel model = (MdxModel) this.model; + if (!model.ok) { + return; + } + final List geosets = model.geosets; + final List layers = model.layers; + final float[][] geosetColors = this.geosetColors; + final float[] layerAlphas = this.layerAlphas; + final int[] layerTextures = this.layerTextures; + final float[][] uvAnims = this.uvAnims; + + // Geoset + for (int i = 0, l = geosets.size(); i < l; i++) { + final Geoset geoset = geosets.get(i); + final GeosetAnimation geosetAnimation = geoset.geosetAnimation; + final float[] geosetColor = geosetColors[i]; + + if (geosetAnimation != null) { + // Color + if (forced || (geosetAnimation.variants.get("color")[sequence] != 0)) { + geosetAnimation.getColor(colorHeap, sequence, frame, counter); + + geosetColor[0] = colorHeap[0]; + geosetColor[1] = colorHeap[1]; + geosetColor[2] = colorHeap[2]; + } + + // Alpha + if (forced || (geosetAnimation.variants.get("alpha")[sequence] != 0)) { + geosetAnimation.getAlpha(alphaHeap, sequence, frame, counter); + + geosetColor[3] = alphaHeap[0]; + } + } else if (forced) { + geosetColor[0] = 1; + geosetColor[1] = 1; + geosetColor[2] = 1; + geosetColor[3] = 1; + } + } + + // Layers + for (int i = 0, l = layers.size(); i < l; i++) { + final Layer layer = layers.get(i); + final TextureAnimation textureAnimation = layer.textureAnimation; + final float[] uvAnim = uvAnims[i]; + + // Alpha + if (forced || (layer.variants.get("alpha")[sequence] != 0)) { + layer.getAlpha(alphaHeap, sequence, frame, counter); + + layerAlphas[i] = alphaHeap[0]; + } + + // Sprite animation + if (forced || (layer.variants.get("textureId")[sequence] != 0)) { + layer.getTextureId(textureIdHeap, sequence, frame, counter); + + layerTextures[i] = (int) textureIdHeap[0]; + } + + if (textureAnimation != null) { + // UV translation animation + if (forced || (textureAnimation.variants.get("translation")[sequence] != 0)) { + textureAnimation.getTranslation(translationHeap, sequence, frame, counter); + + uvAnim[0] = translationHeap[0]; + uvAnim[1] = translationHeap[1]; + } + + // UV rotation animation + if (forced || (textureAnimation.variants.get("rotation")[sequence] != 0)) { + textureAnimation.getRotation(rotationHeap, sequence, frame, counter); + + uvAnim[2] = rotationHeap[2]; + uvAnim[3] = rotationHeap[3]; + } + + // UV scale animation + if (forced || (textureAnimation.variants.get("scale")[sequence] != 0)) { + textureAnimation.getScale(scaleHeap, sequence, frame, counter); + + uvAnim[4] = scaleHeap[0]; + } + } else if (forced) { + uvAnim[0] = 0; + uvAnim[1] = 0; + uvAnim[2] = 0; + uvAnim[3] = 1; + uvAnim[4] = 1; + } + } + } + + public void updateBoneTexture() { + if (this.boneTexture != null) { + this.worldMatricesCopyHeap.clear(); + for (int i = 0, l = this.worldMatrices.length; i < l; i++) { + final Matrix4 worldMatrix = this.worldMatrices[i]; + this.worldMatricesCopyHeap.put((i * 16) + 0, worldMatrix.val[Matrix4.M00]); + this.worldMatricesCopyHeap.put((i * 16) + 1, worldMatrix.val[Matrix4.M10]); + this.worldMatricesCopyHeap.put((i * 16) + 2, worldMatrix.val[Matrix4.M20]); + this.worldMatricesCopyHeap.put((i * 16) + 3, worldMatrix.val[Matrix4.M30]); + this.worldMatricesCopyHeap.put((i * 16) + 4, worldMatrix.val[Matrix4.M01]); + this.worldMatricesCopyHeap.put((i * 16) + 5, worldMatrix.val[Matrix4.M11]); + this.worldMatricesCopyHeap.put((i * 16) + 6, worldMatrix.val[Matrix4.M21]); + this.worldMatricesCopyHeap.put((i * 16) + 7, worldMatrix.val[Matrix4.M31]); + this.worldMatricesCopyHeap.put((i * 16) + 8, worldMatrix.val[Matrix4.M02]); + this.worldMatricesCopyHeap.put((i * 16) + 9, worldMatrix.val[Matrix4.M12]); + this.worldMatricesCopyHeap.put((i * 16) + 10, worldMatrix.val[Matrix4.M22]); + this.worldMatricesCopyHeap.put((i * 16) + 11, worldMatrix.val[Matrix4.M32]); + this.worldMatricesCopyHeap.put((i * 16) + 12, worldMatrix.val[Matrix4.M03]); + this.worldMatricesCopyHeap.put((i * 16) + 13, worldMatrix.val[Matrix4.M13]); + this.worldMatricesCopyHeap.put((i * 16) + 14, worldMatrix.val[Matrix4.M23]); + this.worldMatricesCopyHeap.put((i * 16) + 15, worldMatrix.val[Matrix4.M33]); + } + this.boneTexture.bindAndUpdate(this.worldMatricesCopyHeap); + } + } + + @Override + public void renderOpaque(final Matrix4 mvp) { + final MdxModel model = (MdxModel) this.model; + + for (final GenericGroup group : model.opaqueGroups) { + group.render(this, mvp); + } + } + + @Override + public void renderTranslucent() { + if (DynamicShadowManager.IS_SHADOW_MAPPING) { + return; + } + final MdxModel model = (MdxModel) this.model; + + for (final GenericGroup group : model.translucentGroups) { + group.render(this, this.scene.camera.viewProjectionMatrix); + } + } + + @Override + public void updateAnimations(final float dt) { + final MdxModel model = (MdxModel) this.model; + final int sequenceId = this.sequence; + + if (sequenceId != -1) { + final Sequence sequence = model.sequences.get(sequenceId); + final long[] interval = sequence.getInterval(); + final float frameTime = (dt * 1000 * this.animationSpeed); + + final int lastIntegerFrame = this.frame; + this.floatingFrame += frameTime; + this.frame = (int) this.floatingFrame; + final int integerFrameTime = this.frame - lastIntegerFrame; + this.counter += integerFrameTime; + this.allowParticleSpawn = true; + + if (this.floatingFrame >= interval[1]) { + if ((this.sequenceLoopMode == SequenceLoopMode.ALWAYS_LOOP) + || ((this.sequenceLoopMode == SequenceLoopMode.MODEL_LOOP) && (sequence.getFlags() == 0))) { + this.floatingFrame = this.frame = (int) interval[0]; // TODO not cast + + this.resetEventEmitters(); + } else if (this.sequenceLoopMode == SequenceLoopMode.LOOP_TO_NEXT_ANIMATION) { // faux queued animation + // mode + final float framesPast = this.floatingFrame - interval[1]; + + final List sequences = model.sequences; + this.sequence = (this.sequence + 1) % sequences.size(); + this.floatingFrame = sequences.get(this.sequence).getInterval()[0] + framesPast; // TODO not cast + this.frame = (int) this.floatingFrame; + this.sequenceEnded = false; + this.resetEventEmitters(); + this.forced = true; + } else { + this.floatingFrame = this.frame = (int) interval[1]; // TODO not cast + this.counter -= integerFrameTime; + this.allowParticleSpawn = false; + } + if (this.sequenceLoopMode == SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE) { + hide(); + } + + this.sequenceEnded = true; + } else { + this.sequenceEnded = false; + } + } + + final boolean forced = this.forced; + + if (sequenceId == -1) { + if (forced) { + // Update the nodes + this.updateNodes(dt, forced); + + this.updateBoneTexture(); + + // Update the batches + this.updateBatches(forced); + } + } else { + // let variants = model.variants; + + // if (forced || variants.nodes[sequenceId]) { + // Update the nodes + this.updateNodes(dt, forced); + + this.updateBoneTexture(); + // } + + // if (forced || variants.batches[sequenceId]) { + // Update the batches + this.updateBatches(forced); + // } + } + + this.forced = false; + + } + + @Override + protected void updateLights(final Scene scene) { + for (final LightInstance light : this.lights) { + light.update(scene); + } + } + + @Override + protected void removeLights(final Scene scene2) { + for (final LightInstance light : this.lights) { + light.remove(this.scene); + } + } + + /** + * Set the team color of this instance. + */ + public MdxComplexInstance setTeamColor(final int id) { + this.replaceableTextures[1] = (Texture) this.model.viewer.load( + "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(id) + ".blp", + PathSolver.DEFAULT, null); + this.replaceableTextures[2] = (Texture) this.model.viewer.load( + "ReplaceableTextures\\" + ReplaceableIds.getPathString(2) + ReplaceableIds.getIdString(id) + ".blp", + PathSolver.DEFAULT, null); + return this; + } + + @Override + public void setReplaceableTexture(final int replaceableTextureId, final String replaceableTextureFile) { + this.replaceableTextures[replaceableTextureId] = (Texture) this.model.viewer.load(replaceableTextureFile, + PathSolver.DEFAULT, null); + } + + /** + * Set the vertex color of this instance. + */ + public MdxComplexInstance setVertexColor(final float[] color) { + System.arraycopy(color, 0, this.vertexColor, 0, color.length); + + return this; + } + + /** + * Set the sequence of this instance. + */ + public MdxComplexInstance setSequence(final int id) { + final MdxModel model = (MdxModel) this.model; + + this.sequence = id; + + if (model.ok) { + final List sequences = model.sequences; + + if ((id < 0) || (id > (sequences.size() - 1))) { + this.sequence = -1; + this.frame = 0; + this.floatingFrame = 0; + this.allowParticleSpawn = false; + } else { + this.frame = (int) sequences.get(id).getInterval()[0]; // TODO not cast + this.floatingFrame = this.frame; + this.sequenceEnded = false; + } + + this.resetEventEmitters(); + + this.forced = true; + } + + return this; + } + + /** + * Set the seuqnece loop mode. 0 to never loop, 1 to loop based on the model, + * and 2 to always loop. 3 was added by Retera as "hide after done" for gameplay + * spawned effects + */ + public MdxComplexInstance setSequenceLoopMode(final SequenceLoopMode mode) { + this.sequenceLoopMode = mode; + + return this; + } + + /** + * Get an attachment node. + */ + public MdxNode getAttachment(final int id) { + final MdxModel model = (MdxModel) this.model; + final Attachment attachment = model.attachments.get(id); + + if (attachment != null) { + return this.nodes[attachment.index]; + } + + return null; + } + + /** + * Event emitters depend on keyframe index changes to emit, rather than only + * values. To work, they need to check what the last keyframe was, and only if + * it's a different one, do something. When changing sequences, these states + * need to be reset, so they can immediately emit things if needed. + */ + private void resetEventEmitters() { + /// TODO: Update this. Said Ghostwolf. + for (final EventObjectEmitter eventObjectEmitter : this.eventObjectEmitters) { + eventObjectEmitter.reset(); + } + } + + @Override + protected RenderBatch getBatch(final TextureMapper textureMapper2) { + throw new UnsupportedOperationException("NOT API"); + } + + public void intersectRayBounds(final Ray ray, final Vector3 intersection) { + this.model.bounds.intersectRay(ray, intersection); + } + + /** + * Intersects a world ray with the model's CollisionShapes. Only ever call this + * function on the Gdx thread because it uses static variables to hold state + * while processing. + * + * @param ray + */ + public boolean intersectRayWithCollision(final Ray ray, final Vector3 intersection, final boolean alwaysUseMesh, final boolean onlyUseMesh) { + final MdxModel mdxModel = (MdxModel) this.model; + final List collisionShapes = mdxModel.collisionShapes; + if (!onlyUseMesh) { + for (final CollisionShape collisionShape : collisionShapes) { + final MdxNode mdxNode = this.nodes[collisionShape.index]; + if (collisionShape.checkIntersect(ray, mdxNode, intersection)) { + return true; + } + } + } + if (collisionShapes.isEmpty() || alwaysUseMesh) { + for (final Geoset geoset : mdxModel.geosets) { + if (!geoset.unselectable) { + geoset.getAlpha(alphaHeap, this.sequence, this.frame, this.counter); + if (alphaHeap[0] > 0) { + final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset = geoset.mdlxGeoset; + if (CollisionShape.intersectRayTriangles(ray, this, mdlxGeoset.getVertices(), + mdlxGeoset.getFaces(), 3, intersection)) { + return true; + } + } + } + } + } + return false; + } + + public void setAnimationSpeed(final float speedRatio) { + this.animationSpeed = speedRatio; + } + + public void setFrame(final int frame) { + this.frame = frame; + this.floatingFrame = frame; + } + + public void setFrameByRatio(final float ratioOfAnimationCompleted) { + if (this.sequence != -1) { + final Sequence currentlyPlayingSequence = ((MdxModel) this.model).sequences.get(this.sequence); + this.floatingFrame = currentlyPlayingSequence.getInterval()[0] + + ((currentlyPlayingSequence.getInterval()[1] - currentlyPlayingSequence.getInterval()[0]) + * ratioOfAnimationCompleted); + this.frame = (int) this.floatingFrame; + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java index 921a24b..e2b7ee0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java @@ -32,14 +32,14 @@ public class MdxHandler extends ModelHandler { Shaders.extendedShadowMap = viewer.webGL.createShaderProgram( "#define EXTENDED_BONES\r\n" + MdxShaders.vsComplex, MdxShaders.fsComplexShadowMap); Shaders.particles = viewer.webGL.createShaderProgram(MdxShaders.vsParticles, MdxShaders.fsParticles); - Shaders.simple = viewer.webGL.createShaderProgram(MdxShaders.vsSimple, MdxShaders.fsSimple); + //Shaders.simple = viewer.webGL.createShaderProgram(MdxShaders.vsSimple, MdxShaders.fsSimple); // Shaders.hd = viewer.webGL.createShaderProgram(MdxShaders.vsHd, MdxShaders.fsHd); // TODO HD reforged // If a shader failed to compile, don't allow the handler to be registered, and // send an error instead. return Shaders.complex.isCompiled() && Shaders.extended.isCompiled() && Shaders.particles.isCompiled() - && Shaders.simple.isCompiled() /* && Shaders.hd.isCompiled() */; + /* && Shaders.simple.isCompiled() && Shaders.hd.isCompiled() */; } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java index 58c91da..06517db 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java @@ -152,6 +152,9 @@ public final class SdSequence { } private TYPE[] fixTimelineArray(final Timeline timeline, final TYPE[] values) { + if(values == null) { + return null; + } if (timeline.getName().equals(AnimationMap.KLAC.getWar3id()) || timeline.getName().equals(AnimationMap.KLBC.getWar3id())) { final float[][] flippedColorData = new float[values.length][3]; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDestructable.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDestructable.java index 5a55972..ed8b672 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDestructable.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDestructable.java @@ -2,25 +2,38 @@ package com.etheller.warsmash.viewer5.handlers.w3x; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; +import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; public class RenderDestructable extends RenderDoodad { + private static final War3ID TEX_FILE = War3ID.fromString("btxf"); + private static final War3ID TEX_ID = War3ID.fromString("btxi"); - private final float life; + private final float life; - public RenderDestructable(final War3MapViewer map, final MdxModel model, final MutableGameObject row, - final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type, - final float maxPitch, final float maxRoll, final float life) { - super(map, model, row, doodad, type, maxPitch, maxRoll); - this.life = life; - } + public RenderDestructable(final War3MapViewer map, final MdxModel model, final MutableGameObject row, + final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type, + final float maxPitch, final float maxRoll, final float life) { + super(map, model, row, doodad, type, maxPitch, maxRoll); + this.life = life; + String replaceableTextureFile = row.getFieldAsString(TEX_FILE, 0); + final int replaceableTextureId = row.getFieldAsInteger(TEX_ID, 0); + if ((replaceableTextureFile != null) && (replaceableTextureFile.length() > 1)) { + int dotIndex = replaceableTextureFile.lastIndexOf('.'); + if (dotIndex != -1) { + replaceableTextureFile = replaceableTextureFile.substring(0, dotIndex); + } + replaceableTextureFile += ".blp"; + instance.setReplaceableTexture(replaceableTextureId, replaceableTextureFile); + } + } - @Override - public PrimaryTag getAnimation() { - if (this.life <= 0) { - return PrimaryTag.DEATH; - } - return super.getAnimation(); - } + @Override + public PrimaryTag getAnimation() { + if (this.life <= 0) { + return PrimaryTag.DEATH; + } + return super.getAnimation(); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDoodad.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDoodad.java index 59a3e72..504e9fc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDoodad.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDoodad.java @@ -12,86 +12,72 @@ import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; public class RenderDoodad { - private static final int SAMPLE_RADIUS = 4; - private static final War3ID TEX_FILE = War3ID.fromString("btxf"); - private static final War3ID TEX_ID = War3ID.fromString("btxi"); - public final ModelInstance instance; - private final MutableGameObject row; - private final float maxPitch; - private final float maxRoll; + private static final int SAMPLE_RADIUS = 4; + public final ModelInstance instance; + private final MutableGameObject row; + private final float maxPitch; + private final float maxRoll; - public RenderDoodad(final War3MapViewer map, final MdxModel model, final MutableGameObject row, - final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type, - final float maxPitch, final float maxRoll) { - this.maxPitch = maxPitch; - this.maxRoll = maxRoll; - final boolean isSimple = row.readSLKTagBoolean("lightweight"); - ModelInstance instance; + public RenderDoodad(final War3MapViewer map, final MdxModel model, final MutableGameObject row, + final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type, + final float maxPitch, final float maxRoll) { + this.maxPitch = maxPitch; + this.maxRoll = maxRoll; + final boolean isSimple = row.readSLKTagBoolean("lightweight"); + ModelInstance instance; - if (isSimple && false) { - instance = model.addInstance(1); - } - else { - instance = model.addInstance(); - ((MdxComplexInstance) instance).setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); - } + if (isSimple && false) { + instance = model.addInstance(1); + } else { + instance = model.addInstance(); + ((MdxComplexInstance) instance).setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); + } - instance.move(doodad.getLocation()); - // TODO: the following pitch/roll system is a heuristic, and we probably want to - // revisit it later. - // Specifically, I was pretty convinced that whichever is applied first - // (pitch/roll) should be used to do a projection onto the already-tilted plane - // to find the angle used for the other of the two - // (instead of measuring down from an imaginary flat ground plane, as we do - // currently). - final float facingRadians = doodad.getAngle(); - float pitch, roll; - final float x = doodad.getLocation()[0]; - final float y = doodad.getLocation()[1]; - final float pitchSampleForwardX = x + (SAMPLE_RADIUS * (float) Math.cos(facingRadians)); - final float pitchSampleForwardY = y + (SAMPLE_RADIUS * (float) Math.sin(facingRadians)); - final float pitchSampleBackwardX = x - (SAMPLE_RADIUS * (float) Math.cos(facingRadians)); - final float pitchSampleBackwardY = y - (SAMPLE_RADIUS * (float) Math.sin(facingRadians)); - final float pitchSampleGroundHeight1 = map.terrain.getGroundHeight(pitchSampleBackwardX, pitchSampleBackwardY); - final float pitchSampleGorundHeight2 = map.terrain.getGroundHeight(pitchSampleForwardX, pitchSampleForwardY); - pitch = Math.max(-maxPitch, Math.min(maxPitch, - (float) Math.atan2(pitchSampleGorundHeight2 - pitchSampleGroundHeight1, SAMPLE_RADIUS * 2))); - final double leftOfFacingAngle = facingRadians + (Math.PI / 2); - final float rollSampleForwardX = x + (SAMPLE_RADIUS * (float) Math.cos(leftOfFacingAngle)); - final float rollSampleForwardY = y + (SAMPLE_RADIUS * (float) Math.sin(leftOfFacingAngle)); - final float rollSampleBackwardX = x - (SAMPLE_RADIUS * (float) Math.cos(leftOfFacingAngle)); - final float rollSampleBackwardY = y - (SAMPLE_RADIUS * (float) Math.sin(leftOfFacingAngle)); - final float rollSampleGroundHeight1 = map.terrain.getGroundHeight(rollSampleBackwardX, rollSampleBackwardY); - final float rollSampleGroundHeight2 = map.terrain.getGroundHeight(rollSampleForwardX, rollSampleForwardY); - roll = Math.max(-maxRoll, Math.min(maxRoll, - (float) Math.atan2(rollSampleGroundHeight2 - rollSampleGroundHeight1, SAMPLE_RADIUS * 2))); - instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, facingRadians)); - instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Y, -pitch)); - instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_X, roll)); + instance.move(doodad.getLocation()); + // TODO: the following pitch/roll system is a heuristic, and we probably want to + // revisit it later. + // Specifically, I was pretty convinced that whichever is applied first + // (pitch/roll) should be used to do a projection onto the already-tilted plane + // to find the angle used for the other of the two + // (instead of measuring down from an imaginary flat ground plane, as we do + // currently). + final float facingRadians = doodad.getAngle(); + float pitch, roll; + final float x = doodad.getLocation()[0]; + final float y = doodad.getLocation()[1]; + final float pitchSampleForwardX = x + (SAMPLE_RADIUS * (float) Math.cos(facingRadians)); + final float pitchSampleForwardY = y + (SAMPLE_RADIUS * (float) Math.sin(facingRadians)); + final float pitchSampleBackwardX = x - (SAMPLE_RADIUS * (float) Math.cos(facingRadians)); + final float pitchSampleBackwardY = y - (SAMPLE_RADIUS * (float) Math.sin(facingRadians)); + final float pitchSampleGroundHeight1 = map.terrain.getGroundHeight(pitchSampleBackwardX, pitchSampleBackwardY); + final float pitchSampleGorundHeight2 = map.terrain.getGroundHeight(pitchSampleForwardX, pitchSampleForwardY); + pitch = Math.max(-maxPitch, Math.min(maxPitch, + (float) Math.atan2(pitchSampleGorundHeight2 - pitchSampleGroundHeight1, SAMPLE_RADIUS * 2))); + final double leftOfFacingAngle = facingRadians + (Math.PI / 2); + final float rollSampleForwardX = x + (SAMPLE_RADIUS * (float) Math.cos(leftOfFacingAngle)); + final float rollSampleForwardY = y + (SAMPLE_RADIUS * (float) Math.sin(leftOfFacingAngle)); + final float rollSampleBackwardX = x - (SAMPLE_RADIUS * (float) Math.cos(leftOfFacingAngle)); + final float rollSampleBackwardY = y - (SAMPLE_RADIUS * (float) Math.sin(leftOfFacingAngle)); + final float rollSampleGroundHeight1 = map.terrain.getGroundHeight(rollSampleBackwardX, rollSampleBackwardY); + final float rollSampleGroundHeight2 = map.terrain.getGroundHeight(rollSampleForwardX, rollSampleForwardY); + roll = Math.max(-maxRoll, Math.min(maxRoll, + (float) Math.atan2(rollSampleGroundHeight2 - rollSampleGroundHeight1, SAMPLE_RADIUS * 2))); + instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, facingRadians)); + instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Y, -pitch)); + instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_X, roll)); // instance.rotate(new Quaternion().setEulerAnglesRad(facingRadians, 0, 0)); - instance.scale(doodad.getScale()); - if (type == WorldEditorDataType.DOODADS) { - final float defScale = row.readSLKTagFloat("defScale"); - instance.uniformScale(defScale); - } - if (type == WorldEditorDataType.DESTRUCTIBLES) { - // TODO destructables need to be their own type, game simulation, etc - String replaceableTextureFile = row.getFieldAsString(TEX_FILE, 0); - final int replaceableTextureId = row.getFieldAsInteger(TEX_ID, 0); - if ((replaceableTextureFile != null) && (replaceableTextureFile.length() > 1)) { - if (!replaceableTextureFile.toLowerCase().endsWith(".blp")) { - replaceableTextureFile += ".blp"; - } - instance.setReplaceableTexture(replaceableTextureId, replaceableTextureFile); - } - } - instance.setScene(map.worldScene); + instance.scale(doodad.getScale()); + if (type == WorldEditorDataType.DOODADS) { + final float defScale = row.readSLKTagFloat("defScale"); + instance.uniformScale(defScale); + } + instance.setScene(map.worldScene); - this.instance = instance; - this.row = row; - } + this.instance = instance; + this.row = row; + } - public PrimaryTag getAnimation() { - return PrimaryTag.STAND; - } + public PrimaryTag getAnimation() { + return PrimaryTag.STAND; + } } 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 1a797b9..4397358 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -19,6 +19,8 @@ import java.util.function.Consumer; import javax.imageio.ImageIO; +import com.badlogic.gdx.math.collision.BoundingBox; +import com.etheller.warsmash.util.*; import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; @@ -47,11 +49,6 @@ import com.etheller.warsmash.units.StandardObjectData; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; -import com.etheller.warsmash.util.MappedData; -import com.etheller.warsmash.util.RenderMathUtils; -import com.etheller.warsmash.util.War3ID; -import com.etheller.warsmash.util.WarsmashConstants; -import com.etheller.warsmash.util.WorldEditStrings; import com.etheller.warsmash.viewer5.CanvasProvider; import com.etheller.warsmash.viewer5.GenericResource; import com.etheller.warsmash.viewer5.Grid; @@ -98,1282 +95,1349 @@ import mpq.MPQArchive; import mpq.MPQException; public class War3MapViewer extends ModelViewer { - private static final War3ID UNIT_FILE = War3ID.fromString("umdl"); - private static final War3ID UBER_SPLAT = War3ID.fromString("uubs"); - private static final War3ID UNIT_SHADOW = War3ID.fromString("ushu"); - private static final War3ID UNIT_SHADOW_X = War3ID.fromString("ushx"); - private static final War3ID UNIT_SHADOW_Y = War3ID.fromString("ushy"); - private static final War3ID UNIT_SHADOW_W = War3ID.fromString("ushw"); - private static final War3ID UNIT_SHADOW_H = War3ID.fromString("ushh"); - private static final War3ID BUILDING_SHADOW = War3ID.fromString("ushb"); - public static final War3ID UNIT_SELECT_SCALE = War3ID.fromString("ussc"); - private static final War3ID UNIT_SELECT_HEIGHT = War3ID.fromString("uslz"); - private static final War3ID UNIT_SOUNDSET = War3ID.fromString("usnd"); - private static final War3ID ITEM_FILE = War3ID.fromString("ifil"); - private static final War3ID UNIT_PATHING = War3ID.fromString("upat"); - private static final War3ID DESTRUCTABLE_PATHING = War3ID.fromString("bptx"); - private static final War3ID ELEVATION_SAMPLE_RADIUS = War3ID.fromString("uerd"); - private static final War3ID MAX_PITCH = War3ID.fromString("umxp"); - private static final War3ID MAX_ROLL = War3ID.fromString("umxr"); - private static final War3ID sloc = War3ID.fromString("sloc"); - private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); - private static final float[] rayHeap = new float[6]; - private static final Ray gdxRayHeap = new Ray(); - private static final Vector2 mousePosHeap = new Vector2(); - private static final Vector3 normalHeap = new Vector3(); - private static final Vector3 intersectionHeap = new Vector3(); - public static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); - - public PathSolver wc3PathSolver = PathSolver.DEFAULT; - public SolverParams solverParams = new SolverParams(); - public WorldScene worldScene; - public boolean anyReady; - public MappedData terrainData = new MappedData(); - public MappedData cliffTypesData = new MappedData(); - public MappedData waterData = new MappedData(); - public boolean terrainReady; - public boolean cliffsReady; - public boolean doodadsAndDestructiblesLoaded; - public MappedData doodadsData = new MappedData(); - public MappedData doodadMetaData = new MappedData(); - public MappedData destructableMetaData = new MappedData(); - public List doodads = new ArrayList<>(); - public List terrainDoodads = new ArrayList<>(); - public boolean doodadsReady; - public boolean unitsAndItemsLoaded; - public MappedData unitsData = new MappedData(); - public MappedData unitMetaData = new MappedData(); - public List units = new ArrayList<>(); - public List items = new ArrayList<>(); - public List projectiles = new ArrayList<>(); - public boolean unitsReady; - public War3Map mapMpq; - public PathSolver mapPathSolver = PathSolver.DEFAULT; - - private final DataSource gameDataSource; - - public Terrain terrain; - public int renderPathing = 0; - public int renderLighting = 1; - - public List selModels = new ArrayList<>(); - public List selected = new ArrayList<>(); - private DataTable unitAckSoundsTable; - private DataTable unitCombatSoundsTable; - public DataTable miscData; - private DataTable unitGlobalStrings; - private MdxComplexInstance confirmationInstance; - public MdxComplexInstance dncUnit; - public MdxComplexInstance dncUnitDay; - public MdxComplexInstance dncTerrain; - public MdxComplexInstance dncTarget; - public CSimulation simulation; - private float updateTime; - - // for World Editor, I think - public Vector2[] startLocations = new Vector2[WarsmashConstants.MAX_PLAYERS]; - - private final DynamicShadowManager dynamicShadowManager = new DynamicShadowManager(); - - private final Random seededRandom = new Random(1337L); - - private final Map filePathToPathingMap = new HashMap<>(); - - private final List selectionCircleSizes = new ArrayList<>(); - - private final Map unitToRenderPeer = new HashMap<>(); - private final Map unitIdToTypeData = new HashMap<>(); - private GameUI gameUI; - private Vector3 lightDirection; - - public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { - super(dataSource, canvas); - this.gameDataSource = dataSource; - - final WebGL webGL = this.webGL; - - this.addHandler(new MdxHandler()); - - this.wc3PathSolver = PathSolver.DEFAULT; - - this.worldScene = this.addWorldScene(); - - if (!this.dynamicShadowManager.setup(webGL)) { - throw new IllegalStateException("FrameBuffer setup failed"); - } - } - - public void loadSLKs(final WorldEditStrings worldEditStrings) throws IOException { - final GenericResource terrain = this.loadMapGeneric("TerrainArt\\Terrain.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource cliffTypes = this.loadMapGeneric("TerrainArt\\CliffTypes.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource water = this.loadMapGeneric("TerrainArt\\Water.slk", FetchDataTypeName.SLK, - stringDataCallback); - - // == when loaded, which is always in our system == - this.terrainData.load(terrain.data.toString()); - this.cliffTypesData.load(cliffTypes.data.toString()); - this.waterData.load(water.data.toString()); - // emit terrain loaded?? - - final GenericResource doodads = this.loadMapGeneric("Doodads\\Doodads.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource doodadMetaData = this.loadMapGeneric("Doodads\\DoodadMetaData.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource destructableData = this.loadMapGeneric("Units\\DestructableData.slk", - FetchDataTypeName.SLK, stringDataCallback); - final GenericResource destructableMetaData = this.loadMapGeneric("Units\\DestructableMetaData.slk", - FetchDataTypeName.SLK, stringDataCallback); - - // == when loaded, which is always in our system == - this.doodadsAndDestructiblesLoaded = true; - this.doodadsData.load(doodads.data.toString()); - this.doodadMetaData.load(doodadMetaData.data.toString()); - this.doodadsData.load(destructableData.data.toString()); - this.destructableMetaData.load(destructableData.data.toString()); - // emit doodads loaded - - final GenericResource unitData = this.loadMapGeneric("Units\\UnitData.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource unitUi = this.loadMapGeneric("Units\\unitUI.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource itemData = this.loadMapGeneric("Units\\ItemData.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource unitMetaData = this.loadMapGeneric("Units\\UnitMetaData.slk", FetchDataTypeName.SLK, - stringDataCallback); - - // == when loaded, which is always in our system == - this.unitsAndItemsLoaded = true; - this.unitsData.load(unitData.data.toString()); - this.unitsData.load(unitUi.data.toString()); - this.unitsData.load(itemData.data.toString()); - this.unitMetaData.load(unitMetaData.data.toString()); - // emit loaded - - this.unitAckSoundsTable = new DataTable(worldEditStrings); - try (InputStream terrainSlkStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UnitAckSounds.slk")) { - this.unitAckSoundsTable.readSLK(terrainSlkStream); - } - this.unitCombatSoundsTable = new DataTable(worldEditStrings); - try (InputStream terrainSlkStream = this.dataSource - .getResourceAsStream("UI\\SoundInfo\\UnitCombatSounds.slk")) { - this.unitCombatSoundsTable.readSLK(terrainSlkStream); - } - this.miscData = new DataTable(worldEditStrings); - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\MiscData.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscData.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscGame.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\MiscData.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - if (this.dataSource.has("war3mapMisc.txt")) { - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("war3mapMisc.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - } - final Element light = this.miscData.get("Light"); - final float lightX = light.getFieldFloatValue("Direction", 0); - final float lightY = light.getFieldFloatValue("Direction", 1); - final float lightZ = light.getFieldFloatValue("Direction", 2); - this.lightDirection = new Vector3(lightX, lightY, lightZ).nor(); - this.unitGlobalStrings = new DataTable(worldEditStrings); - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) { - this.unitGlobalStrings.readTXT(miscDataTxtStream, true); - } - final Element categories = this.unitGlobalStrings.get("Categories"); - for (final CUnitClassification unitClassification : CUnitClassification.values()) { - if (unitClassification.getLocaleKey() != null) { - final String displayName = categories.getField(unitClassification.getLocaleKey()); - unitClassification.setDisplayName(displayName); - } - } - this.selectionCircleSizes.clear(); - final Element selectionCircleData = this.miscData.get("SelectionCircle"); - final int selectionCircleNumSizes = selectionCircleData.getFieldValue("NumSizes"); - for (int i = 0; i < selectionCircleNumSizes; i++) { - final String indexString = i < 10 ? "0" + i : Integer.toString(i); - final float size = selectionCircleData.getFieldFloatValue("Size" + indexString); - final String texture = selectionCircleData.getField("Texture" + indexString); - final String textureDotted = selectionCircleData.getField("TextureDotted" + indexString); - this.selectionCircleSizes.add(new SelectionCircleSize(size, texture, textureDotted)); - } - this.selectionCircleScaleFactor = selectionCircleData.getFieldFloatValue("ScaleFactor"); - } - - public GenericResource loadMapGeneric(final String path, final FetchDataTypeName dataType, - final LoadGenericCallback callback) { - if (this.mapMpq == null) { - return loadGeneric(path, dataType, callback); - } - else { - return loadGeneric(path, dataType, callback, this.dataSource); - } - } - - public void loadMap(final String mapFilePath) throws IOException { - final War3Map war3Map = new War3Map(this.gameDataSource, mapFilePath); - - this.mapMpq = war3Map; - - final PathSolver wc3PathSolver = this.wc3PathSolver; - - char tileset = 'A'; - - final War3MapW3i w3iFile = this.mapMpq.readMapInformation(); - - tileset = w3iFile.getTileset(); - - DataSource tilesetSource; - try { - // Slightly complex. Here's the theory: - // 1.) Copy map into RAM - // 2.) Setup a Data Source that will read assets - // from either the map or the game, giving the map priority. - SeekableByteChannel sbc; - final CompoundDataSource compoundDataSource = war3Map.getCompoundDataSource(); - try (InputStream mapStream = compoundDataSource.getResourceAsStream(tileset + ".mpq")) { - if (mapStream == null) { - tilesetSource = new CompoundDataSource(Arrays.asList(compoundDataSource, - new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), - new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); - } - else { - final byte[] mapData = IOUtils.toByteArray(mapStream); - sbc = new SeekableInMemoryByteChannel(mapData); - final DataSource internalMpqContentsDataSource = new MpqDataSource(new MPQArchive(sbc), sbc); - tilesetSource = new CompoundDataSource( - Arrays.asList(compoundDataSource, internalMpqContentsDataSource)); - } - } - catch (final IOException exc) { - tilesetSource = new CompoundDataSource( - Arrays.asList(compoundDataSource, new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), - new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); - } - } - catch (final MPQException e) { - throw new RuntimeException(e); - } - setDataSource(tilesetSource); - this.worldEditStrings = new WorldEditStrings(this.dataSource); - loadSLKs(this.worldEditStrings); - - this.solverParams.tileset = Character.toLowerCase(tileset); - - final War3MapW3e terrainData = this.mapMpq.readEnvironment(); - - final War3MapWpm terrainPathing = this.mapMpq.readPathing(); - - final StandardObjectData standardObjectData = new StandardObjectData(this.dataSource); - this.worldEditData = standardObjectData.getWorldEditData(); - - this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, - this.worldEditStrings, this, this.worldEditData); - - final float[] centerOffset = terrainData.getCenterOffset(); - final int[] mapSize = terrainData.getMapSize(); - - this.terrainReady = true; - this.anyReady = true; - this.cliffsReady = true; - - // Override the grid based on the map. - this.worldScene.grid = new Grid(centerOffset[0], centerOffset[1], (mapSize[0] * 128) - 128, - (mapSize[1] * 128) - 128, 16 * 128, 16 * 128); - - final MdxModel confirmation = (MdxModel) load("UI\\Feedback\\Confirmation\\Confirmation.mdx", - PathSolver.DEFAULT, null); - this.confirmationInstance = (MdxComplexInstance) confirmation.addInstance(); - this.confirmationInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE); - this.confirmationInstance.setSequence(0); - this.confirmationInstance.setScene(this.worldScene); - - this.allObjectData = this.mapMpq.readModifications(); - this.simulation = new CSimulation(this.miscData, this.allObjectData.getUnits(), - this.allObjectData.getAbilities(), new SimulationRenderController() { - private final Map keyToCombatSound = new HashMap<>(); - - @Override - public CAttackProjectile createAttackProjectile(final CSimulation simulation, final float launchX, - final float launchY, final float launchFacing, final CUnit source, - final CUnitAttackMissile unitAttack, final CWidget target, final float damage, - final int bounceIndex) { - final War3ID typeId = source.getTypeId(); - final int projectileSpeed = unitAttack.getProjectileSpeed(); - final float projectileArc = unitAttack.getProjectileArc(); - String missileArt = unitAttack.getProjectileArt(); - final float projectileLaunchX = simulation.getUnitData().getProjectileLaunchX(typeId); - final float projectileLaunchY = simulation.getUnitData().getProjectileLaunchY(typeId); - final float projectileLaunchZ = simulation.getUnitData().getProjectileLaunchZ(typeId); - - missileArt = mdx(missileArt); - final float facing = launchFacing; - final float sinFacing = (float) Math.sin(facing); - final float cosFacing = (float) Math.cos(facing); - final float x = (launchX + (projectileLaunchY * cosFacing)) + (projectileLaunchX * sinFacing); - final float y = (launchY + (projectileLaunchY * sinFacing)) - (projectileLaunchX * cosFacing); - - final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight() - + projectileLaunchZ; - final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y, - projectileSpeed, target, source, damage, unitAttack, bounceIndex); - - final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, - War3MapViewer.this.solverParams); - final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); - modelInstance.setTeamColor(source.getPlayerIndex()); - modelInstance.setScene(War3MapViewer.this.worldScene); - if (bounceIndex == 0) { - SequenceUtils.randomBirthSequence(modelInstance); - } - else { - SequenceUtils.randomStandSequence(modelInstance); - } - modelInstance.setLocation(x, y, height); - final RenderAttackProjectile renderAttackProjectile = new RenderAttackProjectile( - simulationAttackProjectile, modelInstance, height, projectileArc, War3MapViewer.this); - - War3MapViewer.this.projectiles.add(renderAttackProjectile); - - return simulationAttackProjectile; - } - - @Override - public void createInstantAttackEffect(final CSimulation cSimulation, final CUnit source, - final CUnitAttackInstant unitAttack, final CWidget target) { - final War3ID typeId = source.getTypeId(); - - String missileArt = unitAttack.getProjectileArt(); - final float projectileLaunchX = War3MapViewer.this.simulation.getUnitData() - .getProjectileLaunchX(typeId); - final float projectileLaunchY = War3MapViewer.this.simulation.getUnitData() - .getProjectileLaunchY(typeId); - missileArt = mdx(missileArt); - final float facing = (float) Math.toRadians(source.getFacing()); - final float sinFacing = (float) Math.sin(facing); - final float cosFacing = (float) Math.cos(facing); - final float x = (source.getX() + (projectileLaunchY * cosFacing)) - + (projectileLaunchX * sinFacing); - final float y = (source.getY() + (projectileLaunchY * sinFacing)) - - (projectileLaunchX * cosFacing); - - final float targetX = target.getX(); - final float targetY = target.getY(); - final float angleToTarget = (float) Math.atan2(targetY - y, targetX - x); - - final float height = War3MapViewer.this.terrain.getGroundHeight(targetX, targetY) - + target.getFlyHeight() + target.getImpactZ(); - - final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, - War3MapViewer.this.solverParams); - final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); - modelInstance.setTeamColor(source.getPlayerIndex()); - modelInstance.setScene(War3MapViewer.this.worldScene); - SequenceUtils.randomBirthSequence(modelInstance); - modelInstance.setLocation(targetX, targetY, height); - War3MapViewer.this.projectiles - .add(new RenderAttackInstant(modelInstance, War3MapViewer.this, angleToTarget)); - } - - @Override - public void spawnUnitDamageSound(final CUnit damagedUnit, final String weaponSound, - final String armorType) { - final String key = weaponSound + armorType; - UnitSound combatSound = this.keyToCombatSound.get(key); - if (combatSound == null) { - combatSound = UnitSound.create(War3MapViewer.this.dataSource, - War3MapViewer.this.unitCombatSoundsTable, weaponSound, armorType); - this.keyToCombatSound.put(key, combatSound); - } - combatSound.play(War3MapViewer.this.worldScene.audioContext, damagedUnit.getX(), - damagedUnit.getY()); - } - - @Override - public void removeUnit(final CUnit unit) { - final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.remove(unit); - War3MapViewer.this.units.remove(renderUnit); - War3MapViewer.this.worldScene.removeInstance(renderUnit.instance); - } - }, this.terrain.pathingGrid, - new Rectangle(centerOffset[0], centerOffset[1], (mapSize[0] * 128f) - 128, (mapSize[1] * 128f) - 128), - this.seededRandom, w3iFile.getPlayers()); - - if (this.doodadsAndDestructiblesLoaded) { - this.loadDoodadsAndDestructibles(this.allObjectData); - } - else { - throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); - } - - this.terrain.createWaves(); - - loadLightsAndShading(tileset); - } - - /** - * Loads the map information that should be loaded after UI, such as units, who - * need to be able to setup their UI counterparts (icons, etc) for their - * abilities while loading. This allows the dynamic creation of units while the - * game is playing to better share code with the startup sequence's creation of - * units. - * - * @throws IOException - */ - public void loadAfterUI() throws IOException { - if (this.unitsAndItemsLoaded) { - this.loadUnitsAndItems(this.allObjectData); - } - else { - throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); - } - - // After we finish loading units, we need to update & create the stored shadow - // information for all unit shadows - this.terrain.initShadows(); - } - - private void loadLightsAndShading(final char tileset) { - // TODO this should be set by the war3map.j actually, not by the tileset, so the - // call to set day night models is just for testing to make the test look pretty - final Element defaultTerrainLights = this.worldEditData.get("TerrainLights"); - final Element defaultUnitLights = this.worldEditData.get("UnitLights"); - setDayNightModels(defaultTerrainLights.getField(Character.toString(tileset)), - defaultUnitLights.getField(Character.toString(tileset))); - - } - - private void loadDoodadsAndDestructibles(final Warcraft3MapObjectData modifications) throws IOException { - this.applyModificationFile(this.doodadsData, this.doodadMetaData, modifications.getDoodads(), - WorldEditorDataType.DOODADS); - this.applyModificationFile(this.doodadsData, this.destructableMetaData, modifications.getDestructibles(), - WorldEditorDataType.DESTRUCTIBLES); - - final War3MapDoo doo = this.mapMpq.readDoodads(); - - for (final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad : doo.getDoodads()) { - WorldEditorDataType type = WorldEditorDataType.DOODADS; - MutableGameObject row = modifications.getDoodads().get(doodad.getId()); - if (row == null) { - row = modifications.getDestructibles().get(doodad.getId()); - type = WorldEditorDataType.DESTRUCTIBLES; - } - if (row != null) { - String file = row.readSLKTag("file"); - final int numVar = row.readSLKTagInt("numVar"); - - if (file.endsWith(".mdx") || file.endsWith(".mdl")) { - file = file.substring(0, file.length() - 4); - } - - String fileVar = file; - - file += ".mdx"; - - if (numVar > 1) { - fileVar += Math.min(doodad.getVariation(), numVar - 1); - } - - fileVar += ".mdx"; - - final float maxPitch = row.readSLKTagFloat("maxPitch"); - final float maxRoll = row.readSLKTagFloat("maxRoll"); - if (type == WorldEditorDataType.DESTRUCTIBLES) { - final String shadowString = row.readSLKTag("shadow"); - if ((shadowString != null) && (shadowString.length() > 0) && !"_".equals(shadowString)) { - this.terrain.addShadow(shadowString, doodad.getLocation()[0], doodad.getLocation()[1]); - } - - final String pathingTexture = row.readSLKTag("pathTex"); - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - - BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (bufferedImage == null) { - if (this.mapMpq.has(pathingTexture)) { - try { - bufferedImage = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage); - } - catch (final Exception exc) { - exc.printStackTrace(); - } - } - } - if (bufferedImage != null) { - this.terrain.pathingGrid.blitPathingOverlayTexture(doodad.getLocation()[0], - doodad.getLocation()[1], (int) Math.toDegrees(doodad.getAngle()), bufferedImage); - } - } - } - // First see if the model is local. - // Doodads referring to local models may have invalid variations, so if the - // variation doesn't exist, try without a variation. - - String path; - if (this.mapMpq.has(fileVar)) { - path = fileVar; - } - else { - path = file; - } - MdxModel model; - if (this.mapMpq.has(path)) { - model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); - } - else { - model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams); - } - - if (type == WorldEditorDataType.DESTRUCTIBLES) { - this.doodads.add(new RenderDestructable(this, model, row, doodad, type, maxPitch, maxRoll, - doodad.getLife())); - } - else { - this.doodads.add(new RenderDoodad(this, model, row, doodad, type, maxPitch, maxRoll)); - } - } - } - - // Cliff/Terrain doodads. - for (final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad : doo.getTerrainDoodads()) { - final MutableGameObject row = modifications.getDoodads().get(doodad.getId()); - String file = row.readSLKTag("file");// - if ("".equals(file)) { - final String blaBla = row.readSLKTag("file"); - System.out.println("bla"); - } - if (file.toLowerCase().endsWith(".mdl")) { - file = file.substring(0, file.length() - 4); - } - if (!file.toLowerCase().endsWith(".mdx")) { - file += ".mdx"; - } - final MdxModel model = (MdxModel) this.load(file, this.mapPathSolver, this.solverParams); - - final String pathingTexture = row.readSLKTag("pathTex"); - BufferedImage pathingTextureImage; - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - - pathingTextureImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (pathingTextureImage == null) { - if (this.mapMpq.has(pathingTexture)) { - try { - pathingTextureImage = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), pathingTextureImage); - } - catch (final Exception exc) { - exc.printStackTrace(); - } - } - } - } - else { - pathingTextureImage = null; - } - if (pathingTextureImage != null) { - // blit out terrain cells under this TerrainDoodad - final int textureWidth = pathingTextureImage.getWidth(); - final int textureHeight = pathingTextureImage.getHeight(); - final int textureWidthTerrainCells = textureWidth / 4; - final int textureHeightTerrainCells = textureHeight / 4; - final int minCellX = ((int) doodad.getLocation()[0]); - final int minCellY = ((int) doodad.getLocation()[1]); - final int maxCellX = (minCellX + textureWidthTerrainCells) - 1; - final int maxCellY = (minCellY + textureHeightTerrainCells) - 1; - for (int j = minCellY; j <= maxCellY; j++) { - for (int i = minCellX; i <= maxCellX; i++) { - this.terrain.removeTerrainCellWithoutFlush(i, j); - } - } - this.terrain.flushRemovedTerrainCells(); - } - - System.out.println("Loading terrain doodad: " + file); - this.terrainDoodads.add(new TerrainDoodad(this, model, row, doodad, pathingTextureImage)); - } - - this.doodadsReady = true; - this.anyReady = true; - } - - private void applyModificationFile(final MappedData doodadsData2, final MappedData doodadMetaData2, - final MutableObjectData destructibles, final WorldEditorDataType dataType) { - // TODO condense ported MappedData from Ghostwolf and MutableObjectData from - // Retera - - } - - private void loadUnitsAndItems(final Warcraft3MapObjectData modifications) throws IOException { - final War3Map mpq = this.mapMpq; - - if (this.dataSource.has("war3mapUnits.doo")) { - final War3MapUnitsDoo dooFile = mpq.readUnits(); - - final Map soundsetNameToSoundset = new HashMap<>(); - - // Collect the units and items data. - UnitSoundset soundset = null; - for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) { - MutableGameObject row = null; - String path = null; - Splat unitShadowSplat = null; - BufferedImage buildingPathingPixelMap = null; - - // Hardcoded? - WorldEditorDataType type = null; - if (sloc.equals(unit.getId())) { + private static final War3ID UNIT_FILE = War3ID.fromString("umdl"); + private static final War3ID UBER_SPLAT = War3ID.fromString("uubs"); + private static final War3ID UNIT_SHADOW = War3ID.fromString("ushu"); + private static final War3ID UNIT_SHADOW_X = War3ID.fromString("ushx"); + private static final War3ID UNIT_SHADOW_Y = War3ID.fromString("ushy"); + private static final War3ID UNIT_SHADOW_W = War3ID.fromString("ushw"); + private static final War3ID UNIT_SHADOW_H = War3ID.fromString("ushh"); + private static final War3ID BUILDING_SHADOW = War3ID.fromString("ushb"); + public static final War3ID UNIT_SELECT_SCALE = War3ID.fromString("ussc"); + private static final War3ID UNIT_SELECT_HEIGHT = War3ID.fromString("uslz"); + private static final War3ID UNIT_SOUNDSET = War3ID.fromString("usnd"); + private static final War3ID ITEM_FILE = War3ID.fromString("ifil"); + private static final War3ID UNIT_PATHING = War3ID.fromString("upat"); + private static final War3ID DESTRUCTABLE_PATHING = War3ID.fromString("bptx"); + private static final War3ID ELEVATION_SAMPLE_RADIUS = War3ID.fromString("uerd"); + private static final War3ID MAX_PITCH = War3ID.fromString("umxp"); + private static final War3ID MAX_ROLL = War3ID.fromString("umxr"); + private static final War3ID sloc = War3ID.fromString("sloc"); + private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); + private static final float[] rayHeap = new float[6]; + public static final Ray gdxRayHeap = new Ray(); + private static final Vector2 mousePosHeap = new Vector2(); + private static final Vector3 normalHeap = new Vector3(); + public static final Vector3 intersectionHeap = new Vector3(); + private static final Rectangle rectangleHeap = new Rectangle(); + public static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); + + public PathSolver wc3PathSolver = PathSolver.DEFAULT; + public SolverParams solverParams = new SolverParams(); + public WorldScene worldScene; + public boolean anyReady; + public MappedData terrainData = new MappedData(); + public MappedData cliffTypesData = new MappedData(); + public MappedData waterData = new MappedData(); + public boolean terrainReady; + public boolean cliffsReady; + public boolean doodadsAndDestructiblesLoaded; + public MappedData doodadsData = new MappedData(); + public MappedData doodadMetaData = new MappedData(); + public MappedData destructableMetaData = new MappedData(); + public List doodads = new ArrayList<>(); + public List terrainDoodads = new ArrayList<>(); + public boolean doodadsReady; + public boolean unitsAndItemsLoaded; + public MappedData unitsData = new MappedData(); + public MappedData unitMetaData = new MappedData(); + public List units = new ArrayList<>(); + public List items = new ArrayList<>(); + public List projectiles = new ArrayList<>(); + public boolean unitsReady; + public War3Map mapMpq; + public PathSolver mapPathSolver = PathSolver.DEFAULT; + + private final DataSource gameDataSource; + + public Terrain terrain; + public int renderPathing = 0; + public int renderLighting = 1; + + public List selModels = new ArrayList<>(); + public List selected = new ArrayList<>(); + private DataTable unitAckSoundsTable; + private DataTable unitCombatSoundsTable; + public DataTable miscData; + private DataTable unitGlobalStrings; + private MdxComplexInstance confirmationInstance; + public MdxComplexInstance dncUnit; + public MdxComplexInstance dncUnitDay; + public MdxComplexInstance dncTerrain; + public MdxComplexInstance dncTarget; + public CSimulation simulation; + private float updateTime; + + // for World Editor, I think + public Vector2[] startLocations = new Vector2[WarsmashConstants.MAX_PLAYERS]; + + private final DynamicShadowManager dynamicShadowManager = new DynamicShadowManager(); + + private final Random seededRandom = new Random(1337L); + + private final Map filePathToPathingMap = new HashMap<>(); + + private final List selectionCircleSizes = new ArrayList<>(); + + private final Map unitToRenderPeer = new HashMap<>(); + private final Map unitIdToTypeData = new HashMap<>(); + private GameUI gameUI; + private Vector3 lightDirection; + + private Quadtree walkableObjectsTree; + private QuadtreeIntersectorFindsWalkableRenderHeight walkablesIntersector = new QuadtreeIntersectorFindsWalkableRenderHeight(); + private QuadtreeIntersectorFindsHitPoint walkablesIntersectionFinder = new QuadtreeIntersectorFindsHitPoint(); + private QuadtreeIntersectorFindsHighestWalkable intersectorFindsHighestWalkable = new QuadtreeIntersectorFindsHighestWalkable(); + + public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { + super(dataSource, canvas); + this.gameDataSource = dataSource; + + final WebGL webGL = this.webGL; + + this.addHandler(new MdxHandler()); + + this.wc3PathSolver = PathSolver.DEFAULT; + + this.worldScene = this.addWorldScene(); + + if (!this.dynamicShadowManager.setup(webGL)) { + throw new IllegalStateException("FrameBuffer setup failed"); + } + } + + public void loadSLKs(final WorldEditStrings worldEditStrings) throws IOException { + final GenericResource terrain = this.loadMapGeneric("TerrainArt\\Terrain.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource cliffTypes = this.loadMapGeneric("TerrainArt\\CliffTypes.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource water = this.loadMapGeneric("TerrainArt\\Water.slk", FetchDataTypeName.SLK, + stringDataCallback); + + // == when loaded, which is always in our system == + this.terrainData.load(terrain.data.toString()); + this.cliffTypesData.load(cliffTypes.data.toString()); + this.waterData.load(water.data.toString()); + // emit terrain loaded?? + + final GenericResource doodads = this.loadMapGeneric("Doodads\\Doodads.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource doodadMetaData = this.loadMapGeneric("Doodads\\DoodadMetaData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource destructableData = this.loadMapGeneric("Units\\DestructableData.slk", + FetchDataTypeName.SLK, stringDataCallback); + final GenericResource destructableMetaData = this.loadMapGeneric("Units\\DestructableMetaData.slk", + FetchDataTypeName.SLK, stringDataCallback); + + // == when loaded, which is always in our system == + this.doodadsAndDestructiblesLoaded = true; + this.doodadsData.load(doodads.data.toString()); + this.doodadMetaData.load(doodadMetaData.data.toString()); + this.doodadsData.load(destructableData.data.toString()); + this.destructableMetaData.load(destructableData.data.toString()); + // emit doodads loaded + + final GenericResource unitData = this.loadMapGeneric("Units\\UnitData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource unitUi = this.loadMapGeneric("Units\\unitUI.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource itemData = this.loadMapGeneric("Units\\ItemData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource unitMetaData = this.loadMapGeneric("Units\\UnitMetaData.slk", FetchDataTypeName.SLK, + stringDataCallback); + + // == when loaded, which is always in our system == + this.unitsAndItemsLoaded = true; + this.unitsData.load(unitData.data.toString()); + this.unitsData.load(unitUi.data.toString()); + this.unitsData.load(itemData.data.toString()); + this.unitMetaData.load(unitMetaData.data.toString()); + // emit loaded + + this.unitAckSoundsTable = new DataTable(worldEditStrings); + try (InputStream terrainSlkStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UnitAckSounds.slk")) { + this.unitAckSoundsTable.readSLK(terrainSlkStream); + } + this.unitCombatSoundsTable = new DataTable(worldEditStrings); + try (InputStream terrainSlkStream = this.dataSource + .getResourceAsStream("UI\\SoundInfo\\UnitCombatSounds.slk")) { + this.unitCombatSoundsTable.readSLK(terrainSlkStream); + } + this.miscData = new DataTable(worldEditStrings); + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscGame.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + if (this.dataSource.has("war3mapMisc.txt")) { + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("war3mapMisc.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + } + final Element light = this.miscData.get("Light"); + final float lightX = light.getFieldFloatValue("Direction", 0); + final float lightY = light.getFieldFloatValue("Direction", 1); + final float lightZ = light.getFieldFloatValue("Direction", 2); + this.lightDirection = new Vector3(lightX, lightY, lightZ).nor(); + this.unitGlobalStrings = new DataTable(worldEditStrings); + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) { + this.unitGlobalStrings.readTXT(miscDataTxtStream, true); + } + final Element categories = this.unitGlobalStrings.get("Categories"); + for (final CUnitClassification unitClassification : CUnitClassification.values()) { + if (unitClassification.getLocaleKey() != null) { + final String displayName = categories.getField(unitClassification.getLocaleKey()); + unitClassification.setDisplayName(displayName); + } + } + this.selectionCircleSizes.clear(); + final Element selectionCircleData = this.miscData.get("SelectionCircle"); + final int selectionCircleNumSizes = selectionCircleData.getFieldValue("NumSizes"); + for (int i = 0; i < selectionCircleNumSizes; i++) { + final String indexString = i < 10 ? "0" + i : Integer.toString(i); + final float size = selectionCircleData.getFieldFloatValue("Size" + indexString); + final String texture = selectionCircleData.getField("Texture" + indexString); + final String textureDotted = selectionCircleData.getField("TextureDotted" + indexString); + this.selectionCircleSizes.add(new SelectionCircleSize(size, texture, textureDotted)); + } + this.selectionCircleScaleFactor = selectionCircleData.getFieldFloatValue("ScaleFactor"); + } + + public GenericResource loadMapGeneric(final String path, final FetchDataTypeName dataType, + final LoadGenericCallback callback) { + if (this.mapMpq == null) { + return loadGeneric(path, dataType, callback); + } else { + return loadGeneric(path, dataType, callback, this.dataSource); + } + } + + public void loadMap(final String mapFilePath) throws IOException { + final War3Map war3Map = new War3Map(this.gameDataSource, mapFilePath); + + this.mapMpq = war3Map; + + final PathSolver wc3PathSolver = this.wc3PathSolver; + + char tileset = 'A'; + + final War3MapW3i w3iFile = this.mapMpq.readMapInformation(); + + tileset = w3iFile.getTileset(); + + DataSource tilesetSource; + try { + // Slightly complex. Here's the theory: + // 1.) Copy map into RAM + // 2.) Setup a Data Source that will read assets + // from either the map or the game, giving the map priority. + SeekableByteChannel sbc; + final CompoundDataSource compoundDataSource = war3Map.getCompoundDataSource(); + try (InputStream mapStream = compoundDataSource.getResourceAsStream(tileset + ".mpq")) { + if (mapStream == null) { + tilesetSource = new CompoundDataSource(Arrays.asList(compoundDataSource, + new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), + new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); + } else { + final byte[] mapData = IOUtils.toByteArray(mapStream); + sbc = new SeekableInMemoryByteChannel(mapData); + final DataSource internalMpqContentsDataSource = new MpqDataSource(new MPQArchive(sbc), sbc); + tilesetSource = new CompoundDataSource( + Arrays.asList(compoundDataSource, internalMpqContentsDataSource)); + } + } catch (final IOException exc) { + tilesetSource = new CompoundDataSource( + Arrays.asList(compoundDataSource, new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), + new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); + } + } catch (final MPQException e) { + throw new RuntimeException(e); + } + setDataSource(tilesetSource); + this.worldEditStrings = new WorldEditStrings(this.dataSource); + loadSLKs(this.worldEditStrings); + + this.solverParams.tileset = Character.toLowerCase(tileset); + + final War3MapW3e terrainData = this.mapMpq.readEnvironment(); + + final War3MapWpm terrainPathing = this.mapMpq.readPathing(); + + final StandardObjectData standardObjectData = new StandardObjectData(this.dataSource); + this.worldEditData = standardObjectData.getWorldEditData(); + + this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, + this.worldEditStrings, this, this.worldEditData); + + final float[] centerOffset = terrainData.getCenterOffset(); + final int[] mapSize = terrainData.getMapSize(); + + this.terrainReady = true; + this.anyReady = true; + this.cliffsReady = true; + + // Override the grid based on the map. + this.worldScene.grid = new Grid(centerOffset[0], centerOffset[1], (mapSize[0] * 128) - 128, + (mapSize[1] * 128) - 128, 16 * 128, 16 * 128); + + final MdxModel confirmation = (MdxModel) load("UI\\Feedback\\Confirmation\\Confirmation.mdx", + PathSolver.DEFAULT, null); + this.confirmationInstance = (MdxComplexInstance) confirmation.addInstance(); + this.confirmationInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE); + this.confirmationInstance.setSequence(0); + this.confirmationInstance.setScene(this.worldScene); + + this.allObjectData = this.mapMpq.readModifications(); + this.simulation = new CSimulation(this.miscData, this.allObjectData.getUnits(), + this.allObjectData.getAbilities(), new SimulationRenderController() { + private final Map keyToCombatSound = new HashMap<>(); + + @Override + public CAttackProjectile createAttackProjectile(final CSimulation simulation, final float launchX, + final float launchY, final float launchFacing, final CUnit source, + final CUnitAttackMissile unitAttack, final CWidget target, final float damage, + final int bounceIndex) { + final War3ID typeId = source.getTypeId(); + final int projectileSpeed = unitAttack.getProjectileSpeed(); + final float projectileArc = unitAttack.getProjectileArc(); + String missileArt = unitAttack.getProjectileArt(); + final float projectileLaunchX = simulation.getUnitData().getProjectileLaunchX(typeId); + final float projectileLaunchY = simulation.getUnitData().getProjectileLaunchY(typeId); + final float projectileLaunchZ = simulation.getUnitData().getProjectileLaunchZ(typeId); + + missileArt = mdx(missileArt); + final float facing = launchFacing; + final float sinFacing = (float) Math.sin(facing); + final float cosFacing = (float) Math.cos(facing); + final float x = (launchX + (projectileLaunchY * cosFacing)) + (projectileLaunchX * sinFacing); + final float y = (launchY + (projectileLaunchY * sinFacing)) - (projectileLaunchX * cosFacing); + + final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight() + + projectileLaunchZ; + final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y, + projectileSpeed, target, source, damage, unitAttack, bounceIndex); + + final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, + War3MapViewer.this.solverParams); + final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); + modelInstance.setTeamColor(source.getPlayerIndex()); + modelInstance.setScene(War3MapViewer.this.worldScene); + if (bounceIndex == 0) { + SequenceUtils.randomBirthSequence(modelInstance); + } else { + SequenceUtils.randomStandSequence(modelInstance); + } + modelInstance.setLocation(x, y, height); + final RenderAttackProjectile renderAttackProjectile = new RenderAttackProjectile( + simulationAttackProjectile, modelInstance, height, projectileArc, War3MapViewer.this); + + War3MapViewer.this.projectiles.add(renderAttackProjectile); + + return simulationAttackProjectile; + } + + @Override + public void createInstantAttackEffect(final CSimulation cSimulation, final CUnit source, + final CUnitAttackInstant unitAttack, final CWidget target) { + final War3ID typeId = source.getTypeId(); + + String missileArt = unitAttack.getProjectileArt(); + final float projectileLaunchX = War3MapViewer.this.simulation.getUnitData() + .getProjectileLaunchX(typeId); + final float projectileLaunchY = War3MapViewer.this.simulation.getUnitData() + .getProjectileLaunchY(typeId); + missileArt = mdx(missileArt); + final float facing = (float) Math.toRadians(source.getFacing()); + final float sinFacing = (float) Math.sin(facing); + final float cosFacing = (float) Math.cos(facing); + final float x = (source.getX() + (projectileLaunchY * cosFacing)) + + (projectileLaunchX * sinFacing); + final float y = (source.getY() + (projectileLaunchY * sinFacing)) + - (projectileLaunchX * cosFacing); + + final float targetX = target.getX(); + final float targetY = target.getY(); + final float angleToTarget = (float) Math.atan2(targetY - y, targetX - x); + + final float height = War3MapViewer.this.terrain.getGroundHeight(targetX, targetY) + + target.getFlyHeight() + target.getImpactZ(); + + final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, + War3MapViewer.this.solverParams); + final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); + modelInstance.setTeamColor(source.getPlayerIndex()); + modelInstance.setScene(War3MapViewer.this.worldScene); + SequenceUtils.randomBirthSequence(modelInstance); + modelInstance.setLocation(targetX, targetY, height); + War3MapViewer.this.projectiles + .add(new RenderAttackInstant(modelInstance, War3MapViewer.this, angleToTarget)); + } + + @Override + public void spawnUnitDamageSound(final CUnit damagedUnit, final String weaponSound, + final String armorType) { + final String key = weaponSound + armorType; + UnitSound combatSound = this.keyToCombatSound.get(key); + if (combatSound == null) { + combatSound = UnitSound.create(War3MapViewer.this.dataSource, + War3MapViewer.this.unitCombatSoundsTable, weaponSound, armorType); + this.keyToCombatSound.put(key, combatSound); + } + combatSound.play(War3MapViewer.this.worldScene.audioContext, damagedUnit.getX(), + damagedUnit.getY()); + } + + @Override + public void removeUnit(final CUnit unit) { + final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.remove(unit); + War3MapViewer.this.units.remove(renderUnit); + War3MapViewer.this.worldScene.removeInstance(renderUnit.instance); + } + }, this.terrain.pathingGrid, + terrain.getEntireMap(), + this.seededRandom, w3iFile.getPlayers()); + + walkableObjectsTree = new Quadtree<>(terrain.getEntireMap()); + if (this.doodadsAndDestructiblesLoaded) { + this.loadDoodadsAndDestructibles(this.allObjectData); + } else { + throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); + } + + this.terrain.createWaves(); + + loadLightsAndShading(tileset); + } + + /** + * Loads the map information that should be loaded after UI, such as units, who + * need to be able to setup their UI counterparts (icons, etc) for their + * abilities while loading. This allows the dynamic creation of units while the + * game is playing to better share code with the startup sequence's creation of + * units. + * + * @throws IOException + */ + public void loadAfterUI() throws IOException { + if (this.unitsAndItemsLoaded) { + this.loadUnitsAndItems(this.allObjectData); + } else { + throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); + } + + // After we finish loading units, we need to update & create the stored shadow + // information for all unit shadows + this.terrain.initShadows(); + } + + private void loadLightsAndShading(final char tileset) { + // TODO this should be set by the war3map.j actually, not by the tileset, so the + // call to set day night models is just for testing to make the test look pretty + final Element defaultTerrainLights = this.worldEditData.get("TerrainLights"); + final Element defaultUnitLights = this.worldEditData.get("UnitLights"); + setDayNightModels(defaultTerrainLights.getField(Character.toString(tileset)), + defaultUnitLights.getField(Character.toString(tileset))); + + } + + private void loadDoodadsAndDestructibles(final Warcraft3MapObjectData modifications) throws IOException { + this.applyModificationFile(this.doodadsData, this.doodadMetaData, modifications.getDoodads(), + WorldEditorDataType.DOODADS); + this.applyModificationFile(this.doodadsData, this.destructableMetaData, modifications.getDestructibles(), + WorldEditorDataType.DESTRUCTIBLES); + + final War3MapDoo doo = this.mapMpq.readDoodads(); + + for (final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad : doo.getDoodads()) { + WorldEditorDataType type = WorldEditorDataType.DOODADS; + MutableGameObject row = modifications.getDoodads().get(doodad.getId()); + if (row == null) { + row = modifications.getDestructibles().get(doodad.getId()); + type = WorldEditorDataType.DESTRUCTIBLES; + } + if (row != null) { + String file = row.readSLKTag("file"); + final int numVar = row.readSLKTagInt("numVar"); + + if (file.endsWith(".mdx") || file.endsWith(".mdl")) { + file = file.substring(0, file.length() - 4); + } + + String fileVar = file; + + file += ".mdx"; + + if (numVar > 1) { + fileVar += Math.min(doodad.getVariation(), numVar - 1); + } + + fileVar += ".mdx"; + + final float maxPitch = row.readSLKTagFloat("maxPitch"); + final float maxRoll = row.readSLKTagFloat("maxRoll"); + if (type == WorldEditorDataType.DESTRUCTIBLES) { + final String shadowString = row.readSLKTag("shadow"); + if ((shadowString != null) && (shadowString.length() > 0) && !"_".equals(shadowString)) { + this.terrain.addShadow(shadowString, doodad.getLocation()[0], doodad.getLocation()[1]); + } + + final String pathingTexture = row.readSLKTag("pathTex"); + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + + BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (bufferedImage == null) { + if (this.mapMpq.has(pathingTexture)) { + try { + bufferedImage = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage); + } catch (final Exception exc) { + exc.printStackTrace(); + } + } + } + if (bufferedImage != null) { + this.terrain.pathingGrid.blitPathingOverlayTexture(doodad.getLocation()[0], + doodad.getLocation()[1], (int) Math.toDegrees(doodad.getAngle()), bufferedImage); + } + } + } + // First see if the model is local. + // Doodads referring to local models may have invalid variations, so if the + // variation doesn't exist, try without a variation. + + String path; + if (this.mapMpq.has(fileVar)) { + path = fileVar; + } else { + path = file; + } + MdxModel model; + if (this.mapMpq.has(path)) { + model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); + } else { + model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams); + } + + if (type == WorldEditorDataType.DESTRUCTIBLES) { + RenderDestructable renderDestructable = new RenderDestructable(this, model, row, doodad, type, maxPitch, maxRoll, + doodad.getLife()); + if (row.readSLKTagBoolean("walkable")) { + float x = doodad.getLocation()[0]; + float y = doodad.getLocation()[1]; + BoundingBox boundingBox = model.bounds.getBoundingBox(); + float minX = boundingBox.min.x + x; + float minY = boundingBox.min.y + y; + Rectangle renderDestructableBounds = new Rectangle(minX, minY, boundingBox.getWidth(), boundingBox.getHeight()); + walkableObjectsTree.add((MdxComplexInstance) renderDestructable.instance, renderDestructableBounds); + } + this.doodads.add(renderDestructable); + } else { + this.doodads.add(new RenderDoodad(this, model, row, doodad, type, maxPitch, maxRoll)); + } + } + } + + // Cliff/Terrain doodads. + for (final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad : doo.getTerrainDoodads()) { + final MutableGameObject row = modifications.getDoodads().get(doodad.getId()); + String file = row.readSLKTag("file");// + if ("".equals(file)) { + final String blaBla = row.readSLKTag("file"); + System.out.println("bla"); + } + if (file.toLowerCase().endsWith(".mdl")) { + file = file.substring(0, file.length() - 4); + } + if (!file.toLowerCase().endsWith(".mdx")) { + file += ".mdx"; + } + final MdxModel model = (MdxModel) this.load(file, this.mapPathSolver, this.solverParams); + + final String pathingTexture = row.readSLKTag("pathTex"); + BufferedImage pathingTextureImage; + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + + pathingTextureImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (pathingTextureImage == null) { + if (this.mapMpq.has(pathingTexture)) { + try { + pathingTextureImage = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), pathingTextureImage); + } catch (final Exception exc) { + exc.printStackTrace(); + } + } + } + } else { + pathingTextureImage = null; + } + if (pathingTextureImage != null) { + // blit out terrain cells under this TerrainDoodad + final int textureWidth = pathingTextureImage.getWidth(); + final int textureHeight = pathingTextureImage.getHeight(); + final int textureWidthTerrainCells = textureWidth / 4; + final int textureHeightTerrainCells = textureHeight / 4; + final int minCellX = ((int) doodad.getLocation()[0]); + final int minCellY = ((int) doodad.getLocation()[1]); + final int maxCellX = (minCellX + textureWidthTerrainCells) - 1; + final int maxCellY = (minCellY + textureHeightTerrainCells) - 1; + for (int j = minCellY; j <= maxCellY; j++) { + for (int i = minCellX; i <= maxCellX; i++) { + this.terrain.removeTerrainCellWithoutFlush(i, j); + } + } + this.terrain.flushRemovedTerrainCells(); + } + + System.out.println("Loading terrain doodad: " + file); + this.terrainDoodads.add(new TerrainDoodad(this, model, row, doodad, pathingTextureImage)); + } + + this.doodadsReady = true; + this.anyReady = true; + } + + private void applyModificationFile(final MappedData doodadsData2, final MappedData doodadMetaData2, + final MutableObjectData destructibles, final WorldEditorDataType dataType) { + // TODO condense ported MappedData from Ghostwolf and MutableObjectData from + // Retera + + } + + private void loadUnitsAndItems(final Warcraft3MapObjectData modifications) throws IOException { + final War3Map mpq = this.mapMpq; + + if (this.dataSource.has("war3mapUnits.doo")) { + final War3MapUnitsDoo dooFile = mpq.readUnits(); + + final Map soundsetNameToSoundset = new HashMap<>(); + + // Collect the units and items data. + UnitSoundset soundset = null; + for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) { + MutableGameObject row = null; + String path = null; + Splat unitShadowSplat = null; + BufferedImage buildingPathingPixelMap = null; + + // Hardcoded? + WorldEditorDataType type = null; + if (sloc.equals(unit.getId())) { // path = "Objects\\StartLocation\\StartLocation.mdx"; - type = null; /// ?????? - this.startLocations[unit.getPlayer()] = new Vector2(unit.getLocation()[0], unit.getLocation()[1]); - } - else { - row = modifications.getUnits().get(unit.getId()); - if (row == null) { - row = modifications.getItems().get(unit.getId()); - if (row != null) { - type = WorldEditorDataType.ITEM; - path = row.getFieldAsString(ITEM_FILE, 0); + type = null; /// ?????? + this.startLocations[unit.getPlayer()] = new Vector2(unit.getLocation()[0], unit.getLocation()[1]); + } else { + row = modifications.getUnits().get(unit.getId()); + if (row == null) { + row = modifications.getItems().get(unit.getId()); + if (row != null) { + type = WorldEditorDataType.ITEM; + path = row.getFieldAsString(ITEM_FILE, 0); - if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { - path = path.substring(0, path.length() - 4); - } + if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { + path = path.substring(0, path.length() - 4); + } - final String unitShadow = "Shadow"; - if ((unitShadow != null) && !"_".equals(unitShadow)) { - final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; - final float shadowX = 50; - final float shadowY = 50; - final float shadowWidth = 128; - final float shadowHeight = 128; - if (!this.terrain.splats.containsKey(texture)) { - final Splat splat = new Splat(); - splat.opacity = 0.5f; - this.terrain.splats.put(texture, splat); - } - final float x = unit.getLocation()[0] - shadowX; - final float y = unit.getLocation()[1] - shadowY; - this.terrain.splats.get(texture).locations - .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); - unitShadowSplat = this.terrain.splats.get(texture); - } + final String unitShadow = "Shadow"; + if ((unitShadow != null) && !"_".equals(unitShadow)) { + final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; + final float shadowX = 50; + final float shadowY = 50; + final float shadowWidth = 128; + final float shadowHeight = 128; + if (!this.terrain.splats.containsKey(texture)) { + final Splat splat = new Splat(); + splat.opacity = 0.5f; + this.terrain.splats.put(texture, splat); + } + final float x = unit.getLocation()[0] - shadowX; + final float y = unit.getLocation()[1] - shadowY; + this.terrain.splats.get(texture).locations + .add(new float[]{x, y, x + shadowWidth, y + shadowHeight, 3}); + unitShadowSplat = this.terrain.splats.get(texture); + } - path += ".mdx"; - } - } - else { - type = WorldEditorDataType.UNITS; - path = row.getFieldAsString(UNIT_FILE, 0); + path += ".mdx"; + } + } else { + type = WorldEditorDataType.UNITS; + path = row.getFieldAsString(UNIT_FILE, 0); - if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { - path = path.substring(0, path.length() - 4); - } - if ((row.readSLKTagInt("fileVerFlags") == 2) && this.dataSource.has(path + "_V1.mdx")) { - path += "_V1"; - } + if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { + path = path.substring(0, path.length() - 4); + } + if ((row.readSLKTagInt("fileVerFlags") == 2) && this.dataSource.has(path + "_V1.mdx")) { + path += "_V1"; + } - path += ".mdx"; + path += ".mdx"; - final String uberSplat = row.getFieldAsString(UBER_SPLAT, 0); - if (uberSplat != null) { - final Element uberSplatInfo = this.terrain.uberSplatTable.get(uberSplat); - if (uberSplatInfo != null) { - final String texturePath = uberSplatInfo.getField("Dir") + "\\" - + uberSplatInfo.getField("file") + ".blp"; - if (!this.terrain.splats.containsKey(texturePath)) { - this.terrain.splats.put(texturePath, new Splat()); - } - final float x = unit.getLocation()[0]; - final float y = unit.getLocation()[1]; - final float s = uberSplatInfo.getFieldFloatValue("Scale"); - this.terrain.splats.get(texturePath).locations - .add(new float[] { x - s, y - s, x + s, y + s, 1 }); - } - } + final String uberSplat = row.getFieldAsString(UBER_SPLAT, 0); + if (uberSplat != null) { + final Element uberSplatInfo = this.terrain.uberSplatTable.get(uberSplat); + if (uberSplatInfo != null) { + final String texturePath = uberSplatInfo.getField("Dir") + "\\" + + uberSplatInfo.getField("file") + ".blp"; + if (!this.terrain.splats.containsKey(texturePath)) { + this.terrain.splats.put(texturePath, new Splat()); + } + final float x = unit.getLocation()[0]; + final float y = unit.getLocation()[1]; + final float s = uberSplatInfo.getFieldFloatValue("Scale"); + this.terrain.splats.get(texturePath).locations + .add(new float[]{x - s, y - s, x + s, y + s, 1}); + } + } - final String unitShadow = row.getFieldAsString(UNIT_SHADOW, 0); - if ((unitShadow != null) && !"_".equals(unitShadow)) { - final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; - final float shadowX = row.getFieldAsFloat(UNIT_SHADOW_X, 0); - final float shadowY = row.getFieldAsFloat(UNIT_SHADOW_Y, 0); - final float shadowWidth = row.getFieldAsFloat(UNIT_SHADOW_W, 0); - final float shadowHeight = row.getFieldAsFloat(UNIT_SHADOW_H, 0); - if (this.mapMpq.has(texture)) { - if (!this.terrain.splats.containsKey(texture)) { - final Splat splat = new Splat(); - splat.opacity = 0.5f; - this.terrain.splats.put(texture, splat); - } - final float x = unit.getLocation()[0] - shadowX; - final float y = unit.getLocation()[1] - shadowY; - this.terrain.splats.get(texture).locations - .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); - unitShadowSplat = this.terrain.splats.get(texture); - } - } + final String unitShadow = row.getFieldAsString(UNIT_SHADOW, 0); + if ((unitShadow != null) && !"_".equals(unitShadow)) { + final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; + final float shadowX = row.getFieldAsFloat(UNIT_SHADOW_X, 0); + final float shadowY = row.getFieldAsFloat(UNIT_SHADOW_Y, 0); + final float shadowWidth = row.getFieldAsFloat(UNIT_SHADOW_W, 0); + final float shadowHeight = row.getFieldAsFloat(UNIT_SHADOW_H, 0); + if (this.mapMpq.has(texture)) { + if (!this.terrain.splats.containsKey(texture)) { + final Splat splat = new Splat(); + splat.opacity = 0.5f; + this.terrain.splats.put(texture, splat); + } + final float x = unit.getLocation()[0] - shadowX; + final float y = unit.getLocation()[1] - shadowY; + this.terrain.splats.get(texture).locations + .add(new float[]{x, y, x + shadowWidth, y + shadowHeight, 3}); + unitShadowSplat = this.terrain.splats.get(texture); + } + } - final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); - if ((buildingShadow != null) && !"_".equals(buildingShadow)) { - this.terrain.addShadow(buildingShadow, unit.getLocation()[0], unit.getLocation()[1]); - } - final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0); - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - buildingPathingPixelMap = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (buildingPathingPixelMap == null) { - if (pathingTexture.toLowerCase().endsWith(".tga")) { - buildingPathingPixelMap = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - } - else { - try (InputStream stream = this.mapMpq.getResourceAsStream(pathingTexture)) { - buildingPathingPixelMap = ImageIO.read(stream); - System.out.println("LOADING BLP PATHING: " + pathingTexture); - } - } - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), buildingPathingPixelMap); - } - this.terrain.pathingGrid.blitPathingOverlayTexture(unit.getLocation()[0], - unit.getLocation()[1], (int) Math.toDegrees(unit.getAngle()), - buildingPathingPixelMap); - } + final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); + if ((buildingShadow != null) && !"_".equals(buildingShadow)) { + this.terrain.addShadow(buildingShadow, unit.getLocation()[0], unit.getLocation()[1]); + } + final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0); + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + buildingPathingPixelMap = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (buildingPathingPixelMap == null) { + if (pathingTexture.toLowerCase().endsWith(".tga")) { + buildingPathingPixelMap = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + } else { + try (InputStream stream = this.mapMpq.getResourceAsStream(pathingTexture)) { + buildingPathingPixelMap = ImageIO.read(stream); + System.out.println("LOADING BLP PATHING: " + pathingTexture); + } + } + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), buildingPathingPixelMap); + } + this.terrain.pathingGrid.blitPathingOverlayTexture(unit.getLocation()[0], + unit.getLocation()[1], (int) Math.toDegrees(unit.getAngle()), + buildingPathingPixelMap); + } - final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); - UnitSoundset unitSoundset = soundsetNameToSoundset.get(soundName); - if (unitSoundset == null) { - unitSoundset = new UnitSoundset(this.dataSource, this.unitAckSoundsTable, soundName); - soundsetNameToSoundset.put(soundName, unitSoundset); - } - soundset = unitSoundset; - } - } + final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); + UnitSoundset unitSoundset = soundsetNameToSoundset.get(soundName); + if (unitSoundset == null) { + unitSoundset = new UnitSoundset(this.dataSource, this.unitAckSoundsTable, soundName); + soundsetNameToSoundset.put(soundName, unitSoundset); + } + soundset = unitSoundset; + } + } - if (path != null) { - final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); - MdxModel portraitModel; - final String portraitPath = path.substring(0, path.length() - 4) + "_portrait.mdx"; - if (this.dataSource.has(portraitPath)) { - portraitModel = (MdxModel) this.load(portraitPath, this.mapPathSolver, this.solverParams); - } - else { - portraitModel = model; - } - if (type == WorldEditorDataType.UNITS) { - final float angle = (float) Math.toDegrees(unit.getAngle()); - final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), unit.getPlayer(), - unit.getLocation()[0], unit.getLocation()[1], angle, buildingPathingPixelMap); - final RenderUnitTypeData typeData = getUnitTypeData(unit.getId(), row); - final RenderUnit renderUnit = new RenderUnit(this, model, row, unit, soundset, portraitModel, - simulationUnit, typeData); - this.unitToRenderPeer.put(simulationUnit, renderUnit); - this.units.add(renderUnit); - if (unitShadowSplat != null) { - unitShadowSplat.unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { - renderUnit.shadow = t; - } - }); - } - } - else { - this.items.add(new RenderItem(this, model, row, unit, soundset, portraitModel)); // TODO store - // somewhere - if (unitShadowSplat != null) { - unitShadowSplat.unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { + if (path != null) { + final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); + MdxModel portraitModel; + final String portraitPath = path.substring(0, path.length() - 4) + "_portrait.mdx"; + if (this.dataSource.has(portraitPath)) { + portraitModel = (MdxModel) this.load(portraitPath, this.mapPathSolver, this.solverParams); + } else { + portraitModel = model; + } + if (type == WorldEditorDataType.UNITS) { + final float angle = (float) Math.toDegrees(unit.getAngle()); + final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), unit.getPlayer(), + unit.getLocation()[0], unit.getLocation()[1], angle, buildingPathingPixelMap); + final RenderUnitTypeData typeData = getUnitTypeData(unit.getId(), row); + final RenderUnit renderUnit = new RenderUnit(this, model, row, unit, soundset, portraitModel, + simulationUnit, typeData); + this.unitToRenderPeer.put(simulationUnit, renderUnit); + this.units.add(renderUnit); + if (unitShadowSplat != null) { + unitShadowSplat.unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + renderUnit.shadow = t; + } + }); + } + } else { + this.items.add(new RenderItem(this, model, row, unit, soundset, portraitModel)); // TODO store + // somewhere + if (unitShadowSplat != null) { + unitShadowSplat.unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { - } - }); - } - } - } - else { - System.err.println("Unknown unit ID: " + unit.getId()); - } - } - } + } + }); + } + } + } else { + System.err.println("Unknown unit ID: " + unit.getId()); + } + } + } - this.terrain.loadSplats(); + this.terrain.loadSplats(); - this.unitsReady = true; - this.anyReady = true; - } + this.unitsReady = true; + this.anyReady = true; + } - private RenderUnitTypeData getUnitTypeData(final War3ID key, final MutableGameObject row) { - RenderUnitTypeData unitTypeData = this.unitIdToTypeData.get(key); - if (unitTypeData == null) { - unitTypeData = new RenderUnitTypeData(row.getFieldAsFloat(MAX_PITCH, 0), row.getFieldAsFloat(MAX_ROLL, 0), - row.getFieldAsFloat(ELEVATION_SAMPLE_RADIUS, 0)); - this.unitIdToTypeData.put(key, unitTypeData); - } - return unitTypeData; - } + private RenderUnitTypeData getUnitTypeData(final War3ID key, final MutableGameObject row) { + RenderUnitTypeData unitTypeData = this.unitIdToTypeData.get(key); + if (unitTypeData == null) { + unitTypeData = new RenderUnitTypeData(row.getFieldAsFloat(MAX_PITCH, 0), row.getFieldAsFloat(MAX_ROLL, 0), + row.getFieldAsFloat(ELEVATION_SAMPLE_RADIUS, 0)); + this.unitIdToTypeData.put(key, unitTypeData); + } + return unitTypeData; + } - @Override - public void update() { - if (this.anyReady) { - this.terrain.update(); + @Override + public void update() { + if (this.anyReady) { + this.terrain.update(); - super.update(); + super.update(); - for (final RenderUnit unit : this.units) { - unit.updateAnimations(this); - } - final Iterator projectileIterator = this.projectiles.iterator(); - while (projectileIterator.hasNext()) { - final RenderEffect projectile = projectileIterator.next(); - if (projectile.updateAnimations(this, Gdx.graphics.getDeltaTime())) { - projectileIterator.remove(); - } - } - for (final RenderItem item : this.items) { - final MdxComplexInstance instance = item.instance; - final MdxComplexInstance mdxComplexInstance = instance; - if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { - SequenceUtils.randomStandSequence(mdxComplexInstance); - } - } - for (final RenderDoodad item : this.doodads) { - final ModelInstance instance = item.instance; - if (instance instanceof MdxComplexInstance) { - final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance; - if ((mdxComplexInstance.sequence == -1) - || (mdxComplexInstance.sequenceEnded/* - * && (((MdxModel) mdxComplexInstance.model).sequences - * .get(mdxComplexInstance.sequence).getFlags() == 0) - */)) { - SequenceUtils.randomSequence(mdxComplexInstance, item.getAnimation(), SequenceUtils.EMPTY, - true); - } - } - } + for (final RenderUnit unit : this.units) { + unit.updateAnimations(this); + } + final Iterator projectileIterator = this.projectiles.iterator(); + while (projectileIterator.hasNext()) { + final RenderEffect projectile = projectileIterator.next(); + if (projectile.updateAnimations(this, Gdx.graphics.getDeltaTime())) { + projectileIterator.remove(); + } + } + for (final RenderItem item : this.items) { + final MdxComplexInstance instance = item.instance; + final MdxComplexInstance mdxComplexInstance = instance; + if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { + SequenceUtils.randomStandSequence(mdxComplexInstance); + } + } + for (final RenderDoodad item : this.doodads) { + final ModelInstance instance = item.instance; + if (instance instanceof MdxComplexInstance) { + final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance; + if ((mdxComplexInstance.sequence == -1) + || (mdxComplexInstance.sequenceEnded/* + * && (((MdxModel) mdxComplexInstance.model).sequences + * .get(mdxComplexInstance.sequence).getFlags() == 0) + */)) { + SequenceUtils.randomSequence(mdxComplexInstance, item.getAnimation(), SequenceUtils.EMPTY, + true); + } + } + } - final float rawDeltaTime = Gdx.graphics.getRawDeltaTime(); - this.updateTime += rawDeltaTime; - while (this.updateTime >= WarsmashConstants.SIMULATION_STEP_TIME) { - this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME; - this.simulation.update(); - } - this.dncTerrain.setFrameByRatio( - this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); - this.dncTerrain.update(rawDeltaTime, null); - this.dncUnit.setFrameByRatio( - this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); - this.dncUnit.update(rawDeltaTime, null); - this.dncUnitDay.setFrameByRatio(0.5f); - this.dncUnitDay.update(rawDeltaTime, null); - this.dncTarget.setFrameByRatio( - this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); - this.dncTarget.update(rawDeltaTime, null); - } - } + final float rawDeltaTime = Gdx.graphics.getRawDeltaTime(); + this.updateTime += rawDeltaTime; + while (this.updateTime >= WarsmashConstants.SIMULATION_STEP_TIME) { + this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME; + this.simulation.update(); + } + this.dncTerrain.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncTerrain.update(rawDeltaTime, null); + this.dncUnit.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncUnit.update(rawDeltaTime, null); + this.dncUnitDay.setFrameByRatio(0.5f); + this.dncUnitDay.update(rawDeltaTime, null); + this.dncTarget.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncTarget.update(rawDeltaTime, null); + } + } - @Override - public void render() { - if (this.anyReady) { - final Scene worldScene = this.worldScene; + @Override + public void render() { + if (this.anyReady) { + final Scene worldScene = this.worldScene; - startFrame(); - worldScene.startFrame(); - worldScene.renderOpaque(this.dynamicShadowManager, this.webGL); - this.terrain.renderGround(this.dynamicShadowManager); - this.terrain.renderCliffs(); - worldScene.renderOpaque(); - this.terrain.renderUberSplats(); - this.terrain.renderWater(); - worldScene.renderTranslucent(); + startFrame(); + worldScene.startFrame(); + worldScene.renderOpaque(this.dynamicShadowManager, this.webGL); + this.terrain.renderGround(this.dynamicShadowManager); + this.terrain.renderCliffs(); + worldScene.renderOpaque(); + this.terrain.renderUberSplats(); + this.terrain.renderWater(); + worldScene.renderTranslucent(); - final List scenes = this.scenes; - for (final Scene scene : scenes) { - if (scene != worldScene) { - scene.startFrame(); - scene.renderOpaque(); - scene.renderTranslucent(); - } - } - } - } + final List scenes = this.scenes; + for (final Scene scene : scenes) { + if (scene != worldScene) { + scene.startFrame(); + scene.renderOpaque(); + scene.renderTranslucent(); + } + } + } + } - public void deselect() { - if (!this.selModels.isEmpty()) { - for (final SplatModel model : this.selModels) { - this.terrain.removeSplatBatchModel(model); - } - this.selModels.clear(); - for (final RenderUnit unit : this.selected) { - unit.selectionCircle = null; - } - } - this.selected.clear(); - } + public void deselect() { + if (!this.selModels.isEmpty()) { + for (final SplatModel model : this.selModels) { + this.terrain.removeSplatBatchModel(model); + } + this.selModels.clear(); + for (final RenderUnit unit : this.selected) { + unit.selectionCircle = null; + } + } + this.selected.clear(); + } - public void doSelectUnit(final List units) { - deselect(); - if (units.isEmpty()) { - return; - } + public void doSelectUnit(final List units) { + deselect(); + if (units.isEmpty()) { + return; + } - final Map splats = new HashMap(); - for (final RenderUnit unit : units) { - if (unit.row != null) { - if (unit.selectionScale > 0) { - final float selectionSize = unit.selectionScale * this.selectionCircleScaleFactor; - String path = null; - for (int i = 0; i < this.selectionCircleSizes.size(); i++) { - final SelectionCircleSize selectionCircleSize = this.selectionCircleSizes.get(i); - if ((selectionSize < selectionCircleSize.size) - || (i == (this.selectionCircleSizes.size() - 1))) { - path = selectionCircleSize.texture; - break; - } - } - if (!path.toLowerCase().endsWith(".blp")) { - path += ".blp"; - } - if (!splats.containsKey(path)) { - splats.put(path, new Splat()); - } - final float x = unit.location[0]; - final float y = unit.location[1]; - System.out.println("Selecting a unit at " + x + "," + y); - final float z = unit.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); - splats.get(path).locations.add(new float[] { x - (selectionSize / 2), y - (selectionSize / 2), - x + (selectionSize / 2), y + (selectionSize / 2), z + 5 }); - splats.get(path).unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { - unit.selectionCircle = t; - } - }); - } - this.selected.add(unit); - } - } - this.selModels.clear(); - for (final Map.Entry entry : splats.entrySet()) { - final String path = entry.getKey(); - final Splat locations = entry.getValue(); - final SplatModel model = new SplatModel(Gdx.gl30, (Texture) load(path, PathSolver.DEFAULT, null), - locations.locations, this.terrain.centerOffset, locations.unitMapping, true); - model.color[0] = 0; - model.color[1] = 1; - model.color[2] = 0; - model.color[3] = 1; - this.selModels.add(model); - this.terrain.addSplatBatchModel(model); - } - } + final Map splats = new HashMap(); + for (final RenderUnit unit : units) { + if (unit.row != null) { + if (unit.selectionScale > 0) { + final float selectionSize = unit.selectionScale * this.selectionCircleScaleFactor; + String path = null; + for (int i = 0; i < this.selectionCircleSizes.size(); i++) { + final SelectionCircleSize selectionCircleSize = this.selectionCircleSizes.get(i); + if ((selectionSize < selectionCircleSize.size) + || (i == (this.selectionCircleSizes.size() - 1))) { + path = selectionCircleSize.texture; + break; + } + } + if (!path.toLowerCase().endsWith(".blp")) { + path += ".blp"; + } + if (!splats.containsKey(path)) { + splats.put(path, new Splat()); + } + final float x = unit.location[0]; + final float y = unit.location[1]; + System.out.println("Selecting a unit at " + x + "," + y); + final float z = unit.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); + splats.get(path).locations.add(new float[]{x - (selectionSize / 2), y - (selectionSize / 2), + x + (selectionSize / 2), y + (selectionSize / 2), z + 5}); + splats.get(path).unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + unit.selectionCircle = t; + } + }); + } + this.selected.add(unit); + } + } + this.selModels.clear(); + for (final Map.Entry entry : splats.entrySet()) { + final String path = entry.getKey(); + final Splat locations = entry.getValue(); + final SplatModel model = new SplatModel(Gdx.gl30, (Texture) load(path, PathSolver.DEFAULT, null), + locations.locations, this.terrain.centerOffset, locations.unitMapping, true); + model.color[0] = 0; + model.color[1] = 1; + model.color[2] = 0; + model.color[3] = 1; + this.selModels.add(model); + this.terrain.addSplatBatchModel(model); + } + } - public void getClickLocation(final Vector3 out, final int screenX, final int screenY) { - final float[] ray = rayHeap; - mousePosHeap.set(screenX, screenY); - this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); - gdxRayHeap.direction.nor();// needed for libgdx - RenderMathUtils.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices, - this.terrain.softwareGroundMesh.indices, 3, out); - } + public void getClickLocation(final Vector3 out, final int screenX, final int screenY) { + final float[] ray = rayHeap; + mousePosHeap.set(screenX, screenY); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx + RenderMathUtils.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices, + this.terrain.softwareGroundMesh.indices, 3, out); + rectangleHeap.set(Math.min(out.x, gdxRayHeap.origin.x), Math.min(out.y, gdxRayHeap.origin.y), Math.abs(out.x - gdxRayHeap.origin.x), Math.abs(out.y - gdxRayHeap.origin.y)); + walkableObjectsTree.intersect(rectangleHeap, walkablesIntersectionFinder.reset(gdxRayHeap)); + if (walkablesIntersectionFinder.found) { + out.set(walkablesIntersectionFinder.intersection); + } else { + out.z = Math.max(getWalkableRenderHeight(out.x, out.y), terrain.getGroundHeight(out.x, out.y)); + } + } - public void showConfirmation(final Vector3 position, final float red, final float green, final float blue) { - this.confirmationInstance.show(); - this.confirmationInstance.setSequence(0); - this.confirmationInstance.setLocation(position); - this.worldScene.instanceMoved(this.confirmationInstance, position.x, position.y); - this.confirmationInstance.vertexColor[0] = red; - this.confirmationInstance.vertexColor[1] = green; - this.confirmationInstance.vertexColor[2] = blue; - } + public void showConfirmation(final Vector3 position, final float red, final float green, final float blue) { + this.confirmationInstance.show(); + this.confirmationInstance.setSequence(0); + this.confirmationInstance.setLocation(position); + this.worldScene.instanceMoved(this.confirmationInstance, position.x, position.y); + this.confirmationInstance.vertexColor[0] = red; + this.confirmationInstance.vertexColor[1] = green; + this.confirmationInstance.vertexColor[2] = blue; + } - public List selectUnit(final float x, final float y, final boolean toggle) { - System.out.println("world: " + x + "," + y); - final float[] ray = rayHeap; - mousePosHeap.set(x, y); - this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); - gdxRayHeap.direction.nor();// needed for libgdx + public List selectUnit(final float x, final float y, final boolean toggle) { + System.out.println("world: " + x + "," + y); + final float[] ray = rayHeap; + mousePosHeap.set(x, y); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx - RenderUnit entity = null; - for (final RenderUnit unit : this.units) { - final MdxComplexInstance instance = unit.instance; - if (instance.isVisible(this.worldScene.camera) - && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap, - unit.getSimulationUnit().getUnitType().isBuilding()) - && !unit.getSimulationUnit().isDead()) { - entity = unit; - } - } - List sel; - if (entity != null) { - if (toggle) { - sel = new ArrayList<>(this.selected); - final int idx = sel.indexOf(entity); - if (idx >= 0) { - sel.remove(idx); - } - else { - sel.add(entity); - } - } - else { - sel = Arrays.asList(entity); - } - } - else { - sel = Collections.emptyList(); - } - this.doSelectUnit(sel); - return sel; - } + RenderUnit entity = null; + for (final RenderUnit unit : this.units) { + final MdxComplexInstance instance = unit.instance; + if (instance.isVisible(this.worldScene.camera) + && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap, + unit.getSimulationUnit().getUnitType().isBuilding(), false) + && !unit.getSimulationUnit().isDead()) { + entity = unit; + } + } + List sel; + if (entity != null) { + if (toggle) { + sel = new ArrayList<>(this.selected); + final int idx = sel.indexOf(entity); + if (idx >= 0) { + sel.remove(idx); + } else { + sel.add(entity); + } + } else { + sel = Arrays.asList(entity); + } + } else { + sel = Collections.emptyList(); + } + this.doSelectUnit(sel); + return sel; + } - public RenderUnit rayPickUnit(final float x, final float y) { - final float[] ray = rayHeap; - mousePosHeap.set(x, y); - this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); - gdxRayHeap.direction.nor();// needed for libgdx + public RenderUnit rayPickUnit(final float x, final float y) { + final float[] ray = rayHeap; + mousePosHeap.set(x, y); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx - RenderUnit entity = null; - for (final RenderUnit unit : this.units) { - final MdxComplexInstance instance = unit.instance; - if (instance.isVisible(this.worldScene.camera) && instance.intersectRayWithCollision(gdxRayHeap, - intersectionHeap, unit.getSimulationUnit().getUnitType().isBuilding())) { - entity = unit; - } - } - return entity; - } + RenderUnit entity = null; + for (final RenderUnit unit : this.units) { + final MdxComplexInstance instance = unit.instance; + if (instance.isVisible(this.worldScene.camera) && instance.intersectRayWithCollision(gdxRayHeap, + intersectionHeap, unit.getSimulationUnit().getUnitType().isBuilding(), false)) { + entity = unit; + } + } + return entity; + } - private static final class MappedDataCallbackImplementation implements LoadGenericCallback { - @Override - public Object call(final InputStream data) { - final StringBuilder stringBuilder = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { - String line; - while ((line = reader.readLine()) != null) { - stringBuilder.append(line); - stringBuilder.append("\n"); - } - } - catch (final UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - catch (final IOException e) { - throw new RuntimeException(e); - } - return new MappedData(stringBuilder.toString()); - } - } + private static final class MappedDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + final StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append("\n"); + } + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } catch (final IOException e) { + throw new RuntimeException(e); + } + return new MappedData(stringBuilder.toString()); + } + } - private static final class StringDataCallbackImplementation implements LoadGenericCallback { - @Override - public Object call(final InputStream data) { - if (data == null) { - System.err.println("data null"); - } - final StringBuilder stringBuilder = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { - String line; - while ((line = reader.readLine()) != null) { - stringBuilder.append(line); - stringBuilder.append("\n"); - } - } - catch (final UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - catch (final IOException e) { - throw new RuntimeException(e); - } - return stringBuilder.toString(); - } - } + private static final class StringDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + if (data == null) { + System.err.println("data null"); + } + final StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append("\n"); + } + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } catch (final IOException e) { + throw new RuntimeException(e); + } + return stringBuilder.toString(); + } + } - private static final class StreamDataCallbackImplementation implements LoadGenericCallback { - @Override - public Object call(final InputStream data) { - return data; - } - } + private static final class StreamDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + return data; + } + } - public static final class SolverParams { - public char tileset; - public boolean reforged; - public boolean hd; - } + public static final class SolverParams { + public char tileset; + public boolean reforged; + public boolean hd; + } - public static final class CliffInfo { - public List locations = new ArrayList<>(); - public List textures = new ArrayList<>(); - } + public static final class CliffInfo { + public List locations = new ArrayList<>(); + public List textures = new ArrayList<>(); + } - private static final int MAXIMUM_ACCEPTED = 1 << 30; - private float selectionCircleScaleFactor; - private DataTable worldEditData; - private WorldEditStrings worldEditStrings; - private Warcraft3MapObjectData allObjectData; - private AbilityDataUI abilityDataUI; + private static final int MAXIMUM_ACCEPTED = 1 << 30; + private float selectionCircleScaleFactor; + private DataTable worldEditData; + private WorldEditStrings worldEditStrings; + private Warcraft3MapObjectData allObjectData; + private AbilityDataUI abilityDataUI; - /** - * Returns a power of two size for the given target capacity. - */ - private static final int pow2GreaterThan(final int capacity) { - int numElements = capacity - 1; - numElements |= numElements >>> 1; - numElements |= numElements >>> 2; - numElements |= numElements >>> 4; - numElements |= numElements >>> 8; - numElements |= numElements >>> 16; - return (numElements < 0) ? 1 : (numElements >= MAXIMUM_ACCEPTED) ? MAXIMUM_ACCEPTED : numElements + 1; - } + /** + * Returns a power of two size for the given target capacity. + */ + private static final int pow2GreaterThan(final int capacity) { + int numElements = capacity - 1; + numElements |= numElements >>> 1; + numElements |= numElements >>> 2; + numElements |= numElements >>> 4; + numElements |= numElements >>> 8; + numElements |= numElements >>> 16; + return (numElements < 0) ? 1 : (numElements >= MAXIMUM_ACCEPTED) ? MAXIMUM_ACCEPTED : numElements + 1; + } - public boolean orderSmart(final float x, final float y) { - mousePosHeap.x = x; - mousePosHeap.y = y; - boolean ordered = false; - for (final RenderUnit unit : this.selected) { - for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { - if (ability instanceof CAbilityMove) { - ability.checkCanUse(this.simulation, unit.getSimulationUnit(), OrderIds.smart, - BooleanAbilityActivationReceiver.INSTANCE); - if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { - ability.checkCanTarget(this.simulation, unit.getSimulationUnit(), OrderIds.smart, mousePosHeap, - PointAbilityTargetCheckReceiver.INSTANCE); - final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (target != null) { - ability.onOrder(this.simulation, unit.getSimulationUnit(), OrderIds.smart, mousePosHeap, - false); - ordered = true; - } - else { - System.err.println("Target not valid."); - } - } - else { - System.err.println("Ability not ok to use."); - } - } - else { - System.err.println("Ability not move."); - } - } + public boolean orderSmart(final float x, final float y) { + mousePosHeap.x = x; + mousePosHeap.y = y; + boolean ordered = false; + for (final RenderUnit unit : this.selected) { + for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { + if (ability instanceof CAbilityMove) { + ability.checkCanUse(this.simulation, unit.getSimulationUnit(), OrderIds.smart, + BooleanAbilityActivationReceiver.INSTANCE); + if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { + ability.checkCanTarget(this.simulation, unit.getSimulationUnit(), OrderIds.smart, mousePosHeap, + PointAbilityTargetCheckReceiver.INSTANCE); + final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (target != null) { + ability.onOrder(this.simulation, unit.getSimulationUnit(), OrderIds.smart, mousePosHeap, + false); + ordered = true; + } else { + System.err.println("Target not valid."); + } + } else { + System.err.println("Ability not ok to use."); + } + } else { + System.err.println("Ability not move."); + } + } - } - return ordered; - } + } + return ordered; + } - public boolean orderSmart(final RenderUnit target) { - boolean ordered = false; - for (final RenderUnit unit : this.selected) { - for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { - if (ability instanceof CAbilityAttack) { - ability.checkCanUse(this.simulation, unit.getSimulationUnit(), OrderIds.smart, - BooleanAbilityActivationReceiver.INSTANCE); - if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { - ability.checkCanTarget(this.simulation, unit.getSimulationUnit(), OrderIds.smart, - target.getSimulationUnit(), CWidgetAbilityTargetCheckReceiver.INSTANCE); - final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (targetWidget != null) { - ability.onOrder(this.simulation, unit.getSimulationUnit(), OrderIds.smart, targetWidget, - false); - ordered = true; - } - else { - System.err.println("Target not valid."); - } - } - else { - System.err.println("Ability not ok to use."); - } - } - else { - System.err.println("Ability not move."); - } - } + public boolean orderSmart(final RenderUnit target) { + boolean ordered = false; + for (final RenderUnit unit : this.selected) { + for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { + if (ability instanceof CAbilityAttack) { + ability.checkCanUse(this.simulation, unit.getSimulationUnit(), OrderIds.smart, + BooleanAbilityActivationReceiver.INSTANCE); + if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { + ability.checkCanTarget(this.simulation, unit.getSimulationUnit(), OrderIds.smart, + target.getSimulationUnit(), CWidgetAbilityTargetCheckReceiver.INSTANCE); + final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (targetWidget != null) { + ability.onOrder(this.simulation, unit.getSimulationUnit(), OrderIds.smart, targetWidget, + false); + ordered = true; + } else { + System.err.println("Target not valid."); + } + } else { + System.err.println("Ability not ok to use."); + } + } else { + System.err.println("Ability not move."); + } + } - } - return ordered; - } + } + return ordered; + } - public void standOnRepeat(final MdxComplexInstance instance) { - instance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - SequenceUtils.randomStandSequence(instance); - } + public void standOnRepeat(final MdxComplexInstance instance) { + instance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + SequenceUtils.randomStandSequence(instance); + } - private static final class SelectionCircleSize { - private final float size; - private final String texture; - private final String textureDotted; + private static final class SelectionCircleSize { + private final float size; + private final String texture; + private final String textureDotted; - public SelectionCircleSize(final float size, final String texture, final String textureDotted) { - this.size = size; - this.texture = texture; - this.textureDotted = textureDotted; - } - } + public SelectionCircleSize(final float size, final String texture, final String textureDotted) { + this.size = size; + this.texture = texture; + this.textureDotted = textureDotted; + } + } - public void setDayNightModels(final String terrainDNCFile, final String unitDNCFile) { - final MdxModel terrainDNCModel = (MdxModel) load(mdx(terrainDNCFile), PathSolver.DEFAULT, null); - this.dncTerrain = (MdxComplexInstance) terrainDNCModel.addInstance(); - this.dncTerrain.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncTerrain.setSequence(0); - final MdxModel unitDNCModel = (MdxModel) load(mdx(unitDNCFile), PathSolver.DEFAULT, null); - this.dncUnit = (MdxComplexInstance) unitDNCModel.addInstance(); - this.dncUnit.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncUnit.setSequence(0); - this.dncUnitDay = (MdxComplexInstance) unitDNCModel.addInstance(); - this.dncUnitDay.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncUnitDay.setSequence(0); - final MdxModel targetDNCModel = (MdxModel) load( - mdx("Environment\\DNC\\DNCLordaeron\\DNCLordaeronTarget\\DNCLordaeronTarget.mdl"), PathSolver.DEFAULT, - null); - this.dncTarget = (MdxComplexInstance) targetDNCModel.addInstance(); - this.dncTarget.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncTarget.setSequence(0); - } + public void setDayNightModels(final String terrainDNCFile, final String unitDNCFile) { + final MdxModel terrainDNCModel = (MdxModel) load(mdx(terrainDNCFile), PathSolver.DEFAULT, null); + this.dncTerrain = (MdxComplexInstance) terrainDNCModel.addInstance(); + this.dncTerrain.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncTerrain.setSequence(0); + final MdxModel unitDNCModel = (MdxModel) load(mdx(unitDNCFile), PathSolver.DEFAULT, null); + this.dncUnit = (MdxComplexInstance) unitDNCModel.addInstance(); + this.dncUnit.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncUnit.setSequence(0); + this.dncUnitDay = (MdxComplexInstance) unitDNCModel.addInstance(); + this.dncUnitDay.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncUnitDay.setSequence(0); + final MdxModel targetDNCModel = (MdxModel) load( + mdx("Environment\\DNC\\DNCLordaeron\\DNCLordaeronTarget\\DNCLordaeronTarget.mdl"), PathSolver.DEFAULT, + null); + this.dncTarget = (MdxComplexInstance) targetDNCModel.addInstance(); + this.dncTarget.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncTarget.setSequence(0); + } - private static String mdx(String mdxPath) { - if (mdxPath.toLowerCase().endsWith(".mdl")) { - mdxPath = mdxPath.substring(0, mdxPath.length() - 4); - } - if (!mdxPath.toLowerCase().endsWith(".mdx")) { - mdxPath += ".mdx"; - } - return mdxPath; - } + private static String mdx(String mdxPath) { + if (mdxPath.toLowerCase().endsWith(".mdl")) { + mdxPath = mdxPath.substring(0, mdxPath.length() - 4); + } + if (!mdxPath.toLowerCase().endsWith(".mdx")) { + mdxPath += ".mdx"; + } + return mdxPath; + } - @Override - public SceneLightManager createLightManager(final boolean simple) { - if (simple) { - return new W3xScenePortraitLightManager(this, this.lightDirection); - } - else { - return new W3xSceneWorldLightManager(this); - } - } + @Override + public SceneLightManager createLightManager(final boolean simple) { + if (simple) { + return new W3xScenePortraitLightManager(this, this.lightDirection); + } else { + return new W3xSceneWorldLightManager(this); + } + } - public WorldEditStrings getWorldEditStrings() { - return this.worldEditStrings; - } + public WorldEditStrings getWorldEditStrings() { + return this.worldEditStrings; + } - public void setGameUI(final GameUI gameUI) { - this.gameUI = gameUI; - this.abilityDataUI = new AbilityDataUI(this.allObjectData.getAbilities(), gameUI); - } + public void setGameUI(final GameUI gameUI) { + this.gameUI = gameUI; + this.abilityDataUI = new AbilityDataUI(this.allObjectData.getAbilities(), gameUI); + } - public GameUI getGameUI() { - return this.gameUI; - } + public GameUI getGameUI() { + return this.gameUI; + } - public AbilityDataUI getAbilityDataUI() { - return this.abilityDataUI; - } + public AbilityDataUI getAbilityDataUI() { + return this.abilityDataUI; + } + + public float getWalkableRenderHeight(float x, float y) { + walkableObjectsTree.intersect(x, y, walkablesIntersector.reset(x, y)); + return walkablesIntersector.z; + } + + public MdxComplexInstance getHighestWalkableUnder(float x, float y) { + walkableObjectsTree.intersect(x, y, intersectorFindsHighestWalkable.reset(x, y)); + return intersectorFindsHighestWalkable.highestInstance; + } + + private static final class QuadtreeIntersectorFindsWalkableRenderHeight implements QuadtreeIntersector { + private float z; + private Ray ray = new Ray(); + private Vector3 intersection = new Vector3(); + + private QuadtreeIntersectorFindsWalkableRenderHeight reset(float x, float y) { + z = -Float.MAX_VALUE; + ray.set(x, y, 4096, 0, 0, -8192); + return this; + } + + @Override + public boolean onIntersect(MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(ray, intersection, true, true)) { + z = Math.max(z, intersection.z); + } + return false; + } + } + + private static final class QuadtreeIntersectorFindsHighestWalkable implements QuadtreeIntersector { + private float z; + private Ray ray = new Ray(); + private Vector3 intersection = new Vector3(); + private MdxComplexInstance highestInstance; + + private QuadtreeIntersectorFindsHighestWalkable reset(float x, float y) { + z = -Float.MAX_VALUE; + ray.set(x, y, 4096, 0, 0, -8192); + highestInstance = null; + return this; + } + + @Override + public boolean onIntersect(MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(ray, intersection, true, true)) { + if(intersection.z > z) { + z = intersection.z; + highestInstance = intersectingObject; + } + } + return false; + } + } + + private static final class QuadtreeIntersectorFindsHitPoint implements QuadtreeIntersector { + private Ray ray; + private Vector3 intersection = new Vector3(); + private boolean found; + + private QuadtreeIntersectorFindsHitPoint reset(Ray ray) { + this.ray = ray; + this.found = false; + return this; + } + + @Override + public boolean onIntersect(MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(ray, intersection, true, true)) { + this.found = true; + return true; + } + return false; + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 08edfec..368a23d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -411,6 +411,7 @@ public class Terrain { this.shaderMapBoundsRectangle = new Rectangle(this.shaderMapBounds[0], this.shaderMapBounds[1], this.shaderMapBounds[2] - this.shaderMapBounds[0], this.shaderMapBounds[3] - this.shaderMapBounds[1]); this.mapSize = w3eFile.getMapSize(); + this.entireMapRectangle = new Rectangle(centerOffset[0], centerOffset[1], (mapSize[0] * 128f) - 128, (mapSize[1] * 128f) - 128); this.softwareGroundMesh = new SoftwareGroundMesh(this.groundHeights, this.groundCornerHeights, this.centerOffset, width, height); @@ -1019,15 +1020,14 @@ public class Terrain { gl.glUniform1i(6, (int) this.waterIndex); gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); - gl.glUniform4fv(9, 1, this.shaderMapBounds, 0); + gl.glUniform4fv(11, 1, this.shaderMapBounds, 0); final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); - final DataTexture unitLightsTexture = lightManager.getTerrainLightsTexture(); + final DataTexture terrainLightsTexture = lightManager.getTerrainLightsTexture(); - unitLightsTexture.bind(21); - gl.glUniform1i(this.waterShader.getUniformLocation("lightTexture"), 21); - gl.glUniform1f(this.waterShader.getUniformLocation("lightCount"), lightManager.getTerrainLightCount()); - gl.glUniform1f(this.waterShader.getUniformLocation("lightCountHeight"), unitLightsTexture.getHeight()); + terrainLightsTexture.bind(3); + gl.glUniform1f(9, lightManager.getTerrainLightCount()); + gl.glUniform1f(10, terrainLightsTexture.getHeight()); gl.glActiveTexture(GL30.GL_TEXTURE0); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight); @@ -1035,7 +1035,7 @@ public class Terrain { gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); gl.glActiveTexture(GL30.GL_TEXTURE2); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterExists); - gl.glActiveTexture(GL30.GL_TEXTURE3); + gl.glActiveTexture(GL30.GL_TEXTURE4); gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.waterTextureArray); gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, Shapes.INSTANCE.vertexBuffer); @@ -1239,6 +1239,7 @@ public class Terrain { private final WaveBuilder waveBuilder; public PathingGrid pathingGrid; private final Rectangle shaderMapBoundsRectangle; + private final Rectangle entireMapRectangle; private static final class UnloadedTexture { private final int width; @@ -1399,4 +1400,8 @@ public class Terrain { public Rectangle getPlayableMapArea() { return this.shaderMapBoundsRectangle; } + + public Rectangle getEntireMap() { + return entireMapRectangle; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java index 3771d07..4509e1e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java @@ -428,9 +428,9 @@ public class TerrainShaders { "layout (location = 3) uniform vec4 deep_color_min;\r\n" + // "layout (location = 4) uniform vec4 deep_color_max;\r\n" + // "layout (location = 5) uniform float water_offset;\r\n" + // - "layout (location = 10) uniform sampler2D lightTexture;\r\n" + // - "layout (location = 11) uniform float lightCount;\r\n" + // - "layout (location = 12) uniform float lightTextureHeight;\r\n" + // + "layout (binding = 3) uniform sampler2D lightTexture;\r\n" + // + "layout (location = 9) uniform float lightCount;\r\n" + // + "layout (location = 10) uniform float lightTextureHeight;\r\n" + // "\r\n" + // "out vec2 UV;\r\n" + // "out vec4 Color;\r\n" + // @@ -477,12 +477,12 @@ public class TerrainShaders { public static final String frag = "#version 450 core\r\n" + // "\r\n" + // - "layout (binding = 3) uniform sampler2DArray water_textures;\r\n" + // + "layout (binding = 4) uniform sampler2DArray water_textures;\r\n" + // "layout (binding = 2) uniform sampler2D water_exists_texture;\r\n" + // "\r\n" + // "\r\n" + // "layout (location = 6) uniform int current_texture;\r\n" + // - "layout (location = 9) uniform vec4 mapBounds;\r\n" + // + "layout (location = 11) uniform vec4 mapBounds;\r\n" + // "\r\n" + // "in vec2 UV;\r\n" + // "in vec4 Color;\r\n" + // diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 8fc6aed..0022736 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -29,374 +29,431 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitAnimationListe import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; public class RenderUnit { - private static final Quaternion tempQuat = new Quaternion(); - private static final War3ID RED = War3ID.fromString("uclr"); - private static final War3ID GREEN = War3ID.fromString("uclg"); - private static final War3ID BLUE = War3ID.fromString("uclb"); - private static final War3ID MODEL_SCALE = War3ID.fromString("usca"); - private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); - private static final War3ID ORIENTATION_INTERPOLATION = War3ID.fromString("uori"); - private static final War3ID ANIM_PROPS = War3ID.fromString("uani"); - private static final float[] heapZ = new float[3]; - public final MdxComplexInstance instance; - public final MutableGameObject row; - public final float[] location = new float[3]; - public float selectionScale; - public UnitSoundset soundset; - public final MdxModel portraitModel; - public int playerIndex; - private final CUnit simulationUnit; - public SplatMover shadow; - public SplatMover selectionCircle; + private static final Quaternion tempQuat = new Quaternion(); + private static final War3ID RED = War3ID.fromString("uclr"); + private static final War3ID GREEN = War3ID.fromString("uclg"); + private static final War3ID BLUE = War3ID.fromString("uclb"); + private static final War3ID MODEL_SCALE = War3ID.fromString("usca"); + private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); + private static final War3ID ORIENTATION_INTERPOLATION = War3ID.fromString("uori"); + private static final War3ID ANIM_PROPS = War3ID.fromString("uani"); + private static final float[] heapZ = new float[3]; + public final MdxComplexInstance instance; + public final MutableGameObject row; + public final float[] location = new float[3]; + public float selectionScale; + public UnitSoundset soundset; + public final MdxModel portraitModel; + public int playerIndex; + private final CUnit simulationUnit; + public SplatMover shadow; + public SplatMover selectionCircle; - private float x; - private float y; - private float facing; + private float x; + private float y; + private float facing; - private boolean swimming; + private boolean swimming; - private boolean dead = false; + private boolean dead = false; - private final UnitAnimationListenerImpl unitAnimationListenerImpl; - private OrientationInterpolation orientationInterpolation; - private float currentTurnVelocity = 0; - public long lastUnitResponseEndTimeMillis; - private boolean corpse; - private boolean boneCorpse; - private final RenderUnitTypeData typeData; + private final UnitAnimationListenerImpl unitAnimationListenerImpl; + private OrientationInterpolation orientationInterpolation; + private float currentTurnVelocity = 0; + public long lastUnitResponseEndTimeMillis; + private boolean corpse; + private boolean boneCorpse; + private final RenderUnitTypeData typeData; - public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, - final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final UnitSoundset soundset, - final MdxModel portraitModel, final CUnit simulationUnit, final RenderUnitTypeData typeData) { - this.portraitModel = portraitModel; - this.simulationUnit = simulationUnit; - this.typeData = typeData; - final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance(); + public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, + final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final UnitSoundset soundset, + final MdxModel portraitModel, final CUnit simulationUnit, final RenderUnitTypeData typeData) { + this.portraitModel = portraitModel; + this.simulationUnit = simulationUnit; + this.typeData = typeData; + final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance(); - final float[] location = unit.getLocation(); - System.arraycopy(location, 0, this.location, 0, 3); - instance.move(location); - this.facing = simulationUnit.getFacing(); - final float angle = (float) Math.toRadians(this.facing); + final float[] location = unit.getLocation(); + System.arraycopy(location, 0, this.location, 0, 3); + instance.move(location); + this.facing = simulationUnit.getFacing(); + final float angle = (float) Math.toRadians(this.facing); // instance.localRotation.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle); - this.x = simulationUnit.getX(); - this.y = simulationUnit.getY(); - instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle)); - instance.scale(unit.getScale()); - this.playerIndex = unit.getPlayer() & 0xFFFF; - instance.setTeamColor(this.playerIndex); - instance.setScene(map.worldScene); - this.unitAnimationListenerImpl = new UnitAnimationListenerImpl(instance); - simulationUnit.setUnitAnimationListener(this.unitAnimationListenerImpl); - final String requiredAnimationNames = row.getFieldAsString(ANIM_PROPS, 0); - TokenLoop: for (final String animationName : requiredAnimationNames.split(",")) { - final String upperCaseToken = animationName.toUpperCase(); - for (final SecondaryTag secondaryTag : SecondaryTag.values()) { - if (upperCaseToken.equals(secondaryTag.name())) { - this.unitAnimationListenerImpl.addSecondaryTag(secondaryTag); - continue TokenLoop; - } - } - } + this.x = simulationUnit.getX(); + this.y = simulationUnit.getY(); + instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle)); + instance.scale(unit.getScale()); + this.playerIndex = unit.getPlayer() & 0xFFFF; + instance.setTeamColor(this.playerIndex); + instance.setScene(map.worldScene); + this.unitAnimationListenerImpl = new UnitAnimationListenerImpl(instance); + simulationUnit.setUnitAnimationListener(this.unitAnimationListenerImpl); + final String requiredAnimationNames = row.getFieldAsString(ANIM_PROPS, 0); + TokenLoop: + for (final String animationName : requiredAnimationNames.split(",")) { + final String upperCaseToken = animationName.toUpperCase(); + for (final SecondaryTag secondaryTag : SecondaryTag.values()) { + if (upperCaseToken.equals(secondaryTag.name())) { + this.unitAnimationListenerImpl.addSecondaryTag(secondaryTag); + continue TokenLoop; + } + } + } - if (row != null) { - heapZ[2] = simulationUnit.getFlyHeight(); - this.location[2] += heapZ[2]; + if (row != null) { + heapZ[2] = simulationUnit.getFlyHeight(); + this.location[2] += heapZ[2]; - instance.move(heapZ); - War3ID red; - War3ID green; - War3ID blue; - War3ID scale; - scale = MODEL_SCALE; - red = RED; - green = GREEN; - blue = BLUE; - instance.setVertexColor(new float[] { (row.getFieldAsInteger(red, 0)) / 255f, - (row.getFieldAsInteger(green, 0)) / 255f, (row.getFieldAsInteger(blue, 0)) / 255f }); - instance.uniformScale(row.getFieldAsFloat(scale, 0)); + instance.move(heapZ); + War3ID red; + War3ID green; + War3ID blue; + War3ID scale; + scale = MODEL_SCALE; + red = RED; + green = GREEN; + blue = BLUE; + instance.setVertexColor(new float[]{(row.getFieldAsInteger(red, 0)) / 255f, + (row.getFieldAsInteger(green, 0)) / 255f, (row.getFieldAsInteger(blue, 0)) / 255f}); + instance.uniformScale(row.getFieldAsFloat(scale, 0)); - this.selectionScale = row.getFieldAsFloat(War3MapViewer.UNIT_SELECT_SCALE, 0); - int orientationInterpolationOrdinal = row.getFieldAsInteger(ORIENTATION_INTERPOLATION, 0); - if ((orientationInterpolationOrdinal < 0) - || (orientationInterpolationOrdinal >= OrientationInterpolation.VALUES.length)) { - orientationInterpolationOrdinal = 0; - } - this.orientationInterpolation = OrientationInterpolation.VALUES[orientationInterpolationOrdinal]; - } + this.selectionScale = row.getFieldAsFloat(War3MapViewer.UNIT_SELECT_SCALE, 0); + int orientationInterpolationOrdinal = row.getFieldAsInteger(ORIENTATION_INTERPOLATION, 0); + if ((orientationInterpolationOrdinal < 0) + || (orientationInterpolationOrdinal >= OrientationInterpolation.VALUES.length)) { + orientationInterpolationOrdinal = 0; + } + this.orientationInterpolation = OrientationInterpolation.VALUES[orientationInterpolationOrdinal]; + } - this.instance = instance; - this.row = row; - this.soundset = soundset; + this.instance = instance; + this.row = row; + this.soundset = soundset; - } + } - public void populateCommandCard(final CommandButtonListener commandButtonListener, - final AbilityDataUI abilityDataUI) { - for (final CAbility ability : this.simulationUnit.getAbilities()) { - ability.visit(CommandCardPopulatingAbilityVisitor.INSTANCE.reset(commandButtonListener, abilityDataUI)); - } - } + public void populateCommandCard(final CommandButtonListener commandButtonListener, + final AbilityDataUI abilityDataUI) { + for (final CAbility ability : this.simulationUnit.getAbilities()) { + ability.visit(CommandCardPopulatingAbilityVisitor.INSTANCE.reset(commandButtonListener, abilityDataUI)); + } + } - public void updateAnimations(final War3MapViewer map) { - final float deltaTime = Gdx.graphics.getDeltaTime(); - final float simulationX = this.simulationUnit.getX(); - final float simulationY = this.simulationUnit.getY(); - final float simDx = simulationX - this.x; - final float simDy = simulationY - this.y; - final float distanceToSimulation = (float) Math.sqrt((simDx * simDx) + (simDy * simDy)); - final int speed = this.simulationUnit.getSpeed(); - final float speedDelta = speed * deltaTime; - if ((distanceToSimulation > speedDelta) && (deltaTime < 1.0)) { - // The 1.0 here says that after 1 second of lag, units just teleport to show - // where they actually are - this.x += (speedDelta * simDx) / distanceToSimulation; - this.y += (speedDelta * simDy) / distanceToSimulation; - } - else { - this.x = simulationX; - this.y = simulationY; - } - final float x = this.x; - final float dx = x - this.location[0]; - this.location[0] = x; - final float y = this.y; - final float dy = y - this.location[1]; - this.location[1] = y; - final float groundHeight; - final MovementType movementType = this.simulationUnit.getUnitType().getMovementType(); - final short terrainPathing = map.terrain.pathingGrid.getPathing(x, y); - final boolean swimming = (movementType == MovementType.AMPHIBIOUS) - && PathingGrid.isPathingFlag(terrainPathing, PathingGrid.PathingType.SWIMMABLE) - && !PathingGrid.isPathingFlag(terrainPathing, PathingGrid.PathingType.WALKABLE); - if ((swimming) || (movementType == MovementType.FLOAT) || (movementType == MovementType.FLY) - || (movementType == MovementType.HOVER)) { - groundHeight = Math.max(map.terrain.getGroundHeight(x, y), map.terrain.getWaterHeight(x, y)); - } - else { - groundHeight = map.terrain.getGroundHeight(x, y); - } - if (swimming && !this.swimming) { - this.unitAnimationListenerImpl.addSecondaryTag(AnimationTokens.SecondaryTag.SWIM); - } - else if (!swimming && this.swimming) { - this.unitAnimationListenerImpl.removeSecondaryTag(AnimationTokens.SecondaryTag.SWIM); - } - this.swimming = swimming; - final boolean dead = this.simulationUnit.isDead(); - final boolean corpse = this.simulationUnit.isCorpse(); - final boolean boneCorpse = this.simulationUnit.isBoneCorpse(); - if (dead && !this.dead) { - this.unitAnimationListenerImpl.playAnimation(true, PrimaryTag.DEATH, SequenceUtils.EMPTY, 1.0f, true); - if (this.shadow != null) { - this.shadow.destroy(Gdx.gl30, map.terrain.centerOffset); - this.shadow = null; - } - if (this.selectionCircle != null) { - this.selectionCircle.destroy(Gdx.gl30, map.terrain.centerOffset); - this.selectionCircle = null; - } - } - if (boneCorpse && !this.boneCorpse) { - this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.BONE, - this.simulationUnit.getEndingDecayTime(map.simulation), true); - } - else if (corpse && !this.corpse) { - this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.FLESH, - map.simulation.getGameplayConstants().getDecayTime(), true); - } - this.dead = dead; - this.corpse = corpse; - this.boneCorpse = boneCorpse; - this.location[2] = this.simulationUnit.getFlyHeight() + groundHeight; - this.instance.moveTo(this.location); - float simulationFacing = this.simulationUnit.getFacing(); - if (simulationFacing < 0) { - simulationFacing += 360; - } - float renderFacing = this.facing; - if (renderFacing < 0) { - renderFacing += 360; - } - float facingDelta = simulationFacing - renderFacing; - if (facingDelta < -180) { - facingDelta = 360 + facingDelta; - } - if (facingDelta > 180) { - facingDelta = -360 + facingDelta; - } - final float absoluteFacingDelta = Math.abs(facingDelta); - final float turningSign = Math.signum(facingDelta); + public void updateAnimations(final War3MapViewer map) { + final float deltaTime = Gdx.graphics.getDeltaTime(); + final float simulationX = this.simulationUnit.getX(); + final float simulationY = this.simulationUnit.getY(); + final float simDx = simulationX - this.x; + final float simDy = simulationY - this.y; + final float distanceToSimulation = (float) Math.sqrt((simDx * simDx) + (simDy * simDy)); + final int speed = this.simulationUnit.getSpeed(); + final float speedDelta = speed * deltaTime; + if ((distanceToSimulation > speedDelta) && (deltaTime < 1.0)) { + // The 1.0 here says that after 1 second of lag, units just teleport to show + // where they actually are + this.x += (speedDelta * simDx) / distanceToSimulation; + this.y += (speedDelta * simDy) / distanceToSimulation; + } else { + this.x = simulationX; + this.y = simulationY; + } + final float x = this.x; + final float dx = x - this.location[0]; + this.location[0] = x; + final float y = this.y; + final float dy = y - this.location[1]; + this.location[1] = y; + final float groundHeight; + final MovementType movementType = this.simulationUnit.getUnitType().getMovementType(); + final short terrainPathing = map.terrain.pathingGrid.getPathing(x, y); + boolean swimming = (movementType == MovementType.AMPHIBIOUS) + && PathingGrid.isPathingFlag(terrainPathing, PathingGrid.PathingType.SWIMMABLE) + && !PathingGrid.isPathingFlag(terrainPathing, PathingGrid.PathingType.WALKABLE); + float groundHeightTerrain = map.terrain.getGroundHeight(x, y); + float groundHeightTerrainAndWater; + MdxComplexInstance currentWalkableUnder; + boolean standingOnWater = (swimming) || (movementType == MovementType.FLOAT) || (movementType == MovementType.FLY) + || (movementType == MovementType.HOVER); + if (standingOnWater) { + groundHeightTerrainAndWater = Math.max(groundHeightTerrain, map.terrain.getWaterHeight(x, y)); + } else { + // land units will have their feet pass under the surface of the water + groundHeightTerrainAndWater = groundHeightTerrain; + } + if(movementType == MovementType.FLOAT) { + // boats cant go on bridges + groundHeight = groundHeightTerrainAndWater; + currentWalkableUnder = null; + } else { + currentWalkableUnder = map.getHighestWalkableUnder(x, y); + if(currentWalkableUnder != null) { + System.out.println("WALKABLE UNDER"); + } + War3MapViewer.gdxRayHeap.set(x, y, 4096, 0, 0, -8192); + if (currentWalkableUnder != null && currentWalkableUnder.intersectRayWithCollision(War3MapViewer.gdxRayHeap, War3MapViewer.intersectionHeap, true, true) + && War3MapViewer.intersectionHeap.z > groundHeightTerrainAndWater) { + groundHeight = War3MapViewer.intersectionHeap.z; + swimming = false; // Naga Royal Guard should slither across a bridge, not swim in rock + } else { + groundHeight = groundHeightTerrainAndWater; + currentWalkableUnder = null; + } + } + if (swimming && !this.swimming) { + this.unitAnimationListenerImpl.addSecondaryTag(AnimationTokens.SecondaryTag.SWIM); + } else if (!swimming && this.swimming) { + this.unitAnimationListenerImpl.removeSecondaryTag(AnimationTokens.SecondaryTag.SWIM); + } + this.swimming = swimming; + final boolean dead = this.simulationUnit.isDead(); + final boolean corpse = this.simulationUnit.isCorpse(); + final boolean boneCorpse = this.simulationUnit.isBoneCorpse(); + if (dead && !this.dead) { + this.unitAnimationListenerImpl.playAnimation(true, PrimaryTag.DEATH, SequenceUtils.EMPTY, 1.0f, true); + if (this.shadow != null) { + this.shadow.destroy(Gdx.gl30, map.terrain.centerOffset); + this.shadow = null; + } + if (this.selectionCircle != null) { + this.selectionCircle.destroy(Gdx.gl30, map.terrain.centerOffset); + this.selectionCircle = null; + } + } + if (boneCorpse && !this.boneCorpse) { + this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.BONE, + this.simulationUnit.getEndingDecayTime(map.simulation), true); + } else if (corpse && !this.corpse) { + this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.FLESH, + map.simulation.getGameplayConstants().getDecayTime(), true); + } + this.dead = dead; + this.corpse = corpse; + this.boneCorpse = boneCorpse; + this.location[2] = this.simulationUnit.getFlyHeight() + groundHeight; + this.instance.moveTo(this.location); + float simulationFacing = this.simulationUnit.getFacing(); + if (simulationFacing < 0) { + simulationFacing += 360; + } + float renderFacing = this.facing; + if (renderFacing < 0) { + renderFacing += 360; + } + float facingDelta = simulationFacing - renderFacing; + if (facingDelta < -180) { + facingDelta = 360 + facingDelta; + } + if (facingDelta > 180) { + facingDelta = -360 + facingDelta; + } + final float absoluteFacingDelta = Math.abs(facingDelta); + final float turningSign = Math.signum(facingDelta); - final float absoluteFacingDeltaRadians = (float) Math.toRadians(absoluteFacingDelta); - float acceleration; - final boolean endPhase = (absoluteFacingDeltaRadians <= this.orientationInterpolation.getEndingAccelCutoff()) - && ((this.currentTurnVelocity * turningSign) > 0); - if (endPhase) { - this.currentTurnVelocity = (1 - - ((this.orientationInterpolation.getEndingAccelCutoff() - absoluteFacingDeltaRadians) - / this.orientationInterpolation.getEndingAccelCutoff())) - * (this.orientationInterpolation.getMaxVelocity()) * turningSign; - } - else { - acceleration = this.orientationInterpolation.getStartingAcceleration() * turningSign; - this.currentTurnVelocity = this.currentTurnVelocity + acceleration; - } - if ((this.currentTurnVelocity * turningSign) > this.orientationInterpolation.getMaxVelocity()) { - this.currentTurnVelocity = this.orientationInterpolation.getMaxVelocity() * turningSign; - } - float angleToAdd = (float) ((Math.toDegrees(this.currentTurnVelocity) * deltaTime) / 0.03f); + final float absoluteFacingDeltaRadians = (float) Math.toRadians(absoluteFacingDelta); + float acceleration; + final boolean endPhase = (absoluteFacingDeltaRadians <= this.orientationInterpolation.getEndingAccelCutoff()) + && ((this.currentTurnVelocity * turningSign) > 0); + if (endPhase) { + this.currentTurnVelocity = (1 + - ((this.orientationInterpolation.getEndingAccelCutoff() - absoluteFacingDeltaRadians) + / this.orientationInterpolation.getEndingAccelCutoff())) + * (this.orientationInterpolation.getMaxVelocity()) * turningSign; + } else { + acceleration = this.orientationInterpolation.getStartingAcceleration() * turningSign; + this.currentTurnVelocity = this.currentTurnVelocity + acceleration; + } + if ((this.currentTurnVelocity * turningSign) > this.orientationInterpolation.getMaxVelocity()) { + this.currentTurnVelocity = this.orientationInterpolation.getMaxVelocity() * turningSign; + } + float angleToAdd = (float) ((Math.toDegrees(this.currentTurnVelocity) * deltaTime) / 0.03f); - if (absoluteFacingDelta < Math.abs(angleToAdd)) { - angleToAdd = facingDelta; - this.currentTurnVelocity = 0.0f; - } - this.facing = (((this.facing + angleToAdd) % 360) + 360) % 360; - this.instance.setLocalRotation(tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, this.facing)); + if (absoluteFacingDelta < Math.abs(angleToAdd)) { + angleToAdd = facingDelta; + this.currentTurnVelocity = 0.0f; + } + this.facing = (((this.facing + angleToAdd) % 360) + 360) % 360; + this.instance.setLocalRotation(tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, this.facing)); - final float facingRadians = (float) Math.toRadians(this.facing); - final float maxPitch = this.typeData.getMaxPitch(); - final float maxRoll = this.typeData.getMaxRoll(); - final float sampleRadius = this.typeData.getElevationSampleRadius(); - float pitch, roll; - final float pitchSampleForwardX = x + (sampleRadius * (float) Math.cos(facingRadians)); - final float pitchSampleForwardY = y + (sampleRadius * (float) Math.sin(facingRadians)); - final float pitchSampleBackwardX = x - (sampleRadius * (float) Math.cos(facingRadians)); - final float pitchSampleBackwardY = y - (sampleRadius * (float) Math.sin(facingRadians)); - final float pitchSampleGroundHeight1 = map.terrain.getGroundHeight(pitchSampleBackwardX, pitchSampleBackwardY); - final float pitchSampleGorundHeight2 = map.terrain.getGroundHeight(pitchSampleForwardX, pitchSampleForwardY); - pitch = Math.max(-maxPitch, Math.min(maxPitch, - (float) Math.atan2(pitchSampleGorundHeight2 - pitchSampleGroundHeight1, sampleRadius * 2))); - final double leftOfFacingAngle = facingRadians + (Math.PI / 2); - final float rollSampleForwardX = x + (sampleRadius * (float) Math.cos(leftOfFacingAngle)); - final float rollSampleForwardY = y + (sampleRadius * (float) Math.sin(leftOfFacingAngle)); - final float rollSampleBackwardX = x - (sampleRadius * (float) Math.cos(leftOfFacingAngle)); - final float rollSampleBackwardY = y - (sampleRadius * (float) Math.sin(leftOfFacingAngle)); - final float rollSampleGroundHeight1 = map.terrain.getGroundHeight(rollSampleBackwardX, rollSampleBackwardY); - final float rollSampleGroundHeight2 = map.terrain.getGroundHeight(rollSampleForwardX, rollSampleForwardY); - roll = Math.max(-maxRoll, Math.min(maxRoll, - (float) Math.atan2(rollSampleGroundHeight2 - rollSampleGroundHeight1, sampleRadius * 2))); - this.instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Y, -pitch)); - this.instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_X, roll)); + final float facingRadians = (float) Math.toRadians(this.facing); + final float maxPitch = this.typeData.getMaxPitch(); + final float maxRoll = this.typeData.getMaxRoll(); + final float sampleRadius = this.typeData.getElevationSampleRadius(); + float pitch, roll; + final float pitchSampleForwardX = x + (sampleRadius * (float) Math.cos(facingRadians)); + final float pitchSampleForwardY = y + (sampleRadius * (float) Math.sin(facingRadians)); + final float pitchSampleBackwardX = x - (sampleRadius * (float) Math.cos(facingRadians)); + final float pitchSampleBackwardY = y - (sampleRadius * (float) Math.sin(facingRadians)); + final double leftOfFacingAngle = facingRadians + (Math.PI / 2); + final float rollSampleForwardX = x + (sampleRadius * (float) Math.cos(leftOfFacingAngle)); + final float rollSampleForwardY = y + (sampleRadius * (float) Math.sin(leftOfFacingAngle)); + final float rollSampleBackwardX = x - (sampleRadius * (float) Math.cos(leftOfFacingAngle)); + final float rollSampleBackwardY = y - (sampleRadius * (float) Math.sin(leftOfFacingAngle)); + final float pitchSampleGroundHeight1; + final float pitchSampleGroundHeight2; + final float rollSampleGroundHeight1; + final float rollSampleGroundHeight2; + if (currentWalkableUnder != null) { + pitchSampleGroundHeight1 = getGroundHeightSample(groundHeight, currentWalkableUnder, pitchSampleBackwardX, pitchSampleBackwardY); + pitchSampleGroundHeight2 = getGroundHeightSample(groundHeight, currentWalkableUnder, pitchSampleForwardX, pitchSampleForwardY); + rollSampleGroundHeight1 = getGroundHeightSample(groundHeight, currentWalkableUnder, rollSampleBackwardX, rollSampleBackwardY); + rollSampleGroundHeight2 = getGroundHeightSample(groundHeight, currentWalkableUnder, rollSampleForwardX, rollSampleForwardY); + } else { + float pitchGroundHeight1 = map.terrain.getGroundHeight(pitchSampleBackwardX, pitchSampleBackwardY); + float pitchGroundHeight2 = map.terrain.getGroundHeight(pitchSampleForwardX, pitchSampleForwardY); + float rollGroundHeight1 = map.terrain.getGroundHeight(rollSampleBackwardX, rollSampleBackwardY); + float rollGroundHeight2 = map.terrain.getGroundHeight(rollSampleForwardX, rollSampleForwardY); + if (standingOnWater) { + pitchSampleGroundHeight1 = Math.max(pitchGroundHeight1, + map.terrain.getWaterHeight(pitchSampleBackwardX, pitchSampleBackwardY)); + pitchSampleGroundHeight2 = Math.max(pitchGroundHeight2, + map.terrain.getWaterHeight(pitchSampleForwardX, pitchSampleForwardY)); + rollSampleGroundHeight1 = Math.max(rollGroundHeight1, + map.terrain.getWaterHeight(rollSampleBackwardX, rollSampleBackwardY)); + rollSampleGroundHeight2 = Math.max(rollGroundHeight2, + map.terrain.getWaterHeight(rollSampleForwardX, rollSampleForwardY)); + } else { + pitchSampleGroundHeight1 = pitchGroundHeight1; + pitchSampleGroundHeight2 = pitchGroundHeight2; + rollSampleGroundHeight1 = rollGroundHeight1; + rollSampleGroundHeight2 = rollGroundHeight2; + } + } + pitch = Math.max(-maxPitch, Math.min(maxPitch, + (float) Math.atan2(pitchSampleGroundHeight2 - pitchSampleGroundHeight1, sampleRadius * 2))); + roll = Math.max(-maxRoll, Math.min(maxRoll, + (float) Math.atan2(rollSampleGroundHeight2 - rollSampleGroundHeight1, sampleRadius * 2))); + this.instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Y, -pitch)); + this.instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_X, roll)); - map.worldScene.instanceMoved(this.instance, this.location[0], this.location[1]); - if (this.shadow != null) { - this.shadow.move(dx, dy, map.terrain.centerOffset); - } - if (this.selectionCircle != null) { - this.selectionCircle.move(dx, dy, map.terrain.centerOffset); - } - this.unitAnimationListenerImpl.update(); - } + map.worldScene.instanceMoved(this.instance, this.location[0], this.location[1]); + if (this.shadow != null) { + this.shadow.move(dx, dy, map.terrain.centerOffset); + } + if (this.selectionCircle != null) { + this.selectionCircle.move(dx, dy, map.terrain.centerOffset); + } + this.unitAnimationListenerImpl.update(); + } - public CUnit getSimulationUnit() { - return this.simulationUnit; - } + private float getGroundHeightSample(float groundHeight, MdxComplexInstance currentWalkableUnder, float sampleX, float sampleY) { + final float sampleGroundHeight; + War3MapViewer.gdxRayHeap.origin.x = sampleX; + War3MapViewer.gdxRayHeap.origin.y = sampleY; + if (currentWalkableUnder.intersectRayWithCollision(War3MapViewer.gdxRayHeap, War3MapViewer.intersectionHeap, true, true)) { + sampleGroundHeight = War3MapViewer.intersectionHeap.z; + } else { + sampleGroundHeight = groundHeight; + } + return sampleGroundHeight; + } - private static final class UnitAnimationListenerImpl implements CUnitAnimationListener { - private final MdxComplexInstance instance; - private final EnumSet secondaryAnimationTags = EnumSet - .noneOf(AnimationTokens.SecondaryTag.class); - private final EnumSet recycleSet = EnumSet - .noneOf(AnimationTokens.SecondaryTag.class); - private PrimaryTag currentAnimation; - private EnumSet currentAnimationSecondaryTags; - private float currentSpeedRatio; - private boolean currentlyAllowingRarityVariations; - private final Queue animationQueue = new LinkedList<>(); + public CUnit getSimulationUnit() { + return this.simulationUnit; + } - public UnitAnimationListenerImpl(final MdxComplexInstance instance) { - this.instance = instance; - } + private static final class UnitAnimationListenerImpl implements CUnitAnimationListener { + private final MdxComplexInstance instance; + private final EnumSet secondaryAnimationTags = EnumSet + .noneOf(AnimationTokens.SecondaryTag.class); + private final EnumSet recycleSet = EnumSet + .noneOf(AnimationTokens.SecondaryTag.class); + private PrimaryTag currentAnimation; + private EnumSet currentAnimationSecondaryTags; + private float currentSpeedRatio; + private boolean currentlyAllowingRarityVariations; + private final Queue animationQueue = new LinkedList<>(); - public void addSecondaryTag(final AnimationTokens.SecondaryTag tag) { - this.secondaryAnimationTags.add(tag); - playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio, - this.currentlyAllowingRarityVariations); - } + public UnitAnimationListenerImpl(final MdxComplexInstance instance) { + this.instance = instance; + } - public void removeSecondaryTag(final AnimationTokens.SecondaryTag tag) { - this.secondaryAnimationTags.remove(tag); - playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio, - this.currentlyAllowingRarityVariations); - } + public void addSecondaryTag(final AnimationTokens.SecondaryTag tag) { + this.secondaryAnimationTags.add(tag); + playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio, + this.currentlyAllowingRarityVariations); + } - @Override - public void playAnimation(final boolean force, final PrimaryTag animationName, - final EnumSet secondaryAnimationTags, final float speedRatio, - final boolean allowRarityVariations) { - this.animationQueue.clear(); - if (force || (animationName != this.currentAnimation)) { - this.currentAnimation = animationName; - this.currentAnimationSecondaryTags = secondaryAnimationTags; - this.currentSpeedRatio = speedRatio; - this.currentlyAllowingRarityVariations = allowRarityVariations; - this.recycleSet.clear(); - this.recycleSet.addAll(this.secondaryAnimationTags); - this.recycleSet.addAll(secondaryAnimationTags); - this.instance.setAnimationSpeed(speedRatio); - SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, allowRarityVariations); - } - } + public void removeSecondaryTag(final AnimationTokens.SecondaryTag tag) { + this.secondaryAnimationTags.remove(tag); + playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio, + this.currentlyAllowingRarityVariations); + } - public void playAnimationWithDuration(final boolean force, final PrimaryTag animationName, - final EnumSet secondaryAnimationTags, final float duration, - final boolean allowRarityVariations) { - this.animationQueue.clear(); - if (force || (animationName != this.currentAnimation)) { - this.currentAnimation = animationName; - this.currentAnimationSecondaryTags = secondaryAnimationTags; - this.currentlyAllowingRarityVariations = allowRarityVariations; - this.recycleSet.clear(); - this.recycleSet.addAll(this.secondaryAnimationTags); - this.recycleSet.addAll(secondaryAnimationTags); - final Sequence sequence = SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, - allowRarityVariations); - if (sequence != null) { - this.currentSpeedRatio = ((sequence.getInterval()[1] - sequence.getInterval()[0]) / 1000.0f) - / duration; - this.instance.setAnimationSpeed(this.currentSpeedRatio); - } - } - } + @Override + public void playAnimation(final boolean force, final PrimaryTag animationName, + final EnumSet secondaryAnimationTags, final float speedRatio, + final boolean allowRarityVariations) { + this.animationQueue.clear(); + if (force || (animationName != this.currentAnimation)) { + this.currentAnimation = animationName; + this.currentAnimationSecondaryTags = secondaryAnimationTags; + this.currentSpeedRatio = speedRatio; + this.currentlyAllowingRarityVariations = allowRarityVariations; + this.recycleSet.clear(); + this.recycleSet.addAll(this.secondaryAnimationTags); + this.recycleSet.addAll(secondaryAnimationTags); + this.instance.setAnimationSpeed(speedRatio); + SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, allowRarityVariations); + } + } - @Override - public void queueAnimation(final PrimaryTag animationName, final EnumSet secondaryAnimationTags, - final boolean allowRarityVariations) { - this.animationQueue.add(new QueuedAnimation(animationName, secondaryAnimationTags, allowRarityVariations)); - } + public void playAnimationWithDuration(final boolean force, final PrimaryTag animationName, + final EnumSet secondaryAnimationTags, final float duration, + final boolean allowRarityVariations) { + this.animationQueue.clear(); + if (force || (animationName != this.currentAnimation)) { + this.currentAnimation = animationName; + this.currentAnimationSecondaryTags = secondaryAnimationTags; + this.currentlyAllowingRarityVariations = allowRarityVariations; + this.recycleSet.clear(); + this.recycleSet.addAll(this.secondaryAnimationTags); + this.recycleSet.addAll(secondaryAnimationTags); + final Sequence sequence = SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, + allowRarityVariations); + if (sequence != null) { + this.currentSpeedRatio = ((sequence.getInterval()[1] - sequence.getInterval()[0]) / 1000.0f) + / duration; + this.instance.setAnimationSpeed(this.currentSpeedRatio); + } + } + } - public void update() { - if (this.instance.sequenceEnded || (this.instance.sequence == -1)) { - // animation done - if ((this.instance.sequence != -1) && (((MdxModel) this.instance.model).getSequences() - .get(this.instance.sequence).getFlags() == 0)) { - // animation is a looping animation - playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, - this.currentSpeedRatio, this.currentlyAllowingRarityVariations); - } - else { - final QueuedAnimation nextAnimation = this.animationQueue.poll(); - if (nextAnimation != null) { - playAnimation(true, nextAnimation.animationName, nextAnimation.secondaryAnimationTags, 1.0f, - nextAnimation.allowRarityVariations); - } - } - } - } + @Override + public void queueAnimation(final PrimaryTag animationName, final EnumSet secondaryAnimationTags, + final boolean allowRarityVariations) { + this.animationQueue.add(new QueuedAnimation(animationName, secondaryAnimationTags, allowRarityVariations)); + } - } + public void update() { + if (this.instance.sequenceEnded || (this.instance.sequence == -1)) { + // animation done + if ((this.instance.sequence != -1) && (((MdxModel) this.instance.model).getSequences() + .get(this.instance.sequence).getFlags() == 0)) { + // animation is a looping animation + playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, + this.currentSpeedRatio, this.currentlyAllowingRarityVariations); + } else { + final QueuedAnimation nextAnimation = this.animationQueue.poll(); + if (nextAnimation != null) { + playAnimation(true, nextAnimation.animationName, nextAnimation.secondaryAnimationTags, 1.0f, + nextAnimation.allowRarityVariations); + } + } + } + } - private static final class QueuedAnimation { - private final PrimaryTag animationName; - private final EnumSet secondaryAnimationTags; - private final boolean allowRarityVariations; + } - public QueuedAnimation(final PrimaryTag animationName, final EnumSet secondaryAnimationTags, - final boolean allowRarityVariations) { - this.animationName = animationName; - this.secondaryAnimationTags = secondaryAnimationTags; - this.allowRarityVariations = allowRarityVariations; - } - } + private static final class QueuedAnimation { + private final PrimaryTag animationName; + private final EnumSet secondaryAnimationTags; + private final boolean allowRarityVariations; + + public QueuedAnimation(final PrimaryTag animationName, final EnumSet secondaryAnimationTags, + final boolean allowRarityVariations) { + this.animationName = animationName; + this.secondaryAnimationTags = secondaryAnimationTags; + this.allowRarityVariations = allowRarityVariations; + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index a9ebc8d..c041354 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -32,388 +32,396 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUni import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; public class CUnitData { - private static final War3ID MANA_INITIAL_AMOUNT = War3ID.fromString("umpi"); - private static final War3ID MANA_MAXIMUM = War3ID.fromString("umpm"); - private static final War3ID HIT_POINT_MAXIMUM = War3ID.fromString("uhpm"); - private static final War3ID MOVEMENT_SPEED_BASE = War3ID.fromString("umvs"); - private static final War3ID PROPULSION_WINDOW = War3ID.fromString("uprw"); - private static final War3ID TURN_RATE = War3ID.fromString("umvr"); - private static final War3ID IS_BLDG = War3ID.fromString("ubdg"); - private static final War3ID NAME = War3ID.fromString("unam"); - private static final War3ID PROJECTILE_LAUNCH_X = War3ID.fromString("ulpx"); - private static final War3ID PROJECTILE_LAUNCH_Y = War3ID.fromString("ulpy"); - private static final War3ID PROJECTILE_LAUNCH_Z = War3ID.fromString("ulpz"); - private static final War3ID ATTACKS_ENABLED = War3ID.fromString("uaen"); - private static final War3ID ATTACK1_BACKSWING_POINT = War3ID.fromString("ubs1"); - private static final War3ID ATTACK1_DAMAGE_POINT = War3ID.fromString("udp1"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua1f"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua1h"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua1q"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua1p"); - private static final War3ID ATTACK1_ATTACK_TYPE = War3ID.fromString("ua1t"); - private static final War3ID ATTACK1_COOLDOWN = War3ID.fromString("ua1c"); - private static final War3ID ATTACK1_DMG_BASE = War3ID.fromString("ua1b"); - private static final War3ID ATTACK1_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd1"); - private static final War3ID ATTACK1_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd1"); - private static final War3ID ATTACK1_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl1"); - private static final War3ID ATTACK1_DMG_DICE = War3ID.fromString("ua1d"); - private static final War3ID ATTACK1_DMG_SIDES_PER_DIE = War3ID.fromString("ua1s"); - private static final War3ID ATTACK1_DMG_SPILL_DIST = War3ID.fromString("usd1"); - private static final War3ID ATTACK1_DMG_SPILL_RADIUS = War3ID.fromString("usr1"); - private static final War3ID ATTACK1_DMG_UPGRADE_AMT = War3ID.fromString("udu1"); - private static final War3ID ATTACK1_TARGET_COUNT = War3ID.fromString("utc1"); - private static final War3ID ATTACK1_PROJECTILE_ARC = War3ID.fromString("uma1"); - private static final War3ID ATTACK1_MISSILE_ART = War3ID.fromString("ua1m"); - private static final War3ID ATTACK1_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh1"); - private static final War3ID ATTACK1_PROJECTILE_SPEED = War3ID.fromString("ua1z"); - private static final War3ID ATTACK1_RANGE = War3ID.fromString("ua1r"); - private static final War3ID ATTACK1_RANGE_MOTION_BUFFER = War3ID.fromString("urb1"); - private static final War3ID ATTACK1_SHOW_UI = War3ID.fromString("uwu1"); - private static final War3ID ATTACK1_TARGETS_ALLOWED = War3ID.fromString("ua1g"); - private static final War3ID ATTACK1_WEAPON_SOUND = War3ID.fromString("ucs1"); - private static final War3ID ATTACK1_WEAPON_TYPE = War3ID.fromString("ua1w"); + private static final War3ID MANA_INITIAL_AMOUNT = War3ID.fromString("umpi"); + private static final War3ID MANA_MAXIMUM = War3ID.fromString("umpm"); + private static final War3ID HIT_POINT_MAXIMUM = War3ID.fromString("uhpm"); + private static final War3ID MOVEMENT_SPEED_BASE = War3ID.fromString("umvs"); + private static final War3ID PROPULSION_WINDOW = War3ID.fromString("uprw"); + private static final War3ID TURN_RATE = War3ID.fromString("umvr"); + private static final War3ID IS_BLDG = War3ID.fromString("ubdg"); + private static final War3ID NAME = War3ID.fromString("unam"); + private static final War3ID PROJECTILE_LAUNCH_X = War3ID.fromString("ulpx"); + private static final War3ID PROJECTILE_LAUNCH_Y = War3ID.fromString("ulpy"); + private static final War3ID PROJECTILE_LAUNCH_Z = War3ID.fromString("ulpz"); + private static final War3ID ATTACKS_ENABLED = War3ID.fromString("uaen"); + private static final War3ID ATTACK1_BACKSWING_POINT = War3ID.fromString("ubs1"); + private static final War3ID ATTACK1_DAMAGE_POINT = War3ID.fromString("udp1"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua1f"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua1h"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua1q"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua1p"); + private static final War3ID ATTACK1_ATTACK_TYPE = War3ID.fromString("ua1t"); + private static final War3ID ATTACK1_COOLDOWN = War3ID.fromString("ua1c"); + private static final War3ID ATTACK1_DMG_BASE = War3ID.fromString("ua1b"); + private static final War3ID ATTACK1_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd1"); + private static final War3ID ATTACK1_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd1"); + private static final War3ID ATTACK1_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl1"); + private static final War3ID ATTACK1_DMG_DICE = War3ID.fromString("ua1d"); + private static final War3ID ATTACK1_DMG_SIDES_PER_DIE = War3ID.fromString("ua1s"); + private static final War3ID ATTACK1_DMG_SPILL_DIST = War3ID.fromString("usd1"); + private static final War3ID ATTACK1_DMG_SPILL_RADIUS = War3ID.fromString("usr1"); + private static final War3ID ATTACK1_DMG_UPGRADE_AMT = War3ID.fromString("udu1"); + private static final War3ID ATTACK1_TARGET_COUNT = War3ID.fromString("utc1"); + private static final War3ID ATTACK1_PROJECTILE_ARC = War3ID.fromString("uma1"); + private static final War3ID ATTACK1_MISSILE_ART = War3ID.fromString("ua1m"); + private static final War3ID ATTACK1_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh1"); + private static final War3ID ATTACK1_PROJECTILE_SPEED = War3ID.fromString("ua1z"); + private static final War3ID ATTACK1_RANGE = War3ID.fromString("ua1r"); + private static final War3ID ATTACK1_RANGE_MOTION_BUFFER = War3ID.fromString("urb1"); + private static final War3ID ATTACK1_SHOW_UI = War3ID.fromString("uwu1"); + private static final War3ID ATTACK1_TARGETS_ALLOWED = War3ID.fromString("ua1g"); + private static final War3ID ATTACK1_WEAPON_SOUND = War3ID.fromString("ucs1"); + private static final War3ID ATTACK1_WEAPON_TYPE = War3ID.fromString("ua1w"); - private static final War3ID ATTACK2_BACKSWING_POINT = War3ID.fromString("ubs2"); - private static final War3ID ATTACK2_DAMAGE_POINT = War3ID.fromString("udp2"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua2f"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua2h"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua2q"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua2p"); - private static final War3ID ATTACK2_ATTACK_TYPE = War3ID.fromString("ua2t"); - private static final War3ID ATTACK2_COOLDOWN = War3ID.fromString("ua2c"); - private static final War3ID ATTACK2_DMG_BASE = War3ID.fromString("ua2b"); - private static final War3ID ATTACK2_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd2"); - private static final War3ID ATTACK2_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd2"); - private static final War3ID ATTACK2_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl2"); - private static final War3ID ATTACK2_DMG_DICE = War3ID.fromString("ua2d"); - private static final War3ID ATTACK2_DMG_SIDES_PER_DIE = War3ID.fromString("ua2s"); - private static final War3ID ATTACK2_DMG_SPILL_DIST = War3ID.fromString("usd2"); - private static final War3ID ATTACK2_DMG_SPILL_RADIUS = War3ID.fromString("usr2"); - private static final War3ID ATTACK2_DMG_UPGRADE_AMT = War3ID.fromString("udu2"); - private static final War3ID ATTACK2_TARGET_COUNT = War3ID.fromString("utc2"); - private static final War3ID ATTACK2_PROJECTILE_ARC = War3ID.fromString("uma2"); - private static final War3ID ATTACK2_MISSILE_ART = War3ID.fromString("ua2m"); - private static final War3ID ATTACK2_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh2"); - private static final War3ID ATTACK2_PROJECTILE_SPEED = War3ID.fromString("ua2z"); - private static final War3ID ATTACK2_RANGE = War3ID.fromString("ua2r"); - private static final War3ID ATTACK2_RANGE_MOTION_BUFFER = War3ID.fromString("urb2"); - private static final War3ID ATTACK2_SHOW_UI = War3ID.fromString("uwu2"); - private static final War3ID ATTACK2_TARGETS_ALLOWED = War3ID.fromString("ua2g"); - private static final War3ID ATTACK2_WEAPON_SOUND = War3ID.fromString("ucs2"); - private static final War3ID ATTACK2_WEAPON_TYPE = War3ID.fromString("ua2w"); + private static final War3ID ATTACK2_BACKSWING_POINT = War3ID.fromString("ubs2"); + private static final War3ID ATTACK2_DAMAGE_POINT = War3ID.fromString("udp2"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua2f"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua2h"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua2q"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua2p"); + private static final War3ID ATTACK2_ATTACK_TYPE = War3ID.fromString("ua2t"); + private static final War3ID ATTACK2_COOLDOWN = War3ID.fromString("ua2c"); + private static final War3ID ATTACK2_DMG_BASE = War3ID.fromString("ua2b"); + private static final War3ID ATTACK2_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd2"); + private static final War3ID ATTACK2_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd2"); + private static final War3ID ATTACK2_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl2"); + private static final War3ID ATTACK2_DMG_DICE = War3ID.fromString("ua2d"); + private static final War3ID ATTACK2_DMG_SIDES_PER_DIE = War3ID.fromString("ua2s"); + private static final War3ID ATTACK2_DMG_SPILL_DIST = War3ID.fromString("usd2"); + private static final War3ID ATTACK2_DMG_SPILL_RADIUS = War3ID.fromString("usr2"); + private static final War3ID ATTACK2_DMG_UPGRADE_AMT = War3ID.fromString("udu2"); + private static final War3ID ATTACK2_TARGET_COUNT = War3ID.fromString("utc2"); + private static final War3ID ATTACK2_PROJECTILE_ARC = War3ID.fromString("uma2"); + private static final War3ID ATTACK2_MISSILE_ART = War3ID.fromString("ua2m"); + private static final War3ID ATTACK2_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh2"); + private static final War3ID ATTACK2_PROJECTILE_SPEED = War3ID.fromString("ua2z"); + private static final War3ID ATTACK2_RANGE = War3ID.fromString("ua2r"); + private static final War3ID ATTACK2_RANGE_MOTION_BUFFER = War3ID.fromString("urb2"); + private static final War3ID ATTACK2_SHOW_UI = War3ID.fromString("uwu2"); + private static final War3ID ATTACK2_TARGETS_ALLOWED = War3ID.fromString("ua2g"); + private static final War3ID ATTACK2_WEAPON_SOUND = War3ID.fromString("ucs2"); + private static final War3ID ATTACK2_WEAPON_TYPE = War3ID.fromString("ua2w"); - private static final War3ID ACQUISITION_RANGE = War3ID.fromString("uacq"); - private static final War3ID MINIMUM_ATTACK_RANGE = War3ID.fromString("uamn"); + private static final War3ID ACQUISITION_RANGE = War3ID.fromString("uacq"); + private static final War3ID MINIMUM_ATTACK_RANGE = War3ID.fromString("uamn"); - private static final War3ID PROJECTILE_IMPACT_Z = War3ID.fromString("uimz"); + private static final War3ID PROJECTILE_IMPACT_Z = War3ID.fromString("uimz"); - private static final War3ID DEATH_TYPE = War3ID.fromString("udea"); - private static final War3ID ARMOR_TYPE = War3ID.fromString("uarm"); + private static final War3ID DEATH_TYPE = War3ID.fromString("udea"); + private static final War3ID ARMOR_TYPE = War3ID.fromString("uarm"); - private static final War3ID DEFENSE = War3ID.fromString("udef"); - private static final War3ID DEFENSE_TYPE = War3ID.fromString("udty"); - private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); - private static final War3ID MOVE_TYPE = War3ID.fromString("umvt"); - private static final War3ID COLLISION_SIZE = War3ID.fromString("ucol"); - private static final War3ID CLASSIFICATION = War3ID.fromString("utyp"); - private static final War3ID DEATH_TIME = War3ID.fromString("udtm"); - private static final War3ID TARGETED_AS = War3ID.fromString("utar"); - private final MutableObjectData unitData; - private final Map unitIdToUnitType = new HashMap<>(); + private static final War3ID DEFENSE = War3ID.fromString("udef"); + private static final War3ID DEFENSE_TYPE = War3ID.fromString("udty"); + private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); + private static final War3ID MOVE_TYPE = War3ID.fromString("umvt"); + private static final War3ID COLLISION_SIZE = War3ID.fromString("ucol"); + private static final War3ID CLASSIFICATION = War3ID.fromString("utyp"); + private static final War3ID DEATH_TIME = War3ID.fromString("udtm"); + private static final War3ID TARGETED_AS = War3ID.fromString("utar"); + private final MutableObjectData unitData; + private final Map unitIdToUnitType = new HashMap<>(); - public CUnitData(final MutableObjectData unitData) { - this.unitData = unitData; - } + public CUnitData(final MutableObjectData unitData) { + this.unitData = unitData; + } - public CUnit create(final CSimulation simulation, final int playerIndex, final War3ID typeId, final float x, - final float y, final float facing, final BufferedImage buildingPathingPixelMap, - final SimulationRenderController simulationRenderController, final HandleIdAllocator handleIdAllocator) { - final MutableGameObject unitType = this.unitData.get(typeId); - final int handleId = handleIdAllocator.createId(); - final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); - final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); - final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); - final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); - final int defense = unitType.getFieldAsInteger(DEFENSE, 0); + public CUnit create(final CSimulation simulation, final int playerIndex, final War3ID typeId, final float x, + final float y, final float facing, final BufferedImage buildingPathingPixelMap, + final SimulationRenderController simulationRenderController, final HandleIdAllocator handleIdAllocator) { + final MutableGameObject unitType = this.unitData.get(typeId); + final int handleId = handleIdAllocator.createId(); + final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); + final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); + final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); + final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); + final int defense = unitType.getFieldAsInteger(DEFENSE, 0); - final CUnitType unitTypeInstance = getUnitTypeInstance(typeId, buildingPathingPixelMap, unitType); + final CUnitType unitTypeInstance = getUnitTypeInstance(typeId, buildingPathingPixelMap, unitType); - final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, - speed, defense, unitTypeInstance); - if (speed > 0) { - unit.add(simulation, new CAbilityMove(handleIdAllocator.createId())); - } - if (!unitTypeInstance.getAttacks().isEmpty()) { - unit.add(simulation, new CAbilityAttack(handleIdAllocator.createId())); - } - return unit; - } + final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, + speed, defense, unitTypeInstance); + if (speed > 0) { + unit.add(simulation, new CAbilityMove(handleIdAllocator.createId())); + } + if (!unitTypeInstance.getAttacks().isEmpty()) { + unit.add(simulation, new CAbilityAttack(handleIdAllocator.createId())); + } + return unit; + } - private CUnitType getUnitTypeInstance(final War3ID typeId, final BufferedImage buildingPathingPixelMap, - final MutableGameObject unitType) { - CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId); - if (unitTypeInstance == null) { - final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); - final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); - final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); - final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); - final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); - final String unitName = unitType.getFieldAsString(NAME, 0); - final float acquisitionRange = unitType.getFieldAsFloat(ACQUISITION_RANGE, 0); - final float minimumAttackRange = unitType.getFieldAsFloat(MINIMUM_ATTACK_RANGE, 0); - final EnumSet targetedAs = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0)); - final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0); - final EnumSet classifications = EnumSet.noneOf(CUnitClassification.class); - if (classificationString != null) { - final String[] classificationValues = classificationString.split(","); - for (final String unitEditorKey : classificationValues) { - final CUnitClassification unitClassification = CUnitClassification - .parseUnitClassification(unitEditorKey); - if (unitClassification != null) { - classifications.add(unitClassification); - } - } - } - final List attacks = new ArrayList<>(); - final int attacksEnabled = unitType.getFieldAsInteger(ATTACKS_ENABLED, 0); - if ((attacksEnabled & 0x1) != 0) { - // attack one - final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK1_BACKSWING_POINT, 0); - final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK1_DAMAGE_POINT, 0); - final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_FULL_DMG, 0); - final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_HALF_DMG, 0); - final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_QUARTER_DMG, 0); - final EnumSet areaOfEffectTargets = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_AREA_OF_EFFECT_TARGETS, 0)); - final CAttackType attackType = CAttackType - .parseAttackType(unitType.getFieldAsString(ATTACK1_ATTACK_TYPE, 0)); - final float cooldownTime = unitType.getFieldAsFloat(ATTACK1_COOLDOWN, 0); - final int damageBase = unitType.getFieldAsInteger(ATTACK1_DMG_BASE, 0); - final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_HALF, 0); - final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_QUARTER, 0); - final float damageLossFactor = unitType.getFieldAsFloat(ATTACK1_DAMAGE_LOSS_FACTOR, 0); - final int damageDice = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); - final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0); - final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_DIST, 0); - final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_RADIUS, 0); - final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0); - final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0); - final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); - final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); - final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED, - 0); - final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); - final int range = unitType.getFieldAsInteger(ATTACK1_RANGE, 0); - final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK1_RANGE_MOTION_BUFFER, 0); - final boolean showUI = unitType.getFieldAsBoolean(ATTACK1_SHOW_UI, 0); - final EnumSet targetsAllowed = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_TARGETS_ALLOWED, 0)); - final String weaponSound = unitType.getFieldAsString(ATTACK1_WEAPON_SOUND, 0); - final CWeaponType weaponType = CWeaponType - .parseWeaponType(unitType.getFieldAsString(ATTACK1_WEAPON_TYPE, 0)); - attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, - areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, - cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice, - damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, - maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed, - range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType)); - } - if ((attacksEnabled & 0x2) != 0) { - // attack two - final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK2_BACKSWING_POINT, 0); - final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK2_DAMAGE_POINT, 0); - final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_FULL_DMG, 0); - final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_HALF_DMG, 0); - final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_QUARTER_DMG, 0); - final EnumSet areaOfEffectTargets = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_AREA_OF_EFFECT_TARGETS, 0)); - final CAttackType attackType = CAttackType - .parseAttackType(unitType.getFieldAsString(ATTACK2_ATTACK_TYPE, 0)); - final float cooldownTime = unitType.getFieldAsFloat(ATTACK2_COOLDOWN, 0); - final int damageBase = unitType.getFieldAsInteger(ATTACK2_DMG_BASE, 0); - final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_HALF, 0); - final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_QUARTER, 0); - final float damageLossFactor = unitType.getFieldAsFloat(ATTACK2_DAMAGE_LOSS_FACTOR, 0); - final int damageDice = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); - final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0); - final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_DIST, 0); - final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_RADIUS, 0); - final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0); - final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0); - final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); - final String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); - final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED, - 0); - final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); - final int range = unitType.getFieldAsInteger(ATTACK2_RANGE, 0); - final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK2_RANGE_MOTION_BUFFER, 0); - final boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0); - final EnumSet targetsAllowed = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_TARGETS_ALLOWED, 0)); - final String weaponSound = unitType.getFieldAsString(ATTACK2_WEAPON_SOUND, 0); - final CWeaponType weaponType = CWeaponType - .parseWeaponType(unitType.getFieldAsString(ATTACK2_WEAPON_TYPE, 0)); - attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, - areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, - cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice, - damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, - maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed, - range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType)); - } - final int deathType = unitType.getFieldAsInteger(DEATH_TYPE, 0); - final boolean raise = (deathType & 0x1) != 0; - final boolean decay = (deathType & 0x2) != 0; - final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0); - final float impactZ = unitType.getFieldAsFloat(PROJECTILE_IMPACT_Z, 0); - final CDefenseType defenseType = CDefenseType.parseDefenseType(unitType.getFieldAsString(DEFENSE_TYPE, 0)); - final float deathTime = unitType.getFieldAsFloat(DEATH_TIME, 0); - unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications, - attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, - targetedAs, acquisitionRange, minimumAttackRange); - this.unitIdToUnitType.put(typeId, unitTypeInstance); - } - return unitTypeInstance; - } + private CUnitType getUnitTypeInstance(final War3ID typeId, final BufferedImage buildingPathingPixelMap, + final MutableGameObject unitType) { + CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId); + if (unitTypeInstance == null) { + final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); + final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); + final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); + final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); + final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); + final String unitName = unitType.getFieldAsString(NAME, 0); + final float acquisitionRange = unitType.getFieldAsFloat(ACQUISITION_RANGE, 0); + final float minimumAttackRange = unitType.getFieldAsFloat(MINIMUM_ATTACK_RANGE, 0); + final EnumSet targetedAs = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0)); + final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0); + final EnumSet classifications = EnumSet.noneOf(CUnitClassification.class); + if (classificationString != null) { + final String[] classificationValues = classificationString.split(","); + for (final String unitEditorKey : classificationValues) { + final CUnitClassification unitClassification = CUnitClassification + .parseUnitClassification(unitEditorKey); + if (unitClassification != null) { + classifications.add(unitClassification); + } + } + } + final List attacks = new ArrayList<>(); + final int attacksEnabled = unitType.getFieldAsInteger(ATTACKS_ENABLED, 0); + if ((attacksEnabled & 0x1) != 0) { + try { + // attack one + final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK1_BACKSWING_POINT, 0); + final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK1_DAMAGE_POINT, 0); + final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_FULL_DMG, 0); + final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_HALF_DMG, 0); + final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_QUARTER_DMG, 0); + final EnumSet areaOfEffectTargets = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_AREA_OF_EFFECT_TARGETS, 0)); + final CAttackType attackType = CAttackType + .parseAttackType(unitType.getFieldAsString(ATTACK1_ATTACK_TYPE, 0)); + final float cooldownTime = unitType.getFieldAsFloat(ATTACK1_COOLDOWN, 0); + final int damageBase = unitType.getFieldAsInteger(ATTACK1_DMG_BASE, 0); + final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_HALF, 0); + final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_QUARTER, 0); + final float damageLossFactor = unitType.getFieldAsFloat(ATTACK1_DAMAGE_LOSS_FACTOR, 0); + final int damageDice = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); + final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0); + final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_DIST, 0); + final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_RADIUS, 0); + final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0); + final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0); + final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); + final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); + final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED, + 0); + final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); + final int range = unitType.getFieldAsInteger(ATTACK1_RANGE, 0); + final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK1_RANGE_MOTION_BUFFER, 0); + final boolean showUI = unitType.getFieldAsBoolean(ATTACK1_SHOW_UI, 0); + final EnumSet targetsAllowed = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_TARGETS_ALLOWED, 0)); + final String weaponSound = unitType.getFieldAsString(ATTACK1_WEAPON_SOUND, 0); + final CWeaponType weaponType = CWeaponType + .parseWeaponType(unitType.getFieldAsString(ATTACK1_WEAPON_TYPE, 0)); + attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, + areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, + cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice, + damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, + maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed, + range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType)); + } catch (Exception exc) { + System.err.println("Attack 1 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); + } + } + if ((attacksEnabled & 0x2) != 0) { + try { + // attack two + final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK2_BACKSWING_POINT, 0); + final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK2_DAMAGE_POINT, 0); + final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_FULL_DMG, 0); + final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_HALF_DMG, 0); + final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_QUARTER_DMG, 0); + final EnumSet areaOfEffectTargets = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_AREA_OF_EFFECT_TARGETS, 0)); + final CAttackType attackType = CAttackType + .parseAttackType(unitType.getFieldAsString(ATTACK2_ATTACK_TYPE, 0)); + final float cooldownTime = unitType.getFieldAsFloat(ATTACK2_COOLDOWN, 0); + final int damageBase = unitType.getFieldAsInteger(ATTACK2_DMG_BASE, 0); + final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_HALF, 0); + final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_QUARTER, 0); + final float damageLossFactor = unitType.getFieldAsFloat(ATTACK2_DAMAGE_LOSS_FACTOR, 0); + final int damageDice = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); + final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0); + final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_DIST, 0); + final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_RADIUS, 0); + final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0); + final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0); + final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); + final String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); + final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED, + 0); + final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); + final int range = unitType.getFieldAsInteger(ATTACK2_RANGE, 0); + final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK2_RANGE_MOTION_BUFFER, 0); + final boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0); + final EnumSet targetsAllowed = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_TARGETS_ALLOWED, 0)); + final String weaponSound = unitType.getFieldAsString(ATTACK2_WEAPON_SOUND, 0); + final CWeaponType weaponType = CWeaponType + .parseWeaponType(unitType.getFieldAsString(ATTACK2_WEAPON_TYPE, 0)); + attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, + areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, + cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice, + damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, + maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed, + range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType)); + } catch (Exception exc) { + System.err.println("Attack 2 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); + } + } + final int deathType = unitType.getFieldAsInteger(DEATH_TYPE, 0); + final boolean raise = (deathType & 0x1) != 0; + final boolean decay = (deathType & 0x2) != 0; + final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0); + final float impactZ = unitType.getFieldAsFloat(PROJECTILE_IMPACT_Z, 0); + final CDefenseType defenseType = CDefenseType.parseDefenseType(unitType.getFieldAsString(DEFENSE_TYPE, 0)); + final float deathTime = unitType.getFieldAsFloat(DEATH_TIME, 0); + unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications, + attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, + targetedAs, acquisitionRange, minimumAttackRange); + this.unitIdToUnitType.put(typeId, unitTypeInstance); + } + return unitTypeInstance; + } - private CUnitAttack createAttack(final float animationBackswingPoint, final float animationDamagePoint, - final int areaOfEffectFullDamage, final int areaOfEffectMediumDamage, final int areaOfEffectSmallDamage, - final EnumSet areaOfEffectTargets, final CAttackType attackType, final float cooldownTime, - final int damageBase, final float damageFactorMedium, final float damageFactorSmall, - final float damageLossFactor, final int damageDice, final int damageSidesPerDie, - final float damageSpillDistance, final float damageSpillRadius, final int damageUpgradeAmount, - final int maximumNumberOfTargets, final float projectileArc, final String projectileArt, - final boolean projectileHomingEnabled, final int projectileSpeed, final int range, - final float rangeMotionBuffer, final boolean showUI, final EnumSet targetsAllowed, - final String weaponSound, final CWeaponType weaponType) { - final CUnitAttack attack; - switch (weaponType) { - case MISSILE: - attack = new CUnitAttackMissile(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, - projectileSpeed); - break; - case MBOUNCE: - attack = new CUnitAttackMissileBounce(animationBackswingPoint, animationDamagePoint, attackType, - cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, - rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, - projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets, - areaOfEffectFullDamage, areaOfEffectTargets); - break; - case MSPLASH: - case ARTILLERY: - attack = new CUnitAttackMissileSplash(animationBackswingPoint, animationDamagePoint, attackType, - cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, - rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, - projectileHomingEnabled, projectileSpeed, areaOfEffectFullDamage, areaOfEffectMediumDamage, - areaOfEffectSmallDamage, areaOfEffectTargets, damageFactorMedium, damageFactorSmall); - break; - case MLINE: - case ALINE: - attack = new CUnitAttackMissileLine(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, - projectileSpeed, damageSpillDistance, damageSpillRadius); - break; - case INSTANT: - attack = new CUnitAttackInstant(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType, projectileArt); - break; - default: - case NORMAL: - attack = new CUnitAttackNormal(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType); - break; - } - return attack; - } + private CUnitAttack createAttack(final float animationBackswingPoint, final float animationDamagePoint, + final int areaOfEffectFullDamage, final int areaOfEffectMediumDamage, final int areaOfEffectSmallDamage, + final EnumSet areaOfEffectTargets, final CAttackType attackType, final float cooldownTime, + final int damageBase, final float damageFactorMedium, final float damageFactorSmall, + final float damageLossFactor, final int damageDice, final int damageSidesPerDie, + final float damageSpillDistance, final float damageSpillRadius, final int damageUpgradeAmount, + final int maximumNumberOfTargets, final float projectileArc, final String projectileArt, + final boolean projectileHomingEnabled, final int projectileSpeed, final int range, + final float rangeMotionBuffer, final boolean showUI, final EnumSet targetsAllowed, + final String weaponSound, final CWeaponType weaponType) { + final CUnitAttack attack; + switch (weaponType) { + case MISSILE: + attack = new CUnitAttackMissile(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed); + break; + case MBOUNCE: + attack = new CUnitAttackMissileBounce(animationBackswingPoint, animationDamagePoint, attackType, + cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, + rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, + projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets, + areaOfEffectFullDamage, areaOfEffectTargets); + break; + case MSPLASH: + case ARTILLERY: + attack = new CUnitAttackMissileSplash(animationBackswingPoint, animationDamagePoint, attackType, + cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, + rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, + projectileHomingEnabled, projectileSpeed, areaOfEffectFullDamage, areaOfEffectMediumDamage, + areaOfEffectSmallDamage, areaOfEffectTargets, damageFactorMedium, damageFactorSmall); + break; + case MLINE: + case ALINE: + attack = new CUnitAttackMissileLine(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed, damageSpillDistance, damageSpillRadius); + break; + case INSTANT: + attack = new CUnitAttackInstant(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArt); + break; + default: + case NORMAL: + attack = new CUnitAttackNormal(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType); + break; + } + return attack; + } - public float getPropulsionWindow(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROPULSION_WINDOW, 0); - } + public float getPropulsionWindow(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROPULSION_WINDOW, 0); + } - public float getTurnRate(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(TURN_RATE, 0); - } + public float getTurnRate(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(TURN_RATE, 0); + } - public boolean isBuilding(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsBoolean(IS_BLDG, 0); - } + public boolean isBuilding(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsBoolean(IS_BLDG, 0); + } - public String getName(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getName(); - } + public String getName(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getName(); + } - public int getA1MinDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) - + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0); - } + public int getA1MinDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) + + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0); + } - public int getA1MaxDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) - + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0) - * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0)); - } + public int getA1MaxDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) + + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0) + * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0)); + } - public int getA2MinDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) - + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0); - } + public int getA2MinDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) + + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0); + } - public int getA2MaxDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) - + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0) - * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0)); - } + public int getA2MaxDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) + + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0) + * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0)); + } - public int getDefense(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(DEFENSE, 0); - } + public int getDefense(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(DEFENSE, 0); + } - public int getA1ProjectileSpeed(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); - } + public int getA1ProjectileSpeed(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); + } - public float getA1ProjectileArc(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); - } + public float getA1ProjectileArc(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); + } - public int getA2ProjectileSpeed(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); - } + public int getA2ProjectileSpeed(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); + } - public float getA2ProjectileArc(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); - } + public float getA2ProjectileArc(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); + } - public String getA1MissileArt(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsString(ATTACK1_MISSILE_ART, 0); - } + public String getA1MissileArt(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsString(ATTACK1_MISSILE_ART, 0); + } - public String getA2MissileArt(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsString(ATTACK2_MISSILE_ART, 0); - } + public String getA2MissileArt(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsString(ATTACK2_MISSILE_ART, 0); + } - public float getA1Cooldown(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_COOLDOWN, 0); - } + public float getA1Cooldown(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_COOLDOWN, 0); + } - public float getA2Cooldown(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_COOLDOWN, 0); - } + public float getA2Cooldown(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_COOLDOWN, 0); + } - public float getProjectileLaunchX(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_X, 0); - } + public float getProjectileLaunchX(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_X, 0); + } - public float getProjectileLaunchY(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Y, 0); - } + public float getProjectileLaunchY(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Y, 0); + } - public float getProjectileLaunchZ(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Z, 0); - } + public float getProjectileLaunchZ(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Z, 0); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java index 7bc942d..8d30b1f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java @@ -138,7 +138,6 @@ public class CAttackOrder implements COrder { else { damage = simulation.getSeededRandom().nextInt(maxDamage - minDamage) + minDamage; } - System.out.println(damage + " from " + minDamage + " to " + maxDamage); this.unitAttack.launch(simulation, this.unit, this.target, damage); this.damagePointLaunchTime = 0; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java index c9fe992..0af31d0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java @@ -177,7 +177,8 @@ public class CPathfindingProcessor { } } - while (!openSet.isEmpty()) { + int searchIterations = 0; + while (!openSet.isEmpty() && searchIterations < 150000) { Node current = openSet.poll(); if (isGoal(current)) { final LinkedList totalPath = new LinkedList<>(); @@ -259,6 +260,7 @@ public class CPathfindingProcessor { } } } + searchIterations++; } return Collections.emptyList(); } diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index 1eaf29d..0675b3a 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -24,7 +24,7 @@ import com.etheller.warsmash.viewer5.gl.SoundLengthExtension; import com.etheller.warsmash.viewer5.gl.WireframeExtension; public class DesktopLauncher { - public static void main(final String[] arg) { + public static void main(String[] arg) { Extensions.angleInstancedArrays = new ANGLEInstancedArrays() { @Override public void glVertexAttribDivisorANGLE(final int index, final int divisor) { @@ -83,10 +83,11 @@ public class DesktopLauncher { config.useGL30 = true; config.gles30ContextMajorVersion = 3; config.gles30ContextMinorVersion = 3; - config.samples = 16; + //config.samples = 16; // config.vSyncEnabled = false; // config.foregroundFPS = 0; // config.backgroundFPS = 0; + arg = new String[]{"-windowed"}; if ((arg.length > 0) && "-windowed".equals(arg[0])) { config.fullscreen = false; }