Merge and some fixes, includes click drag ui test

This commit is contained in:
Retera 2020-10-25 11:52:04 -04:00
commit 5b7db285e2
37 changed files with 2140 additions and 1657 deletions

View File

@ -18,7 +18,7 @@ Path06="."
[Map] [Map]
//FilePath="CombatUnitTests.w3x" //FilePath="CombatUnitTests.w3x"
//FilePath="PitchRoll.w3x" //FilePath="PitchRoll.w3x"
//FilePath="PeonStartingBase.w3x" FilePath="PeonStartingBase.w3x"
//FilePath="DungeonGoldMine.w3m" //FilePath="DungeonGoldMine.w3m"
//FilePath="PlayerPeasants.w3m" //FilePath="PlayerPeasants.w3m"
//FilePath="FireLord.w3x" //FilePath="FireLord.w3x"
@ -29,4 +29,4 @@ Path06="."
//FilePath="OrcAssault.w3x" //FilePath="OrcAssault.w3x"
//FilePath="FrostyVsFarm.w3m" //FilePath="FrostyVsFarm.w3m"
//FilePath="ModelTest.w3x" //FilePath="ModelTest.w3x"
FilePath="SpinningSample.w3x" //FilePath="SpinningSample.w3x"

View File

@ -134,7 +134,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
} }
final Element cameraData = this.viewer.miscData.get("Camera"); 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]; final CameraPreset[] cameraPresets = new CameraPreset[6];
for (int i = 0; i < cameraPresets.length; i++) { for (int i = 0; i < cameraPresets.length; i++) {
cameraPresets[i] = new CameraPreset(cameraData.getFieldFloatValue("AOA", i), cameraPresets[i] = new CameraPreset(cameraData.getFieldFloatValue("AOA", i),
@ -353,6 +356,9 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
@Override @Override
public boolean keyTyped(final char character) { public boolean keyTyped(final char character) {
if (character == '1') {
Gdx.input.setCursorCatched(!Gdx.input.isCursorCatched());
}
return false; return false;
} }
@ -373,6 +379,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
@Override @Override
public boolean touchDragged(final int screenX, final int screenY, final int pointer) { 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; return false;
} }

View File

@ -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.TextJustify;
import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition;
import com.etheller.warsmash.parsers.fdf.frames.AbstractUIFrame; 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.SetPoint;
import com.etheller.warsmash.parsers.fdf.frames.SimpleFrame; import com.etheller.warsmash.parsers.fdf.frames.SimpleFrame;
import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame;
import com.etheller.warsmash.parsers.fdf.frames.StringFrame; import com.etheller.warsmash.parsers.fdf.frames.StringFrame;
import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; import com.etheller.warsmash.parsers.fdf.frames.TextureFrame;
import com.etheller.warsmash.parsers.fdf.frames.UIFrame; 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.DataTable;
import com.etheller.warsmash.units.Element; import com.etheller.warsmash.units.Element;
import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.ImageUtils;
@ -211,6 +213,16 @@ public final class GameUI extends AbstractUIFrame implements UIFrame {
return textureFrame; 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, public StringFrame createStringFrame(final String name, final UIFrame parent, final Color color,
final TextJustify justifyH, final TextJustify justifyV, final float fdfFontSize) { final TextJustify justifyH, final TextJustify justifyV, final float fdfFontSize) {
this.fontParam.size = (int) convertY(this.viewport, fdfFontSize); this.fontParam.size = (int) convertY(this.viewport, fdfFontSize);

View File

@ -25,6 +25,9 @@ public class StringFrame extends AbstractRenderableFrame {
} }
public void setText(final String text) { public void setText(final String text) {
if (text == null) {
throw new IllegalArgumentException();
}
this.text = text; this.text = text;
} }

View File

@ -63,4 +63,12 @@ public class TextureFrame extends AbstractRenderableFrame {
public void setTexture(final TextureRegion texture) { public void setTexture(final TextureRegion texture) {
this.texture = 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);
}
} }

View File

@ -75,6 +75,43 @@ public class Quadtree<T> {
} }
} }
public boolean intersect(float x, float y, final QuadtreeIntersector<T> intersector) {
if (this.leaf) {
for (int i = 0; i < this.nodes.size; i++) {
final Node<T> 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<T> node, final int depth) { private void add(final Node<T> node, final int depth) {
if (this.leaf) { if (this.leaf) {
if ((this.nodes.size >= SPLIT_THRESHOLD) && (depth < MAX_DEPTH)) { if ((this.nodes.size >= SPLIT_THRESHOLD) && (depth < MAX_DEPTH)) {

View File

@ -31,4 +31,8 @@ public class Bounds {
public boolean intersectRayFast(final Ray ray) { public boolean intersectRayFast(final Ray ray) {
return Intersector.intersectRayBoundsFast(ray, this.boundingBox); return Intersector.intersectRayBoundsFast(ray, this.boundingBox);
} }
public BoundingBox getBoundingBox() {
return boundingBox;
}
} }

View File

@ -22,343 +22,328 @@ import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.handlers.EmitterObject; import com.etheller.warsmash.viewer5.handlers.EmitterObject;
public class EventObjectEmitterObject extends GenericObject implements EmitterObject { public class EventObjectEmitterObject extends GenericObject implements EmitterObject {
private static final class LoadGenericSoundCallback implements LoadGenericCallback { private static final class LoadGenericSoundCallback implements LoadGenericCallback {
private final String filename; private final String filename;
public LoadGenericSoundCallback(final String filename) { public LoadGenericSoundCallback(final String filename) {
this.filename = filename; this.filename = filename;
} }
@Override @Override
public Object call(final InputStream data) { public Object call(final InputStream data) {
final FileHandle temp = new FileHandle(this.filename) { final FileHandle temp = new FileHandle(this.filename) {
@Override @Override
public InputStream read() { public InputStream read() {
return data; return data;
}; }
};
if (data != null) {
return Gdx.audio.newSound(temp);
}
else {
System.err.println("Warning: missing sound file: " + this.filename);
return null;
}
}
}
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 private static final LoadGenericCallback mappedDataCallback = new LoadGenericCallback() {
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 int geometryEmitterType = -1; @Override
public final String type; public Object call(final InputStream data) {
private final String id; final StringBuilder stringBuilder = new StringBuilder();
public final long[] keyFrames; try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) {
private long globalSequence = -1; String line;
private final long[] defval = { 1 }; while ((line = reader.readLine()) != null) {
public MdxModel internalModel; stringBuilder.append(line);
public Texture internalTexture; stringBuilder.append("\n");
public float[][] colors; }
public float[] intervalTimes; } catch (final UnsupportedEncodingException e) {
public float scale; throw new RuntimeException(e);
public int columns; } catch (final IOException e) {
public int rows; throw new RuntimeException(e);
public float lifeSpan; }
public int blendSrc; return new MappedData(stringBuilder.toString());
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<Sound> 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;
public EventObjectEmitterObject(final MdxModel model, private int geometryEmitterType = -1;
final com.etheller.warsmash.parsers.mdlx.EventObject eventObject, final int index) { public final String type;
super(model, eventObject, index); 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<Sound> decodedBuffers = new ArrayList<>();
/**
* If this is an SPL/UBR emitter object, ok will be set to true if the tables
* are loaded.
* <p>
* This is because, like the other geometry emitters, it is fine to use them
* even if the textures don't load.
* <p>
* The particles will simply be black.
*/
private boolean ok = false;
final ModelViewer viewer = model.viewer; public EventObjectEmitterObject(final MdxModel model,
final String name = eventObject.getName(); final com.etheller.warsmash.parsers.mdlx.EventObject eventObject, final int index) {
String type = name.substring(0, 3); super(model, eventObject, index);
final String id = name.substring(4);
// Same thing final ModelViewer viewer = model.viewer;
if ("FPT".equals(type)) { final String name = eventObject.getName();
type = "SPL"; String type = name.substring(0, 3);
} final String id = name.substring(4);
if ("SPL".equals(type)) { // Same thing
this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_SPLAT; if ("FPT".equals(type)) {
} type = "SPL";
else if ("UBR".equals(type)) { }
this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_UBERSPLAT;
}
else if ("SPN".equals(type)) {
this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_SPN;
}
this.type = type; if ("SPL".equals(type)) {
this.id = id; this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_SPLAT;
this.keyFrames = eventObject.getKeyFrames(); } 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(); this.type = type;
if (globalSequenceId != -1) { this.id = id;
this.globalSequence = model.getGlobalSequences().get(globalSequenceId); this.keyFrames = eventObject.getKeyFrames();
}
final List<GenericResource> tables = new ArrayList<>(); final int globalSequenceId = eventObject.getGlobalSequenceId();
final PathSolver pathSolver = model.pathSolver; if (globalSequenceId != -1) {
final Object solverParams = model.solverParams; this.globalSequence = model.getGlobalSequences().get(globalSequenceId);
}
if ("SPN".equals(type)) { final List<GenericResource> tables = new ArrayList<>();
tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\SpawnData.slk", solverParams).finalSrc, final PathSolver pathSolver = model.pathSolver;
FetchDataTypeName.SLK, mappedDataCallback)); final Object solverParams = model.solverParams;
}
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;
}
// TODO I am scrapping some async stuff with promises here from the JS and if ("SPN".equals(type)) {
// calling load tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\SpawnData.slk", solverParams).finalSrc,
this.load(tables); 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) { // TODO I am scrapping some async stuff with promises here from the JS and
final Float x = (Float) row.get(name); // calling load
if (x == null) { this.load(tables);
return Float.NaN; }
}
else {
return x.floatValue();
}
}
private int getInt(final MappedDataRow row, final String name) { private float getFloat(final MappedDataRow row, final String name) {
return getInt(row, name, Integer.MIN_VALUE); 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) { private int getInt(final MappedDataRow row, final String name) {
final Number x = (Number) row.get(name); return getInt(row, name, Integer.MIN_VALUE);
if (x == null) { }
return defaultValue;
}
else {
return x.intValue();
}
}
private void load(final List<GenericResource> tables) { private int getInt(final MappedDataRow row, final String name, final int defaultValue) {
final MappedData firstTable = (MappedData) tables.get(0).data; final Number x = (Number) row.get(name);
final MappedDataRow row = firstTable.getRow(this.id.trim()); if (x == null) {
return defaultValue;
} else {
return x.intValue();
}
}
if (row != null) { private void load(final List<GenericResource> tables) {
final MdxModel model = this.model; final MappedData firstTable = (MappedData) tables.get(0).data;
final ModelViewer viewer = model.viewer; if (firstTable == null) {
final PathSolver pathSolver = model.pathSolver; return;
}
final MappedDataRow row = firstTable.getRow(this.id.trim());
if ("SPN".equals(this.type)) { if (row != null) {
this.internalModel = (MdxModel) viewer.load(((String) row.get("Model")).replace(".mdl", ".mdx"), final MdxModel model = this.model;
pathSolver, model.solverParams); final ModelViewer viewer = model.viewer;
final PathSolver pathSolver = model.pathSolver;
if (this.internalModel != null) { if ("SPN".equals(this.type)) {
// TODO javascript async code removed here 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.internalModel.whenLoaded((model) => this.ok = model.ok)
this.ok = this.internalModel.ok; this.ok = this.internalModel.ok;
} }
} } else if ("SPL".equals(this.type) || "UBR".equals(this.type)) {
else if ("SPL".equals(this.type) || "UBR".equals(this.type)) { final String texturesExt = model.reforged ? ".dds" : ".blp";
final String texturesExt = model.reforged ? ".dds" : ".blp";
this.internalTexture = (Texture) viewer.load( this.internalTexture = (Texture) viewer.load(
"ReplaceableTextures\\Splats\\" + row.get("file") + texturesExt, pathSolver, "ReplaceableTextures\\Splats\\" + row.get("file") + texturesExt, pathSolver,
model.solverParams); model.solverParams);
this.scale = getFloat(row, "Scale"); this.scale = getFloat(row, "Scale");
this.colors = new float[][] { this.colors = new float[][]{
{ getFloat(row, "StartR"), getFloat(row, "StartG"), getFloat(row, "StartB"), {getFloat(row, "StartR"), getFloat(row, "StartG"), getFloat(row, "StartB"),
getFloat(row, "StartA") }, getFloat(row, "StartA")},
{ getFloat(row, "MiddleR"), getFloat(row, "MiddleG"), getFloat(row, "MiddleB"), {getFloat(row, "MiddleR"), getFloat(row, "MiddleG"), getFloat(row, "MiddleB"),
getFloat(row, "MiddleA") }, getFloat(row, "MiddleA")},
{ getFloat(row, "EndR"), getFloat(row, "EndG"), getFloat(row, "EndB"), {getFloat(row, "EndR"), getFloat(row, "EndG"), getFloat(row, "EndB"),
getFloat(row, "EndA") } }; getFloat(row, "EndA")}};
if ("SPL".equals(this.type)) { if ("SPL".equals(this.type)) {
this.columns = getInt(row, "Columns"); this.columns = getInt(row, "Columns");
this.rows = getInt(row, "Rows"); this.rows = getInt(row, "Rows");
this.lifeSpan = getFloat(row, "Lifespan") + getFloat(row, "Decay"); this.lifeSpan = getFloat(row, "Lifespan") + getFloat(row, "Decay");
this.intervalTimes = new float[] { getFloat(row, "Lifespan"), getFloat(row, "Decay") }; this.intervalTimes = new float[]{getFloat(row, "Lifespan"), getFloat(row, "Decay")};
this.intervals = new float[][] { this.intervals = new float[][]{
{ getFloat(row, "UVLifespanStart"), getFloat(row, "UVLifespanEnd"), {getFloat(row, "UVLifespanStart"), getFloat(row, "UVLifespanEnd"),
getFloat(row, "LifespanRepeat") }, getFloat(row, "LifespanRepeat")},
{ getFloat(row, "UVDecayStart"), getFloat(row, "UVDecayEnd"), {getFloat(row, "UVDecayStart"), getFloat(row, "UVDecayEnd"),
getFloat(row, "DecayRepeat") }, }; getFloat(row, "DecayRepeat")},};
} } else {
else { this.columns = 1;
this.columns = 1; this.rows = 1;
this.rows = 1; this.lifeSpan = getFloat(row, "BirthTime") + getFloat(row, "PauseTime") + getFloat(row, "Decay");
this.lifeSpan = getFloat(row, "BirthTime") + getFloat(row, "PauseTime") + getFloat(row, "Decay"); this.intervalTimes = new float[]{getFloat(row, "BirthTime"), getFloat(row, "PauseTime"),
this.intervalTimes = new float[] { getFloat(row, "BirthTime"), getFloat(row, "PauseTime"), getFloat(row, "Decay")};
getFloat(row, "Decay") }; }
}
final int[] blendModes = FilterMode final int[] blendModes = FilterMode
.emitterFilterMode(com.etheller.warsmash.parsers.mdlx.ParticleEmitter2.FilterMode .emitterFilterMode(com.etheller.warsmash.parsers.mdlx.ParticleEmitter2.FilterMode
.fromId(getInt(row, "BlendMode"))); .fromId(getInt(row, "BlendMode")));
this.blendSrc = blendModes[0]; this.blendSrc = blendModes[0];
this.blendDst = blendModes[1]; this.blendDst = blendModes[1];
this.ok = true; this.ok = true;
} } else if ("SND".equals(this.type)) {
else if ("SND".equals(this.type)) { // Only load sounds if audio is enabled.
// Only load sounds if audio is enabled. // This is mostly to save on bandwidth and loading time, especially when loading
// This is mostly to save on bandwidth and loading time, especially when loading // full maps.
// full maps. if (viewer.audioEnabled) {
if (viewer.audioEnabled) { final MappedData animSounds = (MappedData) tables.get(1).data;
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) { if (animSoundsRow != null) {
this.distanceCutoff = getFloat(animSoundsRow, "DistanceCutoff"); this.distanceCutoff = getFloat(animSoundsRow, "DistanceCutoff");
this.maxDistance = getFloat(animSoundsRow, "MaxDistance"); this.maxDistance = getFloat(animSoundsRow, "MaxDistance");
this.minDistance = getFloat(animSoundsRow, "MinDistance"); this.minDistance = getFloat(animSoundsRow, "MinDistance");
this.pitch = getFloat(animSoundsRow, "Pitch"); this.pitch = getFloat(animSoundsRow, "Pitch");
this.pitchVariance = getFloat(animSoundsRow, "PitchVariance"); this.pitchVariance = getFloat(animSoundsRow, "PitchVariance");
this.volume = getFloat(animSoundsRow, "Volume"); this.volume = getFloat(animSoundsRow, "Volume");
final String[] fileNames = ((String) animSoundsRow.get("FileNames")).split(","); final String[] fileNames = ((String) animSoundsRow.get("FileNames")).split(",");
final GenericResource[] resources = new GenericResource[fileNames.length]; final GenericResource[] resources = new GenericResource[fileNames.length];
for (int i = 0; i < fileNames.length; i++) { for (int i = 0; i < fileNames.length; i++) {
final String path = ((String) animSoundsRow.get("DirectoryBase")) + fileNames[i]; final String path = ((String) animSoundsRow.get("DirectoryBase")) + fileNames[i];
try { try {
final String pathString = pathSolver.solve(path, model.solverParams).finalSrc; final String pathString = pathSolver.solve(path, model.solverParams).finalSrc;
final GenericResource genericResource = viewer.loadGeneric(pathString, final GenericResource genericResource = viewer.loadGeneric(pathString,
FetchDataTypeName.ARRAY_BUFFER, new LoadGenericSoundCallback(pathString)); FetchDataTypeName.ARRAY_BUFFER, new LoadGenericSoundCallback(pathString));
if (genericResource == null) { if (genericResource == null) {
System.err.println("Null sound: " + fileNames[i]); System.err.println("Null sound: " + fileNames[i]);
} }
resources[i] = genericResource; resources[i] = genericResource;
} } catch (final Exception exc) {
catch (final Exception exc) { System.err.println("Failed to load sound: " + path);
System.err.println("Failed to load sound: " + path); exc.printStackTrace();
exc.printStackTrace(); }
} }
}
// TODO JS async removed // TODO JS async removed
for (final GenericResource resource : resources) { for (final GenericResource resource : resources) {
if (resource != null) { if (resource != null) {
this.decodedBuffers.add((Sound) resource.data); this.decodedBuffers.add((Sound) resource.data);
} }
} }
this.ok = true; this.ok = true;
} }
} }
} } else {
else { System.err.println("Unknown event object type: " + this.type + this.id);
System.err.println("Unknown event object type: " + this.type + this.id); }
} } else {
} System.err.println("Unknown event object ID: " + 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) { public int getValue(final long[] out, final MdxComplexInstance instance) {
if (this.globalSequence != -1) { if (this.globalSequence != -1) {
return this.getValueAtTime(out, instance.counter % this.globalSequence, 0, this.globalSequence); return this.getValueAtTime(out, instance.counter % this.globalSequence, 0, this.globalSequence);
} } else if (instance.sequence != -1) {
else if (instance.sequence != -1) { final long[] interval = this.model.getSequences().get(instance.sequence).getInterval();
final long[] interval = this.model.getSequences().get(instance.sequence).getInterval();
return this.getValueAtTime(out, instance.frame, interval[0], interval[1]); return this.getValueAtTime(out, instance.frame, interval[0], interval[1]);
} } else {
else { out[0] = this.defval[0];
out[0] = this.defval[0];
return -1; return -1;
} }
} }
public int getValueAtTime(final long[] out, final long frame, final long start, final long end) { public int getValueAtTime(final long[] out, final long frame, final long start, final long end) {
if ((frame >= start) && (frame <= end)) { if ((frame >= start) && (frame <= end)) {
for (int i = this.keyFrames.length - 1; i > -1; i--) { for (int i = this.keyFrames.length - 1; i > -1; i--) {
if (this.keyFrames[i] < start) { if (this.keyFrames[i] < start) {
out[0] = 0; out[0] = 0;
return i; return i;
} } else if (this.keyFrames[i] <= frame) {
else if (this.keyFrames[i] <= frame) { out[0] = 1;
out[0] = 1;
return i; return i;
} }
} }
} }
out[0] = 0; out[0] = 0;
return -1; return -1;
} }
@Override @Override
public boolean ok() { public boolean ok() {
return this.ok; return this.ok;
} }
@Override @Override
public int getGeometryEmitterType() { public int getGeometryEmitterType() {
return this.geometryEmitterType; return this.geometryEmitterType;
} }
} }

View File

@ -32,14 +32,14 @@ public class MdxHandler extends ModelHandler {
Shaders.extendedShadowMap = viewer.webGL.createShaderProgram( Shaders.extendedShadowMap = viewer.webGL.createShaderProgram(
"#define EXTENDED_BONES\r\n" + MdxShaders.vsComplex, MdxShaders.fsComplexShadowMap); "#define EXTENDED_BONES\r\n" + MdxShaders.vsComplex, MdxShaders.fsComplexShadowMap);
Shaders.particles = viewer.webGL.createShaderProgram(MdxShaders.vsParticles, MdxShaders.fsParticles); 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); // Shaders.hd = viewer.webGL.createShaderProgram(MdxShaders.vsHd, MdxShaders.fsHd);
// TODO HD reforged // TODO HD reforged
// If a shader failed to compile, don't allow the handler to be registered, and // If a shader failed to compile, don't allow the handler to be registered, and
// send an error instead. // send an error instead.
return Shaders.complex.isCompiled() && Shaders.extended.isCompiled() && Shaders.particles.isCompiled() return Shaders.complex.isCompiled() && Shaders.extended.isCompiled() && Shaders.particles.isCompiled()
&& Shaders.simple.isCompiled() /* && Shaders.hd.isCompiled() */; /* && Shaders.simple.isCompiled() && Shaders.hd.isCompiled() */;
} }
@Override @Override

View File

@ -152,6 +152,9 @@ public final class SdSequence<TYPE> {
} }
private TYPE[] fixTimelineArray(final Timeline<TYPE> timeline, final TYPE[] values) { private TYPE[] fixTimelineArray(final Timeline<TYPE> timeline, final TYPE[] values) {
if(values == null) {
return null;
}
if (timeline.getName().equals(AnimationMap.KLAC.getWar3id()) if (timeline.getName().equals(AnimationMap.KLAC.getWar3id())
|| timeline.getName().equals(AnimationMap.KLBC.getWar3id())) { || timeline.getName().equals(AnimationMap.KLBC.getWar3id())) {
final float[][] flippedColorData = new float[values.length][3]; final float[][] flippedColorData = new float[values.length][3];

View File

@ -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.MutableGameObject;
import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; 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.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
public class RenderDestructable extends RenderDoodad { 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, public RenderDestructable(final War3MapViewer map, final MdxModel model, final MutableGameObject row,
final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type, final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type,
final float maxPitch, final float maxRoll, final float life) { final float maxPitch, final float maxRoll, final float life) {
super(map, model, row, doodad, type, maxPitch, maxRoll); super(map, model, row, doodad, type, maxPitch, maxRoll);
this.life = life; 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 @Override
public PrimaryTag getAnimation() { public PrimaryTag getAnimation() {
if (this.life <= 0) { if (this.life <= 0) {
return PrimaryTag.DEATH; return PrimaryTag.DEATH;
} }
return super.getAnimation(); return super.getAnimation();
} }
} }

View File

@ -12,86 +12,72 @@ import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
public class RenderDoodad { public class RenderDoodad {
private static final int SAMPLE_RADIUS = 4; private static final int SAMPLE_RADIUS = 4;
private static final War3ID TEX_FILE = War3ID.fromString("btxf"); public final ModelInstance instance;
private static final War3ID TEX_ID = War3ID.fromString("btxi"); private final MutableGameObject row;
public final ModelInstance instance; private final float maxPitch;
private final MutableGameObject row; private final float maxRoll;
private final float maxPitch;
private final float maxRoll;
public RenderDoodad(final War3MapViewer map, final MdxModel model, final MutableGameObject row, public RenderDoodad(final War3MapViewer map, final MdxModel model, final MutableGameObject row,
final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type, final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type,
final float maxPitch, final float maxRoll) { final float maxPitch, final float maxRoll) {
this.maxPitch = maxPitch; this.maxPitch = maxPitch;
this.maxRoll = maxRoll; this.maxRoll = maxRoll;
final boolean isSimple = row.readSLKTagBoolean("lightweight"); final boolean isSimple = row.readSLKTagBoolean("lightweight");
ModelInstance instance; ModelInstance instance;
if (isSimple && false) { if (isSimple && false) {
instance = model.addInstance(1); instance = model.addInstance(1);
} } else {
else { instance = model.addInstance();
instance = model.addInstance(); ((MdxComplexInstance) instance).setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP);
((MdxComplexInstance) instance).setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); }
}
instance.move(doodad.getLocation()); instance.move(doodad.getLocation());
// TODO: the following pitch/roll system is a heuristic, and we probably want to // TODO: the following pitch/roll system is a heuristic, and we probably want to
// revisit it later. // revisit it later.
// Specifically, I was pretty convinced that whichever is applied first // Specifically, I was pretty convinced that whichever is applied first
// (pitch/roll) should be used to do a projection onto the already-tilted plane // (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 // to find the angle used for the other of the two
// (instead of measuring down from an imaginary flat ground plane, as we do // (instead of measuring down from an imaginary flat ground plane, as we do
// currently). // currently).
final float facingRadians = doodad.getAngle(); final float facingRadians = doodad.getAngle();
float pitch, roll; float pitch, roll;
final float x = doodad.getLocation()[0]; final float x = doodad.getLocation()[0];
final float y = doodad.getLocation()[1]; final float y = doodad.getLocation()[1];
final float pitchSampleForwardX = x + (SAMPLE_RADIUS * (float) Math.cos(facingRadians)); final float pitchSampleForwardX = x + (SAMPLE_RADIUS * (float) Math.cos(facingRadians));
final float pitchSampleForwardY = y + (SAMPLE_RADIUS * (float) Math.sin(facingRadians)); final float pitchSampleForwardY = y + (SAMPLE_RADIUS * (float) Math.sin(facingRadians));
final float pitchSampleBackwardX = x - (SAMPLE_RADIUS * (float) Math.cos(facingRadians)); final float pitchSampleBackwardX = x - (SAMPLE_RADIUS * (float) Math.cos(facingRadians));
final float pitchSampleBackwardY = y - (SAMPLE_RADIUS * (float) Math.sin(facingRadians)); final float pitchSampleBackwardY = y - (SAMPLE_RADIUS * (float) Math.sin(facingRadians));
final float pitchSampleGroundHeight1 = map.terrain.getGroundHeight(pitchSampleBackwardX, pitchSampleBackwardY); final float pitchSampleGroundHeight1 = map.terrain.getGroundHeight(pitchSampleBackwardX, pitchSampleBackwardY);
final float pitchSampleGorundHeight2 = map.terrain.getGroundHeight(pitchSampleForwardX, pitchSampleForwardY); final float pitchSampleGorundHeight2 = map.terrain.getGroundHeight(pitchSampleForwardX, pitchSampleForwardY);
pitch = Math.max(-maxPitch, Math.min(maxPitch, pitch = Math.max(-maxPitch, Math.min(maxPitch,
(float) Math.atan2(pitchSampleGorundHeight2 - pitchSampleGroundHeight1, SAMPLE_RADIUS * 2))); (float) Math.atan2(pitchSampleGorundHeight2 - pitchSampleGroundHeight1, SAMPLE_RADIUS * 2)));
final double leftOfFacingAngle = facingRadians + (Math.PI / 2); final double leftOfFacingAngle = facingRadians + (Math.PI / 2);
final float rollSampleForwardX = x + (SAMPLE_RADIUS * (float) Math.cos(leftOfFacingAngle)); final float rollSampleForwardX = x + (SAMPLE_RADIUS * (float) Math.cos(leftOfFacingAngle));
final float rollSampleForwardY = y + (SAMPLE_RADIUS * (float) Math.sin(leftOfFacingAngle)); final float rollSampleForwardY = y + (SAMPLE_RADIUS * (float) Math.sin(leftOfFacingAngle));
final float rollSampleBackwardX = x - (SAMPLE_RADIUS * (float) Math.cos(leftOfFacingAngle)); final float rollSampleBackwardX = x - (SAMPLE_RADIUS * (float) Math.cos(leftOfFacingAngle));
final float rollSampleBackwardY = y - (SAMPLE_RADIUS * (float) Math.sin(leftOfFacingAngle)); final float rollSampleBackwardY = y - (SAMPLE_RADIUS * (float) Math.sin(leftOfFacingAngle));
final float rollSampleGroundHeight1 = map.terrain.getGroundHeight(rollSampleBackwardX, rollSampleBackwardY); final float rollSampleGroundHeight1 = map.terrain.getGroundHeight(rollSampleBackwardX, rollSampleBackwardY);
final float rollSampleGroundHeight2 = map.terrain.getGroundHeight(rollSampleForwardX, rollSampleForwardY); final float rollSampleGroundHeight2 = map.terrain.getGroundHeight(rollSampleForwardX, rollSampleForwardY);
roll = Math.max(-maxRoll, Math.min(maxRoll, roll = Math.max(-maxRoll, Math.min(maxRoll,
(float) Math.atan2(rollSampleGroundHeight2 - rollSampleGroundHeight1, SAMPLE_RADIUS * 2))); (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_Z, facingRadians));
instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Y, -pitch)); instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Y, -pitch));
instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_X, roll)); instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_X, roll));
// instance.rotate(new Quaternion().setEulerAnglesRad(facingRadians, 0, 0)); // instance.rotate(new Quaternion().setEulerAnglesRad(facingRadians, 0, 0));
instance.scale(doodad.getScale()); instance.scale(doodad.getScale());
if (type == WorldEditorDataType.DOODADS) { if (type == WorldEditorDataType.DOODADS) {
final float defScale = row.readSLKTagFloat("defScale"); final float defScale = row.readSLKTagFloat("defScale");
instance.uniformScale(defScale); instance.uniformScale(defScale);
} }
if (type == WorldEditorDataType.DESTRUCTIBLES) { instance.setScene(map.worldScene);
// 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);
this.instance = instance; this.instance = instance;
this.row = row; this.row = row;
} }
public PrimaryTag getAnimation() { public PrimaryTag getAnimation() {
return PrimaryTag.STAND; return PrimaryTag.STAND;
} }
} }

View File

@ -26,6 +26,7 @@ import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.math.collision.BoundingBox;
import com.badlogic.gdx.math.collision.Ray; import com.badlogic.gdx.math.collision.Ray;
import com.etheller.warsmash.common.FetchDataTypeName; import com.etheller.warsmash.common.FetchDataTypeName;
import com.etheller.warsmash.common.LoadGenericCallback; 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.MutableGameObject;
import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType;
import com.etheller.warsmash.util.MappedData; 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.RenderMathUtils;
import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.util.War3ID;
import com.etheller.warsmash.util.WarsmashConstants; 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.CWidget;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; 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.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.CUnitAttackInstant;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; 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.combat.projectile.CAttackProjectile;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; 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.BooleanAbilityActivationReceiver;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.CWidgetAbilityTargetCheckReceiver; 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 com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController;
import mpq.MPQArchive; import mpq.MPQArchive;
@ -117,10 +122,11 @@ public class War3MapViewer extends ModelViewer {
private static final War3ID sloc = War3ID.fromString("sloc"); private static final War3ID sloc = War3ID.fromString("sloc");
private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation();
private static final float[] rayHeap = new float[6]; 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 Vector2 mousePosHeap = new Vector2();
private static final Vector3 normalHeap = new Vector3(); 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 static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation();
public PathSolver wc3PathSolver = PathSolver.DEFAULT; public PathSolver wc3PathSolver = PathSolver.DEFAULT;
@ -185,6 +191,11 @@ public class War3MapViewer extends ModelViewer {
private GameUI gameUI; private GameUI gameUI;
private Vector3 lightDirection; private Vector3 lightDirection;
private Quadtree<MdxComplexInstance> 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) { public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) {
super(dataSource, canvas); super(dataSource, canvas);
this.gameDataSource = dataSource; this.gameDataSource = dataSource;
@ -501,10 +512,9 @@ public class War3MapViewer extends ModelViewer {
War3MapViewer.this.units.remove(renderUnit); War3MapViewer.this.units.remove(renderUnit);
War3MapViewer.this.worldScene.removeInstance(renderUnit.instance); War3MapViewer.this.worldScene.removeInstance(renderUnit.instance);
} }
}, this.terrain.pathingGrid, }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, w3iFile.getPlayers());
new Rectangle(centerOffset[0], centerOffset[1], (mapSize[0] * 128f) - 128, (mapSize[1] * 128f) - 128),
this.seededRandom, w3iFile.getPlayers());
this.walkableObjectsTree = new Quadtree<>(this.terrain.getEntireMap());
if (this.doodadsAndDestructiblesLoaded) { if (this.doodadsAndDestructiblesLoaded) {
this.loadDoodadsAndDestructibles(this.allObjectData); this.loadDoodadsAndDestructibles(this.allObjectData);
} }
@ -632,8 +642,20 @@ public class War3MapViewer extends ModelViewer {
} }
if (type == WorldEditorDataType.DESTRUCTIBLES) { if (type == WorldEditorDataType.DESTRUCTIBLES) {
this.doodads.add(new RenderDestructable(this, model, row, doodad, type, maxPitch, maxRoll, final RenderDestructable renderDestructable = new RenderDestructable(this, model, row, doodad, type,
doodad.getLife())); 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 { else {
this.doodads.add(new RenderDoodad(this, model, row, doodad, type, maxPitch, maxRoll)); this.doodads.add(new RenderDoodad(this, model, row, doodad, type, maxPitch, maxRoll));
@ -884,7 +906,7 @@ public class War3MapViewer extends ModelViewer {
} }
else { else {
this.items.add(new RenderItem(this, model, row, unit, soundset, portraitModel)); // TODO store this.items.add(new RenderItem(this, model, row, unit, soundset, portraitModel)); // TODO store
// somewhere // somewhere
if (unitShadowSplat != null) { if (unitShadowSplat != null) {
unitShadowSplat.unitMapping.add(new Consumer<SplatModel.SplatMover>() { unitShadowSplat.unitMapping.add(new Consumer<SplatModel.SplatMover>() {
@Override @Override
@ -1080,6 +1102,15 @@ public class War3MapViewer extends ModelViewer {
gdxRayHeap.direction.nor();// needed for libgdx gdxRayHeap.direction.nor();// needed for libgdx
RenderMathUtils.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices, RenderMathUtils.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices,
this.terrain.softwareGroundMesh.indices, 3, out); 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) { 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; final MdxComplexInstance instance = unit.instance;
if (instance.isVisible(this.worldScene.camera) if (instance.isVisible(this.worldScene.camera)
&& instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap, && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap,
unit.getSimulationUnit().getUnitType().isBuilding()) unit.getSimulationUnit().getUnitType().isBuilding(), false)
&& !unit.getSimulationUnit().isDead()) { && !unit.getSimulationUnit().isDead()) {
entity = unit; if ((entity == null) || (entity.instance.depth > instance.depth)) {
entity = unit;
}
} }
} }
List<RenderUnit> sel; List<RenderUnit> sel;
@ -1148,7 +1181,7 @@ public class War3MapViewer extends ModelViewer {
for (final RenderUnit unit : this.units) { for (final RenderUnit unit : this.units) {
final MdxComplexInstance instance = unit.instance; final MdxComplexInstance instance = unit.instance;
if (instance.isVisible(this.worldScene.camera) && instance.intersectRayWithCollision(gdxRayHeap, 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 (filter.call(unit.getSimulationUnit())) {
if ((entity == null) || (entity.instance.depth > instance.depth)) { if ((entity == null) || (entity.instance.depth > instance.depth)) {
entity = unit; entity = unit;
@ -1242,6 +1275,41 @@ public class War3MapViewer extends ModelViewer {
return (numElements < 0) ? 1 : (numElements >= MAXIMUM_ACCEPTED) ? MAXIMUM_ACCEPTED : numElements + 1; 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) { public boolean orderSmart(final RenderUnit target) {
boolean ordered = false; boolean ordered = false;
for (final RenderUnit unit : this.selected) { for (final RenderUnit unit : this.selected) {
@ -1348,4 +1416,82 @@ public class War3MapViewer extends ModelViewer {
public AbilityDataUI getAbilityDataUI() { public AbilityDataUI getAbilityDataUI() {
return this.abilityDataUI; 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<MdxComplexInstance> {
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<MdxComplexInstance> {
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<MdxComplexInstance> {
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;
}
}
} }

View File

@ -411,6 +411,7 @@ public class Terrain {
this.shaderMapBoundsRectangle = new Rectangle(this.shaderMapBounds[0], this.shaderMapBounds[1], this.shaderMapBoundsRectangle = new Rectangle(this.shaderMapBounds[0], this.shaderMapBounds[1],
this.shaderMapBounds[2] - this.shaderMapBounds[0], this.shaderMapBounds[3] - this.shaderMapBounds[1]); this.shaderMapBounds[2] - this.shaderMapBounds[0], this.shaderMapBounds[3] - this.shaderMapBounds[1]);
this.mapSize = w3eFile.getMapSize(); 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.softwareGroundMesh = new SoftwareGroundMesh(this.groundHeights, this.groundCornerHeights,
this.centerOffset, width, height); this.centerOffset, width, height);
@ -1019,15 +1020,14 @@ public class Terrain {
gl.glUniform1i(6, (int) this.waterIndex); gl.glUniform1i(6, (int) this.waterIndex);
gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]);
gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); 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 W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager();
final DataTexture unitLightsTexture = lightManager.getTerrainLightsTexture(); final DataTexture terrainLightsTexture = lightManager.getTerrainLightsTexture();
unitLightsTexture.bind(21); terrainLightsTexture.bind(3);
gl.glUniform1i(this.waterShader.getUniformLocation("lightTexture"), 21); gl.glUniform1f(9, lightManager.getTerrainLightCount());
gl.glUniform1f(this.waterShader.getUniformLocation("lightCount"), lightManager.getTerrainLightCount()); gl.glUniform1f(10, terrainLightsTexture.getHeight());
gl.glUniform1f(this.waterShader.getUniformLocation("lightCountHeight"), unitLightsTexture.getHeight());
gl.glActiveTexture(GL30.GL_TEXTURE0); gl.glActiveTexture(GL30.GL_TEXTURE0);
gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight);
@ -1035,7 +1035,7 @@ public class Terrain {
gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight);
gl.glActiveTexture(GL30.GL_TEXTURE2); gl.glActiveTexture(GL30.GL_TEXTURE2);
gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterExists); 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.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.waterTextureArray);
gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, Shapes.INSTANCE.vertexBuffer); gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, Shapes.INSTANCE.vertexBuffer);
@ -1239,6 +1239,7 @@ public class Terrain {
private final WaveBuilder waveBuilder; private final WaveBuilder waveBuilder;
public PathingGrid pathingGrid; public PathingGrid pathingGrid;
private final Rectangle shaderMapBoundsRectangle; private final Rectangle shaderMapBoundsRectangle;
private final Rectangle entireMapRectangle;
private static final class UnloadedTexture { private static final class UnloadedTexture {
private final int width; private final int width;
@ -1399,4 +1400,8 @@ public class Terrain {
public Rectangle getPlayableMapArea() { public Rectangle getPlayableMapArea() {
return this.shaderMapBoundsRectangle; return this.shaderMapBoundsRectangle;
} }
public Rectangle getEntireMap() {
return entireMapRectangle;
}
} }

View File

@ -428,9 +428,9 @@ public class TerrainShaders {
"layout (location = 3) uniform vec4 deep_color_min;\r\n" + // "layout (location = 3) uniform vec4 deep_color_min;\r\n" + //
"layout (location = 4) uniform vec4 deep_color_max;\r\n" + // "layout (location = 4) uniform vec4 deep_color_max;\r\n" + //
"layout (location = 5) uniform float water_offset;\r\n" + // "layout (location = 5) uniform float water_offset;\r\n" + //
"layout (location = 10) uniform sampler2D lightTexture;\r\n" + // "layout (binding = 3) uniform sampler2D lightTexture;\r\n" + //
"layout (location = 11) uniform float lightCount;\r\n" + // "layout (location = 9) uniform float lightCount;\r\n" + //
"layout (location = 12) uniform float lightTextureHeight;\r\n" + // "layout (location = 10) uniform float lightTextureHeight;\r\n" + //
"\r\n" + // "\r\n" + //
"out vec2 UV;\r\n" + // "out vec2 UV;\r\n" + //
"out vec4 Color;\r\n" + // "out vec4 Color;\r\n" + //
@ -477,12 +477,12 @@ public class TerrainShaders {
public static final String frag = "#version 450 core\r\n" + // public static final String frag = "#version 450 core\r\n" + //
"\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" + // "layout (binding = 2) uniform sampler2D water_exists_texture;\r\n" + //
"\r\n" + // "\r\n" + //
"\r\n" + // "\r\n" + //
"layout (location = 6) uniform int current_texture;\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" + // "\r\n" + //
"in vec2 UV;\r\n" + // "in vec2 UV;\r\n" + //
"in vec4 Color;\r\n" + // "in vec4 Color;\r\n" + //

View File

@ -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.ability.AbilityDataUI;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.CommandButtonListener; 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.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.CUnit;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitAnimationListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitAnimationListener;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; 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) { final AbilityDataUI abilityDataUI) {
for (final CAbility ability : this.simulationUnit.getAbilities()) { 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 float groundHeight;
final MovementType movementType = this.simulationUnit.getUnitType().getMovementType(); final MovementType movementType = this.simulationUnit.getUnitType().getMovementType();
final short terrainPathing = map.terrain.pathingGrid.getPathing(x, y); 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.SWIMMABLE)
&& !PathingGrid.isPathingFlag(terrainPathing, PathingGrid.PathingType.WALKABLE); && !PathingGrid.isPathingFlag(terrainPathing, PathingGrid.PathingType.WALKABLE);
if ((swimming) || (movementType == MovementType.FLOAT) || (movementType == MovementType.FLY) final float groundHeightTerrain = map.terrain.getGroundHeight(x, y);
|| (movementType == MovementType.HOVER)) { float groundHeightTerrainAndWater;
groundHeight = Math.max(map.terrain.getGroundHeight(x, y), map.terrain.getWaterHeight(x, y)); 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 { 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) { if (swimming && !this.swimming) {
this.unitAnimationListenerImpl.addSecondaryTag(AnimationTokens.SecondaryTag.SWIM); this.unitAnimationListenerImpl.addSecondaryTag(AnimationTokens.SecondaryTag.SWIM);
@ -263,17 +293,49 @@ public class RenderUnit {
final float pitchSampleForwardY = y + (sampleRadius * (float) Math.sin(facingRadians)); final float pitchSampleForwardY = y + (sampleRadius * (float) Math.sin(facingRadians));
final float pitchSampleBackwardX = x - (sampleRadius * (float) Math.cos(facingRadians)); final float pitchSampleBackwardX = x - (sampleRadius * (float) Math.cos(facingRadians));
final float pitchSampleBackwardY = y - (sampleRadius * (float) Math.sin(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 double leftOfFacingAngle = facingRadians + (Math.PI / 2);
final float rollSampleForwardX = x + (sampleRadius * (float) Math.cos(leftOfFacingAngle)); final float rollSampleForwardX = x + (sampleRadius * (float) Math.cos(leftOfFacingAngle));
final float rollSampleForwardY = y + (sampleRadius * (float) Math.sin(leftOfFacingAngle)); final float rollSampleForwardY = y + (sampleRadius * (float) Math.sin(leftOfFacingAngle));
final float rollSampleBackwardX = x - (sampleRadius * (float) Math.cos(leftOfFacingAngle)); final float rollSampleBackwardX = x - (sampleRadius * (float) Math.cos(leftOfFacingAngle));
final float rollSampleBackwardY = y - (sampleRadius * (float) Math.sin(leftOfFacingAngle)); final float rollSampleBackwardY = y - (sampleRadius * (float) Math.sin(leftOfFacingAngle));
final float rollSampleGroundHeight1 = map.terrain.getGroundHeight(rollSampleBackwardX, rollSampleBackwardY); final float pitchSampleGroundHeight1;
final float rollSampleGroundHeight2 = map.terrain.getGroundHeight(rollSampleForwardX, rollSampleForwardY); 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, roll = Math.max(-maxRoll, Math.min(maxRoll,
(float) Math.atan2(rollSampleGroundHeight2 - rollSampleGroundHeight1, sampleRadius * 2))); (float) Math.atan2(rollSampleGroundHeight2 - rollSampleGroundHeight1, sampleRadius * 2)));
this.instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Y, -pitch)); this.instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Y, -pitch));
@ -289,6 +351,21 @@ public class RenderUnit {
this.unitAnimationListenerImpl.update(); 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() { public CUnit getSimulationUnit() {
return this.simulationUnit; return this.simulationUnit;
} }

View File

@ -34,5 +34,6 @@ public interface CommandButtonListener {
// int getButtonPositionY(); // int getButtonPositionY();
// //
// int getOrderId(); // 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);
} }

View File

@ -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.AbilityDataUI;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.IconUI; 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.CAbilityAttack;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; 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.abilities.CAbilityVisitor;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; 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<Void> { public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor<Void> {
public static final CommandCardPopulatingAbilityVisitor INSTANCE = new CommandCardPopulatingAbilityVisitor(); public static final CommandCardPopulatingAbilityVisitor INSTANCE = new CommandCardPopulatingAbilityVisitor();
private CSimulation game;
private CUnit unit;
private CommandButtonListener commandButtonListener; private CommandButtonListener commandButtonListener;
private AbilityDataUI abilityDataUI; private AbilityDataUI abilityDataUI;
private boolean hasStop; private boolean hasStop;
public CommandCardPopulatingAbilityVisitor reset(final CommandButtonListener commandButtonListener, public CommandCardPopulatingAbilityVisitor reset(final CSimulation game, final CUnit unit,
final AbilityDataUI abilityDataUI) { final CommandButtonListener commandButtonListener, final AbilityDataUI abilityDataUI) {
this.game = game;
this.unit = unit;
this.commandButtonListener = commandButtonListener; this.commandButtonListener = commandButtonListener;
this.abilityDataUI = abilityDataUI; this.abilityDataUI = abilityDataUI;
this.hasStop = false; this.hasStop = false;
@ -24,28 +33,41 @@ public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor<Void
@Override @Override
public Void accept(final CAbilityAttack ability) { public Void accept(final CAbilityAttack ability) {
addCommandButton(this.abilityDataUI.getAttackUI(), ability.getHandleId(), OrderIds.attack); addCommandButton(ability, this.abilityDataUI.getAttackUI(), ability.getHandleId(), OrderIds.attack);
if (!this.hasStop) { if (!this.hasStop) {
this.hasStop = true; this.hasStop = true;
addCommandButton(this.abilityDataUI.getStopUI(), -1, OrderIds.stop); addCommandButton(null, this.abilityDataUI.getStopUI(), -1, OrderIds.stop);
} }
return null; return null;
} }
@Override @Override
public Void accept(final CAbilityMove ability) { public Void accept(final CAbilityMove ability) {
addCommandButton(this.abilityDataUI.getMoveUI(), ability.getHandleId(), OrderIds.move); addCommandButton(ability, this.abilityDataUI.getMoveUI(), ability.getHandleId(), OrderIds.move);
addCommandButton(this.abilityDataUI.getHoldPosUI(), ability.getHandleId(), OrderIds.holdposition); addCommandButton(ability, this.abilityDataUI.getHoldPosUI(), ability.getHandleId(), OrderIds.holdposition);
addCommandButton(this.abilityDataUI.getPatrolUI(), ability.getHandleId(), OrderIds.patrol); addCommandButton(ability, this.abilityDataUI.getPatrolUI(), ability.getHandleId(), OrderIds.patrol);
if (!this.hasStop) { if (!this.hasStop) {
this.hasStop = true; this.hasStop = true;
addCommandButton(this.abilityDataUI.getStopUI(), -1, OrderIds.stop); addCommandButton(null, this.abilityDataUI.getStopUI(), -1, OrderIds.stop);
} }
return null; return null;
} }
private void addCommandButton(final IconUI iconUI, final int handleId, final int orderId) { private void addCommandButton(final CAbility ability, final IconUI iconUI, final int handleId, final int orderId) {
final boolean active;
if (this.unit.getCurrentOrder() == null) {
active = (orderId == OrderIds.stop);
}
else {
if (ability == null) {
active = false;
}
else {
ability.checkCanUse(this.game, this.unit, orderId, BooleanAbilityActivationReceiver.INSTANCE);
active = BooleanAbilityActivationReceiver.INSTANCE.isOk();
}
}
this.commandButtonListener.commandButton(iconUI.getButtonPositionX(), iconUI.getButtonPositionY(), this.commandButtonListener.commandButton(iconUI.getButtonPositionX(), iconUI.getButtonPositionY(),
iconUI.getIcon(), handleId, orderId); iconUI.getIcon(), handleId, orderId, active);
} }
} }

View File

@ -17,7 +17,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; 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.CBehaviorAttack;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CBehavior;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; 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.players.CAllianceType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer;
@ -37,8 +38,8 @@ public class CUnit extends CWidget {
private final List<CAbility> abilities = new ArrayList<>(); private final List<CAbility> abilities = new ArrayList<>();
private COrder currentOrder; private CBehavior currentOrder;
private final Queue<COrder> orderQueue = new LinkedList<>(); private final Queue<CBehavior> orderQueue = new LinkedList<>();
private final CUnitType unitType; private final CUnitType unitType;
private Rectangle collisionRectangle; private Rectangle collisionRectangle;
@ -189,6 +190,7 @@ public class CUnit extends CWidget {
// remove current order, because it's completed, polling next // remove current order, because it's completed, polling next
// item from order queue // item from order queue
this.currentOrder = this.orderQueue.poll(); this.currentOrder = this.orderQueue.poll();
this.stateNotifier.ordersChanged();
} }
if (this.currentOrder == null) { if (this.currentOrder == null) {
// maybe order "stop" here // maybe order "stop" here
@ -222,7 +224,7 @@ public class CUnit extends CWidget {
return game.getGameplayConstants().getBoneDecayTime(); return game.getGameplayConstants().getBoneDecayTime();
} }
public void order(final COrder order, final boolean queue) { public void order(final CBehavior order, final boolean queue) {
if (isDead()) { if (isDead()) {
return; return;
} }
@ -231,10 +233,12 @@ public class CUnit extends CWidget {
} }
else { else {
this.currentOrder = order; this.currentOrder = order;
this.orderQueue.clear();
} }
this.stateNotifier.ordersChanged();
} }
public COrder getCurrentOrder() { public CBehavior getCurrentOrder() {
return this.currentOrder; return this.currentOrder;
} }
@ -390,7 +394,7 @@ public class CUnit extends CWidget {
CAllianceType.PASSIVE)) { CAllianceType.PASSIVE)) {
for (final CUnitAttack attack : this.unitType.getAttacks()) { for (final CUnitAttack attack : this.unitType.getAttacks()) {
if (source.canBeTargetedBy(simulation, this, attack.getTargetsAllowed())) { 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; 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; return false;
} }
@ -550,7 +558,7 @@ public class CUnit extends CWidget {
if (this.source.canReach(unit, this.source.acquisitionRange) if (this.source.canReach(unit, this.source.acquisitionRange)
&& unit.canBeTargetedBy(this.game, this.source, attack.getTargetsAllowed()) && unit.canBeTargetedBy(this.game, this.source, attack.getTargetsAllowed())
&& (this.source.distance(unit) >= this.source.getUnitType().getMinimumAttackRange())) { && (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; return true;
} }
} }

View File

@ -5,6 +5,8 @@ import com.etheller.warsmash.util.SubscriberSetNotifier;
public interface CUnitStateListener { public interface CUnitStateListener {
void lifeChanged(); // hp (current) changes void lifeChanged(); // hp (current) changes
void ordersChanged();
public static final class CUnitStateNotifier extends SubscriberSetNotifier<CUnitStateListener> public static final class CUnitStateNotifier extends SubscriberSetNotifier<CUnitStateListener>
implements CUnitStateListener { implements CUnitStateListener {
@Override @Override
@ -13,5 +15,12 @@ public interface CUnitStateListener {
listener.lifeChanged(); listener.lifeChanged();
} }
} }
@Override
public void ordersChanged() {
for (final CUnitStateListener listener : set) {
listener.ordersChanged();
}
}
} }
} }

View File

@ -17,4 +17,5 @@ public interface CAbility extends CAbilityView {
void onOrder(CSimulation game, CUnit caster, int orderId, Vector2 point, boolean queue); void onOrder(CSimulation game, CUnit caster, int orderId, Vector2 point, boolean queue);
void onOrderNoTarget(CSimulation game, CUnit caster, int orderId, boolean queue); void onOrderNoTarget(CSimulation game, CUnit caster, int orderId, boolean queue);
} }

View File

@ -1,15 +1,17 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities;
import com.badlogic.gdx.math.Vector2; 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.CSimulation;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; 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.CWidget;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.StringsToExternalizeLater; 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.combat.attacks.CUnitAttack;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CAttackOrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CBehavior;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CMoveOrder; 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.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.AbilityActivationReceiver;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver.TargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver.TargetType;
@ -30,6 +32,15 @@ public class CAbilityAttack implements CAbility {
@Override @Override
public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target, public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target,
final AbilityTargetCheckReceiver<CWidget> receiver) { final AbilityTargetCheckReceiver<CWidget> 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)) { if ((orderId == OrderIds.smart) || (orderId == OrderIds.attack)) {
boolean canTarget = false; boolean canTarget = false;
for (final CUnitAttack attack : unit.getUnitType().getAttacks()) { for (final CUnitAttack attack : unit.getUnitType().getAttacks()) {
@ -54,7 +65,30 @@ public class CAbilityAttack implements CAbility {
@Override @Override
public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final Vector2 target, public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final Vector2 target,
final AbilityTargetCheckReceiver<Vector2> receiver) { final AbilityTargetCheckReceiver<Vector2> 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 @Override
@ -74,15 +108,15 @@ public class CAbilityAttack implements CAbility {
@Override @Override
public void onOrder(final CSimulation game, final CUnit caster, final int orderId, final CWidget target, public void onOrder(final CSimulation game, final CUnit caster, final int orderId, final CWidget target,
final boolean queue) { final boolean queue) {
COrder order = null; CBehavior order = null;
for (final CUnitAttack attack : caster.getUnitType().getAttacks()) { for (final CUnitAttack attack : caster.getUnitType().getAttacks()) {
if (target.canBeTargetedBy(game, caster, attack.getTargetsAllowed())) { if (target.canBeTargetedBy(game, caster, attack.getTargetsAllowed())) {
order = new CAttackOrder(caster, attack, orderId, target); order = new CBehaviorAttack(caster, attack, orderId, target);
break; break;
} }
} }
if (order == null) { 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); caster.order(order, queue);
} }

View File

@ -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.CUnit;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; 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.StringsToExternalizeLater;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CMoveOrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CBehaviorMove;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CPatrolOrder; 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.orders.OrderIds;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; 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;
@ -85,13 +85,13 @@ public class CAbilityMove implements CAbility {
@Override @Override
public void onOrder(final CSimulation game, final CUnit caster, final int orderId, final CWidget target, public void onOrder(final CSimulation game, final CUnit caster, final int orderId, final CWidget target,
final boolean queue) { final boolean queue) {
caster.order(new CPatrolOrder(caster, orderId, (CUnit) target), queue); caster.order(new CBehaviorPatrol(caster, orderId, (CUnit) target), queue);
} }
@Override @Override
public void onOrder(final CSimulation game, final CUnit caster, final int orderId, final Vector2 target, public void onOrder(final CSimulation game, final CUnit caster, final int orderId, final Vector2 target,
final boolean queue) { 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 @Override

View File

@ -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; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController;
public class CUnitData { public class CUnitData {
private static final War3ID MANA_INITIAL_AMOUNT = War3ID.fromString("umpi"); private static final War3ID MANA_INITIAL_AMOUNT = War3ID.fromString("umpi");
private static final War3ID MANA_MAXIMUM = War3ID.fromString("umpm"); private static final War3ID MANA_MAXIMUM = War3ID.fromString("umpm");
private static final War3ID HIT_POINT_MAXIMUM = War3ID.fromString("uhpm"); private static final War3ID HIT_POINT_MAXIMUM = War3ID.fromString("uhpm");
private static final War3ID MOVEMENT_SPEED_BASE = War3ID.fromString("umvs"); private static final War3ID MOVEMENT_SPEED_BASE = War3ID.fromString("umvs");
private static final War3ID PROPULSION_WINDOW = War3ID.fromString("uprw"); private static final War3ID PROPULSION_WINDOW = War3ID.fromString("uprw");
private static final War3ID TURN_RATE = War3ID.fromString("umvr"); private static final War3ID TURN_RATE = War3ID.fromString("umvr");
private static final War3ID IS_BLDG = War3ID.fromString("ubdg"); private static final War3ID IS_BLDG = War3ID.fromString("ubdg");
private static final War3ID NAME = War3ID.fromString("unam"); private static final War3ID NAME = War3ID.fromString("unam");
private static final War3ID PROJECTILE_LAUNCH_X = War3ID.fromString("ulpx"); 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_Y = War3ID.fromString("ulpy");
private static final War3ID PROJECTILE_LAUNCH_Z = War3ID.fromString("ulpz"); private static final War3ID PROJECTILE_LAUNCH_Z = War3ID.fromString("ulpz");
private static final War3ID ATTACKS_ENABLED = War3ID.fromString("uaen"); private static final War3ID ATTACKS_ENABLED = War3ID.fromString("uaen");
private static final War3ID ATTACK1_BACKSWING_POINT = War3ID.fromString("ubs1"); 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_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_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_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_QUARTER_DMG = War3ID.fromString("ua1q");
private static final War3ID ATTACK1_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua1p"); 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_ATTACK_TYPE = War3ID.fromString("ua1t");
private static final War3ID ATTACK1_COOLDOWN = War3ID.fromString("ua1c"); private static final War3ID ATTACK1_COOLDOWN = War3ID.fromString("ua1c");
private static final War3ID ATTACK1_DMG_BASE = War3ID.fromString("ua1b"); 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_HALF = War3ID.fromString("uhd1");
private static final War3ID ATTACK1_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd1"); 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_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl1");
private static final War3ID ATTACK1_DMG_DICE = War3ID.fromString("ua1d"); 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_SIDES_PER_DIE = War3ID.fromString("ua1s");
private static final War3ID ATTACK1_DMG_SPILL_DIST = War3ID.fromString("usd1"); 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_SPILL_RADIUS = War3ID.fromString("usr1");
private static final War3ID ATTACK1_DMG_UPGRADE_AMT = War3ID.fromString("udu1"); 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_TARGET_COUNT = War3ID.fromString("utc1");
private static final War3ID ATTACK1_PROJECTILE_ARC = War3ID.fromString("uma1"); 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_MISSILE_ART = War3ID.fromString("ua1m");
private static final War3ID ATTACK1_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh1"); 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_PROJECTILE_SPEED = War3ID.fromString("ua1z");
private static final War3ID ATTACK1_RANGE = War3ID.fromString("ua1r"); 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_RANGE_MOTION_BUFFER = War3ID.fromString("urb1");
private static final War3ID ATTACK1_SHOW_UI = War3ID.fromString("uwu1"); 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_TARGETS_ALLOWED = War3ID.fromString("ua1g");
private static final War3ID ATTACK1_WEAPON_SOUND = War3ID.fromString("ucs1"); private static final War3ID ATTACK1_WEAPON_SOUND = War3ID.fromString("ucs1");
private static final War3ID ATTACK1_WEAPON_TYPE = War3ID.fromString("ua1w"); 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_BACKSWING_POINT = War3ID.fromString("ubs2");
private static final War3ID ATTACK2_DAMAGE_POINT = War3ID.fromString("udp2"); 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_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_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_QUARTER_DMG = War3ID.fromString("ua2q");
private static final War3ID ATTACK2_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua2p"); 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_ATTACK_TYPE = War3ID.fromString("ua2t");
private static final War3ID ATTACK2_COOLDOWN = War3ID.fromString("ua2c"); private static final War3ID ATTACK2_COOLDOWN = War3ID.fromString("ua2c");
private static final War3ID ATTACK2_DMG_BASE = War3ID.fromString("ua2b"); 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_HALF = War3ID.fromString("uhd2");
private static final War3ID ATTACK2_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd2"); 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_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl2");
private static final War3ID ATTACK2_DMG_DICE = War3ID.fromString("ua2d"); 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_SIDES_PER_DIE = War3ID.fromString("ua2s");
private static final War3ID ATTACK2_DMG_SPILL_DIST = War3ID.fromString("usd2"); 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_SPILL_RADIUS = War3ID.fromString("usr2");
private static final War3ID ATTACK2_DMG_UPGRADE_AMT = War3ID.fromString("udu2"); 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_TARGET_COUNT = War3ID.fromString("utc2");
private static final War3ID ATTACK2_PROJECTILE_ARC = War3ID.fromString("uma2"); 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_MISSILE_ART = War3ID.fromString("ua2m");
private static final War3ID ATTACK2_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh2"); 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_PROJECTILE_SPEED = War3ID.fromString("ua2z");
private static final War3ID ATTACK2_RANGE = War3ID.fromString("ua2r"); 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_RANGE_MOTION_BUFFER = War3ID.fromString("urb2");
private static final War3ID ATTACK2_SHOW_UI = War3ID.fromString("uwu2"); 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_TARGETS_ALLOWED = War3ID.fromString("ua2g");
private static final War3ID ATTACK2_WEAPON_SOUND = War3ID.fromString("ucs2"); 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_WEAPON_TYPE = War3ID.fromString("ua2w");
private static final War3ID ACQUISITION_RANGE = War3ID.fromString("uacq"); private static final War3ID ACQUISITION_RANGE = War3ID.fromString("uacq");
private static final War3ID MINIMUM_ATTACK_RANGE = War3ID.fromString("uamn"); 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 DEATH_TYPE = War3ID.fromString("udea");
private static final War3ID ARMOR_TYPE = War3ID.fromString("uarm"); private static final War3ID ARMOR_TYPE = War3ID.fromString("uarm");
private static final War3ID DEFENSE = War3ID.fromString("udef"); private static final War3ID DEFENSE = War3ID.fromString("udef");
private static final War3ID DEFENSE_TYPE = War3ID.fromString("udty"); private static final War3ID DEFENSE_TYPE = War3ID.fromString("udty");
private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh");
private static final War3ID MOVE_TYPE = War3ID.fromString("umvt"); private static final War3ID MOVE_TYPE = War3ID.fromString("umvt");
private static final War3ID COLLISION_SIZE = War3ID.fromString("ucol"); private static final War3ID COLLISION_SIZE = War3ID.fromString("ucol");
private static final War3ID CLASSIFICATION = War3ID.fromString("utyp"); private static final War3ID CLASSIFICATION = War3ID.fromString("utyp");
private static final War3ID DEATH_TIME = War3ID.fromString("udtm"); private static final War3ID DEATH_TIME = War3ID.fromString("udtm");
private static final War3ID TARGETED_AS = War3ID.fromString("utar"); private static final War3ID TARGETED_AS = War3ID.fromString("utar");
private final MutableObjectData unitData; private final MutableObjectData unitData;
private final Map<War3ID, CUnitType> unitIdToUnitType = new HashMap<>(); private final Map<War3ID, CUnitType> unitIdToUnitType = new HashMap<>();
public CUnitData(final MutableObjectData unitData) { public CUnitData(final MutableObjectData unitData) {
this.unitData = unitData; this.unitData = unitData;
} }
public CUnit create(final CSimulation simulation, final int playerIndex, final War3ID typeId, final float x, 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 float y, final float facing, final BufferedImage buildingPathingPixelMap,
final SimulationRenderController simulationRenderController, final HandleIdAllocator handleIdAllocator) { final SimulationRenderController simulationRenderController, final HandleIdAllocator handleIdAllocator) {
final MutableGameObject unitType = this.unitData.get(typeId); final MutableGameObject unitType = this.unitData.get(typeId);
final int handleId = handleIdAllocator.createId(); final int handleId = handleIdAllocator.createId();
final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0);
final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0);
final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0);
final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0);
final int defense = unitType.getFieldAsInteger(DEFENSE, 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, final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum,
speed, defense, unitTypeInstance); speed, defense, unitTypeInstance);
if (speed > 0) { if (speed > 0) {
unit.add(simulation, new CAbilityMove(handleIdAllocator.createId())); unit.add(simulation, new CAbilityMove(handleIdAllocator.createId()));
} }
if (!unitTypeInstance.getAttacks().isEmpty()) { if (!unitTypeInstance.getAttacks().isEmpty()) {
unit.add(simulation, new CAbilityAttack(handleIdAllocator.createId())); unit.add(simulation, new CAbilityAttack(handleIdAllocator.createId()));
} }
return unit; return unit;
} }
private CUnitType getUnitTypeInstance(final War3ID typeId, final BufferedImage buildingPathingPixelMap, private CUnitType getUnitTypeInstance(final War3ID typeId, final BufferedImage buildingPathingPixelMap,
final MutableGameObject unitType) { final MutableGameObject unitType) {
CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId); CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId);
if (unitTypeInstance == null) { if (unitTypeInstance == null) {
final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0);
final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0);
final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0);
final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0);
final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp);
final String unitName = unitType.getFieldAsString(NAME, 0); final String unitName = unitType.getFieldAsString(NAME, 0);
final float acquisitionRange = unitType.getFieldAsFloat(ACQUISITION_RANGE, 0); final float acquisitionRange = unitType.getFieldAsFloat(ACQUISITION_RANGE, 0);
final float minimumAttackRange = unitType.getFieldAsFloat(MINIMUM_ATTACK_RANGE, 0); final float minimumAttackRange = unitType.getFieldAsFloat(MINIMUM_ATTACK_RANGE, 0);
final EnumSet<CTargetType> targetedAs = CTargetType final EnumSet<CTargetType> targetedAs = CTargetType
.parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0)); .parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0));
final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0); final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0);
final EnumSet<CUnitClassification> classifications = EnumSet.noneOf(CUnitClassification.class); final EnumSet<CUnitClassification> classifications = EnumSet.noneOf(CUnitClassification.class);
if (classificationString != null) { if (classificationString != null) {
final String[] classificationValues = classificationString.split(","); final String[] classificationValues = classificationString.split(",");
for (final String unitEditorKey : classificationValues) { for (final String unitEditorKey : classificationValues) {
final CUnitClassification unitClassification = CUnitClassification final CUnitClassification unitClassification = CUnitClassification
.parseUnitClassification(unitEditorKey); .parseUnitClassification(unitEditorKey);
if (unitClassification != null) { if (unitClassification != null) {
classifications.add(unitClassification); classifications.add(unitClassification);
} }
} }
} }
final List<CUnitAttack> attacks = new ArrayList<>(); final List<CUnitAttack> attacks = new ArrayList<>();
final int attacksEnabled = unitType.getFieldAsInteger(ATTACKS_ENABLED, 0); final int attacksEnabled = unitType.getFieldAsInteger(ATTACKS_ENABLED, 0);
if ((attacksEnabled & 0x1) != 0) { if ((attacksEnabled & 0x1) != 0) {
// attack one try {
final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK1_BACKSWING_POINT, 0); // attack one
final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK1_DAMAGE_POINT, 0); final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK1_BACKSWING_POINT, 0);
final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_FULL_DMG, 0); final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK1_DAMAGE_POINT, 0);
final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_HALF_DMG, 0); final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_FULL_DMG, 0);
final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_QUARTER_DMG, 0); final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_HALF_DMG, 0);
final EnumSet<CTargetType> areaOfEffectTargets = CTargetType final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_QUARTER_DMG, 0);
.parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_AREA_OF_EFFECT_TARGETS, 0)); final EnumSet<CTargetType> areaOfEffectTargets = CTargetType
final CAttackType attackType = CAttackType .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_AREA_OF_EFFECT_TARGETS, 0));
.parseAttackType(unitType.getFieldAsString(ATTACK1_ATTACK_TYPE, 0)); final CAttackType attackType = CAttackType
final float cooldownTime = unitType.getFieldAsFloat(ATTACK1_COOLDOWN, 0); .parseAttackType(unitType.getFieldAsString(ATTACK1_ATTACK_TYPE, 0));
final int damageBase = unitType.getFieldAsInteger(ATTACK1_DMG_BASE, 0); final float cooldownTime = unitType.getFieldAsFloat(ATTACK1_COOLDOWN, 0);
final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_HALF, 0); final int damageBase = unitType.getFieldAsInteger(ATTACK1_DMG_BASE, 0);
final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_QUARTER, 0); final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_HALF, 0);
final float damageLossFactor = unitType.getFieldAsFloat(ATTACK1_DAMAGE_LOSS_FACTOR, 0); final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_QUARTER, 0);
final int damageDice = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); final float damageLossFactor = unitType.getFieldAsFloat(ATTACK1_DAMAGE_LOSS_FACTOR, 0);
final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0); final int damageDice = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0);
final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_DIST, 0); final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0);
final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_RADIUS, 0); final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_DIST, 0);
final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0); final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_RADIUS, 0);
final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0); final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0);
final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0);
final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0);
final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED, final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0);
0); final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED,
final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); 0);
final int range = unitType.getFieldAsInteger(ATTACK1_RANGE, 0); final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0);
final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK1_RANGE_MOTION_BUFFER, 0); final int range = unitType.getFieldAsInteger(ATTACK1_RANGE, 0);
final boolean showUI = unitType.getFieldAsBoolean(ATTACK1_SHOW_UI, 0); final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK1_RANGE_MOTION_BUFFER, 0);
final EnumSet<CTargetType> targetsAllowed = CTargetType final boolean showUI = unitType.getFieldAsBoolean(ATTACK1_SHOW_UI, 0);
.parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_TARGETS_ALLOWED, 0)); final EnumSet<CTargetType> targetsAllowed = CTargetType
final String weaponSound = unitType.getFieldAsString(ATTACK1_WEAPON_SOUND, 0); .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_TARGETS_ALLOWED, 0));
final CWeaponType weaponType = CWeaponType final String weaponSound = unitType.getFieldAsString(ATTACK1_WEAPON_SOUND, 0);
.parseWeaponType(unitType.getFieldAsString(ATTACK1_WEAPON_TYPE, 0)); final CWeaponType weaponType = CWeaponType
attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, .parseWeaponType(unitType.getFieldAsString(ATTACK1_WEAPON_TYPE, 0));
areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage,
cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice, areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType,
damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice,
maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed, damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount,
range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType)); maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed,
} range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType));
if ((attacksEnabled & 0x2) != 0) { } catch (Exception exc) {
// attack two System.err.println("Attack 1 failed to parse with: " + exc.getClass() + ":" + exc.getMessage());
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); if ((attacksEnabled & 0x2) != 0) {
final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_HALF_DMG, 0); try {
final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_QUARTER_DMG, 0); // attack two
final EnumSet<CTargetType> areaOfEffectTargets = CTargetType final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK2_BACKSWING_POINT, 0);
.parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_AREA_OF_EFFECT_TARGETS, 0)); final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK2_DAMAGE_POINT, 0);
final CAttackType attackType = CAttackType final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_FULL_DMG, 0);
.parseAttackType(unitType.getFieldAsString(ATTACK2_ATTACK_TYPE, 0)); final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_HALF_DMG, 0);
final float cooldownTime = unitType.getFieldAsFloat(ATTACK2_COOLDOWN, 0); final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_QUARTER_DMG, 0);
final int damageBase = unitType.getFieldAsInteger(ATTACK2_DMG_BASE, 0); final EnumSet<CTargetType> areaOfEffectTargets = CTargetType
final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_HALF, 0); .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_AREA_OF_EFFECT_TARGETS, 0));
final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_QUARTER, 0); final CAttackType attackType = CAttackType
final float damageLossFactor = unitType.getFieldAsFloat(ATTACK2_DAMAGE_LOSS_FACTOR, 0); .parseAttackType(unitType.getFieldAsString(ATTACK2_ATTACK_TYPE, 0));
final int damageDice = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); final float cooldownTime = unitType.getFieldAsFloat(ATTACK2_COOLDOWN, 0);
final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0); final int damageBase = unitType.getFieldAsInteger(ATTACK2_DMG_BASE, 0);
final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_DIST, 0); final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_HALF, 0);
final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_RADIUS, 0); final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_QUARTER, 0);
final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0); final float damageLossFactor = unitType.getFieldAsFloat(ATTACK2_DAMAGE_LOSS_FACTOR, 0);
final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0); final int damageDice = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0);
final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0);
final String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_DIST, 0);
final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED, final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_RADIUS, 0);
0); final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0);
final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0);
final int range = unitType.getFieldAsInteger(ATTACK2_RANGE, 0); final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0);
final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK2_RANGE_MOTION_BUFFER, 0); final String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0);
final boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0); final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED,
final EnumSet<CTargetType> targetsAllowed = CTargetType 0);
.parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_TARGETS_ALLOWED, 0)); final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0);
final String weaponSound = unitType.getFieldAsString(ATTACK2_WEAPON_SOUND, 0); final int range = unitType.getFieldAsInteger(ATTACK2_RANGE, 0);
final CWeaponType weaponType = CWeaponType final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK2_RANGE_MOTION_BUFFER, 0);
.parseWeaponType(unitType.getFieldAsString(ATTACK2_WEAPON_TYPE, 0)); final boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0);
attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, final EnumSet<CTargetType> targetsAllowed = CTargetType
areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_TARGETS_ALLOWED, 0));
cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice, final String weaponSound = unitType.getFieldAsString(ATTACK2_WEAPON_SOUND, 0);
damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, final CWeaponType weaponType = CWeaponType
maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed, .parseWeaponType(unitType.getFieldAsString(ATTACK2_WEAPON_TYPE, 0));
range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType)); attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage,
} areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType,
final int deathType = unitType.getFieldAsInteger(DEATH_TYPE, 0); cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice,
final boolean raise = (deathType & 0x1) != 0; damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount,
final boolean decay = (deathType & 0x2) != 0; maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed,
final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0); range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType));
final float impactZ = unitType.getFieldAsFloat(PROJECTILE_IMPACT_Z, 0); } catch (Exception exc) {
final CDefenseType defenseType = CDefenseType.parseDefenseType(unitType.getFieldAsString(DEFENSE_TYPE, 0)); System.err.println("Attack 2 failed to parse with: " + exc.getClass() + ":" + exc.getMessage());
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, final int deathType = unitType.getFieldAsInteger(DEATH_TYPE, 0);
targetedAs, acquisitionRange, minimumAttackRange); final boolean raise = (deathType & 0x1) != 0;
this.unitIdToUnitType.put(typeId, unitTypeInstance); final boolean decay = (deathType & 0x2) != 0;
} final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0);
return unitTypeInstance; 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, private CUnitAttack createAttack(final float animationBackswingPoint, final float animationDamagePoint,
final int areaOfEffectFullDamage, final int areaOfEffectMediumDamage, final int areaOfEffectSmallDamage, final int areaOfEffectFullDamage, final int areaOfEffectMediumDamage, final int areaOfEffectSmallDamage,
final EnumSet<CTargetType> areaOfEffectTargets, final CAttackType attackType, final float cooldownTime, final EnumSet<CTargetType> areaOfEffectTargets, final CAttackType attackType, final float cooldownTime,
final int damageBase, final float damageFactorMedium, final float damageFactorSmall, final int damageBase, final float damageFactorMedium, final float damageFactorSmall,
final float damageLossFactor, final int damageDice, final int damageSidesPerDie, final float damageLossFactor, final int damageDice, final int damageSidesPerDie,
final float damageSpillDistance, final float damageSpillRadius, final int damageUpgradeAmount, final float damageSpillDistance, final float damageSpillRadius, final int damageUpgradeAmount,
final int maximumNumberOfTargets, final float projectileArc, final String projectileArt, final int maximumNumberOfTargets, final float projectileArc, final String projectileArt,
final boolean projectileHomingEnabled, final int projectileSpeed, final int range, final boolean projectileHomingEnabled, final int projectileSpeed, final int range,
final float rangeMotionBuffer, final boolean showUI, final EnumSet<CTargetType> targetsAllowed, final float rangeMotionBuffer, final boolean showUI, final EnumSet<CTargetType> targetsAllowed,
final String weaponSound, final CWeaponType weaponType) { final String weaponSound, final CWeaponType weaponType) {
final CUnitAttack attack; final CUnitAttack attack;
switch (weaponType) { switch (weaponType) {
case MISSILE: case MISSILE:
attack = new CUnitAttackMissile(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, attack = new CUnitAttackMissile(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime,
damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI,
targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled,
projectileSpeed); projectileSpeed);
break; break;
case MBOUNCE: case MBOUNCE:
attack = new CUnitAttackMissileBounce(animationBackswingPoint, animationDamagePoint, attackType, attack = new CUnitAttackMissileBounce(animationBackswingPoint, animationDamagePoint, attackType,
cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range,
rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt,
projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets, projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets,
areaOfEffectFullDamage, areaOfEffectTargets); areaOfEffectFullDamage, areaOfEffectTargets);
break; break;
case MSPLASH: case MSPLASH:
case ARTILLERY: case ARTILLERY:
attack = new CUnitAttackMissileSplash(animationBackswingPoint, animationDamagePoint, attackType, attack = new CUnitAttackMissileSplash(animationBackswingPoint, animationDamagePoint, attackType,
cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range,
rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt,
projectileHomingEnabled, projectileSpeed, areaOfEffectFullDamage, areaOfEffectMediumDamage, projectileHomingEnabled, projectileSpeed, areaOfEffectFullDamage, areaOfEffectMediumDamage,
areaOfEffectSmallDamage, areaOfEffectTargets, damageFactorMedium, damageFactorSmall); areaOfEffectSmallDamage, areaOfEffectTargets, damageFactorMedium, damageFactorSmall);
break; break;
case MLINE: case MLINE:
case ALINE: case ALINE:
attack = new CUnitAttackMissileLine(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, attack = new CUnitAttackMissileLine(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime,
damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI,
targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled,
projectileSpeed, damageSpillDistance, damageSpillRadius); projectileSpeed, damageSpillDistance, damageSpillRadius);
break; break;
case INSTANT: case INSTANT:
attack = new CUnitAttackInstant(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, attack = new CUnitAttackInstant(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime,
damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI,
targetsAllowed, weaponSound, weaponType, projectileArt); targetsAllowed, weaponSound, weaponType, projectileArt);
break; break;
default: default:
case NORMAL: case NORMAL:
attack = new CUnitAttackNormal(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, attack = new CUnitAttackNormal(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime,
damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI,
targetsAllowed, weaponSound, weaponType); targetsAllowed, weaponSound, weaponType);
break; break;
} }
return attack; return attack;
} }
public float getPropulsionWindow(final War3ID unitTypeId) { public float getPropulsionWindow(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsFloat(PROPULSION_WINDOW, 0); return this.unitData.get(unitTypeId).getFieldAsFloat(PROPULSION_WINDOW, 0);
} }
public float getTurnRate(final War3ID unitTypeId) { public float getTurnRate(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsFloat(TURN_RATE, 0); return this.unitData.get(unitTypeId).getFieldAsFloat(TURN_RATE, 0);
} }
public boolean isBuilding(final War3ID unitTypeId) { public boolean isBuilding(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsBoolean(IS_BLDG, 0); return this.unitData.get(unitTypeId).getFieldAsBoolean(IS_BLDG, 0);
} }
public String getName(final War3ID unitTypeId) { public String getName(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getName(); return this.unitData.get(unitTypeId).getName();
} }
public int getA1MinDamage(final War3ID unitTypeId) { public int getA1MinDamage(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) 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_DICE, 0);
} }
public int getA1MaxDamage(final War3ID unitTypeId) { public int getA1MaxDamage(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) 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_DICE, 0)
* this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0)); * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0));
} }
public int getA2MinDamage(final War3ID unitTypeId) { public int getA2MinDamage(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) 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_DICE, 0);
} }
public int getA2MaxDamage(final War3ID unitTypeId) { public int getA2MaxDamage(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) 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_DICE, 0)
* this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0)); * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0));
} }
public int getDefense(final War3ID unitTypeId) { public int getDefense(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsInteger(DEFENSE, 0); return this.unitData.get(unitTypeId).getFieldAsInteger(DEFENSE, 0);
} }
public int getA1ProjectileSpeed(final War3ID unitTypeId) { public int getA1ProjectileSpeed(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0);
} }
public float getA1ProjectileArc(final War3ID unitTypeId) { public float getA1ProjectileArc(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0);
} }
public int getA2ProjectileSpeed(final War3ID unitTypeId) { public int getA2ProjectileSpeed(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0);
} }
public float getA2ProjectileArc(final War3ID unitTypeId) { public float getA2ProjectileArc(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0);
} }
public String getA1MissileArt(final War3ID unitTypeId) { public String getA1MissileArt(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsString(ATTACK1_MISSILE_ART, 0); return this.unitData.get(unitTypeId).getFieldAsString(ATTACK1_MISSILE_ART, 0);
} }
public String getA2MissileArt(final War3ID unitTypeId) { public String getA2MissileArt(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsString(ATTACK2_MISSILE_ART, 0); return this.unitData.get(unitTypeId).getFieldAsString(ATTACK2_MISSILE_ART, 0);
} }
public float getA1Cooldown(final War3ID unitTypeId) { public float getA1Cooldown(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_COOLDOWN, 0); return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_COOLDOWN, 0);
} }
public float getA2Cooldown(final War3ID unitTypeId) { public float getA2Cooldown(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_COOLDOWN, 0); return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_COOLDOWN, 0);
} }
public float getProjectileLaunchX(final War3ID unitTypeId) { public float getProjectileLaunchX(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_X, 0); return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_X, 0);
} }
public float getProjectileLaunchY(final War3ID unitTypeId) { public float getProjectileLaunchY(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Y, 0); return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Y, 0);
} }
public float getProjectileLaunchZ(final War3ID unitTypeId) { public float getProjectileLaunchZ(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Z, 0); return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Z, 0);
} }
} }

View File

@ -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 * 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 * the order has completed. Many orders may wrap the move order and spend a

View File

@ -3,13 +3,12 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders;
import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.util.WarsmashConstants;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; 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.CSimulation;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; 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.CWidget;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; 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 CUnit unit;
private final int orderId; private final int orderId;
private boolean wasWithinPropWindow = false; private boolean wasWithinPropWindow = false;
@ -17,11 +16,11 @@ public class CAttackOrder implements COrder {
private final CWidget target; private final CWidget target;
private int damagePointLaunchTime; private int damagePointLaunchTime;
private int backSwingTime; private int backSwingTime;
private COrder moveOrder; private CBehavior moveOrder;
private int thisOrderCooldownEndTime; private int thisOrderCooldownEndTime;
private boolean wasInRange = false; 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.unit = unit;
this.unitAttack = unitAttack; this.unitAttack = unitAttack;
this.orderId = orderId; this.orderId = orderId;
@ -32,10 +31,10 @@ public class CAttackOrder implements COrder {
private void createMoveOrder(final CUnit unit, final CWidget target) { private void createMoveOrder(final CUnit unit, final CWidget target) {
if (!unit.isMovementDisabled()) { // TODO: Check mobility instead if (!unit.isMovementDisabled()) { // TODO: Check mobility instead
if ((target instanceof CUnit) && !(((CUnit) target).getUnitType().isBuilding())) { 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 { else {
this.moveOrder = new CMoveOrder(unit, this.orderId, target.getX(), target.getY()); this.moveOrder = new CBehaviorMove(unit, this.orderId, target.getX(), target.getY());
} }
} }
else { else {
@ -50,7 +49,7 @@ public class CAttackOrder implements COrder {
return true; return true;
} }
float range = this.unitAttack.getRange(); 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 /* && (this.damagePointLaunchTime != 0 /*
* only apply range motion buffer if they were already in range and * only apply range motion buffer if they were already in range and
* attacked * attacked
@ -138,7 +137,6 @@ public class CAttackOrder implements COrder {
else { else {
damage = simulation.getSeededRandom().nextInt(maxDamage - minDamage) + minDamage; 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.unitAttack.launch(simulation, this.unit, this.target, damage);
this.damagePointLaunchTime = 0; this.damagePointLaunchTime = 0;
} }

View File

@ -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.SequenceUtils;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; 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.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.CSimulation;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; 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.CWorldCollision;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor; 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 static final Rectangle tempRect = new Rectangle();
private final CUnit unit; private final CUnit unit;
private final int orderId; private final int orderId;
@ -27,7 +26,7 @@ public class CMoveOrder implements COrder {
private int searchCycles = 0; private int searchCycles = 0;
private CUnit followUnit; 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.unit = unit;
this.orderId = orderId; this.orderId = orderId;
this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners(
@ -36,7 +35,7 @@ public class CMoveOrder implements COrder {
this.target = new Point2D.Float(targetX, targetY); 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.unit = unit;
this.orderId = orderId; this.orderId = orderId;
this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners(

View File

@ -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;
}
}

View File

@ -1,12 +1,11 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; 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.CSimulation;
public class CDoNothingOrder implements COrder { public class CBehaviorStop implements CBehavior {
private final int orderId; private final int orderId;
public CDoNothingOrder(final int orderId) { public CBehaviorStop(final int orderId) {
this.orderId = orderId; this.orderId = orderId;
} }

View File

@ -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;
}
}

View File

@ -177,7 +177,8 @@ public class CPathfindingProcessor {
} }
} }
while (!openSet.isEmpty()) { int searchIterations = 0;
while (!openSet.isEmpty() && searchIterations < 150000) {
Node current = openSet.poll(); Node current = openSet.poll();
if (isGoal(current)) { if (isGoal(current)) {
final LinkedList<Point2D.Float> totalPath = new LinkedList<>(); final LinkedList<Point2D.Float> totalPath = new LinkedList<>();
@ -259,6 +260,7 @@ public class CPathfindingProcessor {
} }
} }
} }
searchIterations++;
} }
return Collections.emptyList(); return Collections.emptyList();
} }

View File

@ -83,7 +83,7 @@ public class CPlayerUnitOrderExecutor implements CPlayerUnitOrderListener {
if (this.abilityActivationReceiver.isUseOk()) { if (this.abilityActivationReceiver.isUseOk()) {
final StringMsgTargetCheckReceiver<Void> targetReceiver = this.<Void>targetCheckReceiver(); final StringMsgTargetCheckReceiver<Void> targetReceiver = this.<Void>targetCheckReceiver();
ability.checkCanTargetNoTarget(this.game, unit, orderId, targetReceiver); ability.checkCanTargetNoTarget(this.game, unit, orderId, targetReceiver);
if (targetReceiver.getTarget() != null) { if (targetReceiver.getMessage() == null) {
ability.onOrderNoTarget(this.game, unit, orderId, queue); ability.onOrderNoTarget(this.game, unit, orderId, queue);
return true; return true;
} }

View File

@ -17,6 +17,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandCardCommandL
public class CommandCardIcon extends AbstractRenderableFrame { public class CommandCardIcon extends AbstractRenderableFrame {
private TextureFrame iconFrame; private TextureFrame iconFrame;
private TextureFrame activeHighlightFrame;
private SpriteFrame cooldownFrame; private SpriteFrame cooldownFrame;
private SpriteFrame autocastFrame; private SpriteFrame autocastFrame;
private CommandButton commandButton; private CommandButton commandButton;
@ -30,8 +31,10 @@ public class CommandCardIcon extends AbstractRenderableFrame {
this.commandCardCommandListener = commandCardCommandListener; 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.iconFrame = iconFrame;
this.activeHighlightFrame = activeHighlightFrame;
this.cooldownFrame = cooldownFrame; this.cooldownFrame = cooldownFrame;
this.autocastFrame = autocastFrame; this.autocastFrame = autocastFrame;
} }
@ -40,6 +43,7 @@ public class CommandCardIcon extends AbstractRenderableFrame {
this.commandButton = commandButton; this.commandButton = commandButton;
if (commandButton == null) { if (commandButton == null) {
this.iconFrame.setVisible(false); this.iconFrame.setVisible(false);
this.activeHighlightFrame.setVisible(false);
this.cooldownFrame.setVisible(false); this.cooldownFrame.setVisible(false);
this.autocastFrame.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.iconFrame.setVisible(true);
this.activeHighlightFrame.setVisible(active);
this.cooldownFrame.setVisible(false); this.cooldownFrame.setVisible(false);
this.autocastFrame.setVisible(false); this.autocastFrame.setVisible(false);
this.iconFrame.setTexture(texture); this.iconFrame.setTexture(texture);
@ -76,6 +82,7 @@ public class CommandCardIcon extends AbstractRenderableFrame {
@Override @Override
protected void innerPositionBounds(final Viewport viewport) { protected void innerPositionBounds(final Viewport viewport) {
this.iconFrame.positionBounds(viewport); this.iconFrame.positionBounds(viewport);
this.activeHighlightFrame.positionBounds(viewport);
this.cooldownFrame.positionBounds(viewport); this.cooldownFrame.positionBounds(viewport);
this.autocastFrame.positionBounds(viewport); this.autocastFrame.positionBounds(viewport);
} }
@ -83,6 +90,7 @@ public class CommandCardIcon extends AbstractRenderableFrame {
@Override @Override
protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) {
this.iconFrame.render(batch, baseFont, glyphLayout); this.iconFrame.render(batch, baseFont, glyphLayout);
this.activeHighlightFrame.render(batch, baseFont, glyphLayout);
this.cooldownFrame.render(batch, baseFont, glyphLayout); this.cooldownFrame.render(batch, baseFont, glyphLayout);
this.autocastFrame.render(batch, baseFont, glyphLayout); this.autocastFrame.render(batch, baseFont, glyphLayout);
} }

View File

@ -13,6 +13,8 @@ import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.GlyphLayout;
import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; 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.Rectangle;
import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3; 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.TextureFrame;
import com.etheller.warsmash.parsers.fdf.frames.UIFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame;
import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; 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.FastNumberFormat;
import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.ImageUtils;
import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.util.WarsmashConstants;
@ -135,6 +138,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
private int selectedSoundCount = 0; private int selectedSoundCount = 0;
private final ActiveCommandUnitTargetFilter activeCommandUnitTargetFilter; private final ActiveCommandUnitTargetFilter activeCommandUnitTargetFilter;
private UIFrame clickUI = null;
public MeleeUI(final DataSource dataSource, final Viewport uiViewport, final FreeTypeFontGenerator fontGenerator, public MeleeUI(final DataSource dataSource, final Viewport uiViewport, final FreeTypeFontGenerator fontGenerator,
final Scene uiScene, final Scene portraitScene, final CameraPreset[] cameraPresets, final Scene uiScene, final Scene portraitScene, final CameraPreset[] cameraPresets,
final CameraRates cameraRates, final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener, final CameraRates cameraRates, final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener,
@ -305,6 +310,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
this.rootFrame.add(commandCardIcon); this.rootFrame.add(commandCardIcon);
final TextureFrame iconFrame = this.rootFrame.createTextureFrame( final TextureFrame iconFrame = this.rootFrame.createTextureFrame(
"SmashCommandButton_" + (commandButtonIndex) + "_Icon", this.rootFrame, false, null); "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", final SpriteFrame cooldownFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE",
"SmashCommandButton_" + (commandButtonIndex) + "_Cooldown", this.rootFrame, "", 0); "SmashCommandButton_" + (commandButtonIndex) + "_Cooldown", this.rootFrame, "", 0);
final SpriteFrame autocastFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", 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.setWidth(GameUI.convertX(this.uiViewport, 0.039f));
iconFrame.setHeight(GameUI.convertY(this.uiViewport, 0.039f)); iconFrame.setHeight(GameUI.convertY(this.uiViewport, 0.039f));
iconFrame.setTexture(ImageUtils.DEFAULT_ICON_PATH, this.rootFrame); 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)); cooldownFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0));
this.rootFrame.setSpriteFrameModel(cooldownFrame, this.rootFrame.getSkinField("CommandButtonCooldown")); this.rootFrame.setSpriteFrameModel(cooldownFrame, this.rootFrame.getSkinField("CommandButtonCooldown"));
cooldownFrame.setWidth(GameUI.convertX(this.uiViewport, 0.039f)); 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")); this.rootFrame.setSpriteFrameModel(autocastFrame, this.rootFrame.getSkinField("CommandButtonAutocast"));
autocastFrame.setWidth(GameUI.convertX(this.uiViewport, 0.039f)); autocastFrame.setWidth(GameUI.convertX(this.uiViewport, 0.039f));
autocastFrame.setHeight(GameUI.convertY(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; this.commandCard[j][i] = commandCardIcon;
commandCardIcon.setCommandButton(null); commandCardIcon.setCommandButton(null);
commandButtonIndex++; commandButtonIndex++;
@ -408,7 +421,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
mouseX = Math.max(minX, Math.min(maxX, mouseX)); mouseX = Math.max(minX, Math.min(maxX, mouseX));
mouseY = Math.max(minY, Math.min(maxY, mouseY)); mouseY = Math.max(minY, Math.min(maxY, mouseY));
Gdx.input.setCursorPosition(mouseX, mouseY); // Gdx.input.setCursorPosition(mouseX, mouseY);
screenCoordsVector.set(mouseX, mouseY); screenCoordsVector.set(mouseX, mouseY);
this.uiViewport.unproject(screenCoordsVector); this.uiViewport.unproject(screenCoordsVector);
@ -456,6 +469,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
this.cameraManager.updateCamera(); this.cameraManager.updateCamera();
} }
private final ShapeRenderer shapeRenderer = new ShapeRenderer();
public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) { public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) {
this.rootFrame.render(batch, font20, glyphLayout); this.rootFrame.render(batch, font20, glyphLayout);
if (this.selectedUnit != null) { if (this.selectedUnit != null) {
@ -466,6 +481,18 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
this.meleeUIMinimap.render(batch, this.war3MapViewer.units); this.meleeUIMinimap.render(batch, this.war3MapViewer.units);
this.timeIndicator.setFrameByRatio(this.war3MapViewer.simulation.getGameTimeOfDay() this.timeIndicator.setFrameByRatio(this.war3MapViewer.simulation.getGameTimeOfDay()
/ this.war3MapViewer.simulation.getGameplayConstants().getGameDayHours()); / 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() { public void portraitTalk() {
@ -477,8 +504,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
public boolean call(final CUnit unit) { public boolean call(final CUnit unit) {
final BooleanAbilityTargetCheckReceiver<CWidget> targetReceiver = BooleanAbilityTargetCheckReceiver final BooleanAbilityTargetCheckReceiver<CWidget> targetReceiver = BooleanAbilityTargetCheckReceiver
.<CWidget>getInstance(); .<CWidget>getInstance();
MeleeUI.this.activeCommand.checkCanTarget(MeleeUI.this.war3MapViewer.simulation, unit, MeleeUI.this.activeCommand.checkCanTarget(MeleeUI.this.war3MapViewer.simulation,
MeleeUI.this.activeCommandOrderId, unit, targetReceiver); MeleeUI.this.activeCommandUnit.getSimulationUnit(), MeleeUI.this.activeCommandOrderId, unit,
targetReceiver);
return targetReceiver.isTargetable(); return targetReceiver.isTargetable();
} }
} }
@ -541,11 +569,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
} }
this.portrait.setSelectedUnit(unit); this.portrait.setSelectedUnit(unit);
this.selectedUnit = unit; this.selectedUnit = unit;
for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { clearCommandCard();
for (int i = 0; i < COMMAND_CARD_WIDTH; i++) {
this.commandCard[j][i].setCommandButton(null);
}
}
if (unit == null) { if (unit == null) {
this.simpleNameValue.setText(""); this.simpleNameValue.setText("");
this.unitLifeText.setText(""); this.unitLifeText.setText("");
@ -633,16 +657,24 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
} }
localArmorIconBackdrop.setTexture(defenseTexture); localArmorIconBackdrop.setTexture(defenseTexture);
localArmorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense())); 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 @Override
public void commandButton(final int buttonPositionX, final int buttonPositionY, final Texture icon, 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 x = Math.max(0, Math.min(COMMAND_CARD_WIDTH - 1, buttonPositionX));
final int y = Math.max(0, Math.min(COMMAND_CARD_HEIGHT - 1, buttonPositionY)); 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) { 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() { public RenderUnit getSelectedUnit() {
return this.selectedUnit; return this.selectedUnit;
} }
@ -748,6 +787,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
this.cameraManager.scrolled(amount); this.cameraManager.scrolled(amount);
} }
private float lastX, lastY;
public boolean touchDown(final int screenX, final int screenY, final float worldScreenY, final int button) { public boolean touchDown(final int screenX, final int screenY, final float worldScreenY, final int button) {
screenCoordsVector.set(screenX, screenY); screenCoordsVector.set(screenX, screenY);
this.uiViewport.unproject(screenCoordsVector); this.uiViewport.unproject(screenCoordsVector);
@ -756,6 +797,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
screenCoordsVector.y); screenCoordsVector.y);
this.cameraManager.target.x = worldPoint.x; this.cameraManager.target.x = worldPoint.x;
this.cameraManager.target.y = worldPoint.y; this.cameraManager.target.y = worldPoint.y;
this.lastX = screenCoordsVector.x;
this.lastY = screenCoordsVector.y;
return true; return true;
} }
final UIFrame clickedUIFrame = this.rootFrame.touchDown(screenCoordsVector.x, screenCoordsVector.y, button); final UIFrame clickedUIFrame = this.rootFrame.touchDown(screenCoordsVector.x, screenCoordsVector.y, button);
@ -790,6 +833,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
} }
else { else {
this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY);
clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y);
this.activeCommand.checkCanTarget(this.war3MapViewer.simulation, this.activeCommand.checkCanTarget(this.war3MapViewer.simulation,
this.activeCommandUnit.getSimulationUnit(), this.activeCommandOrderId, this.activeCommandUnit.getSimulationUnit(), this.activeCommandOrderId,
clickLocationTemp2, PointAbilityTargetCheckReceiver.INSTANCE); clickLocationTemp2, PointAbilityTargetCheckReceiver.INSTANCE);
@ -822,10 +867,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
} }
else { else {
if (button == Input.Buttons.RIGHT) { if (button == Input.Buttons.RIGHT) {
final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY);
if (getSelectedUnit() != null) { if (getSelectedUnit() != null) {
if ((rayPickUnit != null) && (rayPickUnit.playerIndex != getSelectedUnit().playerIndex) final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY);
&& !rayPickUnit.getSimulationUnit().isDead()) { if ((rayPickUnit != null) && !rayPickUnit.getSimulationUnit().isDead()) {
boolean ordered = false; boolean ordered = false;
for (final RenderUnit unit : this.war3MapViewer.selected) { for (final RenderUnit unit : this.war3MapViewer.selected) {
for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { 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; return false;
} }
private static boolean isShiftDown() { private static boolean isShiftDown() {
return Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) || Gdx.input.isKeyPressed(Input.Keys.SHIFT_RIGHT); 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;
}
} }

View File

@ -59,4 +59,12 @@ public class MeleeUIMinimap {
public boolean containsMouse(final float x, final float y) { public boolean containsMouse(final float x, final float y) {
return this.minimapFilledArea.contains(x, 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;
}
} }

View File

@ -24,7 +24,7 @@ import com.etheller.warsmash.viewer5.gl.SoundLengthExtension;
import com.etheller.warsmash.viewer5.gl.WireframeExtension; import com.etheller.warsmash.viewer5.gl.WireframeExtension;
public class DesktopLauncher { public class DesktopLauncher {
public static void main(final String[] arg) { public static void main(String[] arg) {
Extensions.angleInstancedArrays = new ANGLEInstancedArrays() { Extensions.angleInstancedArrays = new ANGLEInstancedArrays() {
@Override @Override
public void glVertexAttribDivisorANGLE(final int index, final int divisor) { public void glVertexAttribDivisorANGLE(final int index, final int divisor) {
@ -83,10 +83,11 @@ public class DesktopLauncher {
config.useGL30 = true; config.useGL30 = true;
config.gles30ContextMajorVersion = 3; config.gles30ContextMajorVersion = 3;
config.gles30ContextMinorVersion = 3; config.gles30ContextMinorVersion = 3;
config.samples = 16; //config.samples = 16;
// config.vSyncEnabled = false; // config.vSyncEnabled = false;
// config.foregroundFPS = 0; // config.foregroundFPS = 0;
// config.backgroundFPS = 0; // config.backgroundFPS = 0;
arg = new String[]{"-windowed"};
if ((arg.length > 0) && "-windowed".equals(arg[0])) { if ((arg.length > 0) && "-windowed".equals(arg[0])) {
config.fullscreen = false; config.fullscreen = false;
} }