diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index ec2d2f3..6569b5d 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -18,7 +18,7 @@ Path06="." [Map] //FilePath="CombatUnitTests.w3x" //FilePath="PitchRoll.w3x" -//FilePath="PeonStartingBase.w3x" +FilePath="PeonStartingBase.w3x" //FilePath="DungeonGoldMine.w3m" //FilePath="PlayerPeasants.w3m" //FilePath="FireLord.w3x" @@ -29,4 +29,4 @@ Path06="." //FilePath="OrcAssault.w3x" //FilePath="FrostyVsFarm.w3m" //FilePath="ModelTest.w3x" -FilePath="SpinningSample.w3x" \ No newline at end of file +//FilePath="SpinningSample.w3x" \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 11872d9..5687bd8 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -134,7 +134,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), @@ -353,6 +356,9 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public boolean keyTyped(final char character) { + if (character == '1') { + Gdx.input.setCursorCatched(!Gdx.input.isCursorCatched()); + } return false; } @@ -373,6 +379,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public boolean touchDragged(final int screenX, final int screenY, final int pointer) { + final float worldScreenY = getHeight() - screenY; + if (this.meleeUI.touchDragged(screenX, screenY, worldScreenY, pointer)) { + return false; + } return false; } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index 04a54bb..c296465 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -27,12 +27,14 @@ import com.etheller.warsmash.parsers.fdf.datamodel.SetPointDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.TextJustify; import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; import com.etheller.warsmash.parsers.fdf.frames.AbstractUIFrame; +import com.etheller.warsmash.parsers.fdf.frames.FilterModeTextureFrame; import com.etheller.warsmash.parsers.fdf.frames.SetPoint; import com.etheller.warsmash.parsers.fdf.frames.SimpleFrame; import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; import com.etheller.warsmash.parsers.fdf.frames.StringFrame; import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; +import com.etheller.warsmash.parsers.mdlx.Layer.FilterMode; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; import com.etheller.warsmash.util.ImageUtils; @@ -211,6 +213,16 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { return textureFrame; } + public TextureFrame createTextureFrame(final String name, final UIFrame parent, final boolean decorateFileNames, + final Vector4Definition texCoord, final FilterMode filterMode) { + final FilterModeTextureFrame textureFrame = new FilterModeTextureFrame(name, parent, decorateFileNames, + texCoord); + textureFrame.setFilterMode(filterMode); + this.nameToFrame.put(name, textureFrame); + add(textureFrame); + return textureFrame; + } + public StringFrame createStringFrame(final String name, final UIFrame parent, final Color color, final TextJustify justifyH, final TextJustify justifyV, final float fdfFontSize) { this.fontParam.size = (int) convertY(this.viewport, fdfFontSize); diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java index 92fa4a6..9f7c888 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java @@ -25,6 +25,9 @@ public class StringFrame extends AbstractRenderableFrame { } public void setText(final String text) { + if (text == null) { + throw new IllegalArgumentException(); + } this.text = text; } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java index c3f5190..3e1ae18 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java @@ -63,4 +63,12 @@ public class TextureFrame extends AbstractRenderableFrame { public void setTexture(final TextureRegion texture) { this.texture = texture; } + + @Override + public UIFrame touchDown(final float screenX, final float screenY, final int button) { + if (this.renderBounds.contains(screenX, screenY)) { + return this; + } + return super.touchDown(screenX, screenY, button); + } } 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 80984af..7f8db03 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -26,6 +26,7 @@ import com.badlogic.gdx.Gdx; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.math.collision.BoundingBox; import com.badlogic.gdx.math.collision.Ray; import com.etheller.warsmash.common.FetchDataTypeName; import com.etheller.warsmash.common.LoadGenericCallback; @@ -48,6 +49,8 @@ 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.Quadtree; +import com.etheller.warsmash.util.QuadtreeIntersector; import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.util.WarsmashConstants; @@ -85,12 +88,14 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitFilterFunction import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.CWidgetAbilityTargetCheckReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.PointAbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; import mpq.MPQArchive; @@ -117,10 +122,11 @@ public class War3MapViewer extends ModelViewer { 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(); + public 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 Vector3 intersectionHeap = new Vector3(); + private static final Rectangle rectangleHeap = new Rectangle(); public static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); public PathSolver wc3PathSolver = PathSolver.DEFAULT; @@ -185,6 +191,11 @@ public class War3MapViewer extends ModelViewer { private GameUI gameUI; private Vector3 lightDirection; + private Quadtree walkableObjectsTree; + private final QuadtreeIntersectorFindsWalkableRenderHeight walkablesIntersector = new QuadtreeIntersectorFindsWalkableRenderHeight(); + private final QuadtreeIntersectorFindsHitPoint walkablesIntersectionFinder = new QuadtreeIntersectorFindsHitPoint(); + private final QuadtreeIntersectorFindsHighestWalkable intersectorFindsHighestWalkable = new QuadtreeIntersectorFindsHighestWalkable(); + public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { super(dataSource, canvas); this.gameDataSource = dataSource; @@ -501,10 +512,9 @@ public class War3MapViewer extends ModelViewer { 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()); + }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, w3iFile.getPlayers()); + this.walkableObjectsTree = new Quadtree<>(this.terrain.getEntireMap()); if (this.doodadsAndDestructiblesLoaded) { this.loadDoodadsAndDestructibles(this.allObjectData); } @@ -632,8 +642,20 @@ public class War3MapViewer extends ModelViewer { } if (type == WorldEditorDataType.DESTRUCTIBLES) { - this.doodads.add(new RenderDestructable(this, model, row, doodad, type, maxPitch, maxRoll, - doodad.getLife())); + final RenderDestructable renderDestructable = new RenderDestructable(this, model, row, doodad, type, + maxPitch, maxRoll, doodad.getLife()); + if (row.readSLKTagBoolean("walkable")) { + final float x = doodad.getLocation()[0]; + final float y = doodad.getLocation()[1]; + final BoundingBox boundingBox = model.bounds.getBoundingBox(); + final float minX = boundingBox.min.x + x; + final float minY = boundingBox.min.y + y; + final Rectangle renderDestructableBounds = new Rectangle(minX, minY, boundingBox.getWidth(), + boundingBox.getHeight()); + this.walkableObjectsTree.add((MdxComplexInstance) renderDestructable.instance, + renderDestructableBounds); + } + this.doodads.add(renderDestructable); } else { this.doodads.add(new RenderDoodad(this, model, row, doodad, type, maxPitch, maxRoll)); @@ -884,7 +906,7 @@ public class War3MapViewer extends ModelViewer { } else { this.items.add(new RenderItem(this, model, row, unit, soundset, portraitModel)); // TODO store - // somewhere + // somewhere if (unitShadowSplat != null) { unitShadowSplat.unitMapping.add(new Consumer() { @Override @@ -1080,6 +1102,15 @@ public class War3MapViewer extends ModelViewer { 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)); + this.walkableObjectsTree.intersect(rectangleHeap, this.walkablesIntersectionFinder.reset(gdxRayHeap)); + if (this.walkablesIntersectionFinder.found) { + out.set(this.walkablesIntersectionFinder.intersection); + } + else { + out.z = Math.max(getWalkableRenderHeight(out.x, out.y), this.terrain.getGroundHeight(out.x, out.y)); + } } public void showConfirmation(final Vector3 position, final float red, final float green, final float blue) { @@ -1105,9 +1136,11 @@ public class War3MapViewer extends ModelViewer { final MdxComplexInstance instance = unit.instance; if (instance.isVisible(this.worldScene.camera) && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap, - unit.getSimulationUnit().getUnitType().isBuilding()) + unit.getSimulationUnit().getUnitType().isBuilding(), false) && !unit.getSimulationUnit().isDead()) { - entity = unit; + if ((entity == null) || (entity.instance.depth > instance.depth)) { + entity = unit; + } } } List sel; @@ -1148,7 +1181,7 @@ public class War3MapViewer extends ModelViewer { 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())) { + intersectionHeap, unit.getSimulationUnit().getUnitType().isBuilding(), false)) { if (filter.call(unit.getSimulationUnit())) { if ((entity == null) || (entity.instance.depth > instance.depth)) { entity = unit; @@ -1242,6 +1275,41 @@ public class War3MapViewer extends ModelViewer { 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."); + } + } + + } + return ordered; + } + public boolean orderSmart(final RenderUnit target) { boolean ordered = false; for (final RenderUnit unit : this.selected) { @@ -1348,4 +1416,82 @@ public class War3MapViewer extends ModelViewer { public AbilityDataUI getAbilityDataUI() { return this.abilityDataUI; } + + public float getWalkableRenderHeight(final float x, final float y) { + this.walkableObjectsTree.intersect(x, y, this.walkablesIntersector.reset(x, y)); + return this.walkablesIntersector.z; + } + + public MdxComplexInstance getHighestWalkableUnder(final float x, final float y) { + this.walkableObjectsTree.intersect(x, y, this.intersectorFindsHighestWalkable.reset(x, y)); + return this.intersectorFindsHighestWalkable.highestInstance; + } + + private static final class QuadtreeIntersectorFindsWalkableRenderHeight + implements QuadtreeIntersector { + private float z; + private final Ray ray = new Ray(); + private final Vector3 intersection = new Vector3(); + + private QuadtreeIntersectorFindsWalkableRenderHeight reset(final float x, final float y) { + this.z = -Float.MAX_VALUE; + this.ray.set(x, y, 4096, 0, 0, -8192); + return this; + } + + @Override + public boolean onIntersect(final MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { + this.z = Math.max(this.z, this.intersection.z); + } + return false; + } + } + + private static final class QuadtreeIntersectorFindsHighestWalkable + implements QuadtreeIntersector { + private float z; + private final Ray ray = new Ray(); + private final Vector3 intersection = new Vector3(); + private MdxComplexInstance highestInstance; + + private QuadtreeIntersectorFindsHighestWalkable reset(final float x, final float y) { + this.z = -Float.MAX_VALUE; + this.ray.set(x, y, 4096, 0, 0, -8192); + this.highestInstance = null; + return this; + } + + @Override + public boolean onIntersect(final MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { + if (this.intersection.z > this.z) { + this.z = this.intersection.z; + this.highestInstance = intersectingObject; + } + } + return false; + } + } + + private static final class QuadtreeIntersectorFindsHitPoint implements QuadtreeIntersector { + private Ray ray; + private final Vector3 intersection = new Vector3(); + private boolean found; + + private QuadtreeIntersectorFindsHitPoint reset(final Ray ray) { + this.ray = ray; + this.found = false; + return this; + } + + @Override + public boolean onIntersect(final MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(this.ray, this.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..d0c12de 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 @@ -24,6 +24,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.Moveme import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.CommandButtonListener; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.CommandCardPopulatingAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitAnimationListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; @@ -131,10 +132,11 @@ public class RenderUnit { } - public void populateCommandCard(final CommandButtonListener commandButtonListener, + public void populateCommandCard(final CSimulation game, final CommandButtonListener commandButtonListener, final AbilityDataUI abilityDataUI) { for (final CAbility ability : this.simulationUnit.getAbilities()) { - ability.visit(CommandCardPopulatingAbilityVisitor.INSTANCE.reset(commandButtonListener, abilityDataUI)); + ability.visit(CommandCardPopulatingAbilityVisitor.INSTANCE.reset(game, this.simulationUnit, + commandButtonListener, abilityDataUI)); } } @@ -166,15 +168,43 @@ public class RenderUnit { 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) + 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)); + final float groundHeightTerrain = map.terrain.getGroundHeight(x, y); + float groundHeightTerrainAndWater; + MdxComplexInstance currentWalkableUnder; + final boolean standingOnWater = (swimming) || (movementType == MovementType.FLOAT) + || (movementType == MovementType.FLY) || (movementType == MovementType.HOVER); + if (standingOnWater) { + groundHeightTerrainAndWater = Math.max(groundHeightTerrain, map.terrain.getWaterHeight(x, y)); } else { - groundHeight = map.terrain.getGroundHeight(x, y); + // 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); @@ -263,17 +293,49 @@ public class RenderUnit { 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); + 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 { + final float pitchGroundHeight1 = map.terrain.getGroundHeight(pitchSampleBackwardX, pitchSampleBackwardY); + final float pitchGroundHeight2 = map.terrain.getGroundHeight(pitchSampleForwardX, pitchSampleForwardY); + final float rollGroundHeight1 = map.terrain.getGroundHeight(rollSampleBackwardX, rollSampleBackwardY); + final 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)); @@ -289,6 +351,21 @@ public class RenderUnit { this.unitAnimationListenerImpl.update(); } + private float getGroundHeightSample(final float groundHeight, final MdxComplexInstance currentWalkableUnder, + final float sampleX, final 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; + } + public CUnit getSimulationUnit() { return this.simulationUnit; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java index 89a82a9..9971f88 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java @@ -34,5 +34,6 @@ public interface CommandButtonListener { // int getButtonPositionY(); // // int getOrderId(); - void commandButton(int buttonPositionX, int buttonPositionY, Texture icon, int abilityHandleId, int orderId); + void commandButton(int buttonPositionX, int buttonPositionY, Texture icon, int abilityHandleId, int orderId, + boolean active); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java index c0aeb88..014d80b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java @@ -2,20 +2,29 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.IconUI; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver; public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor { public static final CommandCardPopulatingAbilityVisitor INSTANCE = new CommandCardPopulatingAbilityVisitor(); + private CSimulation game; + private CUnit unit; + private CommandButtonListener commandButtonListener; private AbilityDataUI abilityDataUI; private boolean hasStop; - public CommandCardPopulatingAbilityVisitor reset(final CommandButtonListener commandButtonListener, - final AbilityDataUI abilityDataUI) { + public CommandCardPopulatingAbilityVisitor reset(final CSimulation game, final CUnit unit, + final CommandButtonListener commandButtonListener, final AbilityDataUI abilityDataUI) { + this.game = game; + this.unit = unit; this.commandButtonListener = commandButtonListener; this.abilityDataUI = abilityDataUI; this.hasStop = false; @@ -24,28 +33,41 @@ public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor abilities = new ArrayList<>(); - private COrder currentOrder; - private final Queue orderQueue = new LinkedList<>(); + private CBehavior currentOrder; + private final Queue orderQueue = new LinkedList<>(); private final CUnitType unitType; private Rectangle collisionRectangle; @@ -189,6 +190,7 @@ public class CUnit extends CWidget { // remove current order, because it's completed, polling next // item from order queue this.currentOrder = this.orderQueue.poll(); + this.stateNotifier.ordersChanged(); } if (this.currentOrder == null) { // maybe order "stop" here @@ -222,7 +224,7 @@ public class CUnit extends CWidget { return game.getGameplayConstants().getBoneDecayTime(); } - public void order(final COrder order, final boolean queue) { + public void order(final CBehavior order, final boolean queue) { if (isDead()) { return; } @@ -231,10 +233,12 @@ public class CUnit extends CWidget { } else { this.currentOrder = order; + this.orderQueue.clear(); } + this.stateNotifier.ordersChanged(); } - public COrder getCurrentOrder() { + public CBehavior getCurrentOrder() { return this.currentOrder; } @@ -390,7 +394,7 @@ public class CUnit extends CWidget { CAllianceType.PASSIVE)) { for (final CUnitAttack attack : this.unitType.getAttacks()) { if (source.canBeTargetedBy(simulation, this, attack.getTargetsAllowed())) { - this.order(new CAttackOrder(this, attack, OrderIds.attack, source), false); + this.order(new CBehaviorAttack(this, attack, OrderIds.attack, source), false); break; } } @@ -521,6 +525,10 @@ public class CUnit extends CWidget { } } } + else { + System.err.println("No targeting because " + targetsAllowed + " does not contain all of " + + this.unitType.getTargetedAs()); + } return false; } @@ -550,7 +558,7 @@ public class CUnit extends CWidget { if (this.source.canReach(unit, this.source.acquisitionRange) && unit.canBeTargetedBy(this.game, this.source, attack.getTargetsAllowed()) && (this.source.distance(unit) >= this.source.getUnitType().getMinimumAttackRange())) { - this.source.order(new CAttackOrder(this.source, attack, OrderIds.attack, unit), false); + this.source.order(new CBehaviorAttack(this.source, attack, OrderIds.attack, unit), false); return true; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java index 7109037..c1d3f51 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java @@ -5,6 +5,8 @@ import com.etheller.warsmash.util.SubscriberSetNotifier; public interface CUnitStateListener { void lifeChanged(); // hp (current) changes + void ordersChanged(); + public static final class CUnitStateNotifier extends SubscriberSetNotifier implements CUnitStateListener { @Override @@ -13,5 +15,12 @@ public interface CUnitStateListener { listener.lifeChanged(); } } + + @Override + public void ordersChanged() { + for (final CUnitStateListener listener : set) { + listener.ordersChanged(); + } + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java index 9b5db10..743def9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java @@ -17,4 +17,5 @@ public interface CAbility extends CAbilityView { void onOrder(CSimulation game, CUnit caster, int orderId, Vector2 point, boolean queue); void onOrderNoTarget(CSimulation game, CUnit caster, int orderId, boolean queue); + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java index 26d07c3..c85cb66 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java @@ -1,15 +1,17 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; import com.badlogic.gdx.math.Vector2; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.StringsToExternalizeLater; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CAttackOrder; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CMoveOrder; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CBehaviorAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CBehaviorMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver.TargetType; @@ -30,6 +32,15 @@ public class CAbilityAttack implements CAbility { @Override public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target, final AbilityTargetCheckReceiver receiver) { + if (orderId == OrderIds.smart) { + if (target instanceof CUnit) { + if (game.getPlayer(unit.getPlayerIndex()).hasAlliance(((CUnit) target).getPlayerIndex(), + CAllianceType.PASSIVE)) { + receiver.orderIdNotAccepted(); + return; + } + } + } if ((orderId == OrderIds.smart) || (orderId == OrderIds.attack)) { boolean canTarget = false; for (final CUnitAttack attack : unit.getUnitType().getAttacks()) { @@ -54,7 +65,30 @@ public class CAbilityAttack implements CAbility { @Override public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final Vector2 target, final AbilityTargetCheckReceiver receiver) { - receiver.mustTargetType(TargetType.UNIT); + switch (orderId) { + case OrderIds.attack: + receiver.targetOk(target); + break; + case OrderIds.attackground: + boolean allowAttackGround = false; + for (final CUnitAttack attack : unit.getUnitType().getAttacks()) { + if ((attack.getWeaponType() == CWeaponType.ARTILLERY) + || (attack.getWeaponType() == CWeaponType.ALINE)) { + allowAttackGround = true; + break; + } + } + if (allowAttackGround) { + receiver.targetOk(target); + } + else { + receiver.orderIdNotAccepted(); + } + break; + default: + receiver.orderIdNotAccepted(); + break; + } } @Override @@ -74,15 +108,15 @@ public class CAbilityAttack implements CAbility { @Override public void onOrder(final CSimulation game, final CUnit caster, final int orderId, final CWidget target, final boolean queue) { - COrder order = null; + CBehavior order = null; for (final CUnitAttack attack : caster.getUnitType().getAttacks()) { if (target.canBeTargetedBy(game, caster, attack.getTargetsAllowed())) { - order = new CAttackOrder(caster, attack, orderId, target); + order = new CBehaviorAttack(caster, attack, orderId, target); break; } } if (order == null) { - order = new CMoveOrder(caster, orderId, target.getX(), target.getY()); + order = new CBehaviorMove(caster, orderId, target.getX(), target.getY()); } caster.order(order, queue); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java index 6b71c20..19d1765 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java @@ -5,8 +5,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.StringsToExternalizeLater; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CMoveOrder; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CPatrolOrder; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CBehaviorMove; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CBehaviorPatrol; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; @@ -85,13 +85,13 @@ public class CAbilityMove implements CAbility { @Override public void onOrder(final CSimulation game, final CUnit caster, final int orderId, final CWidget target, final boolean queue) { - caster.order(new CPatrolOrder(caster, orderId, (CUnit) target), queue); + caster.order(new CBehaviorPatrol(caster, orderId, (CUnit) target), queue); } @Override public void onOrder(final CSimulation game, final CUnit caster, final int orderId, final Vector2 target, final boolean queue) { - caster.order(new CMoveOrder(caster, orderId, target.x, target.y), queue); + caster.order(new CBehaviorMove(caster, orderId, target.x, target.y), queue); } @Override 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/COrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehavior.java similarity index 73% rename from core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/COrder.java rename to core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehavior.java index b7821a1..a5643e0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/COrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehavior.java @@ -1,6 +1,8 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation; +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; -public interface COrder { +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; + +public interface CBehavior { /** * Executes one step of game simulation of the current order, returning true if * the order has completed. Many orders may wrap the move order and spend a 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/CBehaviorAttack.java similarity index 91% rename from core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java rename to core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorAttack.java index 7bc942d..c444bfb 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/CBehaviorAttack.java @@ -3,13 +3,12 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; -public class CAttackOrder implements COrder { +public class CBehaviorAttack implements CBehavior { private final CUnit unit; private final int orderId; private boolean wasWithinPropWindow = false; @@ -17,11 +16,11 @@ public class CAttackOrder implements COrder { private final CWidget target; private int damagePointLaunchTime; private int backSwingTime; - private COrder moveOrder; + private CBehavior moveOrder; private int thisOrderCooldownEndTime; private boolean wasInRange = false; - public CAttackOrder(final CUnit unit, final CUnitAttack unitAttack, final int orderId, final CWidget target) { + public CBehaviorAttack(final CUnit unit, final CUnitAttack unitAttack, final int orderId, final CWidget target) { this.unit = unit; this.unitAttack = unitAttack; this.orderId = orderId; @@ -32,10 +31,10 @@ public class CAttackOrder implements COrder { private void createMoveOrder(final CUnit unit, final CWidget target) { if (!unit.isMovementDisabled()) { // TODO: Check mobility instead if ((target instanceof CUnit) && !(((CUnit) target).getUnitType().isBuilding())) { - this.moveOrder = new CMoveOrder(unit, this.orderId, (CUnit) target); + this.moveOrder = new CBehaviorMove(unit, this.orderId, (CUnit) target); } else { - this.moveOrder = new CMoveOrder(unit, this.orderId, target.getX(), target.getY()); + this.moveOrder = new CBehaviorMove(unit, this.orderId, target.getX(), target.getY()); } } else { @@ -50,7 +49,7 @@ public class CAttackOrder implements COrder { return true; } float range = this.unitAttack.getRange(); - if ((this.target instanceof CUnit) && (((CUnit) this.target).getCurrentOrder() instanceof CMoveOrder) + if ((this.target instanceof CUnit) && (((CUnit) this.target).getCurrentOrder() instanceof CBehaviorMove) && (this.damagePointLaunchTime != 0 /* * only apply range motion buffer if they were already in range and * attacked @@ -138,7 +137,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/orders/CMoveOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorMove.java similarity index 97% rename from core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java rename to core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorMove.java index 57b0e38..2bca116 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorMove.java @@ -10,13 +10,12 @@ import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWorldCollision; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor; -public class CMoveOrder implements COrder { +public class CBehaviorMove implements CBehavior { private static final Rectangle tempRect = new Rectangle(); private final CUnit unit; private final int orderId; @@ -27,7 +26,7 @@ public class CMoveOrder implements COrder { private int searchCycles = 0; private CUnit followUnit; - public CMoveOrder(final CUnit unit, final int orderId, final float targetX, final float targetY) { + public CBehaviorMove(final CUnit unit, final int orderId, final float targetX, final float targetY) { this.unit = unit; this.orderId = orderId; this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( @@ -36,7 +35,7 @@ public class CMoveOrder implements COrder { this.target = new Point2D.Float(targetX, targetY); } - public CMoveOrder(final CUnit unit, final int orderId, final CUnit followUnit) { + public CBehaviorMove(final CUnit unit, final int orderId, final CUnit followUnit) { this.unit = unit; this.orderId = orderId; this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorPatrol.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorPatrol.java new file mode 100644 index 0000000..c1891d2 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorPatrol.java @@ -0,0 +1,96 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; + +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; + +public class CBehaviorPatrol implements CBehavior { + private final CUnit unit; + private final int orderId; + private final CWidget target; + private CBehavior moveOrder; + + public CBehaviorPatrol(final CUnit unit, final int orderId, final CUnit target) { + this.unit = unit; + this.orderId = orderId; + this.target = target; + createMoveOrder(unit, target); + } + + private void createMoveOrder(final CUnit unit, final CUnit target) { + if (!unit.isMovementDisabled()) { // TODO: Check mobility instead + if ((target instanceof CUnit) && !(target.getUnitType().isBuilding())) { + this.moveOrder = new CBehaviorMove(unit, this.orderId, target); + } + else { + this.moveOrder = new CBehaviorMove(unit, this.orderId, target.getX(), target.getY()); + } + } + else { + this.moveOrder = null; + } + } + + @Override + public boolean update(final CSimulation simulation) { + if (this.target.isDead()) { + return true; + } + final float range = this.unit.getAcquisitionRange(); + if (!this.unit.canReach(this.target, range)) { + if (this.moveOrder == null) { + return true; + } + if (this.moveOrder.update(simulation)) { + return true; // we just cant reach them + } + return false; + } + if (!this.unit.isMovementDisabled()) { + final float prevX = this.unit.getX(); + final float prevY = this.unit.getY(); + final float deltaY = this.target.getY() - prevY; + final float deltaX = this.target.getX() - prevX; + final double goalAngleRad = Math.atan2(deltaY, deltaX); + float goalAngle = (float) Math.toDegrees(goalAngleRad); + if (goalAngle < 0) { + goalAngle += 360; + } + float facing = this.unit.getFacing(); + float delta = goalAngle - facing; + final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); + + if (delta < -180) { + delta = 360 + delta; + } + if (delta > 180) { + delta = -360 + delta; + } + final float absDelta = Math.abs(delta); + + if ((absDelta <= 1.0) && (absDelta != 0)) { + this.unit.setFacing(goalAngle); + } + else { + float angleToAdd = Math.signum(delta) * (float) Math.toDegrees(turnRate); + if (absDelta < Math.abs(angleToAdd)) { + angleToAdd = delta; + } + facing += angleToAdd; + this.unit.setFacing(facing); + } + } + else { + } + this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f, false); + return false; + } + + @Override + public int getOrderId() { + return this.orderId; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CDoNothingOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorStop.java similarity index 68% rename from core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CDoNothingOrder.java rename to core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorStop.java index 9067fa7..f850e63 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CDoNothingOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorStop.java @@ -1,12 +1,11 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; -public class CDoNothingOrder implements COrder { +public class CBehaviorStop implements CBehavior { private final int orderId; - public CDoNothingOrder(final int orderId) { + public CBehaviorStop(final int orderId) { this.orderId = orderId; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CPatrolOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CPatrolOrder.java deleted file mode 100644 index 078e83f..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CPatrolOrder.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; - -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; - -public class CPatrolOrder implements COrder { - private final CUnit unit; - private final int orderId; - private final CWidget target; - private COrder moveOrder; - - public CPatrolOrder(final CUnit unit, final int orderId, final CUnit target) { - this.unit = unit; - this.orderId = orderId; - this.target = target; - createMoveOrder(unit, target); - } - - private void createMoveOrder(final CUnit unit, final CUnit target) { - if (!unit.isMovementDisabled()) { // TODO: Check mobility instead - if ((target instanceof CUnit) && !(target.getUnitType().isBuilding())) { - this.moveOrder = new CMoveOrder(unit, this.orderId, target); - } - else { - this.moveOrder = new CMoveOrder(unit, this.orderId, target.getX(), target.getY()); - } - } - else { - this.moveOrder = null; - } - } - - @Override - public boolean update(final CSimulation simulation) { - if (this.target.isDead()) { - return true; - } - final float range = this.unit.getAcquisitionRange(); - if (!this.unit.canReach(this.target, range)) { - if (this.moveOrder == null) { - return true; - } - if (this.moveOrder.update(simulation)) { - return true; // we just cant reach them - } - return false; - } - return false; - } - - @Override - public int getOrderId() { - return this.orderId; - } - -} 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/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java index ea56443..0ba7d64 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java @@ -83,7 +83,7 @@ public class CPlayerUnitOrderExecutor implements CPlayerUnitOrderListener { if (this.abilityActivationReceiver.isUseOk()) { final StringMsgTargetCheckReceiver targetReceiver = this.targetCheckReceiver(); ability.checkCanTargetNoTarget(this.game, unit, orderId, targetReceiver); - if (targetReceiver.getTarget() != null) { + if (targetReceiver.getMessage() == null) { ability.onOrderNoTarget(this.game, unit, orderId, queue); return true; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java index fc7cbd3..e0f0fb5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java @@ -17,6 +17,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandCardCommandL public class CommandCardIcon extends AbstractRenderableFrame { private TextureFrame iconFrame; + private TextureFrame activeHighlightFrame; private SpriteFrame cooldownFrame; private SpriteFrame autocastFrame; private CommandButton commandButton; @@ -30,8 +31,10 @@ public class CommandCardIcon extends AbstractRenderableFrame { this.commandCardCommandListener = commandCardCommandListener; } - public void set(final TextureFrame iconFrame, final SpriteFrame cooldownFrame, final SpriteFrame autocastFrame) { + public void set(final TextureFrame iconFrame, final TextureFrame activeHighlightFrame, + final SpriteFrame cooldownFrame, final SpriteFrame autocastFrame) { this.iconFrame = iconFrame; + this.activeHighlightFrame = activeHighlightFrame; this.cooldownFrame = cooldownFrame; this.autocastFrame = autocastFrame; } @@ -40,6 +43,7 @@ public class CommandCardIcon extends AbstractRenderableFrame { this.commandButton = commandButton; if (commandButton == null) { this.iconFrame.setVisible(false); + this.activeHighlightFrame.setVisible(false); this.cooldownFrame.setVisible(false); this.autocastFrame.setVisible(false); } @@ -64,8 +68,10 @@ public class CommandCardIcon extends AbstractRenderableFrame { } } - public void setCommandButtonData(final Texture texture, final int abilityHandleId, final int orderId) { + public void setCommandButtonData(final Texture texture, final int abilityHandleId, final int orderId, + final boolean active) { this.iconFrame.setVisible(true); + this.activeHighlightFrame.setVisible(active); this.cooldownFrame.setVisible(false); this.autocastFrame.setVisible(false); this.iconFrame.setTexture(texture); @@ -76,6 +82,7 @@ public class CommandCardIcon extends AbstractRenderableFrame { @Override protected void innerPositionBounds(final Viewport viewport) { this.iconFrame.positionBounds(viewport); + this.activeHighlightFrame.positionBounds(viewport); this.cooldownFrame.positionBounds(viewport); this.autocastFrame.positionBounds(viewport); } @@ -83,6 +90,7 @@ public class CommandCardIcon extends AbstractRenderableFrame { @Override protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { this.iconFrame.render(batch, baseFont, glyphLayout); + this.activeHighlightFrame.render(batch, baseFont, glyphLayout); this.cooldownFrame.render(batch, baseFont, glyphLayout); this.autocastFrame.render(batch, baseFont, glyphLayout); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 8ae6024..86643d5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -13,6 +13,8 @@ import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; @@ -28,6 +30,7 @@ import com.etheller.warsmash.parsers.fdf.frames.StringFrame; import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; +import com.etheller.warsmash.parsers.mdlx.Layer.FilterMode; import com.etheller.warsmash.util.FastNumberFormat; import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.WarsmashConstants; @@ -135,6 +138,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private int selectedSoundCount = 0; private final ActiveCommandUnitTargetFilter activeCommandUnitTargetFilter; + private UIFrame clickUI = null; + public MeleeUI(final DataSource dataSource, final Viewport uiViewport, final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, final CameraPreset[] cameraPresets, final CameraRates cameraRates, final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener, @@ -305,6 +310,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.rootFrame.add(commandCardIcon); final TextureFrame iconFrame = this.rootFrame.createTextureFrame( "SmashCommandButton_" + (commandButtonIndex) + "_Icon", this.rootFrame, false, null); + final TextureFrame activeHighlightFrame = this.rootFrame.createTextureFrame( + "SmashCommandButton_" + (commandButtonIndex) + "_ActiveHighlight", this.rootFrame, true, null, + FilterMode.ADDALPHA); final SpriteFrame cooldownFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashCommandButton_" + (commandButtonIndex) + "_Cooldown", this.rootFrame, "", 0); final SpriteFrame autocastFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", @@ -318,6 +326,11 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma iconFrame.setWidth(GameUI.convertX(this.uiViewport, 0.039f)); iconFrame.setHeight(GameUI.convertY(this.uiViewport, 0.039f)); iconFrame.setTexture(ImageUtils.DEFAULT_ICON_PATH, this.rootFrame); + activeHighlightFrame + .addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + activeHighlightFrame.setWidth(GameUI.convertX(this.uiViewport, 0.039f)); + activeHighlightFrame.setHeight(GameUI.convertY(this.uiViewport, 0.039f)); + activeHighlightFrame.setTexture("CommandButtonActiveHighlight", this.rootFrame); cooldownFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); this.rootFrame.setSpriteFrameModel(cooldownFrame, this.rootFrame.getSkinField("CommandButtonCooldown")); cooldownFrame.setWidth(GameUI.convertX(this.uiViewport, 0.039f)); @@ -326,7 +339,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.rootFrame.setSpriteFrameModel(autocastFrame, this.rootFrame.getSkinField("CommandButtonAutocast")); autocastFrame.setWidth(GameUI.convertX(this.uiViewport, 0.039f)); autocastFrame.setHeight(GameUI.convertY(this.uiViewport, 0.039f)); - commandCardIcon.set(iconFrame, cooldownFrame, autocastFrame); + commandCardIcon.set(iconFrame, activeHighlightFrame, cooldownFrame, autocastFrame); this.commandCard[j][i] = commandCardIcon; commandCardIcon.setCommandButton(null); commandButtonIndex++; @@ -408,7 +421,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma mouseX = Math.max(minX, Math.min(maxX, mouseX)); mouseY = Math.max(minY, Math.min(maxY, mouseY)); - Gdx.input.setCursorPosition(mouseX, mouseY); +// Gdx.input.setCursorPosition(mouseX, mouseY); screenCoordsVector.set(mouseX, mouseY); this.uiViewport.unproject(screenCoordsVector); @@ -456,6 +469,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.cameraManager.updateCamera(); } + private final ShapeRenderer shapeRenderer = new ShapeRenderer(); + public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) { this.rootFrame.render(batch, font20, glyphLayout); if (this.selectedUnit != null) { @@ -466,6 +481,18 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.meleeUIMinimap.render(batch, this.war3MapViewer.units); this.timeIndicator.setFrameByRatio(this.war3MapViewer.simulation.getGameTimeOfDay() / this.war3MapViewer.simulation.getGameplayConstants().getGameDayHours()); + + if (this.clickUI != null) { + batch.end(); + this.shapeRenderer.setProjectionMatrix(this.uiViewport.getCamera().combined); + this.shapeRenderer.begin(ShapeType.Line); + this.shapeRenderer.rect(this.clickUI.getFramePointX(FramePoint.LEFT), + this.clickUI.getFramePointY(FramePoint.BOTTOM), + this.clickUI.getFramePointX(FramePoint.RIGHT) - this.clickUI.getFramePointX(FramePoint.LEFT), + this.clickUI.getFramePointY(FramePoint.TOP) - this.clickUI.getFramePointY(FramePoint.BOTTOM)); + this.shapeRenderer.end(); + batch.begin(); + } } public void portraitTalk() { @@ -477,8 +504,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma public boolean call(final CUnit unit) { final BooleanAbilityTargetCheckReceiver targetReceiver = BooleanAbilityTargetCheckReceiver .getInstance(); - MeleeUI.this.activeCommand.checkCanTarget(MeleeUI.this.war3MapViewer.simulation, unit, - MeleeUI.this.activeCommandOrderId, unit, targetReceiver); + MeleeUI.this.activeCommand.checkCanTarget(MeleeUI.this.war3MapViewer.simulation, + MeleeUI.this.activeCommandUnit.getSimulationUnit(), MeleeUI.this.activeCommandOrderId, unit, + targetReceiver); return targetReceiver.isTargetable(); } } @@ -541,11 +569,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } this.portrait.setSelectedUnit(unit); this.selectedUnit = unit; - for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { - for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { - this.commandCard[j][i].setCommandButton(null); - } - } + clearCommandCard(); if (unit == null) { this.simpleNameValue.setText(""); this.unitLifeText.setText(""); @@ -633,16 +657,24 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } localArmorIconBackdrop.setTexture(defenseTexture); localArmorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense())); - unit.populateCommandCard(this, this.war3MapViewer.getAbilityDataUI()); + unit.populateCommandCard(this.war3MapViewer.simulation, this, this.war3MapViewer.getAbilityDataUI()); + } + } + + private void clearCommandCard() { + for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { + for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { + this.commandCard[j][i].setCommandButton(null); + } } } @Override public void commandButton(final int buttonPositionX, final int buttonPositionY, final Texture icon, - final int abilityHandleId, final int orderId) { + final int abilityHandleId, final int orderId, final boolean active) { final int x = Math.max(0, Math.min(COMMAND_CARD_WIDTH - 1, buttonPositionX)); final int y = Math.max(0, Math.min(COMMAND_CARD_HEIGHT - 1, buttonPositionY)); - this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId); + this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, active); } public void resize(final Rectangle viewport) { @@ -732,6 +764,13 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } + @Override + public void ordersChanged() { + clearCommandCard(); + this.selectedUnit.populateCommandCard(this.war3MapViewer.simulation, this, + this.war3MapViewer.getAbilityDataUI()); + } + public RenderUnit getSelectedUnit() { return this.selectedUnit; } @@ -748,6 +787,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.cameraManager.scrolled(amount); } + private float lastX, lastY; + public boolean touchDown(final int screenX, final int screenY, final float worldScreenY, final int button) { screenCoordsVector.set(screenX, screenY); this.uiViewport.unproject(screenCoordsVector); @@ -756,6 +797,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma screenCoordsVector.y); this.cameraManager.target.x = worldPoint.x; this.cameraManager.target.y = worldPoint.y; + this.lastX = screenCoordsVector.x; + this.lastY = screenCoordsVector.y; return true; } final UIFrame clickedUIFrame = this.rootFrame.touchDown(screenCoordsVector.x, screenCoordsVector.y, button); @@ -790,6 +833,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } else { this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); + clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); + this.activeCommand.checkCanTarget(this.war3MapViewer.simulation, this.activeCommandUnit.getSimulationUnit(), this.activeCommandOrderId, clickLocationTemp2, PointAbilityTargetCheckReceiver.INSTANCE); @@ -822,10 +867,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } else { if (button == Input.Buttons.RIGHT) { - final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY); if (getSelectedUnit() != null) { - if ((rayPickUnit != null) && (rayPickUnit.playerIndex != getSelectedUnit().playerIndex) - && !rayPickUnit.getSimulationUnit().isDead()) { + final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY); + if ((rayPickUnit != null) && !rayPickUnit.getSimulationUnit().isDead()) { boolean ordered = false; for (final RenderUnit unit : this.war3MapViewer.selected) { for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { @@ -929,10 +973,34 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } } + else { + this.clickUI = clickedUIFrame; + this.lastX = screenCoordsVector.x; + this.lastY = screenCoordsVector.y; + } return false; } private static boolean isShiftDown() { return Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) || Gdx.input.isKeyPressed(Input.Keys.SHIFT_RIGHT); } + + public boolean touchDragged(final int screenX, final int screenY, final float worldScreenY, final int pointer) { + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + final float dx = screenCoordsVector.x - this.lastX; + final float dy = screenCoordsVector.y - this.lastY; + + if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { + this.meleeUIMinimap.touchDragged(screenX, screenY, worldScreenY, pointer, dx, dy); + } + else if (this.clickUI != null) { + this.clickUI.setFramePointX(FramePoint.LEFT, this.clickUI.getFramePointX(FramePoint.LEFT) + dx); + this.clickUI.setFramePointY(FramePoint.BOTTOM, this.clickUI.getFramePointY(FramePoint.BOTTOM) + dy); + } + + this.lastX = screenCoordsVector.x; + this.lastY = screenCoordsVector.y; + return false; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUIMinimap.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUIMinimap.java index fcaf00f..ccd0ee9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUIMinimap.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUIMinimap.java @@ -59,4 +59,12 @@ public class MeleeUIMinimap { public boolean containsMouse(final float x, final float y) { return this.minimapFilledArea.contains(x, y); } + + public void touchDragged(final int screenX, final int screenY, final float worldScreenY, final int pointer, + final float dx, final float dy) { + this.minimapFilledArea.x += dx; + this.minimapFilledArea.y += dy; + this.minimap.x += dx; + this.minimap.y += dy; + } } 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; }