From 59d350dd9e17a2a13160f5c34bb08431aa1ff562 Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 26 Sep 2020 16:02:20 -0400 Subject: [PATCH] Add game clock functionality and begin work on lighting system --- core/assets/warsmash.ini | 3 +- .../etheller/warsmash/WarsmashGdxGame.java | 3 +- .../etheller/warsmash/WarsmashGdxMapGame.java | 203 +++++++++++------- .../etheller/warsmash/parsers/fdf/GameUI.java | 1 - .../parsers/fdf/frames/SpriteFrame.java | 18 ++ .../etheller/warsmash/units/GameObject.java | 7 + .../warsmash/units/HashedGameObject.java | 12 ++ .../warsmash/units/StandardObjectData.java | 11 + .../warsmash/util/RenderMathUtils.java | 124 +++++++++++ .../warsmash/viewer5/ModelInstance.java | 4 + .../warsmash/viewer5/ModelViewer.java | 4 +- .../com/etheller/warsmash/viewer5/Scene.java | 11 + .../warsmash/viewer5/SceneLightInstance.java | 5 + .../warsmash/viewer5/SceneLightManager.java | 7 + .../viewer5/handlers/mdx/CollisionShape.java | 20 ++ .../warsmash/viewer5/handlers/mdx/Geoset.java | 7 +- .../warsmash/viewer5/handlers/mdx/Light.java | 30 ++- .../viewer5/handlers/mdx/LightInstance.java | 78 +++++++ .../handlers/mdx/MdxComplexInstance.java | 51 ++++- .../handlers/mdx/MdxSimpleInstance.java | 5 + .../viewer5/handlers/mdx/MdxViewer.java | 20 ++ .../viewer5/handlers/mdx/SetupGeosets.java | 3 +- .../viewer5/handlers/w3x/SequenceUtils.java | 6 +- .../viewer5/handlers/w3x/W3xSceneLight.java | 9 + .../handlers/w3x/W3xSceneLightManager.java | 35 +++ .../viewer5/handlers/w3x/War3MapViewer.java | 81 ++++--- .../handlers/w3x/environment/Terrain.java | 63 +----- .../w3x/simulation/CGameplayConstants.java | 25 +++ .../handlers/w3x/simulation/CSimulation.java | 8 + .../w3x/simulation/data/CUnitData.java | 36 ++-- .../viewer5/handlers/w3x/ui/MeleeUI.java | 43 +++- 31 files changed, 734 insertions(+), 199 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/SceneLightInstance.java create mode 100644 core/src/com/etheller/warsmash/viewer5/SceneLightManager.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/LightInstance.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxViewer.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLight.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLightManager.java diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index 549c548..c828594 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -17,7 +17,8 @@ Path06="." [Map] //FilePath="CombatUnitTests.w3x" -FilePath="PitchRoll.w3x" +//FilePath="PitchRoll.w3x" +FilePath="PeonStartingBase.w3x" //FilePath="PlayerPeasants.w3m" //FilePath="FireLord.w3x" //FilePath="Maps\Campaign\NightElf03.w3m" \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java index 3b3121d..674083a 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -27,6 +27,7 @@ import com.etheller.warsmash.viewer5.SolvedPath; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxViewer; import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvider { @@ -62,7 +63,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide final FolderDataSourceDescriptor currentFolder = new FolderDataSourceDescriptor("."); this.codebase = new CompoundDataSourceDescriptor( Arrays.asList(war3mpq, testingFolder, currentFolder)).createDataSource(); - this.viewer = new ModelViewer(this.codebase, this); + this.viewer = new MdxViewer(this.codebase, this); this.viewer.addHandler(new MdxHandler()); this.viewer.enableAudio(); diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index e7aa3ea..0818154 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -56,15 +56,13 @@ import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; import com.etheller.warsmash.viewer5.handlers.w3x.ui.MeleeUI; public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { - private static final float HORIZONTAL_MAXIMUM = (float) Math.toRadians(56); - private static final float HORIZONTAL_MINIMUM = (float) -Math.toRadians(56); private static final double HORIZONTAL_ANGLE_INCREMENT = Math.PI / 60; private static final Vector3 clickLocationTemp = new Vector3(); private static final Vector2 clickLocationTemp2 = new Vector2(); private DataSource codebase; private War3MapViewer viewer; - private CameraManager cameraManager; + private GameCameraManager cameraManager; private final Rectangle tempRect = new Rectangle(); // libGDX stuff @@ -146,7 +144,20 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv throw new RuntimeException(e); } - this.cameraManager = new CameraManager(); + final Element cameraData = this.viewer.miscData.get("Camera"); + final Element cameraListenerData = this.viewer.miscData.get("Listener"); + final CameraPreset[] cameraPresets = new CameraPreset[6]; + for (int i = 0; i < cameraPresets.length; i++) { + cameraPresets[i] = new CameraPreset(cameraData.getFieldFloatValue("AOA", i), + cameraData.getFieldFloatValue("FOV", i), cameraData.getFieldFloatValue("Rotation", i), + cameraData.getFieldFloatValue("Rotation", i + cameraPresets.length), + cameraData.getFieldFloatValue("Rotation", i + (cameraPresets.length * 2)), + cameraData.getFieldFloatValue("Distance", i), cameraData.getFieldFloatValue("FarZ", i), + cameraData.getFieldFloatValue("NearZ", i), cameraData.getFieldFloatValue("Height", i), + cameraListenerData.getFieldFloatValue("ListenerDistance", i), + cameraListenerData.getFieldFloatValue("ListenerAOA", i)); + } + this.cameraManager = new GameCameraManager(cameraPresets); this.cameraManager.setupCamera(this.viewer.worldScene); @@ -287,9 +298,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final float deltaTime = Gdx.graphics.getDeltaTime(); Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO); this.cameraManager.target.add(this.cameraVelocity.x * deltaTime, this.cameraVelocity.y * deltaTime, 0); - this.cameraManager.target.z = Math.max( + this.cameraManager.target.z = (Math.max( this.viewer.terrain.getGroundHeight(this.cameraManager.target.x, this.cameraManager.target.y), - this.viewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)) - 256; + this.viewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)) - 256) + + this.cameraManager.presets[this.cameraManager.currentPreset].height + 256; this.cameraManager.updateCamera(); this.meleeUI.updatePortrait(); this.viewer.updateAndRender(); @@ -400,28 +412,26 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.meleeUI.resize(); } - public static class CameraManager { - private final float[] cameraPositionTemp = new float[3]; - private final float[] cameraTargetTemp = new float[3]; - public com.etheller.warsmash.viewer5.handlers.mdx.Camera modelCamera; - private MdxComplexInstance modelInstance; - private CanvasProvider canvas; - private Camera camera; - private float moveSpeed; - private float rotationSpeed; - private float zoomFactor; - private float horizontalAngle; - private float verticalAngle; - private float verticalAngleAcceleration; - private float distance; - private Vector3 position; - private Vector3 target; - private Vector3 worldUp; - private Vector3 vecHeap; - private Quaternion quatHeap; - private Quaternion quatHeap2; - private boolean insertDown; - private boolean deleteDown; + public static abstract class CameraManager { + protected final float[] cameraPositionTemp = new float[3]; + protected final float[] cameraTargetTemp = new float[3]; + protected CanvasProvider canvas; + protected Camera camera; + protected float moveSpeed; + protected float rotationSpeed; + protected float zoomFactor; + protected float horizontalAngle; + protected float verticalAngle; + protected float distance; + protected Vector3 position; + protected Vector3 target; + protected Vector3 worldUp; + protected Vector3 vecHeap; + protected Quaternion quatHeap; + protected Quaternion quatHeap2; + + public CameraManager() { + } // An orbit camera setup example. // Left mouse button controls the orbit itself. @@ -449,45 +459,58 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv // cameraUpdate(); } + public abstract void updateCamera(); + +// private void cameraUpdate() { +// +// } + } + + public static final class GameCameraManager extends CameraManager { + private final CameraPreset[] presets; + private int currentPreset = 0; + + protected boolean insertDown; + protected boolean deleteDown; + + public GameCameraManager(final CameraPreset[] presets) { + this.presets = presets; + } + + @Override + public void updateCamera() { + this.quatHeap2.idt(); + final CameraPreset cameraPreset = this.presets[this.currentPreset]; + this.quatHeap.idt(); + this.horizontalAngle = (float) Math + .toRadians(cameraPreset.getRotation(this.insertDown, this.deleteDown) - 90); + this.quatHeap.setFromAxisRad(0, 0, 1, this.horizontalAngle); + this.distance = Math.max(1200, cameraPreset.distance); + this.verticalAngle = (float) Math.toRadians(Math.min(335, cameraPreset.aoa) - 270); + this.quatHeap2.setFromAxisRad(1, 0, 0, this.verticalAngle); + this.quatHeap.mul(this.quatHeap2); + + this.position.set(0, 0, 1); + this.quatHeap.transform(this.position); + this.position.nor(); + this.position.scl(this.distance); + this.position = this.position.add(this.target); + this.camera.perspective((float) Math.toRadians(cameraPreset.fov / 2), this.camera.getAspect(), + cameraPreset.nearZ, cameraPreset.farZ); + + this.camera.moveToAndFace(this.position, this.target, this.worldUp); + } + } + + public static final class PortraitCameraManager extends CameraManager { + public com.etheller.warsmash.viewer5.handlers.mdx.Camera modelCamera; + protected MdxComplexInstance modelInstance; + + @Override public void updateCamera() { this.quatHeap.idt(); this.quatHeap.setFromAxisRad(0, 0, 1, this.horizontalAngle); - if (this.insertDown && !this.deleteDown) { - this.horizontalAngle -= HORIZONTAL_ANGLE_INCREMENT; - if (this.horizontalAngle < HORIZONTAL_MINIMUM) { - this.horizontalAngle = HORIZONTAL_MINIMUM; - } - } - else if (this.deleteDown && !this.insertDown) { - this.horizontalAngle += HORIZONTAL_ANGLE_INCREMENT; - if (this.horizontalAngle > HORIZONTAL_MAXIMUM) { - this.horizontalAngle = HORIZONTAL_MAXIMUM; - } - } - else { - if (Math.abs(this.horizontalAngle) < HORIZONTAL_ANGLE_INCREMENT) { - this.horizontalAngle = 0; - } - else { - this.horizontalAngle -= HORIZONTAL_ANGLE_INCREMENT * Math.signum(this.horizontalAngle); - } - } - this.quatHeap2.idt(); - this.verticalAngle += this.verticalAngleAcceleration; - this.verticalAngleAcceleration *= 0.975f; - if (this.verticalAngle > ((Math.PI / 2) - Math.toRadians(17))) { - this.verticalAngle = (float) ((Math.PI / 2) - Math.toRadians(17)); - if (this.verticalAngleAcceleration > 0) { - this.verticalAngleAcceleration = 0; - } - } - if (this.verticalAngle < (float) Math.toRadians(34)) { - this.verticalAngle = (float) Math.toRadians(34); - if (this.verticalAngleAcceleration < 0) { - this.verticalAngleAcceleration = 0; - } - } this.quatHeap2.setFromAxisRad(1, 0, 0, this.verticalAngle); this.quatHeap.mul(this.quatHeap2); @@ -525,10 +548,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.modelCamera = portraitModel.getCameras().get(0); } } - -// private void cameraUpdate() { -// -// } } private final float cameraSpeed = 4096.0f; // per second @@ -697,12 +716,12 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public boolean scrolled(final int amount) { - this.cameraManager.verticalAngleAcceleration -= amount / 100.f; - if (this.cameraManager.verticalAngleAcceleration > (Math.PI / 128)) { - this.cameraManager.verticalAngleAcceleration = (float) (Math.PI / 128); + this.cameraManager.currentPreset -= amount; + if (this.cameraManager.currentPreset < 0) { + this.cameraManager.currentPreset = 0; } - if (this.cameraManager.verticalAngleAcceleration < (-Math.PI / 128)) { - this.cameraManager.verticalAngleAcceleration = -(float) (Math.PI / 128); + if (this.cameraManager.currentPreset >= this.cameraManager.presets.length) { + this.cameraManager.currentPreset = this.cameraManager.presets.length - 1; } return true; } @@ -716,4 +735,44 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.text = text; } } + + private static class CameraPreset { + private final float aoa; + private final float fov; + private final float rotation; + private final float rotationInsert; + private final float rotationDelete; + private final float distance; + private final float farZ; + private final float nearZ; + private final float height; + private final float listenerDistance; + private final float listenerAOA; + + public CameraPreset(final float aoa, final float fov, final float rotation, final float rotationInsert, + final float rotationDelete, final float distance, final float farZ, final float nearZ, + final float height, final float listenerDistance, final float listenerAOA) { + this.aoa = aoa; + this.fov = fov; + this.rotation = rotation; + this.rotationInsert = rotationInsert; + this.rotationDelete = rotationDelete; + this.distance = distance; + this.farZ = farZ; + this.nearZ = nearZ; + this.height = height; + this.listenerDistance = listenerDistance; + this.listenerAOA = listenerAOA; + } + + public float getRotation(final boolean insertDown, final boolean deleteDown) { + if (insertDown && !deleteDown) { + return this.rotationInsert; + } + if (!insertDown && deleteDown) { + return this.rotationDelete; + } + return this.rotation; + } + } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index 994aa5b..048720d 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -209,7 +209,6 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { final MdxModel model = (MdxModel) this.modelViewer.load(backgroundArt, this.modelViewer.mapPathSolver, this.modelViewer.solverParams); spriteFrame.setModel(model); - spriteFrame.setSequence(0); viewport2 = this.fdfCoordinateResolutionDummyViewport; inflatedFrame = spriteFrame; } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java index b783df6..28fb028 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java @@ -50,4 +50,22 @@ public class SpriteFrame extends AbstractRenderableFrame { } } + public void setAnimationSpeed(final float speedRatio) { + if (this.instance != null) { + this.instance.setAnimationSpeed(speedRatio); + } + } + + public void setFrame(final int animationFrame) { + if (this.instance != null) { + this.instance.setFrame(animationFrame); + } + } + + public void setFrameByRatio(final float ratioOfAnimationCompleted) { + if (this.instance != null) { + this.instance.setFrameByRatio(ratioOfAnimationCompleted); + } + } + } diff --git a/core/src/com/etheller/warsmash/units/GameObject.java b/core/src/com/etheller/warsmash/units/GameObject.java index e630a4a..bc5f5d0 100644 --- a/core/src/com/etheller/warsmash/units/GameObject.java +++ b/core/src/com/etheller/warsmash/units/GameObject.java @@ -20,6 +20,8 @@ public interface GameObject { public float getFieldFloatValue(String field); + public float getFieldFloatValue(String field, int index); + public List getFieldAsList(String field, ObjectData objectData); public String getId(); @@ -75,6 +77,11 @@ public interface GameObject { return 0; } + @Override + public float getFieldFloatValue(final String field, final int index) { + return 0; + } + @Override public List getFieldAsList(final String field, final ObjectData objectData) { return Collections.emptyList(); diff --git a/core/src/com/etheller/warsmash/units/HashedGameObject.java b/core/src/com/etheller/warsmash/units/HashedGameObject.java index a138344..326a500 100644 --- a/core/src/com/etheller/warsmash/units/HashedGameObject.java +++ b/core/src/com/etheller/warsmash/units/HashedGameObject.java @@ -79,6 +79,18 @@ public abstract class HashedGameObject implements GameObject { return i; } + @Override + public float getFieldFloatValue(final String field, final int index) { + float i = 0; + try { + i = Float.parseFloat(getField(field, index)); + } + catch (final NumberFormatException e) { + + } + return i; + } + @Override public void setField(final String field, final String value, final int index) { final StringKey key = new StringKey(field); diff --git a/core/src/com/etheller/warsmash/units/StandardObjectData.java b/core/src/com/etheller/warsmash/units/StandardObjectData.java index 34c71f7..913f982 100644 --- a/core/src/com/etheller/warsmash/units/StandardObjectData.java +++ b/core/src/com/etheller/warsmash/units/StandardObjectData.java @@ -504,6 +504,17 @@ public class StandardObjectData { return 0f; } + @Override + public float getFieldFloatValue(final String field, final int index) { + for (final DataTable table : this.dataSource.getTables()) { + final Element element = table.get(this.id); + if ((element != null) && element.hasField(field)) { + return element.getFieldFloatValue(field, index); + } + } + return 0f; + } + /* * (non-Javadoc) I'm not entirely sure this is still safe to use * diff --git a/core/src/com/etheller/warsmash/util/RenderMathUtils.java b/core/src/com/etheller/warsmash/util/RenderMathUtils.java index 6c554fd..5030190 100644 --- a/core/src/com/etheller/warsmash/util/RenderMathUtils.java +++ b/core/src/com/etheller/warsmash/util/RenderMathUtils.java @@ -8,10 +8,12 @@ import java.nio.IntBuffer; import java.nio.ShortBuffer; import java.util.List; +import com.badlogic.gdx.math.Intersector; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.math.collision.Ray; public enum RenderMathUtils { ; @@ -451,6 +453,128 @@ public enum RenderMathUtils { return out; } + static Vector3 best = new Vector3(); + static Vector3 tmp = new Vector3(); + static Vector3 tmp1 = new Vector3(); + static Vector3 tmp2 = new Vector3(); + static Vector3 tmp3 = new Vector3(); + + /** + * Intersects the given ray with list of triangles. Returns the nearest + * intersection point in intersection + * + * @param ray The ray + * @param vertices the vertices + * @param indices the indices, each successive 3 shorts index the 3 + * vertices of a triangle + * @param vertexSize the size of a vertex in floats + * @param intersection The nearest intersection point (optional) + * @return Whether the ray and the triangles intersect. + */ + public static boolean intersectRayTriangles(final Ray ray, final float[] vertices, final int[] indices, + final int vertexSize, final Vector3 intersection) { + float min_dist = Float.MAX_VALUE; + boolean hit = false; + + if ((indices.length % 3) != 0) { + throw new RuntimeException("triangle list size is not a multiple of 3"); + } + + for (int i = 0; i < indices.length; i += 3) { + final int i1 = indices[i] * vertexSize; + final int i2 = indices[i + 1] * vertexSize; + final int i3 = indices[i + 2] * vertexSize; + + final boolean result = Intersector.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); + + if (result == true) { + final float dist = ray.origin.dst2(tmp); + if (dist < min_dist) { + min_dist = dist; + best.set(tmp); + hit = true; + } + } + } + + if (hit == false) { + return false; + } + else { + if (intersection != null) { + intersection.set(best); + } + return true; + } + } + + /** + * Intersects the given ray with list of triangles. Returns the nearest + * intersection point in intersection + * + * @param ray The ray + * @param vertices the vertices + * @param indices the indices, each successive 3 shorts index the 3 + * vertices of a triangle + * @param vertexSize the size of a vertex in floats + * @param intersection The nearest intersection point (optional) + * @return Whether the ray and the triangles intersect. + */ + public static boolean intersectRayTriangles(final Ray ray, final float[] vertices, final int[] indices, + final int vertexSize, final Vector3 worldLocation, final float facingRadians, final Vector3 intersection) { + float min_dist = Float.MAX_VALUE; + boolean hit = false; + + if ((indices.length % 3) != 0) { + throw new RuntimeException("triangle list size is not a multiple of 3"); + } + + final float facingX_X = (float) Math.cos(facingRadians); + final float facingX_Y = (float) Math.sin(facingRadians); + final double halfPi = Math.PI / 2; + final float facingY_X = (float) Math.cos(facingRadians + halfPi); + final float facingY_Y = (float) Math.sin(facingRadians + halfPi); + for (int i = 0; i < indices.length; i += 3) { + final int i1 = indices[i] * vertexSize; + final int i2 = indices[i + 1] * vertexSize; + final int i3 = indices[i + 2] * vertexSize; + + final boolean result = Intersector.intersectRayTriangle(ray, + tmp1.set((vertices[i1] * facingX_X) + (vertices[i1 + 1] * facingY_X) + worldLocation.x, + (vertices[i1] * facingX_Y) + (vertices[i1 + 1] * facingY_Y) + worldLocation.y, + vertices[i1 + 2] + worldLocation.z), + tmp2.set((vertices[i2] * facingX_X) + (vertices[i2 + 1] * facingY_X) + worldLocation.x, + (vertices[i2] * facingX_Y) + (vertices[i2 + 1] * facingY_Y) + worldLocation.y, + vertices[i2 + 2] + worldLocation.z), + tmp3.set((vertices[i3] * facingX_X) + (vertices[i3 + 1] * facingY_X) + worldLocation.x, + (vertices[i3] * facingX_Y) + (vertices[i3 + 1] * facingY_Y) + worldLocation.y, + vertices[i3 + 2] + worldLocation.z), + tmp); + + if (result == true) { + final float dist = ray.origin.dst2(tmp); + if (dist < min_dist) { + min_dist = dist; + best.set(tmp); + hit = true; + } + } + } + + if (hit == false) { + return false; + } + else { + if (intersection != null) { + intersection.set(best); + } + return true; + } + } + // ==== All of the following "wrap" calls are horribly inefficient. Eventually // they should be removed entirely with better design. // Until that happens, be sure to only call them during setup and not while the diff --git a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java index 2c4f334..d9d527e 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java @@ -77,9 +77,13 @@ public abstract class ModelInstance extends Node { } } + this.updateLights(scene); + this.updateFrame = this.model.viewer.frame; } + protected abstract void updateLights(Scene scene2); + public boolean setScene(final Scene scene) { return scene.addInstance(this); } diff --git a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java index af93a7c..00ada9c 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java @@ -20,7 +20,7 @@ import com.etheller.warsmash.viewer5.gl.WebGL; import com.etheller.warsmash.viewer5.handlers.ResourceHandler; import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams; -public class ModelViewer { +public abstract class ModelViewer { public DataSource dataSource; public final CanvasProvider canvas; public List resources; @@ -366,4 +366,6 @@ public class ModelViewer { private void onResourceLoadError() { System.err.println("error, this, InvalidHandler, FailedToLoad"); } + + public abstract SceneLightManager createLightManager(); } diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java index 930e8d6..fda3f6f 100644 --- a/core/src/com/etheller/warsmash/viewer5/Scene.java +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -58,6 +58,7 @@ public abstract class Scene { * If true, alpha works as usual. */ public boolean alpha = false; + private final SceneLightManager lightManager; public Scene(final ModelViewer viewer) { final CanvasProvider canvas = viewer.canvas; @@ -85,6 +86,8 @@ public abstract class Scene { this.instanceDepthComparator = new InstanceDepthComparator(); this.visibleCells = 0; this.visibleInstances = 0; + + this.lightManager = this.viewer.createLightManager(); } public boolean enableAudio() { @@ -300,6 +303,14 @@ public abstract class Scene { } } + public void addLight(final SceneLightInstance lightInstance) { + this.lightManager.add(lightInstance); + } + + public void removeLight(final SceneLightInstance lightInstance) { + this.lightManager.remove(lightInstance); + } + private static final class InstanceDepthComparator implements Comparator { @Override public int compare(final ModelInstance o1, final ModelInstance o2) { diff --git a/core/src/com/etheller/warsmash/viewer5/SceneLightInstance.java b/core/src/com/etheller/warsmash/viewer5/SceneLightInstance.java new file mode 100644 index 0000000..8a94922 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/SceneLightInstance.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5; + +public interface SceneLightInstance { + +} diff --git a/core/src/com/etheller/warsmash/viewer5/SceneLightManager.java b/core/src/com/etheller/warsmash/viewer5/SceneLightManager.java new file mode 100644 index 0000000..407f083 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/SceneLightManager.java @@ -0,0 +1,7 @@ +package com.etheller.warsmash.viewer5; + +public interface SceneLightManager { + public void add(final SceneLightInstance lightInstance); + + public void remove(final SceneLightInstance lightInstance); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/CollisionShape.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/CollisionShape.java index d5db9d1..a6e1a6a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/CollisionShape.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/CollisionShape.java @@ -5,6 +5,8 @@ import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.math.collision.BoundingBox; import com.badlogic.gdx.math.collision.Ray; +import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.viewer5.GenericNode; public class CollisionShape extends GenericObject { private static Vector3 intersectHeap = new Vector3(); @@ -87,4 +89,22 @@ public class CollisionShape extends GenericObject { return Intersector.intersectRaySphere(ray, intersectHeap, this.radius, intersection); } } + + public static boolean intersectRayTriangles(final Ray ray, final GenericNode mdxNode, final float[] vertices, + final int[] indices, final int vertexSize, final Vector3 intersection) { + intersectMatrixHeap.set(mdxNode.worldMatrix); + Matrix4.inv(intersectMatrixHeap.val); + intersectHeap.set(ray.origin); + intersectHeap2.set(ray.direction); + intersectHeap2.add(ray.origin); + intersectHeap.prj(intersectMatrixHeap); + intersectHeap2.prj(intersectMatrixHeap); + intersectHeap2.sub(intersectHeap); + intersectRayHeap.set(intersectHeap, intersectHeap2); + if (RenderMathUtils.intersectRayTriangles(intersectRayHeap, vertices, indices, vertexSize, intersection)) { + intersection.prj(mdxNode.worldMatrix); + return true; + } + return false; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java index b3975f1..a52c46b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java @@ -24,10 +24,13 @@ public class Geoset { private final int openGLSkinType; private final int skinStride; private final int boneCountOffsetBytes; + public final boolean unselectable; + public final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset; public Geoset(final MdxModel model, final int index, final int positionOffset, final int normalOffset, final int uvOffset, final int skinOffset, final int faceOffset, final int vertices, final int elements, - final int openGLSkinType, final int skinStride, final int boneCountOffsetBytes) { + final int openGLSkinType, final int skinStride, final int boneCountOffsetBytes, final boolean unselectable, + final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset) { this.model = model; this.index = index; this.positionOffset = positionOffset; @@ -40,6 +43,8 @@ public class Geoset { this.openGLSkinType = openGLSkinType; this.skinStride = skinStride; this.boneCountOffsetBytes = boneCountOffsetBytes; + this.unselectable = unselectable; + this.mdlxGeoset = mdlxGeoset; for (final GeosetAnimation geosetAnimation : model.getGeosetAnimations()) { if (geosetAnimation.geosetId == index) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Light.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Light.java index c3a9179..d6bd2cd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Light.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Light.java @@ -4,7 +4,7 @@ import com.etheller.warsmash.parsers.mdlx.AnimationMap; public class Light extends GenericObject { - private final int type; + private final Type type; private final float[] attenuation; private final float[] color; private final float intensity; @@ -14,7 +14,18 @@ public class Light extends GenericObject { public Light(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Light light, final int index) { super(model, light, index); - this.type = light.getType(); + switch (light.getType()) { + case 0: + this.type = Type.OMNIDIRECTIONAL; + break; + case 2: + this.type = Type.DIRECTIONAL; + break; + default: + case 1: + this.type = Type.AMBIENT; + break; + } this.attenuation = light.getAttenuation(); this.color = light.getColor(); this.intensity = light.getIntensity(); @@ -22,6 +33,10 @@ public class Light extends GenericObject { this.ambientIntensity = light.getAmbientIntensity(); } + public Type getType() { + return this.type; + } + public int getAttenuationStart(final float[] out, final int sequence, final int frame, final int counter) { return this.getScalarValue(out, AnimationMap.KLAS.getWar3id(), sequence, frame, counter, this.attenuation[0]); } @@ -45,4 +60,15 @@ public class Light extends GenericObject { public int getAmbientColor(final float[] out, final int sequence, final int frame, final int counter) { return this.getVectorValue(out, AnimationMap.KLBC.getWar3id(), sequence, frame, counter, this.ambientColor); } + + public static enum Type { + // Omnidirectional light used for in-game sun + OMNIDIRECTIONAL, + // Directional light used for torches in the game world, and similar objects + // that "glow" + DIRECTIONAL, + // Directional ambient light used for torches in the game world, and similar + // objects that "glow" + AMBIENT; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/LightInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/LightInstance.java new file mode 100644 index 0000000..c7c5b59 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/LightInstance.java @@ -0,0 +1,78 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import java.nio.FloatBuffer; + +import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.SceneLightInstance; +import com.etheller.warsmash.viewer5.UpdatableObject; + +public class LightInstance implements UpdatableObject, SceneLightInstance { + private static final float[] vectorHeap = new float[3]; + private static final float[] scalarHeap = new float[1]; + protected final MdxNode node; + protected final Light light; + private boolean visible; + private boolean loadedInScene; + + public LightInstance(final MdxComplexInstance instance, final Light light) { + this.node = instance.nodes[light.index]; + this.light = light; + } + + public void bind(final int offset, final FloatBuffer floatBuffer, final int sequence, final int frame, + final int counter) { + this.light.getAttenuationStart(scalarHeap, sequence, frame, counter); + final float attenuationStart = scalarHeap[0]; + this.light.getAttenuationEnd(scalarHeap, sequence, frame, counter); + final float attenuationEnd = scalarHeap[0]; + this.light.getIntensity(scalarHeap, sequence, frame, counter); + final float intensity = scalarHeap[0]; + this.light.getColor(vectorHeap, sequence, frame, counter); + final float colorRed = vectorHeap[0]; + final float colorGreen = vectorHeap[1]; + final float colorBlue = vectorHeap[2]; + this.light.getAmbientIntensity(scalarHeap, sequence, frame, counter); + final float ambientIntensity = scalarHeap[0]; + this.light.getAmbientColor(vectorHeap, sequence, frame, counter); + final float ambientColorRed = vectorHeap[0]; + final float ambientColorGreen = vectorHeap[1]; + final float ambientColorBlue = vectorHeap[2]; + floatBuffer.put(offset, this.node.worldLocation.x); + floatBuffer.put(offset + 1, this.node.worldLocation.y); + floatBuffer.put(offset + 2, this.node.worldLocation.z); + // I use some padding to make the memory structure of the light be a 4x4 float + // grid, when somebody who actually has experience with this stuff comes along + // to change this to something smart, maybe they'll remove the padding if it's + // not necessary. I'm basing how I implement this on how Ghostwolf did + // BoneTexture + floatBuffer.put(offset + 3, 0); + floatBuffer.put(offset + 4, this.light.getType().ordinal()); + floatBuffer.put(offset + 5, attenuationStart); + floatBuffer.put(offset + 6, attenuationEnd); + floatBuffer.put(offset + 7, 0); + floatBuffer.put(offset + 8, colorRed); + floatBuffer.put(offset + 9, colorGreen); + floatBuffer.put(offset + 10, colorBlue); + floatBuffer.put(offset + 11, intensity); + floatBuffer.put(offset + 12, ambientColorRed); + floatBuffer.put(offset + 13, ambientColorGreen); + floatBuffer.put(offset + 14, ambientColorBlue); + floatBuffer.put(offset + 15, ambientIntensity); + } + + @Override + public void update(final float dt, final boolean visible) { + this.visible = visible; + } + + public void update(final Scene scene) { + if (this.loadedInScene != this.visible) { + if (this.visible) { + scene.addLight(this); + } + else { + scene.removeLight(this); + } + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index 16887a9..b7eb675 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -34,6 +34,7 @@ public class MdxComplexInstance extends ModelInstance { private static final float[] alphaHeap = new float[1]; private static final long[] textureIdHeap = new long[1]; + public List lights = new ArrayList<>(); public List attachments = new ArrayList<>(); public List particleEmitters = new ArrayList<>(); public List particleEmitters2 = new ArrayList<>(); @@ -106,7 +107,9 @@ public class MdxComplexInstance extends ModelInstance { } for (final Light light : model.lights) { - this.initNode(this.nodes, this.nodes[nodeIndex++], light); + final LightInstance lightInstance = new LightInstance(this, light); + this.lights.add(lightInstance); + this.initNode(this.nodes, this.nodes[nodeIndex++], light, lightInstance); } for (final Helper helper : model.helpers) { @@ -601,6 +604,13 @@ public class MdxComplexInstance extends ModelInstance { } + @Override + protected void updateLights(final Scene scene) { + for (final LightInstance light : this.lights) { + light.update(scene); + } + } + /** * Set the team color of this instance. */ @@ -715,10 +725,28 @@ public class MdxComplexInstance extends ModelInstance { * @param ray */ public boolean intersectRayWithCollision(final Ray ray, final Vector3 intersection) { - for (final CollisionShape collisionShape : ((MdxModel) this.model).collisionShapes) { - final MdxNode mdxNode = this.nodes[collisionShape.index]; - if (collisionShape.checkIntersect(ray, mdxNode, intersection)) { - return true; + final MdxModel mdxModel = (MdxModel) this.model; + final List collisionShapes = mdxModel.collisionShapes; + if (collisionShapes.isEmpty()) { + for (final Geoset geoset : mdxModel.geosets) { + if (!geoset.unselectable) { + geoset.getAlpha(alphaHeap, this.sequence, this.frame, this.counter); + if (alphaHeap[0] > 0) { + final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset = geoset.mdlxGeoset; + if (CollisionShape.intersectRayTriangles(ray, this, mdlxGeoset.getVertices(), + mdlxGeoset.getFaces(), 3, intersection)) { + return true; + } + } + } + } + } + else { + for (final CollisionShape collisionShape : collisionShapes) { + final MdxNode mdxNode = this.nodes[collisionShape.index]; + if (collisionShape.checkIntersect(ray, mdxNode, intersection)) { + return true; + } } } return false; @@ -727,4 +755,17 @@ public class MdxComplexInstance extends ModelInstance { public void setAnimationSpeed(final float speedRatio) { this.animationSpeed = speedRatio; } + + public void setFrame(final int frame) { + this.frame = frame; + this.floatingFrame = frame; + } + + public void setFrameByRatio(final float ratioOfAnimationCompleted) { + final Sequence currentlyPlayingSequence = ((MdxModel) this.model).sequences.get(this.sequence); + this.floatingFrame = currentlyPlayingSequence.getInterval()[0] + + ((currentlyPlayingSequence.getInterval()[1] - currentlyPlayingSequence.getInterval()[0]) + * ratioOfAnimationCompleted); + this.frame = (int) this.floatingFrame; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java index 50190e1..b176f1e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java @@ -6,6 +6,7 @@ import com.etheller.warsmash.viewer5.BatchedInstance; import com.etheller.warsmash.viewer5.Model; import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.RenderBatch; +import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.TextureMapper; @@ -32,6 +33,10 @@ public class MdxSimpleInstance extends BatchedInstance { public void renderTranslucent() { } + @Override + protected void updateLights(final Scene scene2) { + } + @Override public void load() { } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxViewer.java new file mode 100644 index 0000000..990205b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxViewer.java @@ -0,0 +1,20 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.viewer5.CanvasProvider; +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.SceneLightManager; + +public class MdxViewer extends ModelViewer { + + public MdxViewer(final DataSource dataSource, final CanvasProvider canvas) { + super(dataSource, canvas); + } + + @Override + public SceneLightManager createLightManager() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java index ad214d8..fe5b4d7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java @@ -155,9 +155,10 @@ public class SetupGeosets { } } + final boolean unselectable = geoset.getSelectionFlags() == 4; final Geoset vGeoset = new Geoset(model, model.getGeosets().size(), positionOffset, normalOffset, uvOffset, skinOffset, faceOffset, vertices, faces.length, openGLSkinType, skinStride, - boneCountOffsetBytes); + boneCountOffsetBytes, unselectable, geoset); model.getGeosets().add(vGeoset); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java index 6dcc083..b819645 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java @@ -67,6 +67,7 @@ public class SequenceUtils { filtered.sort(STAND_SEQUENCE_COMPARATOR); int i = 0; + final double randomRoll = Math.random() * 100; for (final int l = filtered.size(); i < l; i++) { final Sequence sequence = filtered.get(i).sequence; final float rarity = sequence.getRarity(); @@ -75,7 +76,7 @@ public class SequenceUtils { break; } - if ((Math.random() * 10) > rarity) { + if (randomRoll < (10 - rarity)) { return filtered.get(i); } } @@ -98,6 +99,7 @@ public class SequenceUtils { filtered.sort(STAND_SEQUENCE_COMPARATOR); int i = 0; + final double randomRoll = Math.random() * 100; for (final int l = filtered.size(); i < l; i++) { final Sequence sequence = filtered.get(i).sequence; final float rarity = sequence.getRarity(); @@ -106,7 +108,7 @@ public class SequenceUtils { break; } - if (((Math.random() * 10) > rarity) && allowRarityVariations) { + if ((randomRoll < (10 - rarity)) && allowRarityVariations) { return filtered.get(i); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLight.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLight.java new file mode 100644 index 0000000..1bc01c4 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLight.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +public class W3xSceneLight { + + public static enum Type { + OMNIDIRECTIONAL, + DIRECTIONAL; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLightManager.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLightManager.java new file mode 100644 index 0000000..a46c9a3 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLightManager.java @@ -0,0 +1,35 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.viewer5.SceneLightInstance; +import com.etheller.warsmash.viewer5.SceneLightManager; +import com.etheller.warsmash.viewer5.gl.DataTexture; +import com.etheller.warsmash.viewer5.handlers.mdx.LightInstance; + +public class W3xSceneLightManager implements SceneLightManager { + public final List lights; + private final DataTexture unitLightsTexture; + private final DataTexture terrainLightsTexture; + + public W3xSceneLightManager(final War3MapViewer viewer) { + this.lights = new ArrayList<>(); + this.unitLightsTexture = new DataTexture(viewer.gl, 4, 4, 1); + this.terrainLightsTexture = new DataTexture(viewer.gl, 4, 4, 1); + } + + @Override + public void add(final SceneLightInstance lightInstance) { + // TODO redesign to avoid cast + final LightInstance mdxLight = (LightInstance) lightInstance; + this.lights.add(mdxLight); + } + + @Override + public void remove(final SceneLightInstance lightInstance) { + // TODO redesign to avoid cast + final LightInstance mdxLight = (LightInstance) lightInstance; + this.lights.remove(mdxLight); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 30f82e5..a49b942 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -42,10 +42,12 @@ import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i; import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; +import com.etheller.warsmash.units.StandardObjectData; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; import com.etheller.warsmash.util.MappedData; +import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.util.WorldEditStrings; @@ -56,6 +58,7 @@ import com.etheller.warsmash.viewer5.ModelInstance; import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.SceneLightManager; import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.WorldScene; import com.etheller.warsmash.viewer5.gl.WebGL; @@ -118,7 +121,6 @@ public class War3MapViewer extends ModelViewer { public SolverParams solverParams = new SolverParams(); public WorldScene worldScene; public boolean anyReady; - public boolean terrainCliffsAndWaterLoaded; public MappedData terrainData = new MappedData(); public MappedData cliffTypesData = new MappedData(); public MappedData waterData = new MappedData(); @@ -151,9 +153,11 @@ public class War3MapViewer extends ModelViewer { public List selected = new ArrayList<>(); private DataTable unitAckSoundsTable; private DataTable unitCombatSoundsTable; - private DataTable miscData; + public DataTable miscData; private DataTable unitGlobalStrings; private MdxComplexInstance confirmationInstance; + private MdxComplexInstance dncUnit; + private MdxComplexInstance dncTerrain; public CSimulation simulation; private float updateTime; @@ -195,7 +199,6 @@ public class War3MapViewer extends ModelViewer { stringDataCallback); // == when loaded, which is always in our system == - this.terrainCliffsAndWaterLoaded = true; this.terrainData.load(terrain.data.toString()); this.cliffTypesData.load(cliffTypes.data.toString()); this.waterData.load(water.data.toString()); @@ -254,6 +257,9 @@ public class War3MapViewer extends ModelViewer { try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscGame.txt")) { this.miscData.readTXT(miscDataTxtStream, true); } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } this.unitGlobalStrings = new DataTable(worldEditStrings); try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) { this.unitGlobalStrings.readTXT(miscDataTxtStream, true); @@ -342,8 +348,11 @@ public class War3MapViewer extends ModelViewer { final War3MapWpm terrainPathing = this.mapMpq.readPathing(); + final StandardObjectData standardObjectData = new StandardObjectData(this.dataSource); + this.worldEditData = standardObjectData.getWorldEditData(); + this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, worldEditStrings, - this); + this, this.worldEditData); final float[] centerOffset = terrainData.getCenterOffset(); final int[] mapSize = terrainData.getMapSize(); @@ -363,13 +372,6 @@ public class War3MapViewer extends ModelViewer { this.confirmationInstance.setSequence(0); this.confirmationInstance.setScene(this.worldScene); - if (this.terrainCliffsAndWaterLoaded) { - this.loadTerrainCliffsAndWater(terrainData); - } - else { - throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); - } - final Warcraft3MapObjectData modifications = this.mapMpq.readModifications(); this.simulation = new CSimulation(this.miscData, modifications.getUnits(), modifications.getAbilities(), new SimulationRenderController() { @@ -388,12 +390,7 @@ public class War3MapViewer extends ModelViewer { final float projectileLaunchY = simulation.getUnitData().getProjectileLaunchY(typeId); final float projectileLaunchZ = simulation.getUnitData().getProjectileLaunchZ(typeId); - if (missileArt.toLowerCase().endsWith(".mdl")) { - missileArt = missileArt.substring(0, missileArt.length() - 4); - } - if (!missileArt.toLowerCase().endsWith(".mdx")) { - missileArt += ".mdx"; - } + missileArt = mdx(missileArt); final float facing = launchFacing; final float sinFacing = (float) Math.sin(facing); final float cosFacing = (float) Math.cos(facing); @@ -435,12 +432,7 @@ public class War3MapViewer extends ModelViewer { .getProjectileLaunchX(typeId); final float projectileLaunchY = War3MapViewer.this.simulation.getUnitData() .getProjectileLaunchY(typeId); - if (missileArt.toLowerCase().endsWith(".mdl")) { - missileArt = missileArt.substring(0, missileArt.length() - 4); - } - if (!missileArt.toLowerCase().endsWith(".mdx")) { - missileArt += ".mdx"; - } + missileArt = mdx(missileArt); final float facing = (float) Math.toRadians(source.getFacing()); final float sinFacing = (float) Math.sin(facing); final float cosFacing = (float) Math.cos(facing); @@ -507,9 +499,17 @@ public class War3MapViewer extends ModelViewer { this.terrain.initShadows(); this.terrain.createWaves(); + + loadLightsAndShading(tileset); } - private void loadTerrainCliffsAndWater(final War3MapW3e w3e) { + private void loadLightsAndShading(final char tileset) { + // TODO this should be set by the war3map.j actually, not by the tileset, so the + // call to set day night models is just for testing to make the test look pretty + final Element defaultTerrainLights = this.worldEditData.get("TerrainLights"); + final Element defaultUnitLights = this.worldEditData.get("UnitLights"); + setDayNightModels(defaultTerrainLights.getField(Character.toString(tileset)), + defaultUnitLights.getField(Character.toString(tileset))); } @@ -902,6 +902,10 @@ public class War3MapViewer extends ModelViewer { this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME; this.simulation.update(); } + this.dncTerrain.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncUnit.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); } } @@ -1007,7 +1011,7 @@ public class War3MapViewer extends ModelViewer { 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 - Terrain.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices, + RenderMathUtils.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices, this.terrain.softwareGroundMesh.indices, 3, out); } @@ -1144,6 +1148,7 @@ public class War3MapViewer extends ModelViewer { private static final int MAXIMUM_ACCEPTED = 1 << 30; private float selectionCircleScaleFactor; + private DataTable worldEditData; /** * Returns a power of two size for the given target capacity. @@ -1240,4 +1245,30 @@ public class War3MapViewer extends ModelViewer { this.textureDotted = textureDotted; } } + + public void setDayNightModels(final String terrainDNCFile, final String unitDNCFile) { + final MdxModel terrainDNCModel = (MdxModel) load(mdx(terrainDNCFile), PathSolver.DEFAULT, null); + this.dncTerrain = (MdxComplexInstance) terrainDNCModel.addInstance(); + this.dncTerrain.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncTerrain.setSequence(0); + final MdxModel unitDNCModel = (MdxModel) load(mdx(unitDNCFile), PathSolver.DEFAULT, null); + this.dncUnit = (MdxComplexInstance) unitDNCModel.addInstance(); + this.dncUnit.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncUnit.setSequence(0); + } + + private static String mdx(String mdxPath) { + if (mdxPath.toLowerCase().endsWith(".mdl")) { + mdxPath = mdxPath.substring(0, mdxPath.length() - 4); + } + if (!mdxPath.toLowerCase().endsWith(".mdx")) { + mdxPath += ".mdx"; + } + return mdxPath; + } + + @Override + public SceneLightManager createLightManager() { + return new W3xSceneLightManager(this); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 4799364..ca66d73 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -26,7 +26,6 @@ import com.badlogic.gdx.math.Intersector; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector3; -import com.badlogic.gdx.math.collision.Ray; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.parsers.w3x.w3e.Corner; import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e; @@ -34,7 +33,6 @@ import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i; import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; -import com.etheller.warsmash.units.StandardObjectData; import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; @@ -127,7 +125,7 @@ public class Terrain { public Terrain(final War3MapW3e w3eFile, final War3MapWpm terrainPathing, final War3MapW3i w3iFile, final WebGL webGL, final DataSource dataSource, final WorldEditStrings worldEditStrings, - final War3MapViewer viewer) throws IOException { + final War3MapViewer viewer, final DataTable worldEditData) throws IOException { this.webGL = webGL; this.viewer = viewer; this.camera = viewer.worldScene.camera; @@ -221,8 +219,6 @@ public class Terrain { this.groundTextureToId.put(groundTile.asStringValue(), this.groundTextures.size() - 1); } - final StandardObjectData standardObjectData = new StandardObjectData(dataSource); - final DataTable worldEditData = standardObjectData.getWorldEditData(); final Element tilesets = worldEditData.get("TileSets"); this.blightTextureIndex = this.groundTextures.size(); @@ -1179,67 +1175,10 @@ public class Terrain { // return out; // } - static Vector3 best = new Vector3(); - static Vector3 tmp = new Vector3(); - static Vector3 tmp1 = new Vector3(); - static Vector3 tmp2 = new Vector3(); - static Vector3 tmp3 = new Vector3(); private final WaveBuilder waveBuilder; public PathingGrid pathingGrid; private final Rectangle shaderMapBoundsRectangle; - /** - * Intersects the given ray with list of triangles. Returns the nearest - * intersection point in intersection - * - * @param ray The ray - * @param vertices the vertices - * @param indices the indices, each successive 3 shorts index the 3 - * vertices of a triangle - * @param vertexSize the size of a vertex in floats - * @param intersection The nearest intersection point (optional) - * @return Whether the ray and the triangles intersect. - */ - public static boolean intersectRayTriangles(final Ray ray, final float[] vertices, final int[] indices, - final int vertexSize, final Vector3 intersection) { - float min_dist = Float.MAX_VALUE; - boolean hit = false; - - if ((indices.length % 3) != 0) { - throw new RuntimeException("triangle list size is not a multiple of 3"); - } - - for (int i = 0; i < indices.length; i += 3) { - final int i1 = indices[i] * vertexSize; - final int i2 = indices[i + 1] * vertexSize; - final int i3 = indices[i + 2] * vertexSize; - - final boolean result = Intersector.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); - - if (result == true) { - final float dist = ray.origin.dst2(tmp); - if (dist < min_dist) { - min_dist = dist; - best.set(tmp); - hit = true; - } - } - } - - if (hit == false) { - return false; - } - else { - if (intersection != null) { - intersection.set(best); - } - return true; - } - } - private static final class UnloadedTexture { private final int width; private final int height; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java index 12a1a9a..9960c94 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java @@ -19,6 +19,10 @@ public class CGameplayConstants { private final float boneDecayTime; private final float bulletDeathTime; private final float closeEnoughRange; + private final float dawnTimeGameHours; + private final float duskTimeGameHours; + private final float gameDayHours; + private final float gameDayLength; public CGameplayConstants(final DataTable parsedDataTable) { final Element miscData = parsedDataTable.get("Misc"); @@ -30,6 +34,11 @@ public class CGameplayConstants { this.bulletDeathTime = miscData.getFieldFloatValue("BulletDeathTime"); this.closeEnoughRange = miscData.getFieldFloatValue("CloseEnoughRange"); + this.dawnTimeGameHours = miscData.getFieldFloatValue("Dawn"); + this.duskTimeGameHours = miscData.getFieldFloatValue("Dusk"); + this.gameDayHours = miscData.getFieldFloatValue("DayHours"); + this.gameDayLength = miscData.getFieldFloatValue("DayLength"); + final CDefenseType[] defenseTypeOrder = { CDefenseType.SMALL, CDefenseType.MEDIUM, CDefenseType.LARGE, CDefenseType.FORT, CDefenseType.NORMAL, CDefenseType.HERO, CDefenseType.DIVINE, CDefenseType.NONE, }; this.damageBonusTable = new float[CAttackType.values().length][defenseTypeOrder.length]; @@ -80,4 +89,20 @@ public class CGameplayConstants { public float getCloseEnoughRange() { return this.closeEnoughRange; } + + public float getGameDayHours() { + return this.gameDayHours; + } + + public float getGameDayLength() { + return this.gameDayLength; + } + + public float getDawnTimeGameHours() { + return this.dawnTimeGameHours; + } + + public float getDuskTimeGameHours() { + return this.duskTimeGameHours; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 75c81e7..913b0cd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -40,6 +40,7 @@ public class CSimulation { private final CPathfindingProcessor pathfindingProcessor; private final CGameplayConstants gameplayConstants; private final Random seededRandom; + private float currentGameDayTimeElapsed; public CSimulation(final DataTable miscData, final MutableObjectData parsedUnitData, final MutableObjectData parsedAbilityData, final SimulationRenderController simulationRenderController, @@ -145,6 +146,13 @@ public class CSimulation { this.projectiles.addAll(this.newProjectiles); this.newProjectiles.clear(); this.gameTurnTick++; + this.currentGameDayTimeElapsed = (this.currentGameDayTimeElapsed + WarsmashConstants.SIMULATION_STEP_TIME) + % this.gameplayConstants.getGameDayLength(); + } + + public float getGameTimeOfDay() { + return (this.currentGameDayTimeElapsed / this.gameplayConstants.getGameDayLength()) + * this.gameplayConstants.getGameDayHours(); } public int getGameTurnTick() { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index f89f4cf..7b6128b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -132,6 +132,26 @@ public class CUnitData { final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); final int defense = unitType.getFieldAsInteger(DEFENSE, 0); + final CUnitType unitTypeInstance = getUnitTypeInstance(typeId, buildingPathingPixelMap, unitType); + + final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, + speed, defense, unitTypeInstance); + if (speed > 0) { + unit.add(simulation, CAbilityMove.INSTANCE); + unit.add(simulation, CAbilityPatrol.INSTANCE); + unit.add(simulation, CAbilityHoldPosition.INSTANCE); + unit.add(simulation, CAbilityStop.INSTANCE); + } + final int dmgDice1 = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); + final int dmgDice2 = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); + if ((dmgDice1 != 0) || (dmgDice2 != 0)) { + unit.add(simulation, CAbilityAttack.INSTANCE); + } + return unit; + } + + private CUnitType getUnitTypeInstance(final War3ID typeId, final BufferedImage buildingPathingPixelMap, + final MutableGameObject unitType) { CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId); if (unitTypeInstance == null) { final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); @@ -252,21 +272,7 @@ public class CUnitData { targetedAs); this.unitIdToUnitType.put(typeId, unitTypeInstance); } - - final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, - speed, defense, unitTypeInstance); - if (speed > 0) { - unit.add(simulation, CAbilityMove.INSTANCE); - unit.add(simulation, CAbilityPatrol.INSTANCE); - unit.add(simulation, CAbilityHoldPosition.INSTANCE); - unit.add(simulation, CAbilityStop.INSTANCE); - } - final int dmgDice1 = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); - final int dmgDice2 = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); - if ((dmgDice1 != 0) || (dmgDice2 != 0)) { - unit.add(simulation, CAbilityAttack.INSTANCE); - } - return unit; + return unitTypeInstance; } private CUnitAttack createAttack(final float animationBackswingPoint, final float animationDamagePoint, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index e41a302..a21a4af 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -18,6 +18,7 @@ import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; import com.etheller.warsmash.parsers.fdf.frames.SetPoint; +import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; import com.etheller.warsmash.parsers.fdf.frames.StringFrame; import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; @@ -55,7 +56,7 @@ public class MeleeUI implements CUnitStateListener { private StringFrame resourceBarLumberText; private StringFrame resourceBarSupplyText; private StringFrame resourceBarUpkeepText; - private UIFrame timeIndicator; + private SpriteFrame timeIndicator; private UIFrame unitPortrait; private StringFrame unitLifeText; private StringFrame unitManaText; @@ -107,7 +108,7 @@ public class MeleeUI implements CUnitStateListener { // ================================= // Load skins and templates // ================================= - this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 3), this.uiViewport, + this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 0), this.uiViewport, this.fontGenerator, this.uiScene, this.war3MapViewer); try { this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); @@ -148,7 +149,9 @@ public class MeleeUI implements CUnitStateListener { this.resourceBarUpkeepText.setColor(Color.RED); // Create the Time Indicator (clock) - this.timeIndicator = this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0); + this.timeIndicator = (SpriteFrame) this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0); + this.timeIndicator.setSequence(0); // play the stand + this.timeIndicator.setAnimationSpeed(0.0f); // do not advance automatically // Create the unit portrait stuff this.portrait = new Portrait(this.war3MapViewer); @@ -221,6 +224,9 @@ public class MeleeUI implements CUnitStateListener { } } } + + this.timeIndicator.setFrameByRatio(this.war3MapViewer.simulation.getGameTimeOfDay() + / this.war3MapViewer.simulation.getGameplayConstants().getGameDayHours()); } public void portraitTalk() { @@ -229,12 +235,12 @@ public class MeleeUI implements CUnitStateListener { private static final class Portrait { private MdxComplexInstance modelInstance; - private final WarsmashGdxMapGame.CameraManager portraitCameraManager; + private final WarsmashGdxMapGame.PortraitCameraManager portraitCameraManager; private final Scene portraitScene; public Portrait(final War3MapViewer war3MapViewer) { this.portraitScene = war3MapViewer.addSimpleScene(); - this.portraitCameraManager = new WarsmashGdxMapGame.CameraManager(); + this.portraitCameraManager = new WarsmashGdxMapGame.PortraitCameraManager(); this.portraitCameraManager.setupCamera(this.portraitScene); this.portraitScene.camera.viewport(new Rectangle(100, 0, 6400, 48)); } @@ -303,6 +309,11 @@ public class MeleeUI implements CUnitStateListener { this.simpleNameValue.setText(unit.getSimulationUnit().getUnitType().getName()); String classText = null; for (final CUnitClassification classification : unit.getSimulationUnit().getClassifications()) { + if ((classification == CUnitClassification.MECHANICAL) + && unit.getSimulationUnit().getUnitType().isBuilding()) { + // buildings dont display MECHANICAL + continue; + } if (classification.getDisplayName() != null) { classText = classification.getDisplayName(); } @@ -325,7 +336,11 @@ public class MeleeUI implements CUnitStateListener { } this.simpleBuildingActionLabel.setText(""); - if (unit.getSimulationUnit().getUnitType().getAttacks().size() > 0) { + final boolean anyAttacks = unit.getSimulationUnit().getUnitType().getAttacks().size() > 0; + final UIFrame localArmorIcon; + final TextureFrame localArmorIconBackdrop; + final StringFrame localArmorInfoPanelIconValue; + if (anyAttacks) { final CUnitAttack attackOne = unit.getSimulationUnit().getUnitType().getAttacks().get(0); this.attack1Icon.setVisible(attackOne.isShowUI()); this.attack1IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackOne.getAttackType())); @@ -339,21 +354,29 @@ public class MeleeUI implements CUnitStateListener { else { this.attack2Icon.setVisible(false); } + + localArmorIcon = this.armorIcon; + localArmorIconBackdrop = this.armorIconBackdrop; + localArmorInfoPanelIconValue = this.armorInfoPanelIconValue; } else { - this.attack1Icon.setVisible(false); + this.armorIcon.setVisible(false); this.attack2Icon.setVisible(false); + + localArmorIcon = this.attack1Icon; + localArmorIconBackdrop = this.attack1IconBackdrop; + localArmorInfoPanelIconValue = this.attack1InfoPanelIconValue; } - this.armorIcon.setVisible(true); + localArmorIcon.setVisible(true); final Texture defenseTexture = this.defenseBackdrops .getTexture(unit.getSimulationUnit().getUnitType().getDefenseType()); if (defenseTexture == null) { throw new RuntimeException( unit.getSimulationUnit().getUnitType().getDefenseType() + " can't find texture!"); } - this.armorIconBackdrop.setTexture(defenseTexture); - this.armorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense())); + localArmorIconBackdrop.setTexture(defenseTexture); + localArmorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense())); } }