Clickable destructables, some cliff changes, shadows on bridges

This commit is contained in:
Retera 2021-01-23 03:09:55 -05:00
parent 9da2160565
commit 4f7368eb57
26 changed files with 670 additions and 317 deletions

View File

@ -1,5 +1,5 @@
[DataSources]
Count=7
Count=8
Type00=MPQ
Path00="D:\Games\Warcraft III Patch 1.22\war3.mpq"
Type01=MPQ
@ -13,12 +13,15 @@ Path04="..\..\resources"
Type05=Folder
Path05="D:\Backups\Warsmash\Data"
Type06=Folder
Path06="."
Path06="D:\Games\Warcraft III Patch 1.22\Maps"
Type07=Folder
Path07="."
[Map]
//FilePath="CombatUnitTests.w3x"
//FilePath="PitchRoll.w3x"
FilePath="PeonStartingBase.w3x"
//FilePath="PeonStartingBase.w3x"
FilePath="MyStromguarde.w3m"
//FilePath="ColdArrows.w3m"
//FilePath="DungeonGoldMine.w3m"
//FilePath="PlayerPeasants.w3m"

View File

@ -56,7 +56,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.SettableCommandErro
public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor {
private static final boolean ENABLE_AUDIO = true;
private static final boolean ENABLE_MUSIC = false;
private static final boolean ENABLE_MUSIC = true;
private DataSource codebase;
private War3MapViewer viewer;
private final Rectangle tempRect = new Rectangle();
@ -215,7 +215,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
final String musicField = rootFrame.getSkinField("Music_V1");
final String[] musics = musicField.split(";");
String musicPath = musics[(int) (Math.random() * musics.length)];
if (true) {
if (false) {
musicPath = "Sound\\Music\\mp3Music\\OrcTheme.mp3";
}
final Music music = Gdx.audio.newMusic(

View File

@ -0,0 +1,82 @@
package com.etheller.warsmash.util;
import com.badlogic.gdx.math.Intersector;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Plane;
import com.badlogic.gdx.math.Plane.PlaneSide;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.math.collision.Ray;
public class FixedIntersector {
private final static Vector3 v0 = new Vector3();
private final static Vector3 v1 = new Vector3();
private final static Vector3 v2 = new Vector3();
private static final Plane p = new Plane(new Vector3(), 0);
private static final Vector3 i = new Vector3();
/**
* Intersect a {@link Ray} and a triangle, returning the intersection point in
* intersection.
*
* @param ray The ray
* @param t1 The first vertex of the triangle
* @param t2 The second vertex of the triangle
* @param t3 The third vertex of the triangle
* @param intersection The intersection point (optional)
* @return True in case an intersection is present.
*/
public static boolean intersectRayTriangle(final Ray ray, final Vector3 t1, final Vector3 t2, final Vector3 t3,
final Vector3 intersection) {
if (t2.epsilonEquals(t3)) {
return false;
}
final Vector3 edge1 = v0.set(t2).sub(t1);
final Vector3 edge2 = v1.set(t3).sub(t1);
final Vector3 pvec = v2.set(ray.direction).crs(edge2);
float det = edge1.dot(pvec);
if (MathUtils.isZero(det)) {
p.set(t1, t2, t3);
if ((p.testPoint(ray.origin) == PlaneSide.OnPlane)
&& Intersector.isPointInTriangle(ray.origin, t1, t2, t3)) {
if (intersection != null) {
intersection.set(ray.origin);
}
return true;
}
return false;
}
det = 1.0f / det;
final Vector3 tvec = i.set(ray.origin).sub(t1);
final float u = tvec.dot(pvec) * det;
if ((u < 0.0f) || (u > 1.0f)) {
return false;
}
final Vector3 qvec = tvec.crs(edge1);
final float v = ray.direction.dot(qvec) * det;
if ((v < 0.0f) || ((u + v) > 1.0f)) {
return false;
}
final float t = edge2.dot(qvec) * det;
if (t < 0) {
return false;
}
if (intersection != null) {
if (t <= MathUtils.FLOAT_ROUNDING_ERROR) {
intersection.set(ray.origin);
}
else {
ray.getEndPoint(intersection, t);
}
}
return true;
}
}

View File

@ -486,7 +486,7 @@ public enum RenderMathUtils {
final int i2 = indices[i + 1] * vertexSize;
final int i3 = indices[i + 2] * vertexSize;
final boolean result = Intersector.intersectRayTriangle(ray,
final boolean result = FixedIntersector.intersectRayTriangle(ray,
tmp1.set(vertices[i1], vertices[i1 + 1], vertices[i1 + 2]),
tmp2.set(vertices[i2], vertices[i2 + 1], vertices[i2 + 2]),
tmp3.set(vertices[i3], vertices[i3 + 1], vertices[i3 + 2]), tmp);

View File

@ -1,42 +0,0 @@
package com.etheller.warsmash.viewer5.handlers.w3x;
import com.badlogic.gdx.math.Rectangle;
import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject;
import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType;
import com.etheller.warsmash.util.War3ID;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.BuildingShadow;
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;
public Rectangle walkableBounds;
public RenderDestructable(final War3MapViewer map, final MdxModel model, final MutableGameObject row,
final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type,
final float maxPitch, final float maxRoll, final float life, final BuildingShadow destructableShadow) {
super(map, model, row, doodad, type, maxPitch, maxRoll);
this.life = life;
String replaceableTextureFile = row.getFieldAsString(TEX_FILE, 0);
final int replaceableTextureId = row.getFieldAsInteger(TEX_ID, 0);
if ((replaceableTextureFile != null) && (replaceableTextureFile.length() > 1)) {
final int dotIndex = replaceableTextureFile.lastIndexOf('.');
if (dotIndex != -1) {
replaceableTextureFile = replaceableTextureFile.substring(0, dotIndex);
}
replaceableTextureFile += ".blp";
this.instance.setReplaceableTexture(replaceableTextureId, replaceableTextureFile);
}
}
@Override
public PrimaryTag getAnimation() {
if (this.life <= 0) {
return PrimaryTag.DEATH;
}
return super.getAnimation();
}
}

View File

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

View File

@ -22,6 +22,7 @@ import com.etheller.warsmash.viewer5.ViewerTextureRenderable;
*/
public class SplatModel {
private static final int MAX_VERTICES = 65000;
private static final float NO_ABS_HEIGHT = -257f;
private final ViewerTextureRenderable texture;
private final List<Batch> batches;
public final float[] color;
@ -77,6 +78,7 @@ public class SplatModel {
private void loadBatches(final GL30 gl, final float[] centerOffset) {
final List<float[]> vertices = new ArrayList<>();
final List<float[]> uvs = new ArrayList<>();
final List<float[]> absoluteHeights = new ArrayList<>();
final List<int[]> indices = new ArrayList<>();
final List<SplatMover> batchRenderUnits = new ArrayList<>();
final int instances = this.locations.size();
@ -112,9 +114,10 @@ public class SplatModel {
final int step = (ix1 - ix0) + 1;
if ((start + numVertsToCrate) > MAX_VERTICES) {
this.addBatch(gl, vertices, uvs, indices, batchRenderUnits);
this.addBatch(gl, vertices, uvs, absoluteHeights, indices, batchRenderUnits);
vertices.clear();
uvs.clear();
absoluteHeights.clear();
indices.clear();
batchRenderUnits.clear();
start = 0;
@ -130,9 +133,12 @@ public class SplatModel {
vertices.add(vertex);
final float[] uv = new float[] { (x - x0) / uvXScale, 1.0f - ((y - y0) / uvYScale) };
uvs.add(uv);
final float[] absHeight = new float[] { NO_ABS_HEIGHT };
absoluteHeights.add(absHeight);
if (splatMover != null) {
splatMover.vertices.add(vertex);
splatMover.uvs.add(uv);
splatMover.absoluteHeights.add(absHeight);
}
}
}
@ -152,8 +158,11 @@ public class SplatModel {
vertices.add(vertex);
final float[] uv = new float[] { (x - x0) / uvXScale, 1.0f - ((y - y0) / uvYScale) };
uvs.add(uv);
final float[] absHeight = new float[] { NO_ABS_HEIGHT };
absoluteHeights.add(absHeight);
splatMover.vertices.add(vertex);
splatMover.uvs.add(uv);
splatMover.absoluteHeights.add(absHeight);
}
}
for (int i = 0; i < (iy1 - iy0); ++i) {
@ -179,7 +188,7 @@ public class SplatModel {
}
if (indices.size() > 0) {
this.addBatch(gl, vertices, uvs, indices, batchRenderUnits);
this.addBatch(gl, vertices, uvs, absoluteHeights, indices, batchRenderUnits);
}
if (this.splatInstances != null) {
for (final SplatMover splatMover : this.splatInstances) {
@ -191,25 +200,30 @@ public class SplatModel {
}
private void addBatch(final GL30 gl, final List<float[]> vertices, final List<float[]> uvs,
final List<int[]> indices, final List<SplatMover> batchRenderUnits) {
final List<float[]> absoluteHeights, final List<int[]> indices, final List<SplatMover> batchRenderUnits) {
final int uvsOffset = vertices.size() * 3 * 4;
final int paramsOffset = uvsOffset + (uvs.size() * 4 * 2);
final int vertexBuffer = gl.glGenBuffer();
gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, vertexBuffer);
gl.glBufferData(GL30.GL_ARRAY_BUFFER, uvsOffset + (uvs.size() * 4 * 2), null, GL30.GL_STATIC_DRAW);
gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, 0, vertices.size() * 4 * 5, RenderMathUtils.wrap(vertices));
gl.glBufferData(GL30.GL_ARRAY_BUFFER, uvsOffset + (uvs.size() * 4 * 2) + (absoluteHeights.size() * 4), null,
GL30.GL_STATIC_DRAW);
gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, 0, vertices.size() * 4 * 3, RenderMathUtils.wrap(vertices));
gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, uvsOffset, uvs.size() * 4 * 2, RenderMathUtils.wrap(uvs));
gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, paramsOffset, absoluteHeights.size() * 4,
RenderMathUtils.wrap(absoluteHeights));
final int faceBuffer = gl.glGenBuffer();
gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, faceBuffer);
gl.glBufferData(GL30.GL_ELEMENT_ARRAY_BUFFER, indices.size() * 6 * 2, RenderMathUtils.wrapFaces(indices),
GL30.GL_STATIC_DRAW);
this.batches.add(new Batch(uvsOffset, vertexBuffer, faceBuffer, indices.size() * 6));
this.batches.add(new Batch(uvsOffset, vertexBuffer, faceBuffer, indices.size() * 6, paramsOffset));
for (final SplatMover mover : batchRenderUnits) {
mover.vertexBuffer = vertexBuffer;
mover.uvsOffset = uvsOffset;
mover.faceBuffer = faceBuffer;
mover.absHeightsOffset = paramsOffset;
}
}
@ -232,6 +246,7 @@ public class SplatModel {
gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, b.vertexBuffer);
shader.setVertexAttribute("a_position", 3, GL30.GL_FLOAT, false, 12, 0);
shader.setVertexAttribute("a_uv", 2, GL30.GL_FLOAT, false, 8, b.uvsOffset);
shader.setVertexAttribute("a_absoluteHeight", 1, GL30.GL_FLOAT, false, 4, b.paramsOffset);
// Faces.
gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, b.faceBuffer);
@ -266,16 +281,20 @@ public class SplatModel {
private final int vertexBuffer;
private final int faceBuffer;
private final int elements;
private final int paramsOffset;
public Batch(final int uvsOffset, final int vertexBuffer, final int faceBuffer, final int elements) {
public Batch(final int uvsOffset, final int vertexBuffer, final int faceBuffer, final int elements,
final int paramsOffset) {
this.uvsOffset = uvsOffset;
this.vertexBuffer = vertexBuffer;
this.faceBuffer = faceBuffer;
this.elements = elements;
this.paramsOffset = paramsOffset;
}
}
public static final class SplatMover {
public int absHeightsOffset;
public int faceBuffer;
public int uvsOffset;
public int iy1;
@ -290,11 +309,14 @@ public class SplatModel {
private int start;
private final List<float[]> vertices = new ArrayList<>();
private final List<float[]> uvs = new ArrayList<>();
private final List<float[]> absoluteHeights = new ArrayList<>();
private final List<int[]> indices = new ArrayList<>();
private int indicesStartOffset;
private int index;
private final SplatModel splatModel;
private boolean hidden = false;
private boolean heightIsAbsolute = false;
private float absoluteHeightValue = 0.0f;
private SplatMover(final SplatModel splatModel) {
this.splatModel = splatModel;
@ -307,6 +329,7 @@ public class SplatModel {
this.index = index;
this.vertices.clear();
this.uvs.clear();
this.absoluteHeights.clear();
this.indices.clear();
return this;
}
@ -423,6 +446,9 @@ public class SplatModel {
}
gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, this.uvsOffset + ((this.startOffset / 3) * 2),
4 * 2 * this.uvs.size(), RenderMathUtils.wrap(this.uvs));
if (this.heightIsAbsolute) {
updateAbsoluteHeightParams();
}
}
public void destroy(final GL30 gl, final float[] centerOffset) {
@ -468,5 +494,24 @@ public class SplatModel {
move(0, 0, centerOffset);
this.hidden = false;
}
public void setHeightAbsolute(final boolean absolute, final float absoluteHeightValue) {
this.absoluteHeightValue = absoluteHeightValue;
if (absolute != this.heightIsAbsolute) {
this.heightIsAbsolute = absolute;
updateAbsoluteHeightParams();
}
}
private void updateAbsoluteHeightParams() {
final GL30 gl = Gdx.gl30;
final float height = this.heightIsAbsolute ? this.absoluteHeightValue : NO_ABS_HEIGHT;
for (final float[] absHeight : this.absoluteHeights) {
absHeight[0] = height;
}
gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.vertexBuffer);
gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, this.absHeightsOffset + (this.startOffset / 3),
this.absoluteHeights.size() * 4, RenderMathUtils.wrap(this.absoluteHeights));
}
}
}

View File

@ -144,11 +144,16 @@ public class Variations {
}
public static int getCliffVariation(final String dir, final String tag, final int variation) {
final Integer vars;
if ("Cliffs".equals(dir)) {
return Math.min(variation, CLIFF_VARS.get(tag));
vars = CLIFF_VARS.get(tag);
}
else {
return Math.min(variation, CITY_CLIFF_VARS.get(tag));
vars = CITY_CLIFF_VARS.get(tag);
}
if (variation < vars) {
return variation;
}
return variation % (vars + 1);
}
}

View File

@ -20,6 +20,7 @@ public class W3xShaders {
" uniform float u_lightTextureHeight;\r\n" + //
" attribute vec3 a_position;\r\n" + //
" attribute vec2 a_uv;\r\n" + //
" attribute float a_absoluteHeight;\r\n" + //
" varying vec2 v_uv;\r\n" + //
" varying vec2 v_suv;\r\n" + //
" varying vec3 v_normal;\r\n" + //
@ -29,19 +30,28 @@ public class W3xShaders {
" void main() {\r\n" + //
" vec2 halfPixel = u_pixel * 0.5;\r\n" + //
" vec2 base = (a_position.xy - u_centerOffset) / 128.0;\r\n" + //
" float height = texture2D(u_heightMap, base * u_pixel + halfPixel).r;\r\n" + //
" float hL = texture2D(u_heightMap, vec2(base - vec2(normalDist, 0.0)) * u_pixel + halfPixel).r;\r\n"
+ //
" float hR = texture2D(u_heightMap, vec2(base + vec2(normalDist, 0.0)) * u_pixel + halfPixel).r;\r\n"
+ //
" float hD = texture2D(u_heightMap, vec2(base - vec2(0.0, normalDist)) * u_pixel + halfPixel).r;\r\n"
+ //
" float hU = texture2D(u_heightMap, vec2(base + vec2(0.0, normalDist)) * u_pixel + halfPixel).r;\r\n"
+ //
" float height;\r\n" + //
" float hL;\r\n" + //
" float hR;\r\n" + //
" float hD;\r\n" + //
" float hU;\r\n" + //
" if (a_absoluteHeight < -256.0) {\r\n" + //
" height = texture2D(u_heightMap, base * u_pixel + halfPixel).r * 128.0;\r\n" + //
" hL = texture2D(u_heightMap, vec2(base - vec2(normalDist, 0.0)) * u_pixel + halfPixel).r;\r\n" + //
" hR = texture2D(u_heightMap, vec2(base + vec2(normalDist, 0.0)) * u_pixel + halfPixel).r;\r\n" + //
" hD = texture2D(u_heightMap, vec2(base - vec2(0.0, normalDist)) * u_pixel + halfPixel).r;\r\n" + //
" hU = texture2D(u_heightMap, vec2(base + vec2(0.0, normalDist)) * u_pixel + halfPixel).r;\r\n" + //
" } else {\r\n" + //
" height = a_absoluteHeight;\r\n" + //
" hL = a_absoluteHeight;\r\n" + //
" hR = a_absoluteHeight;\r\n" + //
" hD = a_absoluteHeight;\r\n" + //
" hU = a_absoluteHeight;\r\n" + //
" }\r\n" + //
" v_normal = normalize(vec3(hL - hR, hD - hU, normalDist * 2.0));\r\n" + //
" v_uv = a_uv;\r\n" + //
" v_suv = base / u_size;\r\n" + //
" vec3 myposition = vec3(a_position.xy, height * 128.0 + a_position.z);\r\n" + //
" vec3 myposition = vec3(a_position.xy, height + a_position.z);\r\n" + //
" gl_Position = u_mvp * vec4(myposition.xyz, 1.0);\r\n" + //
" a_positionHeight = a_position.z;\r\n" + //
Shaders.lightSystem("v_normal", "myposition", "u_lightTexture", "u_lightTextureHeight", "u_lightCount",

View File

@ -78,17 +78,20 @@ import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain.Splat;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderAttackInstant;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderAttackProjectile;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderDestructable;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderDoodad;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderEffect;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderItem;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnitTypeData;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderWidget;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitFilterFunction;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidgetFilterFunction;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile;
@ -111,7 +114,6 @@ public class War3MapViewer extends AbstractMdxModelViewer {
private static final War3ID UNIT_SHADOW_H = War3ID.fromString("ushh");
private static final War3ID BUILDING_SHADOW = War3ID.fromString("ushb");
public static final War3ID UNIT_SELECT_SCALE = War3ID.fromString("ussc");
private static final War3ID UNIT_SELECT_HEIGHT = War3ID.fromString("uslz");
private static final War3ID UNIT_SOUNDSET = War3ID.fromString("usnd");
private static final War3ID ITEM_FILE = War3ID.fromString("ifil");
private static final War3ID UNIT_PATHING = War3ID.fromString("upat");
@ -147,6 +149,7 @@ public class War3MapViewer extends AbstractMdxModelViewer {
public boolean unitsAndItemsLoaded;
public MappedData unitsData = new MappedData();
public MappedData unitMetaData = new MappedData();
public List<RenderWidget> widgets = new ArrayList<>();
public List<RenderUnit> units = new ArrayList<>();
public List<RenderItem> items = new ArrayList<>();
public List<RenderEffect> projectiles = new ArrayList<>();
@ -160,7 +163,7 @@ public class War3MapViewer extends AbstractMdxModelViewer {
public int renderLighting = 1;
public List<SplatModel> selModels = new ArrayList<>();
public List<RenderUnit> selected = new ArrayList<>();
public List<RenderWidget> selected = new ArrayList<>();
private DataTable unitAckSoundsTable;
private DataTable unitCombatSoundsTable;
public DataTable miscData;
@ -336,6 +339,7 @@ public class War3MapViewer extends AbstractMdxModelViewer {
this.selectionCircleSizes.add(new SelectionCircleSize(size, texture, textureDotted));
}
this.selectionCircleScaleFactor = selectionCircleData.getFieldFloatValue("ScaleFactor");
this.imageWalkableZOffset = selectionCircleData.getFieldValue("ImageWalkableZOffset");
this.uiSoundsTable = new DataTable(worldEditStrings);
try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UISounds.slk")) {
@ -524,7 +528,7 @@ public class War3MapViewer extends AbstractMdxModelViewer {
}
@Override
public void spawnUnitDamageSound(final CUnit damagedUnit, final String weaponSound,
public void spawnDamageSound(final CWidget damagedDestructable, final String weaponSound,
final String armorType) {
final String key = weaponSound + armorType;
UnitSound combatSound = this.keyToCombatSound.get(key);
@ -533,8 +537,8 @@ public class War3MapViewer extends AbstractMdxModelViewer {
War3MapViewer.this.unitCombatSoundsTable, weaponSound, armorType);
this.keyToCombatSound.put(key, combatSound);
}
combatSound.play(War3MapViewer.this.worldScene.audioContext, damagedUnit.getX(),
damagedUnit.getY());
combatSound.play(War3MapViewer.this.worldScene.audioContext, damagedDestructable.getX(),
damagedDestructable.getY());
}
@Override
@ -551,6 +555,7 @@ public class War3MapViewer extends AbstractMdxModelViewer {
@Override
public void removeUnit(final CUnit unit) {
final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.remove(unit);
War3MapViewer.this.widgets.remove(renderUnit);
War3MapViewer.this.units.remove(renderUnit);
War3MapViewer.this.worldScene.removeInstance(renderUnit.instance);
}
@ -777,10 +782,10 @@ public class War3MapViewer extends AbstractMdxModelViewer {
if (type == WorldEditorDataType.DESTRUCTIBLES) {
final float x = doodad.getLocation()[0];
final float y = doodad.getLocation()[1];
this.simulation.createDestructable(row.getAlias(), x, y, destructablePathing,
destructablePathingDeath);
final CDestructable simulationDestructable = this.simulation.createDestructable(row.getAlias(), x,
y, destructablePathing, destructablePathingDeath);
final RenderDestructable renderDestructable = new RenderDestructable(this, model, row, doodad, type,
maxPitch, maxRoll, doodad.getLife(), destructableShadow);
maxPitch, maxRoll, doodad.getLife(), destructableShadow, simulationDestructable);
if (row.readSLKTagBoolean("walkable")) {
final BoundingBox boundingBox = model.bounds.getBoundingBox();
final float minX = boundingBox.min.x + x;
@ -792,6 +797,7 @@ public class War3MapViewer extends AbstractMdxModelViewer {
renderDestructable.walkableBounds = renderDestructableBounds;
}
this.doodads.add(renderDestructable);
this.widgets.add(renderDestructable);
}
else {
this.doodads.add(new RenderDoodad(this, model, row, doodad, type, maxPitch, maxRoll));
@ -1077,8 +1083,10 @@ public class War3MapViewer extends AbstractMdxModelViewer {
angle, buildingPathingPixelMap, pathingInstance);
final RenderUnitTypeData typeData = getUnitTypeData(unitId, row);
final RenderUnit renderUnit = new RenderUnit(this, model, row, unitX, unitY, unitZ, playerIndex,
soundset, portraitModel, simulationUnit, typeData, specialArtModel, buildingShadowInstance);
soundset, portraitModel, simulationUnit, typeData, specialArtModel, buildingShadowInstance,
this.selectionCircleScaleFactor);
this.unitToRenderPeer.put(simulationUnit, renderUnit);
this.widgets.add(renderUnit);
this.units.add(renderUnit);
if (unitShadowSplat != null) {
unitShadowSplat.unitMapping.add(new Consumer<SplatModel.SplatMover>() {
@ -1191,7 +1199,7 @@ public class War3MapViewer extends AbstractMdxModelViewer {
super.update();
for (final RenderUnit unit : this.units) {
for (final RenderWidget unit : this.widgets) {
unit.updateAnimations(this);
}
final Iterator<RenderEffect> projectileIterator = this.projectiles.iterator();
@ -1276,29 +1284,27 @@ public class War3MapViewer extends AbstractMdxModelViewer {
this.terrain.removeSplatBatchModel("selection");
}
this.selModels.clear();
for (final RenderUnit unit : this.selected) {
unit.selectionCircle = null;
for (final RenderWidget unit : this.selected) {
unit.unassignSelectionCircle();
}
}
this.selected.clear();
}
public void doSelectUnit(final List<RenderUnit> units) {
public void doSelectUnit(final List<RenderWidget> units) {
deselect();
if (units.isEmpty()) {
return;
}
final Map<String, Terrain.Splat> splats = new HashMap<String, Terrain.Splat>();
for (final RenderUnit unit : units) {
if (unit.row != null) {
if (unit.selectionScale > 0) {
final float selectionSize = unit.selectionScale * this.selectionCircleScaleFactor;
for (final RenderWidget unit : units) {
if (unit.getSelectionScale() > 0) {
final float selectionSize = unit.getSelectionScale();
String path = null;
for (int i = 0; i < this.selectionCircleSizes.size(); i++) {
final SelectionCircleSize selectionCircleSize = this.selectionCircleSizes.get(i);
if ((selectionSize < selectionCircleSize.size)
|| (i == (this.selectionCircleSizes.size() - 1))) {
if ((selectionSize < selectionCircleSize.size) || (i == (this.selectionCircleSizes.size() - 1))) {
path = selectionCircleSize.texture;
break;
}
@ -1309,25 +1315,24 @@ public class War3MapViewer extends AbstractMdxModelViewer {
if (!splats.containsKey(path)) {
splats.put(path, new Splat());
}
final float x = unit.location[0];
final float y = unit.location[1];
final float x = unit.getX();
final float y = unit.getY();
System.out.println("Selecting a unit at " + x + "," + y);
final float z = unit.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0);
final float z = unit.getSelectionHeight();
splats.get(path).locations.add(new float[] { x - (selectionSize / 2), y - (selectionSize / 2),
x + (selectionSize / 2), y + (selectionSize / 2), z + 5 });
splats.get(path).unitMapping.add(new Consumer<SplatModel.SplatMover>() {
@Override
public void accept(final SplatMover t) {
unit.selectionCircle = t;
if (unit.instance.hidden()) {
unit.selectionCircle.hide();
unit.assignSelectionCircle(t);
if (unit.getInstance().hidden()) {
t.hide();
}
}
});
}
this.selected.add(unit);
}
}
this.selModels.clear();
for (final Map.Entry<String, Terrain.Splat> entry : splats.entrySet()) {
final String path = entry.getKey();
@ -1372,10 +1377,10 @@ public class War3MapViewer extends AbstractMdxModelViewer {
this.confirmationInstance.vertexColor[2] = blue;
}
public List<RenderUnit> selectUnit(final float x, final float y, final boolean toggle) {
public List<RenderWidget> selectUnit(final float x, final float y, final boolean toggle) {
System.out.println("world: " + x + "," + y);
final RenderUnit entity = rayPickUnit(x, y, CUnitFilterFunction.ACCEPT_ALL_LIVING);
List<RenderUnit> sel;
final RenderWidget entity = rayPickUnit(x, y, CWidgetFilterFunction.ACCEPT_ALL_LIVING);
List<RenderWidget> sel;
if (entity != null) {
if (toggle) {
sel = new ArrayList<>(this.selected);
@ -1398,25 +1403,25 @@ public class War3MapViewer extends AbstractMdxModelViewer {
return sel;
}
public RenderUnit rayPickUnit(final float x, final float y) {
return rayPickUnit(x, y, CUnitFilterFunction.ACCEPT_ALL);
public RenderWidget rayPickUnit(final float x, final float y) {
return rayPickUnit(x, y, CWidgetFilterFunction.ACCEPT_ALL);
}
public RenderUnit rayPickUnit(final float x, final float y, final CUnitFilterFunction filter) {
public RenderWidget rayPickUnit(final float x, final float y, final CWidgetFilterFunction filter) {
final float[] ray = rayHeap;
mousePosHeap.set(x, y);
this.worldScene.camera.screenToWorldRay(ray, mousePosHeap);
gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]);
gdxRayHeap.direction.nor();// needed for libgdx
RenderUnit entity = null;
for (final RenderUnit unit : this.units) {
final MdxComplexInstance instance = unit.instance;
if (instance.shown() && instance.isVisible(this.worldScene.camera) && instance.intersectRayWithCollision(
gdxRayHeap, intersectionHeap, unit.getSimulationUnit().getUnitType().isBuilding(), false)) {
if (filter.call(unit.getSimulationUnit()) && (intersectionHeap.z > this.terrain
RenderWidget entity = null;
for (final RenderWidget unit : this.widgets) {
final MdxComplexInstance instance = unit.getInstance();
if (instance.shown() && instance.isVisible(this.worldScene.camera) && instance
.intersectRayWithCollision(gdxRayHeap, intersectionHeap, unit.isIntersectedOnMeshAlways(), false)) {
if (filter.call(unit.getSimulationWidget()) && (intersectionHeap.z > this.terrain
.getGroundHeight(intersectionHeap.x, intersectionHeap.y))) {
if ((entity == null) || (entity.instance.depth > instance.depth)) {
if ((entity == null) || (entity.getInstance().depth > instance.depth)) {
entity = unit;
}
}
@ -1495,6 +1500,7 @@ public class War3MapViewer extends AbstractMdxModelViewer {
private Warcraft3MapObjectData allObjectData;
private AbilityDataUI abilityDataUI;
private Map<String, UnitSoundset> soundsetNameToSoundset;
public int imageWalkableZOffset;
/**
* Returns a power of two size for the given target capacity.

View File

@ -244,7 +244,7 @@ public class Terrain {
// Cliff Textures
for (final War3ID cliffTile : w3eFile.getCliffTiles()) {
final Element cliffInfo = this.cliffTable.get(cliffTile.asStringValue());
if(cliffInfo == null) {
if (cliffInfo == null) {
System.err.println("Missing cliff type: " + cliffTile.asStringValue());
continue;
}
@ -656,10 +656,10 @@ public class Terrain {
// Cliff model path
String fileName = "" + (char) (('A' + bottomLeft.getLayerHeight()) - base)
+ (char) (('A' + topLeft.getLayerHeight()) - base)
String fileName = "" + (char) (('A' + topLeft.getLayerHeight()) - base)
+ (char) (('A' + topRight.getLayerHeight()) - base)
+ (char) (('A' + bottomRight.getLayerHeight()) - base);
+ (char) (('A' + bottomRight.getLayerHeight()) - base)
+ (char) (('A' + bottomLeft.getLayerHeight()) - base);
if ("AAAA".equals(fileName)) {
continue;

View File

@ -35,15 +35,16 @@ public class TerrainShaders {
"out vec3 shadeColor;\r\n" + //
"\r\n" + //
"void main() {\r\n" + //
" pathing_map_uv = (vec2(vPosition.x + 128, vPosition.y) / 128 + vOffset.xy) * 4;\r\n" + //
" pathing_map_uv = (vec2(vPosition.y, -vPosition.x) / 128 + vOffset.xy) * 4;\r\n" + //
" \r\n" + //
" ivec2 size = textureSize(height_texture, 0);\r\n" + //
" ivec2 shadowSize = textureSize(shadowMap, 0);\r\n" + //
" v_suv = pathing_map_uv / shadowSize;\r\n" + //
" float value = texture(height_texture, (vOffset.xy + vec2(vPosition.x + 192, vPosition.y + 64) / 128.0) / vec2(size)).r;\r\n"
" float value = texture(height_texture, (vOffset.xy + vec2(vPosition.y + 64, -vPosition.x + 64) / 128.0) / vec2(size)).r;\r\n"
+ //
"\r\n" + //
" position = (vPosition + vec3(vOffset.xy + vec2(1, 0), vOffset.z + value) * 128 );\r\n" + //
" position = (vec3(vPosition.y, -vPosition.x, vPosition.z) + vec3(vOffset.xy, vOffset.z + value) * 128 );\r\n"
+ //
" vec4 myposition = vec4(position, 1);\r\n" + //
" myposition.x += centerOffsetX;\r\n" + //
" myposition.y += centerOffsetY;\r\n" + //
@ -52,13 +53,14 @@ public class TerrainShaders {
" gl_Position = MVP * myposition;\r\n" + //
" UV = vec3(vUV, vOffset.a);\r\n" + //
"\r\n" + //
" ivec2 height_pos = ivec2(vOffset.xy + vec2(vPosition.x + 128, vPosition.y) / 128);\r\n" + //
" ivec2 height_pos = ivec2(vOffset.xy + vec2(vPosition.y, -vPosition.x) / 128);\r\n" + //
" ivec3 off = ivec3(1, 1, 0);\r\n" + //
" float hL = texelFetch(height_texture, height_pos - off.xz, 0).r;\r\n" + //
" float hR = texelFetch(height_texture, height_pos + off.xz, 0).r;\r\n" + //
" float hD = texelFetch(height_texture, height_pos - off.zy, 0).r;\r\n" + //
" float hU = texelFetch(height_texture, height_pos + off.zy, 0).r;\r\n" + //
" vec3 terrain_normal = normalize(vNormal);//vec3(hL - hR, hD - hU, 2.0)+vNormal);\r\n" + //
" vec3 terrain_normal = normalize(vec3(vNormal.y, -vNormal.x, vNormal.z));//vec3(hL - hR, hD - hU, 2.0)+vNormal);\r\n"
+ //
"\r\n" + //
" Normal = terrain_normal;\r\n" + //
Shaders.lightSystem("terrain_normal", "myposition.xyz", "lightTexture", "lightTextureHeight",

View File

@ -0,0 +1,105 @@
package com.etheller.warsmash.viewer5.handlers.w3x.rendersim;
import com.badlogic.gdx.math.Rectangle;
import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject;
import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType;
import com.etheller.warsmash.util.War3ID;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover;
import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.BuildingShadow;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget;
public class RenderDestructable extends RenderDoodad implements RenderWidget {
private static final War3ID TEX_FILE = War3ID.fromString("btxf");
private static final War3ID TEX_ID = War3ID.fromString("btxi");
private static final War3ID SEL_CIRCLE_SIZE = War3ID.fromString("bgsc");
private final float life;
public Rectangle walkableBounds;
private final CDestructable simulationDestructable;
private SplatMover selectionCircle;
public RenderDestructable(final War3MapViewer map, final MdxModel model, final MutableGameObject row,
final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type,
final float maxPitch, final float maxRoll, final float life, final BuildingShadow destructableShadow,
final CDestructable simulationDestructable) {
super(map, model, row, doodad, type, maxPitch, maxRoll);
this.life = life;
this.simulationDestructable = simulationDestructable;
String replaceableTextureFile = row.getFieldAsString(TEX_FILE, 0);
final int replaceableTextureId = row.getFieldAsInteger(TEX_ID, 0);
if ((replaceableTextureFile != null) && (replaceableTextureFile.length() > 1)) {
final int dotIndex = replaceableTextureFile.lastIndexOf('.');
if (dotIndex != -1) {
replaceableTextureFile = replaceableTextureFile.substring(0, dotIndex);
}
replaceableTextureFile += ".blp";
this.instance.setReplaceableTexture(replaceableTextureId, replaceableTextureFile);
}
this.selectionScale *= row.getFieldAsFloat(SEL_CIRCLE_SIZE, 0);
}
@Override
public PrimaryTag getAnimation() {
if (this.life <= 0) {
return PrimaryTag.DEATH;
}
return super.getAnimation();
}
@Override
public MdxComplexInstance getInstance() {
return (MdxComplexInstance) this.instance;
}
@Override
public CWidget getSimulationWidget() {
return this.simulationDestructable;
}
@Override
public void updateAnimations(final War3MapViewer war3MapViewer) {
// TODO maybe move getAnimation behaviors to here and make this thing not a
// doodad
}
@Override
public boolean isIntersectedOnMeshAlways() {
return false;
}
@Override
public float getSelectionScale() {
return this.selectionScale;
}
@Override
public float getX() {
return this.x;
}
@Override
public float getY() {
return this.y;
}
@Override
public float getSelectionHeight() {
return 0;
}
@Override
public void unassignSelectionCircle() {
this.selectionCircle = null;
}
@Override
public void assignSelectionCircle(final SplatMover selectionCircle) {
this.selectionCircle = selectionCircle;
}
}

View File

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

View File

@ -28,9 +28,10 @@ import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.Comma
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitAnimationListener;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility;
public class RenderUnit {
public class RenderUnit implements RenderWidget {
public static final Quaternion tempQuat = new Quaternion();
private static final War3ID RED = War3ID.fromString("uclr");
private static final War3ID GREEN = War3ID.fromString("uclg");
@ -41,6 +42,7 @@ public class RenderUnit {
private static final War3ID ANIM_PROPS = War3ID.fromString("uani");
private static final War3ID BLEND_TIME = War3ID.fromString("uble");
private static final War3ID BUILD_SOUND_LABEL = War3ID.fromString("ubsl");
private static final War3ID UNIT_SELECT_HEIGHT = War3ID.fromString("uslz");
private static final float[] heapZ = new float[3];
public final MdxComplexInstance instance;
public final MutableGameObject row;
@ -74,7 +76,8 @@ public class RenderUnit {
public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, final float x,
final float y, final float z, final int playerIndex, final UnitSoundset soundset,
final MdxModel portraitModel, final CUnit simulationUnit, final RenderUnitTypeData typeData,
final MdxModel specialArtModel, final BuildingShadow buildingShadow) {
final MdxModel specialArtModel, final BuildingShadow buildingShadow,
final float selectionCircleScaleFactor) {
this.portraitModel = portraitModel;
this.simulationUnit = simulationUnit;
this.typeData = typeData;
@ -123,7 +126,7 @@ public class RenderUnit {
(row.getFieldAsInteger(green, 0)) / 255f, (row.getFieldAsInteger(blue, 0)) / 255f });
instance.uniformScale(row.getFieldAsFloat(scale, 0));
this.selectionScale = row.getFieldAsFloat(War3MapViewer.UNIT_SELECT_SCALE, 0);
this.selectionScale = row.getFieldAsFloat(War3MapViewer.UNIT_SELECT_SCALE, 0) * selectionCircleScaleFactor;
int orientationInterpolationOrdinal = row.getFieldAsInteger(ORIENTATION_INTERPOLATION, 0);
if ((orientationInterpolationOrdinal < 0)
|| (orientationInterpolationOrdinal >= OrientationInterpolation.VALUES.length)) {
@ -150,6 +153,7 @@ public class RenderUnit {
}
}
@Override
public void updateAnimations(final War3MapViewer map) {
final boolean wasHidden = this.instance.hidden();
if (this.simulationUnit.isHidden()) {
@ -390,9 +394,12 @@ public class RenderUnit {
map.worldScene.instanceMoved(this.instance, this.location[0], this.location[1]);
if (this.shadow != null) {
this.shadow.move(dx, dy, map.terrain.centerOffset);
this.shadow.setHeightAbsolute(currentWalkableUnder != null, this.location[2] + map.imageWalkableZOffset);
}
if (this.selectionCircle != null) {
this.selectionCircle.move(dx, dy, map.terrain.centerOffset);
this.selectionCircle.setHeightAbsolute(currentWalkableUnder != null,
this.location[2] + map.imageWalkableZOffset);
}
this.unitAnimationListenerImpl.update();
if (!dead && this.simulationUnit.isConstructing()) {
@ -553,4 +560,49 @@ public class RenderUnit {
this.location[0] = this.simulationUnit.getX();
this.location[1] = this.simulationUnit.getY();
}
@Override
public MdxComplexInstance getInstance() {
return this.instance;
}
@Override
public CWidget getSimulationWidget() {
return this.simulationUnit;
}
@Override
public boolean isIntersectedOnMeshAlways() {
return this.simulationUnit.getUnitType().isBuilding();
}
@Override
public float getSelectionHeight() {
return this.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0);
}
@Override
public float getSelectionScale() {
return this.selectionScale;
}
@Override
public float getX() {
return this.location[0];
}
@Override
public float getY() {
return this.location[1];
}
@Override
public void unassignSelectionCircle() {
this.selectionCircle = null;
}
@Override
public void assignSelectionCircle(final SplatMover t) {
this.selectionCircle = t;
}
}

View File

@ -0,0 +1,28 @@
package com.etheller.warsmash.viewer5.handlers.w3x.rendersim;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover;
import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget;
public interface RenderWidget {
MdxComplexInstance getInstance();
CWidget getSimulationWidget();
void updateAnimations(War3MapViewer war3MapViewer);
boolean isIntersectedOnMeshAlways();
float getSelectionScale();
float getX();
float getY();
float getSelectionHeight();
void unassignSelectionCircle();
void assignSelectionCircle(SplatMover t);
}

View File

@ -36,6 +36,7 @@ public class CDestructable extends CWidget {
public void damage(final CSimulation simulation, final CUnit source, final CAttackType attackType,
final String weaponType, final float damage) {
this.life -= damage;
simulation.destructableDamageEvent(this, weaponType, this.destType.getArmorType());
}
@Override
@ -50,7 +51,7 @@ public class CDestructable extends CWidget {
}
}
else {
System.err.println("No targeting because " + targetsAllowed + " does not contain all of "
System.err.println("Not targeting because " + targetsAllowed + " does not contain all of "
+ this.destType.getTargetedAs());
}
return false;
@ -60,4 +61,8 @@ public class CDestructable extends CWidget {
public <T> T visit(final AbilityTargetVisitor<T> visitor) {
return visitor.accept(this);
}
public CDestructableType getDestType() {
return this.destType;
}
}

View File

@ -239,7 +239,12 @@ public class CSimulation {
}
public void unitDamageEvent(final CUnit damagedUnit, final String weaponSound, final String armorType) {
this.simulationRenderController.spawnUnitDamageSound(damagedUnit, weaponSound, armorType);
this.simulationRenderController.spawnDamageSound(damagedUnit, weaponSound, armorType);
}
public void destructableDamageEvent(final CDestructable damagedDestructable, final String weaponSound,
final String armorType) {
this.simulationRenderController.spawnDamageSound(damagedDestructable, weaponSound, armorType);
}
public void unitConstructedEvent(final CUnit constructingUnit, final CUnit constructedStructure) {
@ -284,4 +289,16 @@ public class CSimulation {
player.setUnitFoodMade(unit, unit.getUnitType().getFoodMade());
}
}
public CWidget getWidget(final int handleId) {
final CUnit unit = this.handleIdToUnit.get(handleId);
if (unit != null) {
return unit;
}
final CDestructable destructable = this.handleIdToDestructable.get(handleId);
if (destructable != null) {
return destructable;
}
return null;
}
}

View File

@ -1,19 +0,0 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
public interface CUnitFilterFunction {
boolean call(CUnit unit);
CUnitFilterFunction ACCEPT_ALL = new CUnitFilterFunction() {
@Override
public boolean call(final CUnit unit) {
return true;
}
};
CUnitFilterFunction ACCEPT_ALL_LIVING = new CUnitFilterFunction() {
@Override
public boolean call(final CUnit unit) {
return !unit.isDead();
}
};
}

View File

@ -0,0 +1,19 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
public interface CWidgetFilterFunction {
boolean call(CWidget unit);
CWidgetFilterFunction ACCEPT_ALL = new CWidgetFilterFunction() {
@Override
public boolean call(final CWidget unit) {
return true;
}
};
CWidgetFilterFunction ACCEPT_ALL_LIVING = new CWidgetFilterFunction() {
@Override
public boolean call(final CWidget unit) {
return !unit.isDead();
}
};
}

View File

@ -47,8 +47,10 @@ public class CBehaviorAttack extends CAbstractRangedBehavior {
if (simulation.getGameTurnTick() < this.unit.getCooldownEndTime()) {
range += this.unitAttack.getRangeMotionBuffer();
}
final double rangeCheckDistance = this.unit.distance(this.target);
System.out.println("rangeCheckDistance=" + rangeCheckDistance);
return this.unit.canReach(this.target, range)
&& (this.unit.distance(this.target) >= this.unit.getUnitType().getMinimumAttackRange());
&& (rangeCheckDistance >= this.unit.getUnitType().getMinimumAttackRange());
}
@Override

View File

@ -123,8 +123,7 @@ public class CUnitAttackMissileSplash extends CUnitAttackMissile {
this.y = y;
this.damage = damage;
this.hitTarget = false;
final float doubleMaxArea = attack.areaOfEffectSmallDamage
+ (this.simulation.getGameplayConstants().getCloseEnoughRange() * 2);
final float doubleMaxArea = (attack.areaOfEffectSmallDamage) * 2;
final float maxArea = doubleMaxArea / 2;
this.rect.set(x - maxArea, y - maxArea, doubleMaxArea, doubleMaxArea);
simulation.getWorldCollision().enumUnitsInRect(this.rect, this);
@ -133,17 +132,17 @@ public class CUnitAttackMissileSplash extends CUnitAttackMissile {
@Override
public boolean call(final CUnit enumUnit) {
if (enumUnit.canBeTargetedBy(this.simulation, this.source, this.attack.areaOfEffectTargets)) {
final double distance = enumUnit.distance(this.x, this.y)
- this.simulation.getGameplayConstants().getCloseEnoughRange();
if (distance <= (this.attack.areaOfEffectFullDamage / 2)) {
final double distance = enumUnit.distance(this.x, this.y);
System.out.println("enum distance=" + distance);
if (distance <= (this.attack.areaOfEffectFullDamage)) {
enumUnit.damage(this.simulation, this.source, this.attack.getAttackType(),
this.attack.getWeaponSound(), this.damage);
}
else if (distance <= (this.attack.areaOfEffectMediumDamage / 2)) {
else if (distance <= (this.attack.areaOfEffectMediumDamage)) {
enumUnit.damage(this.simulation, this.source, this.attack.getAttackType(),
this.attack.getWeaponSound(), this.damage * this.attack.damageFactorMedium);
}
else if (distance <= (this.attack.areaOfEffectSmallDamage / 2)) {
else if (distance <= (this.attack.areaOfEffectSmallDamage)) {
enumUnit.damage(this.simulation, this.source, this.attack.getAttackType(),
this.attack.getWeaponSound(), this.damage * this.attack.damageFactorSmall);
}

View File

@ -47,12 +47,8 @@ public class CAttackProjectile {
final float d1y = dtsy / c;
float travelDistance = Math.min(c, this.speed * WarsmashConstants.SIMULATION_STEP_TIME);
if (c <= travelDistance) {
if (!this.done) {
this.unitAttack.doDamage(cSimulation, this.source, this.target, this.damage, this.x, this.y,
this.bounceIndex);
}
this.done = true;
final boolean done = c <= travelDistance;
if (done) {
travelDistance = c;
}
@ -62,6 +58,11 @@ public class CAttackProjectile {
this.x = this.x + dx;
this.y = this.y + dy;
if (done && !this.done) {
this.unitAttack.doDamage(cSimulation, this.source, this.target, this.damage, this.x, this.y,
this.bounceIndex);
this.done = true;
}
return this.done;
}

View File

@ -34,7 +34,7 @@ public class COrderTargetWidget implements COrder {
@Override
public AbilityTarget getTarget(final CSimulation game) {
final CUnit target = game.getUnit(this.targetHandleId);
final CWidget target = game.getWidget(this.targetHandleId);
return target;
}
@ -48,7 +48,7 @@ public class COrderTargetWidget implements COrder {
final CAbility ability = game.getAbility(this.abilityHandleId);
ability.checkCanUse(game, caster, this.orderId, abilityActivationReceiver.reset());
if (abilityActivationReceiver.isUseOk()) {
final CUnit target = game.getUnit(this.targetHandleId);
final CWidget target = game.getWidget(this.targetHandleId);
final StringMsgTargetCheckReceiver<CWidget> targetReceiver = (StringMsgTargetCheckReceiver<CWidget>) targetCheckReceiver;
ability.checkCanTarget(game, caster, this.orderId, target, targetReceiver);
if (targetReceiver.getTarget() != null) {

View File

@ -21,7 +21,7 @@ public interface SimulationRenderController {
void createInstantAttackEffect(CSimulation cSimulation, CUnit source, CUnitAttackInstant attack, CWidget target);
void spawnUnitDamageSound(CUnit damagedUnit, final String weaponSound, String armorType);
void spawnDamageSound(CWidget damagedDestructable, String weaponSound, String armorType);
void spawnUnitConstructionSound(CUnit constructingUnit, CUnit constructedStructure);

View File

@ -76,6 +76,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.camera.GameCameraManager;
import com.etheller.warsmash.viewer5.handlers.w3x.camera.PortraitCameraManager;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.PathingFlags;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderWidget;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.IconUI;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.CommandButtonListener;
@ -85,10 +86,10 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CPlayerStateListene
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit.QueueItemType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitFilterFunction;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitStateListener;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidgetFilterFunction;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityGeneric;
@ -252,6 +253,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
private MeleeUIAbilityActivationReceiver meleeUIAbilityActivationReceiver;
private MdxModel waypointModel;
private final List<MdxComplexInstance> waypointModelInstances = new ArrayList<>();
private List<RenderUnit> selectedUnits;
public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport,
final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene,
@ -1157,9 +1159,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
}
}
private final class ActiveCommandUnitTargetFilter implements CUnitFilterFunction {
private final class ActiveCommandUnitTargetFilter implements CWidgetFilterFunction {
@Override
public boolean call(final CUnit unit) {
public boolean call(final CWidget unit) {
final BooleanAbilityTargetCheckReceiver<CWidget> targetReceiver = BooleanAbilityTargetCheckReceiver
.<CWidget>getInstance();
MeleeUI.this.activeCommand.checkCanTarget(MeleeUI.this.war3MapViewer.simulation,
@ -1735,16 +1737,18 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
clearAndRepopulateCommandCard();
}
else {
final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY,
final RenderWidget rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY,
this.activeCommandUnitTargetFilter);
final boolean shiftDown = isShiftDown();
if (rayPickUnit != null) {
this.unitOrderListener.issueTargetOrder(
this.activeCommandUnit.getSimulationUnit().getHandleId(),
this.activeCommand.getHandleId(), this.activeCommandOrderId,
rayPickUnit.getSimulationUnit().getHandleId(), shiftDown);
if (getSelectedUnit().soundset.yesAttack
.playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) {
rayPickUnit.getSimulationWidget().getHandleId(), shiftDown);
final UnitSound yesSound = (this.activeCommand instanceof CAbilityAttack)
? getSelectedUnit().soundset.yesAttack
: getSelectedUnit().soundset.yes;
if (yesSound.playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) {
portraitTalk();
}
this.selectedSoundCount = 0;
@ -1808,14 +1812,15 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
else {
if (button == Input.Buttons.RIGHT) {
if (getSelectedUnit() != null) {
final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY);
if ((rayPickUnit != null) && !rayPickUnit.getSimulationUnit().isDead()) {
final RenderWidget rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY);
if ((rayPickUnit != null) && !rayPickUnit.getSimulationWidget().isDead()) {
boolean ordered = false;
boolean rallied = false;
for (final RenderUnit unit : this.war3MapViewer.selected) {
boolean attacked = false;
for (final RenderUnit unit : this.selectedUnits) {
for (final CAbility ability : unit.getSimulationUnit().getAbilities()) {
ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(),
OrderIds.smart, rayPickUnit.getSimulationUnit(),
OrderIds.smart, rayPickUnit.getSimulationWidget(),
CWidgetAbilityTargetCheckReceiver.INSTANCE);
final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget();
if (targetWidget != null) {
@ -1823,14 +1828,17 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
ability.getHandleId(), OrderIds.smart, targetWidget.getHandleId(),
isShiftDown());
rallied |= ability instanceof CAbilityRally;
attacked |= ability instanceof CAbilityAttack;
ordered = true;
}
}
}
if (ordered) {
if (getSelectedUnit().soundset.yesAttack.playUnitResponse(
this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) {
final UnitSound yesSound = attacked ? getSelectedUnit().soundset.yesAttack
: getSelectedUnit().soundset.yes;
if (yesSound.playUnitResponse(this.war3MapViewer.worldScene.audioContext,
getSelectedUnit())) {
portraitTalk();
}
if (rallied) {
@ -1839,53 +1847,19 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
}
this.selectedSoundCount = 0;
}
else {
rightClickMove(screenX, worldScreenY);
}
}
else {
this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY);
this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0);
clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y);
boolean ordered = false;
boolean rallied = false;
for (final RenderUnit unit : this.war3MapViewer.selected) {
for (final CAbility ability : unit.getSimulationUnit().getAbilities()) {
ability.checkCanUse(this.war3MapViewer.simulation, unit.getSimulationUnit(),
OrderIds.smart, BooleanAbilityActivationReceiver.INSTANCE);
if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) {
ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(),
OrderIds.smart, clickLocationTemp2,
PointAbilityTargetCheckReceiver.INSTANCE);
final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget();
if (target != null) {
this.unitOrderListener.issuePointOrder(
unit.getSimulationUnit().getHandleId(), ability.getHandleId(),
OrderIds.smart, clickLocationTemp2.x, clickLocationTemp2.y,
isShiftDown());
rallied |= ability instanceof CAbilityRally;
ordered = true;
}
}
}
}
if (ordered) {
if (getSelectedUnit().soundset.yes.playUnitResponse(
this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) {
portraitTalk();
}
if (rallied) {
this.war3MapViewer.getUiSounds().getSound("RallyPointPlace")
.play(this.uiScene.audioContext, 0, 0);
}
this.selectedSoundCount = 0;
}
rightClickMove(screenX, worldScreenY);
}
}
}
else {
final List<RenderUnit> selectedUnits = this.war3MapViewer.selectUnit(screenX, worldScreenY, false);
selectUnits(selectedUnits);
final List<RenderWidget> selectedUnits = this.war3MapViewer.selectUnit(screenX, worldScreenY,
false);
selectWidgets(selectedUnits);
}
}
}
@ -1898,7 +1872,57 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
return false;
}
private void rightClickMove(final int screenX, final float worldScreenY) {
this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY);
this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0);
clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y);
boolean ordered = false;
boolean rallied = false;
for (final RenderUnit unit : this.selectedUnits) {
for (final CAbility ability : unit.getSimulationUnit().getAbilities()) {
ability.checkCanUse(this.war3MapViewer.simulation, unit.getSimulationUnit(), OrderIds.smart,
BooleanAbilityActivationReceiver.INSTANCE);
if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) {
ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), OrderIds.smart,
clickLocationTemp2, PointAbilityTargetCheckReceiver.INSTANCE);
final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget();
if (target != null) {
this.unitOrderListener.issuePointOrder(unit.getSimulationUnit().getHandleId(),
ability.getHandleId(), OrderIds.smart, clickLocationTemp2.x, clickLocationTemp2.y,
isShiftDown());
rallied |= ability instanceof CAbilityRally;
ordered = true;
}
}
}
}
if (ordered) {
if (getSelectedUnit().soundset.yes.playUnitResponse(this.war3MapViewer.worldScene.audioContext,
getSelectedUnit())) {
portraitTalk();
}
if (rallied) {
this.war3MapViewer.getUiSounds().getSound("RallyPointPlace").play(this.uiScene.audioContext, 0, 0);
}
this.selectedSoundCount = 0;
}
}
private void selectWidgets(final List<RenderWidget> selectedUnits) {
final List<RenderUnit> units = new ArrayList<>();
for (final RenderWidget widget : selectedUnits) {
if (widget instanceof RenderUnit) {
units.add((RenderUnit) widget);
}
}
selectUnits(units);
}
private void selectUnits(final List<RenderUnit> selectedUnits) {
this.selectedUnits = selectedUnits;
if (!selectedUnits.isEmpty()) {
final RenderUnit unit = selectedUnits.get(0);
final boolean selectionChanged = getSelectedUnit() != unit;
@ -2027,10 +2051,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
}
break;
case 1:
final List<RenderUnit> unitList = Arrays.asList(
final List<RenderWidget> unitList = Arrays.asList(
this.war3MapViewer.getRenderPeer(this.selectedUnit.getSimulationUnit().getWorkerInside()));
this.war3MapViewer.doSelectUnit(unitList);
selectUnits(unitList);
selectWidgets(unitList);
break;
}
}