Support for walkable destructables

This commit is contained in:
Retera 2020-10-23 22:35:25 -04:00
parent e919f35904
commit 7cea3e3fcd
17 changed files with 3237 additions and 3081 deletions

View File

@ -38,6 +38,7 @@ import com.etheller.warsmash.parsers.fdf.GameUI;
import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener;
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.units.HashedGameObject;
import com.etheller.warsmash.util.DataSourceFileHandle; import com.etheller.warsmash.util.DataSourceFileHandle;
import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.ImageUtils;
import com.etheller.warsmash.viewer5.CanvasProvider; import com.etheller.warsmash.viewer5.CanvasProvider;
@ -141,7 +142,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
} }
final Element cameraData = this.viewer.miscData.get("Camera"); final Element 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),

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

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

@ -29,374 +29,431 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitAnimationListe
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility;
public class RenderUnit { public class RenderUnit {
private static final Quaternion tempQuat = new Quaternion(); private static final Quaternion tempQuat = new Quaternion();
private static final War3ID RED = War3ID.fromString("uclr"); private static final War3ID RED = War3ID.fromString("uclr");
private static final War3ID GREEN = War3ID.fromString("uclg"); private static final War3ID GREEN = War3ID.fromString("uclg");
private static final War3ID BLUE = War3ID.fromString("uclb"); private static final War3ID BLUE = War3ID.fromString("uclb");
private static final War3ID MODEL_SCALE = War3ID.fromString("usca"); private static final War3ID MODEL_SCALE = War3ID.fromString("usca");
private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh");
private static final War3ID ORIENTATION_INTERPOLATION = War3ID.fromString("uori"); private static final War3ID ORIENTATION_INTERPOLATION = War3ID.fromString("uori");
private static final War3ID ANIM_PROPS = War3ID.fromString("uani"); private static final War3ID ANIM_PROPS = War3ID.fromString("uani");
private static final float[] heapZ = new float[3]; private static final float[] heapZ = new float[3];
public final MdxComplexInstance instance; public final MdxComplexInstance instance;
public final MutableGameObject row; public final MutableGameObject row;
public final float[] location = new float[3]; public final float[] location = new float[3];
public float selectionScale; public float selectionScale;
public UnitSoundset soundset; public UnitSoundset soundset;
public final MdxModel portraitModel; public final MdxModel portraitModel;
public int playerIndex; public int playerIndex;
private final CUnit simulationUnit; private final CUnit simulationUnit;
public SplatMover shadow; public SplatMover shadow;
public SplatMover selectionCircle; public SplatMover selectionCircle;
private float x; private float x;
private float y; private float y;
private float facing; private float facing;
private boolean swimming; private boolean swimming;
private boolean dead = false; private boolean dead = false;
private final UnitAnimationListenerImpl unitAnimationListenerImpl; private final UnitAnimationListenerImpl unitAnimationListenerImpl;
private OrientationInterpolation orientationInterpolation; private OrientationInterpolation orientationInterpolation;
private float currentTurnVelocity = 0; private float currentTurnVelocity = 0;
public long lastUnitResponseEndTimeMillis; public long lastUnitResponseEndTimeMillis;
private boolean corpse; private boolean corpse;
private boolean boneCorpse; private boolean boneCorpse;
private final RenderUnitTypeData typeData; private final RenderUnitTypeData typeData;
public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row,
final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final UnitSoundset soundset, final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final UnitSoundset soundset,
final MdxModel portraitModel, final CUnit simulationUnit, final RenderUnitTypeData typeData) { final MdxModel portraitModel, final CUnit simulationUnit, final RenderUnitTypeData typeData) {
this.portraitModel = portraitModel; this.portraitModel = portraitModel;
this.simulationUnit = simulationUnit; this.simulationUnit = simulationUnit;
this.typeData = typeData; this.typeData = typeData;
final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance(); final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance();
final float[] location = unit.getLocation(); final float[] location = unit.getLocation();
System.arraycopy(location, 0, this.location, 0, 3); System.arraycopy(location, 0, this.location, 0, 3);
instance.move(location); instance.move(location);
this.facing = simulationUnit.getFacing(); this.facing = simulationUnit.getFacing();
final float angle = (float) Math.toRadians(this.facing); final float angle = (float) Math.toRadians(this.facing);
// instance.localRotation.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle); // instance.localRotation.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle);
this.x = simulationUnit.getX(); this.x = simulationUnit.getX();
this.y = simulationUnit.getY(); this.y = simulationUnit.getY();
instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle)); instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle));
instance.scale(unit.getScale()); instance.scale(unit.getScale());
this.playerIndex = unit.getPlayer() & 0xFFFF; this.playerIndex = unit.getPlayer() & 0xFFFF;
instance.setTeamColor(this.playerIndex); instance.setTeamColor(this.playerIndex);
instance.setScene(map.worldScene); instance.setScene(map.worldScene);
this.unitAnimationListenerImpl = new UnitAnimationListenerImpl(instance); this.unitAnimationListenerImpl = new UnitAnimationListenerImpl(instance);
simulationUnit.setUnitAnimationListener(this.unitAnimationListenerImpl); simulationUnit.setUnitAnimationListener(this.unitAnimationListenerImpl);
final String requiredAnimationNames = row.getFieldAsString(ANIM_PROPS, 0); final String requiredAnimationNames = row.getFieldAsString(ANIM_PROPS, 0);
TokenLoop: for (final String animationName : requiredAnimationNames.split(",")) { TokenLoop:
final String upperCaseToken = animationName.toUpperCase(); for (final String animationName : requiredAnimationNames.split(",")) {
for (final SecondaryTag secondaryTag : SecondaryTag.values()) { final String upperCaseToken = animationName.toUpperCase();
if (upperCaseToken.equals(secondaryTag.name())) { for (final SecondaryTag secondaryTag : SecondaryTag.values()) {
this.unitAnimationListenerImpl.addSecondaryTag(secondaryTag); if (upperCaseToken.equals(secondaryTag.name())) {
continue TokenLoop; this.unitAnimationListenerImpl.addSecondaryTag(secondaryTag);
} continue TokenLoop;
} }
} }
}
if (row != null) { if (row != null) {
heapZ[2] = simulationUnit.getFlyHeight(); heapZ[2] = simulationUnit.getFlyHeight();
this.location[2] += heapZ[2]; this.location[2] += heapZ[2];
instance.move(heapZ); instance.move(heapZ);
War3ID red; War3ID red;
War3ID green; War3ID green;
War3ID blue; War3ID blue;
War3ID scale; War3ID scale;
scale = MODEL_SCALE; scale = MODEL_SCALE;
red = RED; red = RED;
green = GREEN; green = GREEN;
blue = BLUE; blue = BLUE;
instance.setVertexColor(new float[] { (row.getFieldAsInteger(red, 0)) / 255f, instance.setVertexColor(new float[]{(row.getFieldAsInteger(red, 0)) / 255f,
(row.getFieldAsInteger(green, 0)) / 255f, (row.getFieldAsInteger(blue, 0)) / 255f }); (row.getFieldAsInteger(green, 0)) / 255f, (row.getFieldAsInteger(blue, 0)) / 255f});
instance.uniformScale(row.getFieldAsFloat(scale, 0)); instance.uniformScale(row.getFieldAsFloat(scale, 0));
this.selectionScale = row.getFieldAsFloat(War3MapViewer.UNIT_SELECT_SCALE, 0); this.selectionScale = row.getFieldAsFloat(War3MapViewer.UNIT_SELECT_SCALE, 0);
int orientationInterpolationOrdinal = row.getFieldAsInteger(ORIENTATION_INTERPOLATION, 0); int orientationInterpolationOrdinal = row.getFieldAsInteger(ORIENTATION_INTERPOLATION, 0);
if ((orientationInterpolationOrdinal < 0) if ((orientationInterpolationOrdinal < 0)
|| (orientationInterpolationOrdinal >= OrientationInterpolation.VALUES.length)) { || (orientationInterpolationOrdinal >= OrientationInterpolation.VALUES.length)) {
orientationInterpolationOrdinal = 0; orientationInterpolationOrdinal = 0;
} }
this.orientationInterpolation = OrientationInterpolation.VALUES[orientationInterpolationOrdinal]; this.orientationInterpolation = OrientationInterpolation.VALUES[orientationInterpolationOrdinal];
} }
this.instance = instance; this.instance = instance;
this.row = row; this.row = row;
this.soundset = soundset; this.soundset = soundset;
} }
public void populateCommandCard(final CommandButtonListener commandButtonListener, public void populateCommandCard(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(commandButtonListener, abilityDataUI));
} }
} }
public void updateAnimations(final War3MapViewer map) { public void updateAnimations(final War3MapViewer map) {
final float deltaTime = Gdx.graphics.getDeltaTime(); final float deltaTime = Gdx.graphics.getDeltaTime();
final float simulationX = this.simulationUnit.getX(); final float simulationX = this.simulationUnit.getX();
final float simulationY = this.simulationUnit.getY(); final float simulationY = this.simulationUnit.getY();
final float simDx = simulationX - this.x; final float simDx = simulationX - this.x;
final float simDy = simulationY - this.y; final float simDy = simulationY - this.y;
final float distanceToSimulation = (float) Math.sqrt((simDx * simDx) + (simDy * simDy)); final float distanceToSimulation = (float) Math.sqrt((simDx * simDx) + (simDy * simDy));
final int speed = this.simulationUnit.getSpeed(); final int speed = this.simulationUnit.getSpeed();
final float speedDelta = speed * deltaTime; final float speedDelta = speed * deltaTime;
if ((distanceToSimulation > speedDelta) && (deltaTime < 1.0)) { if ((distanceToSimulation > speedDelta) && (deltaTime < 1.0)) {
// The 1.0 here says that after 1 second of lag, units just teleport to show // The 1.0 here says that after 1 second of lag, units just teleport to show
// where they actually are // where they actually are
this.x += (speedDelta * simDx) / distanceToSimulation; this.x += (speedDelta * simDx) / distanceToSimulation;
this.y += (speedDelta * simDy) / distanceToSimulation; this.y += (speedDelta * simDy) / distanceToSimulation;
} } else {
else { this.x = simulationX;
this.x = simulationX; this.y = simulationY;
this.y = simulationY; }
} final float x = this.x;
final float x = this.x; final float dx = x - this.location[0];
final float dx = x - this.location[0]; this.location[0] = x;
this.location[0] = x; final float y = this.y;
final float y = this.y; final float dy = y - this.location[1];
final float dy = y - this.location[1]; this.location[1] = y;
this.location[1] = y; 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); boolean swimming = (movementType == MovementType.AMPHIBIOUS)
final 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); float groundHeightTerrain = map.terrain.getGroundHeight(x, y);
if ((swimming) || (movementType == MovementType.FLOAT) || (movementType == MovementType.FLY) float groundHeightTerrainAndWater;
|| (movementType == MovementType.HOVER)) { MdxComplexInstance currentWalkableUnder;
groundHeight = Math.max(map.terrain.getGroundHeight(x, y), map.terrain.getWaterHeight(x, y)); boolean standingOnWater = (swimming) || (movementType == MovementType.FLOAT) || (movementType == MovementType.FLY)
} || (movementType == MovementType.HOVER);
else { if (standingOnWater) {
groundHeight = map.terrain.getGroundHeight(x, y); groundHeightTerrainAndWater = Math.max(groundHeightTerrain, map.terrain.getWaterHeight(x, y));
} } else {
if (swimming && !this.swimming) { // land units will have their feet pass under the surface of the water
this.unitAnimationListenerImpl.addSecondaryTag(AnimationTokens.SecondaryTag.SWIM); groundHeightTerrainAndWater = groundHeightTerrain;
} }
else if (!swimming && this.swimming) { if(movementType == MovementType.FLOAT) {
this.unitAnimationListenerImpl.removeSecondaryTag(AnimationTokens.SecondaryTag.SWIM); // boats cant go on bridges
} groundHeight = groundHeightTerrainAndWater;
this.swimming = swimming; currentWalkableUnder = null;
final boolean dead = this.simulationUnit.isDead(); } else {
final boolean corpse = this.simulationUnit.isCorpse(); currentWalkableUnder = map.getHighestWalkableUnder(x, y);
final boolean boneCorpse = this.simulationUnit.isBoneCorpse(); if(currentWalkableUnder != null) {
if (dead && !this.dead) { System.out.println("WALKABLE UNDER");
this.unitAnimationListenerImpl.playAnimation(true, PrimaryTag.DEATH, SequenceUtils.EMPTY, 1.0f, true); }
if (this.shadow != null) { War3MapViewer.gdxRayHeap.set(x, y, 4096, 0, 0, -8192);
this.shadow.destroy(Gdx.gl30, map.terrain.centerOffset); if (currentWalkableUnder != null && currentWalkableUnder.intersectRayWithCollision(War3MapViewer.gdxRayHeap, War3MapViewer.intersectionHeap, true, true)
this.shadow = null; && War3MapViewer.intersectionHeap.z > groundHeightTerrainAndWater) {
} groundHeight = War3MapViewer.intersectionHeap.z;
if (this.selectionCircle != null) { swimming = false; // Naga Royal Guard should slither across a bridge, not swim in rock
this.selectionCircle.destroy(Gdx.gl30, map.terrain.centerOffset); } else {
this.selectionCircle = null; groundHeight = groundHeightTerrainAndWater;
} currentWalkableUnder = null;
} }
if (boneCorpse && !this.boneCorpse) { }
this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.BONE, if (swimming && !this.swimming) {
this.simulationUnit.getEndingDecayTime(map.simulation), true); this.unitAnimationListenerImpl.addSecondaryTag(AnimationTokens.SecondaryTag.SWIM);
} } else if (!swimming && this.swimming) {
else if (corpse && !this.corpse) { this.unitAnimationListenerImpl.removeSecondaryTag(AnimationTokens.SecondaryTag.SWIM);
this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.FLESH, }
map.simulation.getGameplayConstants().getDecayTime(), true); this.swimming = swimming;
} final boolean dead = this.simulationUnit.isDead();
this.dead = dead; final boolean corpse = this.simulationUnit.isCorpse();
this.corpse = corpse; final boolean boneCorpse = this.simulationUnit.isBoneCorpse();
this.boneCorpse = boneCorpse; if (dead && !this.dead) {
this.location[2] = this.simulationUnit.getFlyHeight() + groundHeight; this.unitAnimationListenerImpl.playAnimation(true, PrimaryTag.DEATH, SequenceUtils.EMPTY, 1.0f, true);
this.instance.moveTo(this.location); if (this.shadow != null) {
float simulationFacing = this.simulationUnit.getFacing(); this.shadow.destroy(Gdx.gl30, map.terrain.centerOffset);
if (simulationFacing < 0) { this.shadow = null;
simulationFacing += 360; }
} if (this.selectionCircle != null) {
float renderFacing = this.facing; this.selectionCircle.destroy(Gdx.gl30, map.terrain.centerOffset);
if (renderFacing < 0) { this.selectionCircle = null;
renderFacing += 360; }
} }
float facingDelta = simulationFacing - renderFacing; if (boneCorpse && !this.boneCorpse) {
if (facingDelta < -180) { this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.BONE,
facingDelta = 360 + facingDelta; this.simulationUnit.getEndingDecayTime(map.simulation), true);
} } else if (corpse && !this.corpse) {
if (facingDelta > 180) { this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.FLESH,
facingDelta = -360 + facingDelta; map.simulation.getGameplayConstants().getDecayTime(), true);
} }
final float absoluteFacingDelta = Math.abs(facingDelta); this.dead = dead;
final float turningSign = Math.signum(facingDelta); this.corpse = corpse;
this.boneCorpse = boneCorpse;
this.location[2] = this.simulationUnit.getFlyHeight() + groundHeight;
this.instance.moveTo(this.location);
float simulationFacing = this.simulationUnit.getFacing();
if (simulationFacing < 0) {
simulationFacing += 360;
}
float renderFacing = this.facing;
if (renderFacing < 0) {
renderFacing += 360;
}
float facingDelta = simulationFacing - renderFacing;
if (facingDelta < -180) {
facingDelta = 360 + facingDelta;
}
if (facingDelta > 180) {
facingDelta = -360 + facingDelta;
}
final float absoluteFacingDelta = Math.abs(facingDelta);
final float turningSign = Math.signum(facingDelta);
final float absoluteFacingDeltaRadians = (float) Math.toRadians(absoluteFacingDelta); final float absoluteFacingDeltaRadians = (float) Math.toRadians(absoluteFacingDelta);
float acceleration; float acceleration;
final boolean endPhase = (absoluteFacingDeltaRadians <= this.orientationInterpolation.getEndingAccelCutoff()) final boolean endPhase = (absoluteFacingDeltaRadians <= this.orientationInterpolation.getEndingAccelCutoff())
&& ((this.currentTurnVelocity * turningSign) > 0); && ((this.currentTurnVelocity * turningSign) > 0);
if (endPhase) { if (endPhase) {
this.currentTurnVelocity = (1 this.currentTurnVelocity = (1
- ((this.orientationInterpolation.getEndingAccelCutoff() - absoluteFacingDeltaRadians) - ((this.orientationInterpolation.getEndingAccelCutoff() - absoluteFacingDeltaRadians)
/ this.orientationInterpolation.getEndingAccelCutoff())) / this.orientationInterpolation.getEndingAccelCutoff()))
* (this.orientationInterpolation.getMaxVelocity()) * turningSign; * (this.orientationInterpolation.getMaxVelocity()) * turningSign;
} } else {
else { acceleration = this.orientationInterpolation.getStartingAcceleration() * turningSign;
acceleration = this.orientationInterpolation.getStartingAcceleration() * turningSign; this.currentTurnVelocity = this.currentTurnVelocity + acceleration;
this.currentTurnVelocity = this.currentTurnVelocity + acceleration; }
} if ((this.currentTurnVelocity * turningSign) > this.orientationInterpolation.getMaxVelocity()) {
if ((this.currentTurnVelocity * turningSign) > this.orientationInterpolation.getMaxVelocity()) { this.currentTurnVelocity = this.orientationInterpolation.getMaxVelocity() * turningSign;
this.currentTurnVelocity = this.orientationInterpolation.getMaxVelocity() * turningSign; }
} float angleToAdd = (float) ((Math.toDegrees(this.currentTurnVelocity) * deltaTime) / 0.03f);
float angleToAdd = (float) ((Math.toDegrees(this.currentTurnVelocity) * deltaTime) / 0.03f);
if (absoluteFacingDelta < Math.abs(angleToAdd)) { if (absoluteFacingDelta < Math.abs(angleToAdd)) {
angleToAdd = facingDelta; angleToAdd = facingDelta;
this.currentTurnVelocity = 0.0f; this.currentTurnVelocity = 0.0f;
} }
this.facing = (((this.facing + angleToAdd) % 360) + 360) % 360; this.facing = (((this.facing + angleToAdd) % 360) + 360) % 360;
this.instance.setLocalRotation(tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, this.facing)); this.instance.setLocalRotation(tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, this.facing));
final float facingRadians = (float) Math.toRadians(this.facing); final float facingRadians = (float) Math.toRadians(this.facing);
final float maxPitch = this.typeData.getMaxPitch(); final float maxPitch = this.typeData.getMaxPitch();
final float maxRoll = this.typeData.getMaxRoll(); final float maxRoll = this.typeData.getMaxRoll();
final float sampleRadius = this.typeData.getElevationSampleRadius(); final float sampleRadius = this.typeData.getElevationSampleRadius();
float pitch, roll; float pitch, roll;
final float pitchSampleForwardX = x + (sampleRadius * (float) Math.cos(facingRadians)); final float pitchSampleForwardX = x + (sampleRadius * (float) Math.cos(facingRadians));
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 double leftOfFacingAngle = facingRadians + (Math.PI / 2);
final float pitchSampleGorundHeight2 = map.terrain.getGroundHeight(pitchSampleForwardX, pitchSampleForwardY); final float rollSampleForwardX = x + (sampleRadius * (float) Math.cos(leftOfFacingAngle));
pitch = Math.max(-maxPitch, Math.min(maxPitch, final float rollSampleForwardY = y + (sampleRadius * (float) Math.sin(leftOfFacingAngle));
(float) Math.atan2(pitchSampleGorundHeight2 - pitchSampleGroundHeight1, sampleRadius * 2))); final float rollSampleBackwardX = x - (sampleRadius * (float) Math.cos(leftOfFacingAngle));
final double leftOfFacingAngle = facingRadians + (Math.PI / 2); final float rollSampleBackwardY = y - (sampleRadius * (float) Math.sin(leftOfFacingAngle));
final float rollSampleForwardX = x + (sampleRadius * (float) Math.cos(leftOfFacingAngle)); final float pitchSampleGroundHeight1;
final float rollSampleForwardY = y + (sampleRadius * (float) Math.sin(leftOfFacingAngle)); final float pitchSampleGroundHeight2;
final float rollSampleBackwardX = x - (sampleRadius * (float) Math.cos(leftOfFacingAngle)); final float rollSampleGroundHeight1;
final float rollSampleBackwardY = y - (sampleRadius * (float) Math.sin(leftOfFacingAngle)); final float rollSampleGroundHeight2;
final float rollSampleGroundHeight1 = map.terrain.getGroundHeight(rollSampleBackwardX, rollSampleBackwardY); if (currentWalkableUnder != null) {
final float rollSampleGroundHeight2 = map.terrain.getGroundHeight(rollSampleForwardX, rollSampleForwardY); pitchSampleGroundHeight1 = getGroundHeightSample(groundHeight, currentWalkableUnder, pitchSampleBackwardX, pitchSampleBackwardY);
roll = Math.max(-maxRoll, Math.min(maxRoll, pitchSampleGroundHeight2 = getGroundHeightSample(groundHeight, currentWalkableUnder, pitchSampleForwardX, pitchSampleForwardY);
(float) Math.atan2(rollSampleGroundHeight2 - rollSampleGroundHeight1, sampleRadius * 2))); rollSampleGroundHeight1 = getGroundHeightSample(groundHeight, currentWalkableUnder, rollSampleBackwardX, rollSampleBackwardY);
this.instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Y, -pitch)); rollSampleGroundHeight2 = getGroundHeightSample(groundHeight, currentWalkableUnder, rollSampleForwardX, rollSampleForwardY);
this.instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_X, roll)); } else {
float pitchGroundHeight1 = map.terrain.getGroundHeight(pitchSampleBackwardX, pitchSampleBackwardY);
float pitchGroundHeight2 = map.terrain.getGroundHeight(pitchSampleForwardX, pitchSampleForwardY);
float rollGroundHeight1 = map.terrain.getGroundHeight(rollSampleBackwardX, rollSampleBackwardY);
float rollGroundHeight2 = map.terrain.getGroundHeight(rollSampleForwardX, rollSampleForwardY);
if (standingOnWater) {
pitchSampleGroundHeight1 = Math.max(pitchGroundHeight1,
map.terrain.getWaterHeight(pitchSampleBackwardX, pitchSampleBackwardY));
pitchSampleGroundHeight2 = Math.max(pitchGroundHeight2,
map.terrain.getWaterHeight(pitchSampleForwardX, pitchSampleForwardY));
rollSampleGroundHeight1 = Math.max(rollGroundHeight1,
map.terrain.getWaterHeight(rollSampleBackwardX, rollSampleBackwardY));
rollSampleGroundHeight2 = Math.max(rollGroundHeight2,
map.terrain.getWaterHeight(rollSampleForwardX, rollSampleForwardY));
} else {
pitchSampleGroundHeight1 = pitchGroundHeight1;
pitchSampleGroundHeight2 = pitchGroundHeight2;
rollSampleGroundHeight1 = rollGroundHeight1;
rollSampleGroundHeight2 = rollGroundHeight2;
}
}
pitch = Math.max(-maxPitch, Math.min(maxPitch,
(float) Math.atan2(pitchSampleGroundHeight2 - pitchSampleGroundHeight1, sampleRadius * 2)));
roll = Math.max(-maxRoll, Math.min(maxRoll,
(float) Math.atan2(rollSampleGroundHeight2 - rollSampleGroundHeight1, sampleRadius * 2)));
this.instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Y, -pitch));
this.instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_X, roll));
map.worldScene.instanceMoved(this.instance, this.location[0], this.location[1]); map.worldScene.instanceMoved(this.instance, this.location[0], this.location[1]);
if (this.shadow != null) { if (this.shadow != null) {
this.shadow.move(dx, dy, map.terrain.centerOffset); this.shadow.move(dx, dy, map.terrain.centerOffset);
} }
if (this.selectionCircle != null) { if (this.selectionCircle != null) {
this.selectionCircle.move(dx, dy, map.terrain.centerOffset); this.selectionCircle.move(dx, dy, map.terrain.centerOffset);
} }
this.unitAnimationListenerImpl.update(); this.unitAnimationListenerImpl.update();
} }
public CUnit getSimulationUnit() { private float getGroundHeightSample(float groundHeight, MdxComplexInstance currentWalkableUnder, float sampleX, float sampleY) {
return this.simulationUnit; final float sampleGroundHeight;
} War3MapViewer.gdxRayHeap.origin.x = sampleX;
War3MapViewer.gdxRayHeap.origin.y = sampleY;
if (currentWalkableUnder.intersectRayWithCollision(War3MapViewer.gdxRayHeap, War3MapViewer.intersectionHeap, true, true)) {
sampleGroundHeight = War3MapViewer.intersectionHeap.z;
} else {
sampleGroundHeight = groundHeight;
}
return sampleGroundHeight;
}
private static final class UnitAnimationListenerImpl implements CUnitAnimationListener { public CUnit getSimulationUnit() {
private final MdxComplexInstance instance; return this.simulationUnit;
private final EnumSet<AnimationTokens.SecondaryTag> secondaryAnimationTags = EnumSet }
.noneOf(AnimationTokens.SecondaryTag.class);
private final EnumSet<AnimationTokens.SecondaryTag> recycleSet = EnumSet
.noneOf(AnimationTokens.SecondaryTag.class);
private PrimaryTag currentAnimation;
private EnumSet<SecondaryTag> currentAnimationSecondaryTags;
private float currentSpeedRatio;
private boolean currentlyAllowingRarityVariations;
private final Queue<QueuedAnimation> animationQueue = new LinkedList<>();
public UnitAnimationListenerImpl(final MdxComplexInstance instance) { private static final class UnitAnimationListenerImpl implements CUnitAnimationListener {
this.instance = instance; private final MdxComplexInstance instance;
} private final EnumSet<AnimationTokens.SecondaryTag> secondaryAnimationTags = EnumSet
.noneOf(AnimationTokens.SecondaryTag.class);
private final EnumSet<AnimationTokens.SecondaryTag> recycleSet = EnumSet
.noneOf(AnimationTokens.SecondaryTag.class);
private PrimaryTag currentAnimation;
private EnumSet<SecondaryTag> currentAnimationSecondaryTags;
private float currentSpeedRatio;
private boolean currentlyAllowingRarityVariations;
private final Queue<QueuedAnimation> animationQueue = new LinkedList<>();
public void addSecondaryTag(final AnimationTokens.SecondaryTag tag) { public UnitAnimationListenerImpl(final MdxComplexInstance instance) {
this.secondaryAnimationTags.add(tag); this.instance = instance;
playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio, }
this.currentlyAllowingRarityVariations);
}
public void removeSecondaryTag(final AnimationTokens.SecondaryTag tag) { public void addSecondaryTag(final AnimationTokens.SecondaryTag tag) {
this.secondaryAnimationTags.remove(tag); this.secondaryAnimationTags.add(tag);
playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio, playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio,
this.currentlyAllowingRarityVariations); this.currentlyAllowingRarityVariations);
} }
@Override public void removeSecondaryTag(final AnimationTokens.SecondaryTag tag) {
public void playAnimation(final boolean force, final PrimaryTag animationName, this.secondaryAnimationTags.remove(tag);
final EnumSet<SecondaryTag> secondaryAnimationTags, final float speedRatio, playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio,
final boolean allowRarityVariations) { this.currentlyAllowingRarityVariations);
this.animationQueue.clear(); }
if (force || (animationName != this.currentAnimation)) {
this.currentAnimation = animationName;
this.currentAnimationSecondaryTags = secondaryAnimationTags;
this.currentSpeedRatio = speedRatio;
this.currentlyAllowingRarityVariations = allowRarityVariations;
this.recycleSet.clear();
this.recycleSet.addAll(this.secondaryAnimationTags);
this.recycleSet.addAll(secondaryAnimationTags);
this.instance.setAnimationSpeed(speedRatio);
SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, allowRarityVariations);
}
}
public void playAnimationWithDuration(final boolean force, final PrimaryTag animationName, @Override
final EnumSet<SecondaryTag> secondaryAnimationTags, final float duration, public void playAnimation(final boolean force, final PrimaryTag animationName,
final boolean allowRarityVariations) { final EnumSet<SecondaryTag> secondaryAnimationTags, final float speedRatio,
this.animationQueue.clear(); final boolean allowRarityVariations) {
if (force || (animationName != this.currentAnimation)) { this.animationQueue.clear();
this.currentAnimation = animationName; if (force || (animationName != this.currentAnimation)) {
this.currentAnimationSecondaryTags = secondaryAnimationTags; this.currentAnimation = animationName;
this.currentlyAllowingRarityVariations = allowRarityVariations; this.currentAnimationSecondaryTags = secondaryAnimationTags;
this.recycleSet.clear(); this.currentSpeedRatio = speedRatio;
this.recycleSet.addAll(this.secondaryAnimationTags); this.currentlyAllowingRarityVariations = allowRarityVariations;
this.recycleSet.addAll(secondaryAnimationTags); this.recycleSet.clear();
final Sequence sequence = SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, this.recycleSet.addAll(this.secondaryAnimationTags);
allowRarityVariations); this.recycleSet.addAll(secondaryAnimationTags);
if (sequence != null) { this.instance.setAnimationSpeed(speedRatio);
this.currentSpeedRatio = ((sequence.getInterval()[1] - sequence.getInterval()[0]) / 1000.0f) SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, allowRarityVariations);
/ duration; }
this.instance.setAnimationSpeed(this.currentSpeedRatio); }
}
}
}
@Override public void playAnimationWithDuration(final boolean force, final PrimaryTag animationName,
public void queueAnimation(final PrimaryTag animationName, final EnumSet<SecondaryTag> secondaryAnimationTags, final EnumSet<SecondaryTag> secondaryAnimationTags, final float duration,
final boolean allowRarityVariations) { final boolean allowRarityVariations) {
this.animationQueue.add(new QueuedAnimation(animationName, secondaryAnimationTags, allowRarityVariations)); this.animationQueue.clear();
} if (force || (animationName != this.currentAnimation)) {
this.currentAnimation = animationName;
this.currentAnimationSecondaryTags = secondaryAnimationTags;
this.currentlyAllowingRarityVariations = allowRarityVariations;
this.recycleSet.clear();
this.recycleSet.addAll(this.secondaryAnimationTags);
this.recycleSet.addAll(secondaryAnimationTags);
final Sequence sequence = SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet,
allowRarityVariations);
if (sequence != null) {
this.currentSpeedRatio = ((sequence.getInterval()[1] - sequence.getInterval()[0]) / 1000.0f)
/ duration;
this.instance.setAnimationSpeed(this.currentSpeedRatio);
}
}
}
public void update() { @Override
if (this.instance.sequenceEnded || (this.instance.sequence == -1)) { public void queueAnimation(final PrimaryTag animationName, final EnumSet<SecondaryTag> secondaryAnimationTags,
// animation done final boolean allowRarityVariations) {
if ((this.instance.sequence != -1) && (((MdxModel) this.instance.model).getSequences() this.animationQueue.add(new QueuedAnimation(animationName, secondaryAnimationTags, allowRarityVariations));
.get(this.instance.sequence).getFlags() == 0)) { }
// animation is a looping animation
playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags,
this.currentSpeedRatio, this.currentlyAllowingRarityVariations);
}
else {
final QueuedAnimation nextAnimation = this.animationQueue.poll();
if (nextAnimation != null) {
playAnimation(true, nextAnimation.animationName, nextAnimation.secondaryAnimationTags, 1.0f,
nextAnimation.allowRarityVariations);
}
}
}
}
} public void update() {
if (this.instance.sequenceEnded || (this.instance.sequence == -1)) {
// animation done
if ((this.instance.sequence != -1) && (((MdxModel) this.instance.model).getSequences()
.get(this.instance.sequence).getFlags() == 0)) {
// animation is a looping animation
playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags,
this.currentSpeedRatio, this.currentlyAllowingRarityVariations);
} else {
final QueuedAnimation nextAnimation = this.animationQueue.poll();
if (nextAnimation != null) {
playAnimation(true, nextAnimation.animationName, nextAnimation.secondaryAnimationTags, 1.0f,
nextAnimation.allowRarityVariations);
}
}
}
}
private static final class QueuedAnimation { }
private final PrimaryTag animationName;
private final EnumSet<SecondaryTag> secondaryAnimationTags;
private final boolean allowRarityVariations;
public QueuedAnimation(final PrimaryTag animationName, final EnumSet<SecondaryTag> secondaryAnimationTags, private static final class QueuedAnimation {
final boolean allowRarityVariations) { private final PrimaryTag animationName;
this.animationName = animationName; private final EnumSet<SecondaryTag> secondaryAnimationTags;
this.secondaryAnimationTags = secondaryAnimationTags; private final boolean allowRarityVariations;
this.allowRarityVariations = allowRarityVariations;
} public QueuedAnimation(final PrimaryTag animationName, final EnumSet<SecondaryTag> secondaryAnimationTags,
} final boolean allowRarityVariations) {
this.animationName = animationName;
this.secondaryAnimationTags = secondaryAnimationTags;
this.allowRarityVariations = allowRarityVariations;
}
}
} }

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

@ -138,7 +138,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

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

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