diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 7944cc3..020d607 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -56,7 +56,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListene public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { private static final boolean ENABLE_AUDIO = true; - private static final boolean ENABLE_MUSIC = true; + private static final boolean ENABLE_MUSIC = false; private DataSource codebase; private War3MapViewer viewer; private final Rectangle tempRect = new Rectangle(); diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index 24edd47..fd56287 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -276,7 +276,9 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { case Frame: if ("SIMPLEFRAME".equals(frameDefinition.getFrameType())) { final SimpleFrame simpleFrame = new SimpleFrame(frameDefinition.getName(), parent); - // TODO: we should not need to put ourselves in this map 2x + // TODO: we should not need to put ourselves in this map 2x, but we do + // since there are nested inflate calls happening before the general case + // mapping this.nameToFrame.put(frameDefinition.getName(), simpleFrame); for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { simpleFrame.add(inflate(childDefinition, simpleFrame, frameDefinition)); @@ -315,7 +317,13 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } break; case Layer: - // NOT HANDLED YET + final SimpleFrame simpleFrame = new SimpleFrame(frameDefinition.getName(), parent); + simpleFrame.setSetAllPoints(true); + this.nameToFrame.put(frameDefinition.getName(), simpleFrame); + for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { + simpleFrame.add(inflate(childDefinition, simpleFrame, frameDefinition)); + } + inflatedFrame = simpleFrame; break; case String: final Float textLength = frameDefinition.getFloat("TextLength"); diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java index f6d0894..ace3f36 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java @@ -307,8 +307,8 @@ public abstract class AbstractRenderableFrame implements UIFrame { } } if (DEBUG_LOG) { - System.out.println( - getClass().getSimpleName() + ":" + this.name + " finishing position bounds: " + this.renderBounds); + System.out.println(getClass().getSimpleName() + ":" + this.name + ":" + hashCode() + + " finishing position bounds: " + this.renderBounds); } innerPositionBounds(viewport); } diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java index 24a43ec..c7cd7a4 100644 --- a/core/src/com/etheller/warsmash/util/WarsmashConstants.java +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -5,4 +5,6 @@ public class WarsmashConstants { public static final int REPLACEABLE_TEXTURE_LIMIT = 64; public static final float SIMULATION_STEP_TIME = 1 / 20f; public static final int PORT_NUMBER = 6115; + public static final float BUILDING_CONSTRUCT_START_LIFE = 0.1f; + public static final int BUILD_QUEUE_SIZE = 7; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java index fa37dfb..eb93ebe 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java @@ -225,9 +225,19 @@ public class SplatModel { } - public void add(float x, float y, float z, float scale, float[] centerOffset) { - locations.add(new float[]{x - scale, y - scale, x + scale, y + scale, z}); + public SplatMover add(final float x, final float y, final float w, final float h, final float zDepthUpward, + final float[] centerOffset) { + this.locations.add(new float[] { x, y, w, h, zDepthUpward }); + final SplatMover splatMover; + if (this.splatInstances != null) { + splatMover = new SplatMover(this); + this.splatInstances.add(splatMover); + } + else { + splatMover = null; + } compact(Gdx.gl30, centerOffset); + return splatMover; } private static final class Batch { @@ -413,5 +423,12 @@ public class SplatModel { gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, this.uvsOffset + ((this.startOffset / 3) * 2), 4 * 2 * this.uvs.size(), RenderMathUtils.wrap(this.uvs)); } + + public void show(final float[] centerOffset) { + // It tries to only update if it is located at a new position... but here we are + // forcing it visible again by putting the position outside the map + this.ix0 = this.ix1 = this.iy0 = this.iy1 = Integer.MIN_VALUE; + move(0, 0, centerOffset); + } } } 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 6b983e0..e773622 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -72,6 +72,8 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; import com.etheller.warsmash.viewer5.handlers.tga.TgaFile; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.BuildingShadow; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.RemovablePathingMapInstance; 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; @@ -97,6 +99,7 @@ import mpq.MPQException; public class War3MapViewer extends ModelViewer { private static final War3ID UNIT_FILE = War3ID.fromString("umdl"); + private static final War3ID UNIT_SPECIAL = War3ID.fromString("uspa"); private static final War3ID UBER_SPLAT = War3ID.fromString("uubs"); private static final War3ID UNIT_SHADOW = War3ID.fromString("ushu"); private static final War3ID UNIT_SHADOW_X = War3ID.fromString("ushx"); @@ -553,6 +556,35 @@ public class War3MapViewer extends ModelViewer { return createNewUnit(War3MapViewer.this.allObjectData, typeId, x, y, 0f, playerIndex, (float) Math.toRadians(facing)); } + + @Override + public void spawnBuildingDeathEffect(final CUnit source) { + final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.get(source); + if (renderUnit.specialArtModel != null) { + final MdxComplexInstance modelInstance = (MdxComplexInstance) renderUnit.specialArtModel + .addInstance(); + modelInstance.setTeamColor(source.getPlayerIndex()); + modelInstance.setLocation(renderUnit.location); + modelInstance.setScene(War3MapViewer.this.worldScene); + SequenceUtils.randomBirthSequence(modelInstance); + War3MapViewer.this.projectiles + .add(new RenderAttackInstant(modelInstance, War3MapViewer.this, + (float) Math.toRadians(renderUnit.getSimulationUnit().getFacing()))); + } + } + + @Override + public void spawnUnitReadySound(final CUnit trainedUnit) { + final RenderUnit renderPeer = War3MapViewer.this.unitToRenderPeer.get(trainedUnit); + renderPeer.soundset.ready.playUnitResponse(War3MapViewer.this.worldScene.audioContext, + renderPeer); + } + + @Override + public void unitRepositioned(final CUnit cUnit) { + final RenderUnit renderPeer = War3MapViewer.this.unitToRenderPeer.get(cUnit); + renderPeer.repositioned(War3MapViewer.this); + } }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, w3iFile.getPlayers()); this.walkableObjectsTree = new Quadtree<>(this.terrain.getEntireMap()); @@ -664,7 +696,7 @@ public class War3MapViewer extends ModelViewer { } } if (bufferedImage != null) { - this.terrain.pathingGrid.blitPathingOverlayTexture(doodad.getLocation()[0], + this.terrain.pathingGrid.blitRemovablePathingOverlayTexture(doodad.getLocation()[0], doodad.getLocation()[1], (int) Math.toDegrees(doodad.getAngle()), bufferedImage); } } @@ -814,8 +846,13 @@ public class War3MapViewer extends ModelViewer { MutableGameObject row = null; String path = null; Splat unitShadowSplat = null; + SplatMover unitShadowSplatDynamicIngame = null; + Splat buildingUberSplat = null; + SplatMover buildingUberSplatDynamicIngame = null; BufferedImage buildingPathingPixelMap = null; final float unitVertexScale = 1.0f; + RemovablePathingMapInstance pathingInstance = null; + BuildingShadow buildingShadowInstance = null; // Hardcoded? WorldEditorDataType type = null; @@ -871,8 +908,8 @@ public class War3MapViewer extends ModelViewer { if (buildingPathingPixelMap != null) { unitX = Math.round(unitX / 64f) * 64f; unitY = Math.round(unitY / 64f) * 64f; - this.terrain.pathingGrid.blitPathingOverlayTexture(unitX, unitY, (int) Math.toDegrees(unitAngle), - buildingPathingPixelMap); + pathingInstance = this.terrain.pathingGrid.blitRemovablePathingOverlayTexture(unitX, unitY, + (int) Math.toDegrees(unitAngle), buildingPathingPixelMap); } final String uberSplat = row.getFieldAsString(UBER_SPLAT, 0); @@ -883,7 +920,7 @@ public class War3MapViewer extends ModelViewer { + ".blp"; final float s = uberSplatInfo.getFieldFloatValue("Scale"); if (this.unitsReady) { - this.terrain.addUberSplat(texturePath, unitX, unitY, 1, s); + buildingUberSplatDynamicIngame = this.terrain.addUberSplat(texturePath, unitX, unitY, 1, s); } else { if (!this.terrain.splats.containsKey(texturePath)) { @@ -891,8 +928,8 @@ public class War3MapViewer extends ModelViewer { } final float x = unitX; final float y = unitY; - this.terrain.splats.get(texturePath).locations - .add(new float[] { x - s, y - s, x + s, y + s, 1 }); + buildingUberSplat = this.terrain.splats.get(texturePath); + buildingUberSplat.locations.add(new float[] { x - s, y - s, x + s, y + s, 1 }); } } } @@ -905,22 +942,28 @@ public class War3MapViewer extends ModelViewer { final float shadowWidth = row.getFieldAsFloat(UNIT_SHADOW_W, 0); final float shadowHeight = row.getFieldAsFloat(UNIT_SHADOW_H, 0); if (this.mapMpq.has(texture)) { - if (!this.terrain.splats.containsKey(texture)) { - final Splat splat = new Splat(); - splat.opacity = 0.5f; - this.terrain.splats.put(texture, splat); - } final float x = unitX - shadowX; final float y = unitY - shadowY; - this.terrain.splats.get(texture).locations - .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); - unitShadowSplat = this.terrain.splats.get(texture); + if (this.unitsReady) { + unitShadowSplatDynamicIngame = this.terrain.addUnitShadowSplat(texture, x, y, + x + shadowWidth, y + shadowHeight, 3, 0.5f); + } + else { + if (!this.terrain.splats.containsKey(texture)) { + final Splat splat = new Splat(); + splat.opacity = 0.5f; + this.terrain.splats.put(texture, splat); + } + this.terrain.splats.get(texture).locations + .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); + unitShadowSplat = this.terrain.splats.get(texture); + } } } final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); if ((buildingShadow != null) && !"_".equals(buildingShadow)) { - this.terrain.addShadow(buildingShadow, unitX, unitY); + buildingShadowInstance = this.terrain.addShadow(buildingShadow, unitX, unitY); } final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); @@ -935,6 +978,21 @@ public class War3MapViewer extends ModelViewer { } if (path != null) { + final String unitSpecialArtPath = row.getFieldAsString(UNIT_SPECIAL, 0); + MdxModel specialArtModel; + if (unitSpecialArtPath != null) { + try { + specialArtModel = (MdxModel) this.load(mdx(unitSpecialArtPath), this.mapPathSolver, + this.solverParams); + } + catch (final Exception exc) { + exc.printStackTrace(); + specialArtModel = null; + } + } + else { + specialArtModel = null; + } final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); MdxModel portraitModel; final String portraitPath = path.substring(0, path.length() - 4) + "_portrait.mdx"; @@ -947,10 +1005,10 @@ public class War3MapViewer extends ModelViewer { if (type == WorldEditorDataType.UNITS) { final float angle = (float) Math.toDegrees(unitAngle); final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), playerIndex, unitX, unitY, - angle, buildingPathingPixelMap); + angle, buildingPathingPixelMap, pathingInstance, buildingShadowInstance); final RenderUnitTypeData typeData = getUnitTypeData(unitId, row); final RenderUnit renderUnit = new RenderUnit(this, model, row, unitX, unitY, unitZ, playerIndex, - soundset, portraitModel, simulationUnit, typeData); + soundset, portraitModel, simulationUnit, typeData, specialArtModel); this.unitToRenderPeer.put(simulationUnit, renderUnit); this.units.add(renderUnit); if (unitShadowSplat != null) { @@ -961,6 +1019,20 @@ public class War3MapViewer extends ModelViewer { } }); } + if (unitShadowSplatDynamicIngame != null) { + renderUnit.shadow = unitShadowSplatDynamicIngame; + } + if (buildingUberSplat != null) { + buildingUberSplat.unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + renderUnit.uberSplat = t; + } + }); + } + if (buildingUberSplatDynamicIngame != null) { + renderUnit.uberSplat = buildingUberSplatDynamicIngame; + } return simulationUnit; } else { @@ -976,6 +1048,8 @@ public class War3MapViewer extends ModelViewer { } }); } + if (unitShadowSplatDynamicIngame != null) { + } } } else { @@ -1222,24 +1296,7 @@ public class War3MapViewer extends ModelViewer { public List selectUnit(final float x, final float y, final boolean toggle) { System.out.println("world: " + x + "," + y); - 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.isVisible(this.worldScene.camera) - && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap, - unit.getSimulationUnit().getUnitType().isBuilding(), false) - && !unit.getSimulationUnit().isDead()) { - if ((entity == null) || (entity.instance.depth > instance.depth)) { - entity = unit; - } - } - } + final RenderUnit entity = rayPickUnit(x, y, CUnitFilterFunction.ACCEPT_ALL_LIVING); List sel; if (entity != null) { if (toggle) { @@ -1277,8 +1334,8 @@ public class War3MapViewer extends ModelViewer { RenderUnit entity = null; for (final RenderUnit unit : this.units) { final MdxComplexInstance instance = unit.instance; - if (instance.isVisible(this.worldScene.camera) && instance.intersectRayWithCollision(gdxRayHeap, - intersectionHeap, unit.getSimulationUnit().getUnitType().isBuilding(), false)) { + 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 .getGroundHeight(intersectionHeap.x, intersectionHeap.y))) { if ((entity == null) || (entity.instance.depth > instance.depth)) { @@ -1438,7 +1495,7 @@ public class War3MapViewer extends ModelViewer { public void setGameUI(final GameUI gameUI) { this.gameUI = gameUI; this.abilityDataUI = new AbilityDataUI(this.allObjectData.getAbilities(), this.allObjectData.getUnits(), - gameUI); + this.allObjectData.getUpgrades(), gameUI); } public GameUI getGameUI() { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/BuildingShadow.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/BuildingShadow.java new file mode 100644 index 0000000..942790e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/BuildingShadow.java @@ -0,0 +1,7 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.environment; + +public interface BuildingShadow { + void remove(); + + void move(float x, float y); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java index 8345ebc..a042d37 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java @@ -1,7 +1,10 @@ package com.etheller.warsmash.viewer5.handlers.w3x.environment; import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm; @@ -20,18 +23,20 @@ public class PathingGrid { private final short[] dynamicPathingOverlay; // for buildings and trees private final int[] pathingGridSizes; private final float[] centerOffset; + private final List dynamicPathingInstances; public PathingGrid(final War3MapWpm terrainPathing, final float[] centerOffset) { this.centerOffset = centerOffset; this.pathingGrid = terrainPathing.getPathing(); this.pathingGridSizes = terrainPathing.getSize(); this.dynamicPathingOverlay = new short[this.pathingGrid.length]; + this.dynamicPathingInstances = new ArrayList<>(); } // this blit function is basically copied from HiveWE, maybe remember to mention // that in credits as well: // https://github.com/stijnherfst/HiveWE/blob/master/Base/PathingMap.cpp - public void blitPathingOverlayTexture(final float positionX, final float positionY, final int rotationInput, + private void blitPathingOverlayTexture(final float positionX, final float positionY, final int rotationInput, final BufferedImage pathingTextureTga) { final int rotation = (rotationInput + 450) % 360; final int divW = ((rotation % 180) != 0) ? pathingTextureTga.getHeight() : pathingTextureTga.getWidth(); @@ -80,6 +85,15 @@ public class PathingGrid { } } + public RemovablePathingMapInstance blitRemovablePathingOverlayTexture(final float positionX, final float positionY, + final int rotationInput, final BufferedImage pathingTextureTga) { + final RemovablePathingMapInstance removablePathingMapInstance = new RemovablePathingMapInstance(positionX, + positionY, rotationInput, pathingTextureTga); + removablePathingMapInstance.blit(); + this.dynamicPathingInstances.add(removablePathingMapInstance); + return removablePathingMapInstance; + } + public int getWidth() { return this.pathingGridSizes[0]; } @@ -294,4 +308,31 @@ public class PathingGrid { this.preventionFlag = preventionFlag; } } + + public final class RemovablePathingMapInstance { + private final float positionX; + private final float positionY; + private final int rotationInput; + private final BufferedImage pathingTextureTga; + + public RemovablePathingMapInstance(final float positionX, final float positionY, final int rotationInput, + final BufferedImage pathingTextureTga) { + this.positionX = positionX; + this.positionY = positionY; + this.rotationInput = rotationInput; + this.pathingTextureTga = pathingTextureTga; + } + + private void blit() { + blitPathingOverlayTexture(this.positionX, this.positionY, this.rotationInput, this.pathingTextureTga); + } + + public void remove() { + PathingGrid.this.dynamicPathingInstances.remove(this); + Arrays.fill(PathingGrid.this.dynamicPathingOverlay, (short) 0); + for (final RemovablePathingMapInstance instance : PathingGrid.this.dynamicPathingInstances) { + instance.blit(); + } + } + } } 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 471801b..930f5ee 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 @@ -5,7 +5,14 @@ import java.io.IOException; import java.io.InputStream; import java.nio.Buffer; import java.nio.FloatBuffer; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeSet; import java.util.function.Consumer; import javax.imageio.ImageIO; @@ -48,1167 +55,1201 @@ import com.etheller.warsmash.viewer5.handlers.w3x.W3xShaders; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; public class Terrain { - private static final String[] colorTags = {"R", "G", "B", "A"}; - private static final float[] sizeHeap = new float[2]; - private static final Vector3 normalHeap1 = new Vector3(); - private static final Vector3 normalHeap2 = new Vector3(); - private static final float[] fourComponentHeap = new float[4]; - private static final Matrix4 tempMatrix = new Matrix4(); - private static final boolean WIREFRAME_TERRAIN = false; + private static final String[] colorTags = { "R", "G", "B", "A" }; + private static final float[] sizeHeap = new float[2]; + private static final Vector3 normalHeap1 = new Vector3(); + private static final Vector3 normalHeap2 = new Vector3(); + private static final float[] fourComponentHeap = new float[4]; + private static final Matrix4 tempMatrix = new Matrix4(); + private static final boolean WIREFRAME_TERRAIN = false; - public ShaderProgram groundShader; - public ShaderProgram waterShader; - public ShaderProgram cliffShader; - public ShaderProgram testShader; - public float waterIndex; - public float waterIncreasePerFrame; - public float waterHeightOffset; + public ShaderProgram groundShader; + public ShaderProgram waterShader; + public ShaderProgram cliffShader; + public ShaderProgram testShader; + public float waterIndex; + public float waterIncreasePerFrame; + public float waterHeightOffset; - // - public List groundTextures = new ArrayList<>(); - public List cliffTextures = new ArrayList<>(); - public RenderCorner[][] corners; - public int columns; - public int rows; - public int blightTextureIndex = -1; - public float[] maxDeepColor = new float[4]; - public float[] minDeepColor = new float[4]; - public float[] maxShallowColor = new float[4]; - public float[] minShallowColor = new float[4]; + // + public List groundTextures = new ArrayList<>(); + public List cliffTextures = new ArrayList<>(); + public RenderCorner[][] corners; + public int columns; + public int rows; + public int blightTextureIndex = -1; + public float[] maxDeepColor = new float[4]; + public float[] minDeepColor = new float[4]; + public float[] maxShallowColor = new float[4]; + public float[] minShallowColor = new float[4]; - private final DataTable terrainTable; - private final DataTable cliffTable; - private final DataTable waterTable; - private final int waterTextureCount; - private int cliffTexturesSize; - private final List cliffMeshes = new ArrayList<>(); - private final Map pathToCliff = new HashMap<>(); - private final Map groundTextureToId = new HashMap<>(); - private final List cliffToGroundTexture = new ArrayList<>(); - private final List cliffs = new ArrayList<>(); - private final DataSource dataSource; - private final float[] groundHeights; - private final float[] groundCornerHeights; - private final short[] groundTextureList; - private final float[] waterHeights; - private final byte[] waterExistsData; + private final DataTable terrainTable; + private final DataTable cliffTable; + private final DataTable waterTable; + private final int waterTextureCount; + private int cliffTexturesSize; + private final List cliffMeshes = new ArrayList<>(); + private final Map pathToCliff = new HashMap<>(); + private final Map groundTextureToId = new HashMap<>(); + private final List cliffToGroundTexture = new ArrayList<>(); + private final List cliffs = new ArrayList<>(); + private final DataSource dataSource; + private final float[] groundHeights; + private final float[] groundCornerHeights; + private final short[] groundTextureList; + private final float[] waterHeights; + private final byte[] waterExistsData; - private int groundTextureData = -1; - private final int groundHeight; - private final int groundCornerHeight; - private final int groundCornerHeightLinear; - private final int cliffTextureArray; - private final int waterHeight; - private final int waterExists; - private final int waterTextureArray; - private final Camera camera; - private final War3MapViewer viewer; - public float[] centerOffset; - private final WebGL webGL; - private final ShaderProgram uberSplatShader; - public final DataTable uberSplatTable; + private int groundTextureData = -1; + private final int groundHeight; + private final int groundCornerHeight; + private final int groundCornerHeightLinear; + private final int cliffTextureArray; + private final int waterHeight; + private final int waterExists; + private final int waterTextureArray; + private final Camera camera; + private final War3MapViewer viewer; + public float[] centerOffset; + private final WebGL webGL; + private final ShaderProgram uberSplatShader; + public final DataTable uberSplatTable; - private final Map uberSplatModels; - private int shadowMap; - public final Map splats = new HashMap<>(); - public final Map> shadows = new HashMap<>(); - public final Map shadowTextures = new HashMap<>(); - private final int[] mapBounds; - private final float[] shaderMapBounds; - private final int[] mapSize; - public final SoftwareGroundMesh softwareGroundMesh; - private final int testArrayBuffer; - private final int testElementBuffer; - private boolean initShadowsFinished = false; - private byte[] shadowData; + private final Map uberSplatModels; + private int shadowMap; + public final Map splats = new HashMap<>(); + public final Map> shadows = new HashMap<>(); + public final Map shadowTextures = new HashMap<>(); + private final int[] mapBounds; + private final float[] shaderMapBounds; + private final int[] mapSize; + public final SoftwareGroundMesh softwareGroundMesh; + private final int testArrayBuffer; + private final int testElementBuffer; + private boolean initShadowsFinished = false; + private byte[] staticShadowData; + private byte[] shadowData; - public Terrain(final War3MapW3e w3eFile, final War3MapWpm terrainPathing, final War3MapW3i w3iFile, - final WebGL webGL, final DataSource dataSource, final WorldEditStrings worldEditStrings, - final War3MapViewer viewer, final DataTable worldEditData) throws IOException { - this.webGL = webGL; - this.viewer = viewer; - this.camera = viewer.worldScene.camera; - this.dataSource = dataSource; - final String texturesExt = ".blp"; - final Corner[][] corners = w3eFile.getCorners(); - this.corners = new RenderCorner[corners[0].length][corners.length]; - for (int i = 0; i < corners.length; i++) { - for (int j = 0; j < corners[i].length; j++) { - this.corners[j][i] = new RenderCorner(corners[i][j]); - } - } - final int width = w3eFile.getMapSize()[0]; - final int height = w3eFile.getMapSize()[1]; - this.columns = width; - this.rows = height; - for (int i = 0; i < (width - 1); i++) { - for (int j = 0; j < (height - 1); j++) { - final RenderCorner bottomLeft = this.corners[i][j]; - final RenderCorner bottomRight = this.corners[i + 1][j]; - final RenderCorner topLeft = this.corners[i][j + 1]; - final RenderCorner topRight = this.corners[i + 1][j + 1]; + public Terrain(final War3MapW3e w3eFile, final War3MapWpm terrainPathing, final War3MapW3i w3iFile, + final WebGL webGL, final DataSource dataSource, final WorldEditStrings worldEditStrings, + final War3MapViewer viewer, final DataTable worldEditData) throws IOException { + this.webGL = webGL; + this.viewer = viewer; + this.camera = viewer.worldScene.camera; + this.dataSource = dataSource; + final String texturesExt = ".blp"; + final Corner[][] corners = w3eFile.getCorners(); + this.corners = new RenderCorner[corners[0].length][corners.length]; + for (int i = 0; i < corners.length; i++) { + for (int j = 0; j < corners[i].length; j++) { + this.corners[j][i] = new RenderCorner(corners[i][j]); + } + } + final int width = w3eFile.getMapSize()[0]; + final int height = w3eFile.getMapSize()[1]; + this.columns = width; + this.rows = height; + for (int i = 0; i < (width - 1); i++) { + for (int j = 0; j < (height - 1); j++) { + final RenderCorner bottomLeft = this.corners[i][j]; + final RenderCorner bottomRight = this.corners[i + 1][j]; + final RenderCorner topLeft = this.corners[i][j + 1]; + final RenderCorner topRight = this.corners[i + 1][j + 1]; - bottomLeft.cliff = (bottomLeft.getLayerHeight() != bottomRight.getLayerHeight()) - || (bottomLeft.getLayerHeight() != topLeft.getLayerHeight()) - || (bottomLeft.getLayerHeight() != topRight.getLayerHeight()); - } - } + bottomLeft.cliff = (bottomLeft.getLayerHeight() != bottomRight.getLayerHeight()) + || (bottomLeft.getLayerHeight() != topLeft.getLayerHeight()) + || (bottomLeft.getLayerHeight() != topRight.getLayerHeight()); + } + } - this.terrainTable = new DataTable(worldEditStrings); - try (InputStream terrainSlkStream = dataSource.getResourceAsStream("TerrainArt\\Terrain.slk")) { - this.terrainTable.readSLK(terrainSlkStream); - } - this.cliffTable = new DataTable(worldEditStrings); - try (InputStream cliffSlkStream = dataSource.getResourceAsStream("TerrainArt\\CliffTypes.slk")) { - this.cliffTable.readSLK(cliffSlkStream); - } - this.waterTable = new DataTable(worldEditStrings); - try (InputStream waterSlkStream = dataSource.getResourceAsStream("TerrainArt\\Water.slk")) { - this.waterTable.readSLK(waterSlkStream); - } - this.uberSplatTable = new DataTable(worldEditStrings); - try (InputStream uberSlkStream = dataSource.getResourceAsStream("Splats\\UberSplatData.slk")) { - this.uberSplatTable.readSLK(uberSlkStream); - } + this.terrainTable = new DataTable(worldEditStrings); + try (InputStream terrainSlkStream = dataSource.getResourceAsStream("TerrainArt\\Terrain.slk")) { + this.terrainTable.readSLK(terrainSlkStream); + } + this.cliffTable = new DataTable(worldEditStrings); + try (InputStream cliffSlkStream = dataSource.getResourceAsStream("TerrainArt\\CliffTypes.slk")) { + this.cliffTable.readSLK(cliffSlkStream); + } + this.waterTable = new DataTable(worldEditStrings); + try (InputStream waterSlkStream = dataSource.getResourceAsStream("TerrainArt\\Water.slk")) { + this.waterTable.readSLK(waterSlkStream); + } + this.uberSplatTable = new DataTable(worldEditStrings); + try (InputStream uberSlkStream = dataSource.getResourceAsStream("Splats\\UberSplatData.slk")) { + this.uberSplatTable.readSLK(uberSlkStream); + } - final char tileset = w3eFile.getTileset(); - final Element waterInfo = this.waterTable.get(tileset + "Sha"); - if (waterInfo != null) { - this.waterHeightOffset = waterInfo.getFieldFloatValue("height"); - this.waterTextureCount = waterInfo.getFieldValue("numTex"); - this.waterIncreasePerFrame = waterInfo.getFieldValue("texRate") / 60f; - } else { - this.waterHeightOffset = 0; - this.waterTextureCount = 0; - this.waterIncreasePerFrame = 0; - } + final char tileset = w3eFile.getTileset(); + final Element waterInfo = this.waterTable.get(tileset + "Sha"); + if (waterInfo != null) { + this.waterHeightOffset = waterInfo.getFieldFloatValue("height"); + this.waterTextureCount = waterInfo.getFieldValue("numTex"); + this.waterIncreasePerFrame = waterInfo.getFieldValue("texRate") / 60f; + } + else { + this.waterHeightOffset = 0; + this.waterTextureCount = 0; + this.waterIncreasePerFrame = 0; + } - loadWaterColor(this.minShallowColor, "Smin", waterInfo); - loadWaterColor(this.maxShallowColor, "Smax", waterInfo); - loadWaterColor(this.minDeepColor, "Dmin", waterInfo); - loadWaterColor(this.maxDeepColor, "Dmax", waterInfo); - for (int i = 0; i < 3; i++) { - if (this.minDeepColor[i] > this.maxDeepColor[i]) { - this.maxDeepColor[i] = this.minDeepColor[i]; - } - } + loadWaterColor(this.minShallowColor, "Smin", waterInfo); + loadWaterColor(this.maxShallowColor, "Smax", waterInfo); + loadWaterColor(this.minDeepColor, "Dmin", waterInfo); + loadWaterColor(this.maxDeepColor, "Dmax", waterInfo); + for (int i = 0; i < 3; i++) { + if (this.minDeepColor[i] > this.maxDeepColor[i]) { + this.maxDeepColor[i] = this.minDeepColor[i]; + } + } - // Cliff Meshes + // Cliff Meshes - Map cliffVars = Variations.CLIFF_VARS; - for (final Map.Entry cliffVar : cliffVars.entrySet()) { - final Integer maxVariations = cliffVar.getValue(); - for (int variation = 0; variation <= maxVariations; variation++) { - final String fileName = "Doodads\\Terrain\\Cliffs\\Cliffs" + cliffVar.getKey() + variation + ".mdx"; - this.cliffMeshes.add(new CliffMesh(fileName, dataSource, Gdx.gl30)); - this.pathToCliff.put("Cliffs" + cliffVar.getKey() + variation, this.cliffMeshes.size() - 1); - } - } - cliffVars = Variations.CITY_CLIFF_VARS; - for (final Map.Entry cliffVar : cliffVars.entrySet()) { - final Integer maxVariations = cliffVar.getValue(); - for (int variation = 0; variation <= maxVariations; variation++) { - final String fileName = "Doodads\\Terrain\\CityCliffs\\CityCliffs" + cliffVar.getKey() + variation - + ".mdx"; - this.cliffMeshes.add(new CliffMesh(fileName, dataSource, Gdx.gl30)); - this.pathToCliff.put("CityCliffs" + cliffVar.getKey() + variation, this.cliffMeshes.size() - 1); - } - } + Map cliffVars = Variations.CLIFF_VARS; + for (final Map.Entry cliffVar : cliffVars.entrySet()) { + final Integer maxVariations = cliffVar.getValue(); + for (int variation = 0; variation <= maxVariations; variation++) { + final String fileName = "Doodads\\Terrain\\Cliffs\\Cliffs" + cliffVar.getKey() + variation + ".mdx"; + this.cliffMeshes.add(new CliffMesh(fileName, dataSource, Gdx.gl30)); + this.pathToCliff.put("Cliffs" + cliffVar.getKey() + variation, this.cliffMeshes.size() - 1); + } + } + cliffVars = Variations.CITY_CLIFF_VARS; + for (final Map.Entry cliffVar : cliffVars.entrySet()) { + final Integer maxVariations = cliffVar.getValue(); + for (int variation = 0; variation <= maxVariations; variation++) { + final String fileName = "Doodads\\Terrain\\CityCliffs\\CityCliffs" + cliffVar.getKey() + variation + + ".mdx"; + this.cliffMeshes.add(new CliffMesh(fileName, dataSource, Gdx.gl30)); + this.pathToCliff.put("CityCliffs" + cliffVar.getKey() + variation, this.cliffMeshes.size() - 1); + } + } - // Ground textures - for (final War3ID groundTile : w3eFile.getGroundTiles()) { - final Element terrainTileInfo = this.terrainTable.get(groundTile.asStringValue()); - if (terrainTileInfo == null) { - throw new RuntimeException("No terrain info for: " + groundTile.asStringValue()); - } - final String dir = terrainTileInfo.getField("dir"); - final String file = terrainTileInfo.getField("file"); - this.groundTextures.add(new GroundTexture(dir + "\\" + file + texturesExt, dataSource, Gdx.gl30)); - this.groundTextureToId.put(groundTile.asStringValue(), this.groundTextures.size() - 1); - } + // Ground textures + for (final War3ID groundTile : w3eFile.getGroundTiles()) { + final Element terrainTileInfo = this.terrainTable.get(groundTile.asStringValue()); + if (terrainTileInfo == null) { + throw new RuntimeException("No terrain info for: " + groundTile.asStringValue()); + } + final String dir = terrainTileInfo.getField("dir"); + final String file = terrainTileInfo.getField("file"); + this.groundTextures.add(new GroundTexture(dir + "\\" + file + texturesExt, dataSource, Gdx.gl30)); + this.groundTextureToId.put(groundTile.asStringValue(), this.groundTextures.size() - 1); + } - final Element tilesets = worldEditData.get("TileSets"); + final Element tilesets = worldEditData.get("TileSets"); - this.blightTextureIndex = this.groundTextures.size(); - this.groundTextures.add(new GroundTexture( - tilesets.getField(Character.toString(tileset)).split(",")[1] + texturesExt, dataSource, Gdx.gl30)); + this.blightTextureIndex = this.groundTextures.size(); + this.groundTextures.add(new GroundTexture( + tilesets.getField(Character.toString(tileset)).split(",")[1] + texturesExt, dataSource, Gdx.gl30)); - // Cliff Textures - for (final War3ID cliffTile : w3eFile.getCliffTiles()) { - final Element cliffInfo = this.cliffTable.get(cliffTile.asStringValue()); - final String texDir = cliffInfo.getField("texDir"); - final String texFile = cliffInfo.getField("texFile"); - try (InputStream imageStream = dataSource.getResourceAsStream(texDir + "\\" + texFile + texturesExt)) { - final BufferedImage image; - if (imageStream == null) { - final String tgaPath = texDir + "\\" + texFile + ".tga"; - try (final InputStream tgaStream = dataSource.getResourceAsStream(tgaPath)) { - if (tgaStream != null) { - image = TgaFile.readTGA(tgaPath, tgaStream); - } else { - throw new IllegalStateException( - "Missing cliff texture: " + texDir + "\\" + texFile + texturesExt); - } - } - } else { - image = ImageIO.read(imageStream); - if (image == null) { - throw new IllegalStateException( - "Missing cliff texture: " + texDir + "\\" + texFile + texturesExt); - } - } - this.cliffTextures.add(new UnloadedTexture(image.getWidth(), image.getHeight(), - ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image)), - cliffInfo.getField("cliffModelDir"), cliffInfo.getField("rampModelDir"))); - } - this.cliffTexturesSize = Math.max(this.cliffTexturesSize, - this.cliffTextures.get(this.cliffTextures.size() - 1).width); - this.cliffToGroundTexture.add(this.groundTextureToId.get(cliffInfo.getField("groundTile"))); - } + // Cliff Textures + for (final War3ID cliffTile : w3eFile.getCliffTiles()) { + final Element cliffInfo = this.cliffTable.get(cliffTile.asStringValue()); + final String texDir = cliffInfo.getField("texDir"); + final String texFile = cliffInfo.getField("texFile"); + try (InputStream imageStream = dataSource.getResourceAsStream(texDir + "\\" + texFile + texturesExt)) { + final BufferedImage image; + if (imageStream == null) { + final String tgaPath = texDir + "\\" + texFile + ".tga"; + try (final InputStream tgaStream = dataSource.getResourceAsStream(tgaPath)) { + if (tgaStream != null) { + image = TgaFile.readTGA(tgaPath, tgaStream); + } + else { + throw new IllegalStateException( + "Missing cliff texture: " + texDir + "\\" + texFile + texturesExt); + } + } + } + else { + image = ImageIO.read(imageStream); + if (image == null) { + throw new IllegalStateException( + "Missing cliff texture: " + texDir + "\\" + texFile + texturesExt); + } + } + this.cliffTextures.add(new UnloadedTexture(image.getWidth(), image.getHeight(), + ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image)), + cliffInfo.getField("cliffModelDir"), cliffInfo.getField("rampModelDir"))); + } + this.cliffTexturesSize = Math.max(this.cliffTexturesSize, + this.cliffTextures.get(this.cliffTextures.size() - 1).width); + this.cliffToGroundTexture.add(this.groundTextureToId.get(cliffInfo.getField("groundTile"))); + } - updateCliffMeshes(new Rectangle(0, 0, width - 1, height - 1)); + updateCliffMeshes(new Rectangle(0, 0, width - 1, height - 1)); - // prepare GPU data - this.groundHeights = new float[width * height]; - this.groundCornerHeights = new float[width * height]; - this.groundTextureList = new short[(width - 1) * (height - 1) * 4]; - this.waterHeights = new float[width * height]; - this.waterExistsData = new byte[width * height]; + // prepare GPU data + this.groundHeights = new float[width * height]; + this.groundCornerHeights = new float[width * height]; + this.groundTextureList = new short[(width - 1) * (height - 1) * 4]; + this.waterHeights = new float[width * height]; + this.waterExistsData = new byte[width * height]; - updateGroundTextures(new Rectangle(0, 0, width - 1, height - 1)); - for (int i = 0; i < width; i++) { - for (int j = 0; j < height; j++) { - this.groundCornerHeights[(j * width) + i] = this.corners[i][j].computeFinalGroundHeight(); - this.waterExistsData[(j * width) + i] = (byte) this.corners[i][j].getWater(); - this.groundHeights[(j * width) + i] = this.corners[i][j].getGroundHeight(); - this.waterHeights[(j * width) + i] = this.corners[i][j].getWaterHeight(); - } - } + updateGroundTextures(new Rectangle(0, 0, width - 1, height - 1)); + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + this.groundCornerHeights[(j * width) + i] = this.corners[i][j].computeFinalGroundHeight(); + this.waterExistsData[(j * width) + i] = (byte) this.corners[i][j].getWater(); + this.groundHeights[(j * width) + i] = this.corners[i][j].getGroundHeight(); + this.waterHeights[(j * width) + i] = this.corners[i][j].getWaterHeight(); + } + } - final GL30 gl = Gdx.gl30; - // Ground - this.groundTextureData = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_RGBA16UI, width - 1, height - 1, 0, GL30.GL_RGBA_INTEGER, - GL30.GL_UNSIGNED_SHORT, RenderMathUtils.wrapShort(this.groundTextureList)); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); + final GL30 gl = Gdx.gl30; + // Ground + this.groundTextureData = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_RGBA16UI, width - 1, height - 1, 0, GL30.GL_RGBA_INTEGER, + GL30.GL_UNSIGNED_SHORT, RenderMathUtils.wrapShort(this.groundTextureList)); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); - this.groundHeight = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR); + this.groundHeight = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, - RenderMathUtils.wrap(this.groundHeights)); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, + RenderMathUtils.wrap(this.groundHeights)); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); - this.groundCornerHeight = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); + this.groundCornerHeight = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, - RenderMathUtils.wrap(this.groundCornerHeights)); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, + RenderMathUtils.wrap(this.groundCornerHeights)); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); - this.groundCornerHeightLinear = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeightLinear); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR); + this.groundCornerHeightLinear = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeightLinear); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, - RenderMathUtils.wrap(this.groundCornerHeights)); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, + RenderMathUtils.wrap(this.groundCornerHeights)); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); - // Cliff - this.cliffTextureArray = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.cliffTextureArray); - gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_RGBA8, this.cliffTexturesSize, this.cliffTexturesSize, - this.cliffTextures.size(), 0, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null); - gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR_MIPMAP_LINEAR); - gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_BASE_LEVEL, 0); + // Cliff + this.cliffTextureArray = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.cliffTextureArray); + gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_RGBA8, this.cliffTexturesSize, this.cliffTexturesSize, + this.cliffTextures.size(), 0, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR_MIPMAP_LINEAR); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_BASE_LEVEL, 0); - int sub = 0; - for (final UnloadedTexture i : this.cliffTextures) { - gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, sub, i.width, i.height, 1, GL30.GL_RGBA, - GL30.GL_UNSIGNED_BYTE, i.data); - sub += 1; - } - gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY); + int sub = 0; + for (final UnloadedTexture i : this.cliffTextures) { + gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, sub, i.width, i.height, 1, GL30.GL_RGBA, + GL30.GL_UNSIGNED_BYTE, i.data); + sub += 1; + } + gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY); - // Water - this.waterHeight = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, - RenderMathUtils.wrap(this.waterHeights)); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); + // Water + this.waterHeight = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, + RenderMathUtils.wrap(this.waterHeights)); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); - this.waterExists = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterExists); - gl.glPixelStorei(GL30.GL_UNPACK_ALIGNMENT, 1); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, width, height, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE, - RenderMathUtils.wrap(this.waterExistsData)); - gl.glPixelStorei(GL30.GL_UNPACK_ALIGNMENT, 4); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); + this.waterExists = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterExists); + gl.glPixelStorei(GL30.GL_UNPACK_ALIGNMENT, 1); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, width, height, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE, + RenderMathUtils.wrap(this.waterExistsData)); + gl.glPixelStorei(GL30.GL_UNPACK_ALIGNMENT, 4); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); - // Water textures - this.waterTextureArray = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.waterTextureArray); - gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_SRGB8_ALPHA8, 128, 128, this.waterTextureCount, 0, - GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null); - gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_BASE_LEVEL, 0); + // Water textures + this.waterTextureArray = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.waterTextureArray); + gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_SRGB8_ALPHA8, 128, 128, this.waterTextureCount, 0, + GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_BASE_LEVEL, 0); - final String fileName = waterInfo.getField("texFile"); - for (int i = 0; i < this.waterTextureCount; i++) { + final String fileName = waterInfo.getField("texFile"); + for (int i = 0; i < this.waterTextureCount; i++) { - try (InputStream imageStream = dataSource - .getResourceAsStream(fileName + (i < 10 ? "0" : "") + Integer.toString(i) + texturesExt)) { - final BufferedImage image = ImageIO.read(imageStream); - if ((image.getWidth() != 128) || (image.getHeight() != 128)) { - System.err.println( - "Odd water texture size detected of " + image.getWidth() + " x " + image.getHeight()); - } + try (InputStream imageStream = dataSource + .getResourceAsStream(fileName + (i < 10 ? "0" : "") + Integer.toString(i) + texturesExt)) { + final BufferedImage image = ImageIO.read(imageStream); + if ((image.getWidth() != 128) || (image.getHeight() != 128)) { + System.err.println( + "Odd water texture size detected of " + image.getWidth() + " x " + image.getHeight()); + } - gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, image.getWidth(), image.getHeight(), 1, - GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, ImageUtils.getTextureBuffer(image)); - } - } - gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY); + gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, image.getWidth(), image.getHeight(), 1, + GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, ImageUtils.getTextureBuffer(image)); + } + } + gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY); - updateGroundHeights(new Rectangle(0, 0, width - 1, height - 1)); + updateGroundHeights(new Rectangle(0, 0, width - 1, height - 1)); - this.groundShader = webGL.createShaderProgram(TerrainShaders.Terrain.vert, TerrainShaders.Terrain.frag); - this.cliffShader = webGL.createShaderProgram(TerrainShaders.Cliffs.vert, TerrainShaders.Cliffs.frag); - this.waterShader = webGL.createShaderProgram(TerrainShaders.Water.vert, TerrainShaders.Water.frag); - this.testShader = webGL.createShaderProgram(TerrainShaders.Test.vert, TerrainShaders.Test.frag); + this.groundShader = webGL.createShaderProgram(TerrainShaders.Terrain.vert, TerrainShaders.Terrain.frag); + this.cliffShader = webGL.createShaderProgram(TerrainShaders.Cliffs.vert, TerrainShaders.Cliffs.frag); + this.waterShader = webGL.createShaderProgram(TerrainShaders.Water.vert, TerrainShaders.Water.frag); + this.testShader = webGL.createShaderProgram(TerrainShaders.Test.vert, TerrainShaders.Test.frag); - this.uberSplatShader = webGL.createShaderProgram(W3xShaders.UberSplat.vert, W3xShaders.UberSplat.frag); + this.uberSplatShader = webGL.createShaderProgram(W3xShaders.UberSplat.vert, W3xShaders.UberSplat.frag); - // TODO collision bodies (?) + // TODO collision bodies (?) - this.centerOffset = w3eFile.getCenterOffset(); - this.uberSplatModels = new LinkedHashMap<>(); - this.mapBounds = w3iFile.getCameraBoundsComplements(); - this.shaderMapBounds = new float[]{(this.mapBounds[0] * 128.0f) + this.centerOffset[0], - (this.mapBounds[2] * 128.0f) + this.centerOffset[1], - ((this.columns - this.mapBounds[1] - 1) * 128.0f) + this.centerOffset[0], - ((this.rows - this.mapBounds[3] - 1) * 128.0f) + this.centerOffset[1]}; - this.shaderMapBoundsRectangle = new Rectangle(this.shaderMapBounds[0], this.shaderMapBounds[1], - this.shaderMapBounds[2] - this.shaderMapBounds[0], this.shaderMapBounds[3] - this.shaderMapBounds[1]); - this.mapSize = w3eFile.getMapSize(); - this.entireMapRectangle = new Rectangle(this.centerOffset[0], this.centerOffset[1], - (this.mapSize[0] * 128f) - 128, (this.mapSize[1] * 128f) - 128); - this.softwareGroundMesh = new SoftwareGroundMesh(this.groundHeights, this.groundCornerHeights, - this.centerOffset, width, height); + this.centerOffset = w3eFile.getCenterOffset(); + this.uberSplatModels = new LinkedHashMap<>(); + this.mapBounds = w3iFile.getCameraBoundsComplements(); + this.shaderMapBounds = new float[] { (this.mapBounds[0] * 128.0f) + this.centerOffset[0], + (this.mapBounds[2] * 128.0f) + this.centerOffset[1], + ((this.columns - this.mapBounds[1] - 1) * 128.0f) + this.centerOffset[0], + ((this.rows - this.mapBounds[3] - 1) * 128.0f) + this.centerOffset[1] }; + this.shaderMapBoundsRectangle = new Rectangle(this.shaderMapBounds[0], this.shaderMapBounds[1], + this.shaderMapBounds[2] - this.shaderMapBounds[0], this.shaderMapBounds[3] - this.shaderMapBounds[1]); + this.mapSize = w3eFile.getMapSize(); + this.entireMapRectangle = new Rectangle(this.centerOffset[0], this.centerOffset[1], + (this.mapSize[0] * 128f) - 128, (this.mapSize[1] * 128f) - 128); + this.softwareGroundMesh = new SoftwareGroundMesh(this.groundHeights, this.groundCornerHeights, + this.centerOffset, width, height); - this.testArrayBuffer = gl.glGenBuffer(); - gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.testArrayBuffer); - gl.glBufferData(GL30.GL_ARRAY_BUFFER, this.softwareGroundMesh.vertices.length, - RenderMathUtils.wrap(this.softwareGroundMesh.vertices), GL30.GL_STATIC_DRAW); + this.testArrayBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.testArrayBuffer); + gl.glBufferData(GL30.GL_ARRAY_BUFFER, this.softwareGroundMesh.vertices.length, + RenderMathUtils.wrap(this.softwareGroundMesh.vertices), GL30.GL_STATIC_DRAW); - this.testElementBuffer = gl.glGenBuffer(); + this.testElementBuffer = gl.glGenBuffer(); // gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, this.testElementBuffer); // gl.glBufferData(GL30.GL_ELEMENT_ARRAY_BUFFER, this.softwareGroundMesh.indices.length, // RenderMathUtils.wrap(this.softwareGroundMesh.indices), GL30.GL_STATIC_DRAW); - this.waveBuilder = new WaveBuilder(this.mapSize, this.waterTable, viewer, this.corners, this.centerOffset, - this.waterHeightOffset, w3eFile, w3iFile); - this.pathingGrid = new PathingGrid(terrainPathing, this.centerOffset); - } - - public void createWaves() { - this.waveBuilder.createWaves(this); - } - - private void updateGroundHeights(final Rectangle area) { - for (int j = (int) area.y; j < (area.y + area.height); j++) { - for (int i = (int) area.x; i < (area.x + area.width); i++) { - this.groundHeights[(j * this.columns) + i] = this.corners[i][j].getGroundHeight(); - - float rampHeight = 0f; - // Check if in one of the configurations the bottom_left is a ramp - XLoop: - for (int xOffset = -1; xOffset <= 0; xOffset++) { - for (int yOffset = -1; yOffset <= 0; yOffset++) { - if (((i + xOffset) >= 0) && ((i + xOffset) < (this.columns - 1)) && ((j + yOffset) >= 0) - && ((j + yOffset) < (this.rows - 1))) { - final RenderCorner bottomLeft = this.corners[i + xOffset][j + yOffset]; - final RenderCorner bottomRight = this.corners[i + 1 + xOffset][j + yOffset]; - final RenderCorner topLeft = this.corners[i + xOffset][j + 1 + yOffset]; - final RenderCorner topRight = this.corners[i + 1 + xOffset][j + 1 + yOffset]; - - final int base = Math.min( - Math.min(bottomLeft.getLayerHeight(), bottomRight.getLayerHeight()), - Math.min(topLeft.getLayerHeight(), topRight.getLayerHeight())); - if (this.corners[i][j].getLayerHeight() != base) { - continue; - } - - if (isCornerRampEntrance(i + xOffset, j + yOffset)) { - rampHeight = 0.5f; - break XLoop; - } - } - } - } - - final RenderCorner corner = this.corners[i][j]; - final float newGroundCornerHeight = corner.computeFinalGroundHeight() + rampHeight; - this.groundCornerHeights[(j * this.columns) + i] = newGroundCornerHeight; - corner.depth = (corner.getWater() != 0) - ? (this.waterHeightOffset + corner.getWaterHeight()) - newGroundCornerHeight - : 0; - } - } - updateGroundHeights(); - updateCornerHeights(); - } - - private void updateGroundHeights() { - Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); - Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns, this.rows, GL30.GL_RED, GL30.GL_FLOAT, - RenderMathUtils.wrap(this.groundHeights)); - } - - private void updateCornerHeights() { - final FloatBuffer groundCornerHeightsWrapped = RenderMathUtils.wrap(this.groundCornerHeights); - Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); - Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns, this.rows, GL30.GL_RED, GL30.GL_FLOAT, - groundCornerHeightsWrapped); - Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeightLinear); - Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns, this.rows, GL30.GL_RED, GL30.GL_FLOAT, - groundCornerHeightsWrapped); - } - - /** - * calculateRamps() is copied from Riv whereas a lot of the rest of the terrain - * was copied from HiveWE - */ - private void calculateRamps() { - final int columns = this.mapSize[0]; - final int rows = this.mapSize[1]; - - final String[] ramps = {"AAHL", "AALH", "ABHL", "AHLA", "ALHA", "ALHB", "BALH", "BHLA", "HAAL", "HBAL", "HLAA", - "HLAB", "LAAH", "LABH", "LHAA", "LHBA"}; - - // Adjust terrain height inside ramps (set rampAdjust) - for (int y = 1; y < (rows - 1); ++y) { - for (int x = 1; x < (columns - 1); ++x) { - final RenderCorner o = this.corners[x][y]; - if (!o.isRamp()) { - continue; - } - final RenderCorner a = this.corners[x - 1][y - 1]; - final RenderCorner b = this.corners[x - 1][y]; - final RenderCorner c = this.corners[x - 1][y + 1]; - final RenderCorner d = this.corners[x][y + 1]; - final RenderCorner e = this.corners[x + 1][y + 1]; - final RenderCorner f = this.corners[x + 1][y]; - final RenderCorner g = this.corners[x + 1][y - 1]; - final RenderCorner h = this.corners[x][y - 1]; - final int base = o.getLayerHeight(); - if ((b.isRamp() && f.isRamp()) || (d.isRamp() && h.isRamp())) { - float adjust = 0; - if (b.isRamp() && f.isRamp()) { - adjust = Math.max(adjust, ((b.getLayerHeight() + f.getLayerHeight()) / 2) - base); - } - if (d.isRamp() && h.isRamp()) { - adjust = Math.max(adjust, ((d.getLayerHeight() + h.getLayerHeight()) / 2) - base); - } - if (a.isRamp() && e.isRamp()) { - adjust = Math.max(adjust, (((a.getLayerHeight() + e.getLayerHeight()) / 2) - base) / 2); - } - if (c.isRamp() && g.isRamp()) { - adjust = Math.max(adjust, (((c.getLayerHeight() + g.getLayerHeight()) / 2) - base) / 2); - } - o.rampAdjust = adjust; - } - } - } - } - - /// TODO clean - /// Function is a bit of a mess - /// Updates the cliff and ramp meshes for an area - private void updateCliffMeshes(final Rectangle area) throws IOException { - // Remove all existing cliff meshes in area - for (int i = this.cliffs.size(); i-- > 0; ) { - final IVec3 pos = this.cliffs.get(i); - if (area.contains(pos.x, pos.y)) { - this.cliffs.remove(i); - } - } - - for (int i = (int) area.getX(); i < (int) (area.getX() + area.getWidth()); i++) { - for (int j = (int) area.getY(); j < (int) (area.getY() + area.getHeight()); j++) { - this.corners[i][j].romp = false; - } - } - - final Rectangle adjusted = new Rectangle(area.x - 2, area.y - 2, area.width + 4, area.height + 4); - final Rectangle rampArea = new Rectangle(); - Intersector.intersectRectangles(new Rectangle(0, 0, this.columns, this.rows), adjusted, rampArea); - - // Add new cliff meshes - final int xLimit = (int) ((rampArea.getX() + rampArea.getWidth()) - 1); - for (int i = (int) rampArea.getX(); i < xLimit; i++) { - final int yLimit = (int) ((rampArea.getY() + rampArea.getHeight()) - 1); - for (int j = (int) rampArea.getY(); j < yLimit; j++) { - final RenderCorner bottomLeft = this.corners[i][j]; - final RenderCorner bottomRight = this.corners[i + 1][j]; - final RenderCorner topLeft = this.corners[i][j + 1]; - final RenderCorner topRight = this.corners[i + 1][j + 1]; - - if (bottomLeft.cliff && !bottomLeft.hideCliff) { - final int base = Math.min(Math.min(bottomLeft.getLayerHeight(), bottomRight.getLayerHeight()), - Math.min(topLeft.getLayerHeight(), topRight.getLayerHeight())); - - final boolean facingDown = (topLeft.getLayerHeight() >= bottomLeft.getLayerHeight()) - && (topRight.getLayerHeight() >= bottomRight.getLayerHeight()); - final boolean facingLeft = (bottomRight.getLayerHeight() >= bottomLeft.getLayerHeight()) - && (topRight.getLayerHeight() >= topLeft.getLayerHeight()); - - int bottomLeftCliffTex = bottomLeft.getCliffTexture(); - if (bottomLeftCliffTex == 15) { - bottomLeftCliffTex -= 14; - } - if (!(facingDown && (j == 0)) && !(!facingDown && (j >= (this.rows - 2))) - && !(facingLeft && (i == 0)) && !(!facingLeft && (i >= (this.columns - 2)))) { - final boolean br = ((bottomLeft.getRamp() != 0) != (bottomRight.getRamp() != 0)) - && ((topLeft.getRamp() != 0) != (topRight.getRamp() != 0)) - && !this.corners[i + (bottomRight.getRamp() != 0 ? 1 : 0)][j - + (facingDown ? -1 : 1)].cliff; - - final boolean bo = ((bottomLeft.getRamp() != 0) != (topLeft.getRamp() != 0)) - && ((bottomRight.getRamp() != 0) != (topRight.getRamp() != 0)) - && !this.corners[i + (facingLeft ? -1 : 1)][j + (topLeft.getRamp() != 0 ? 1 : 0)].cliff; - - if (br || bo) { - String fileName = "" + (char) ((bottomLeft.getRamp() != 0 ? 'L' : 'A') - + ((bottomLeft.getLayerHeight() - base) * (bottomLeft.getRamp() != 0 ? -4 : 1))) - + (char) ((topLeft.getRamp() != 0 ? 'L' : 'A') - + ((topLeft.getLayerHeight() - base) * (topLeft.getRamp() != 0 ? -4 : 1))) - + (char) ((topRight.getRamp() != 0 ? 'L' : 'A') - + ((topRight.getLayerHeight() - base) * (topRight.getRamp() != 0 ? -4 : 1))) - + (char) ((bottomRight.getRamp() != 0 ? 'L' : 'A') - + ((bottomRight.getLayerHeight() - base) - * (bottomRight.getRamp() != 0 ? -4 : 1))); - - final String rampModelDir = this.cliffTextures.get(bottomLeftCliffTex).rampModelDir; - fileName = "Doodads\\Terrain\\" + rampModelDir + "\\" + rampModelDir + fileName + "0.mdx"; - - if (this.dataSource.has(fileName)) { - if (!this.pathToCliff.containsKey(fileName)) { - this.cliffMeshes.add(new CliffMesh(fileName, this.dataSource, Gdx.gl30)); - this.pathToCliff.put(fileName, this.cliffMeshes.size() - 1); - } - - for (int ji = this.cliffs.size(); ji-- > 0; ) { - final IVec3 pos = this.cliffs.get(ji); - if ((pos.x == (i + ((bo ? 1 : 0) * (facingLeft ? 0 : 1)))) - && (pos.y == (j - ((br ? 1 : 0) * (facingDown ? 1 : 0))))) { - this.cliffs.remove(ji); - break; - } - } - - this.cliffs.add(new IVec3((i + ((bo ? 1 : 0) * (facingLeft ? 0 : 1))), - (j - ((br ? 1 : 0) * (facingDown ? 1 : 0))), this.pathToCliff.get(fileName))); - bottomLeft.romp = true; - - this.corners[i + ((facingLeft ? -1 : 1) * (bo ? 1 : 0))][j - + ((facingDown ? -1 : 1) * (br ? 1 : 0))].romp = true; - - continue; - } - } - } - - if (isCornerRampEntrance(i, j)) { - continue; - } - - // Ramps move 1 right/down in some cases and thus their area is one bigger to - // the top and left. - if (!area.contains(i, j)) { - continue; - } - - // Cliff model path - - String fileName = "" + (char) (('A' + bottomLeft.getLayerHeight()) - base) - + (char) (('A' + topLeft.getLayerHeight()) - base) - + (char) (('A' + topRight.getLayerHeight()) - base) - + (char) (('A' + bottomRight.getLayerHeight()) - base); - - if ("AAAA".equals(fileName)) { - continue; - } - - // Clamp to within max variations - - fileName = this.cliffTextures.get(bottomLeftCliffTex).cliffModelDir + fileName - + Variations.getCliffVariation(this.cliffTextures.get(bottomLeftCliffTex).cliffModelDir, - fileName, bottomLeft.getCliffVariation()); - if (!this.pathToCliff.containsKey(fileName)) { - throw new IllegalArgumentException("No such pathToCliff entry: " + fileName); - } - this.cliffs.add(new IVec3(i, j, this.pathToCliff.get(fileName))); - } - } - } - - } - - public void logRomp(final int x, final int y) { - System.out.println("romp: " + this.corners[x][y].romp); - System.out.println("ramp: " + this.corners[x][y].isRamp()); - System.out.println("cliff: " + this.corners[x][y].cliff); - } - - private void updateGroundTextures(final Rectangle area) { - final Rectangle adjusted = new Rectangle(area.x - 1, area.y - 1, area.width + 2, area.height + 2); - final Rectangle updateArea = new Rectangle(); - Intersector.intersectRectangles(new Rectangle(0, 0, this.columns - 1, this.rows - 1), adjusted, updateArea); - - for (int j = (int) (updateArea.getY()); j <= (int) ((updateArea.getY() + updateArea.getHeight()) - 1); j++) { - for (int i = (int) (updateArea.getX()); i <= (int) ((updateArea.getX() + updateArea.getWidth()) - 1); i++) { - getTextureVariations(i, j, this.groundTextureList, ((j * (this.columns - 1)) + i) * 4); - - if (this.corners[i][j].cliff || this.corners[i][j].romp) { - if (isCornerRampEntrance(i, j)) { - continue; - } - this.groundTextureList[(((j * (this.columns - 1)) + i) * 4) + 3] |= 0b1000000000000000; - } - } - } - - uploadGroundTexture(); - } - - public void removeTerrainCell(final int i, final int j) { - this.groundTextureList[(((j * (this.columns - 1)) + i) * 4) + 3] |= 0b1000000000000000; - this.corners[i][j].hideCliff = true; - uploadGroundTexture(); - try { - updateCliffMeshes(new Rectangle(i - 1, j - 1, 1, 1)); // TODO does this work? - } catch (final IOException e) { - throw new RuntimeException(e); - } - } - - public void removeTerrainCellWithoutFlush(final int i, final int j) { - this.groundTextureList[(((j * (this.columns - 1)) + i) * 4) + 3] |= 0b1000000000000000; - this.corners[i][j].hideCliff = true; - } - - public void flushRemovedTerrainCells() { - uploadGroundTexture(); - try { - updateCliffMeshes(new Rectangle(0, 0, this.columns - 1, this.rows - 1)); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } - - private void uploadGroundTexture() { - if (this.groundTextureData != -1) { - Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); - Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns - 1, this.rows - 1, GL30.GL_RGBA_INTEGER, - GL30.GL_UNSIGNED_SHORT, RenderMathUtils.wrapShort(this.groundTextureList)); - } - } - - /// The 4 ground textures of the tilepoint. First 5 bits are which texture array - /// to use and the next 5 bits are which subtexture to use - private void getTextureVariations(final int x, final int y, final short[] out, final int outStartOffset) { - final int bottomLeft = realTileTexture(x, y); - final int bottomRight = realTileTexture(x + 1, y); - final int topLeft = realTileTexture(x, y + 1); - final int topRight = realTileTexture(x + 1, y + 1); - - final TreeSet set = new TreeSet<>(); - set.add(bottomLeft); - set.add(bottomRight); - set.add(topLeft); - set.add(topRight); - Arrays.fill(out, outStartOffset, outStartOffset + 4, (short) 17); - int component = outStartOffset + 1; - - final Iterator iterator = set.iterator(); - iterator.hasNext(); - final short firstValue = iterator.next().shortValue(); - out[outStartOffset] = (short) (firstValue - + (getVariation(firstValue, this.corners[x][y].getGroundVariation()) << 5)); - - int index; - while (iterator.hasNext()) { - index = 0; - final int texture = iterator.next().intValue(); - index |= (bottomRight == texture ? 1 : 0) << 0; - index |= (bottomLeft == texture ? 1 : 0) << 1; - index |= (topRight == texture ? 1 : 0) << 2; - index |= (topLeft == texture ? 1 : 0) << 3; - - out[component++] = (short) (texture + (index << 5)); - } - } - - private int realTileTexture(final int x, final int y) { - ILoop: - for (int i = -1; i < 1; i++) { - for (int j = -1; j < 1; j++) { - if (((x + i) >= 0) && ((x + i) < this.columns) && ((y + j) >= 0) && ((y + j) < this.rows)) { - if (this.corners[x + i][y + j].cliff) { - if (((x + i) < (this.columns - 1)) && ((y + j) < (this.rows - 1))) { - final RenderCorner bottomLeft = this.corners[x + i][y + j]; - final RenderCorner bottomRight = this.corners[x + i + 1][y + j]; - final RenderCorner topLeft = this.corners[x + i][y + j + 1]; - final RenderCorner topRight = this.corners[x + i + 1][y + j + 1]; - - if ((bottomLeft.getRamp() != 0) && (topLeft.getRamp() != 0) && (bottomRight.getRamp() != 0) - && (topRight.getRamp() != 0) && (!bottomLeft.romp) && (!bottomRight.romp) - && (!topLeft.romp) && (!topRight.romp)) { - break ILoop; - } - } - } - if (this.corners[x + i][y + j].romp || this.corners[x + i][y + j].cliff) { - int texture = this.corners[x + i][y + j].getCliffTexture(); - // Number 15 seems to be something - if (texture == 15) { - texture -= 14; - } - - return this.cliffToGroundTexture.get(texture); - } - } - } - } - - if (this.corners[x][y].getBlight() != 0) { - return this.blightTextureIndex; - } - - return this.corners[x][y].getGroundTexture(); - } - - private boolean isCornerRampEntrance(final int x, final int y) { - if ((x == this.columns) || (y == this.rows)) { - return false; - } - - final RenderCorner bottomLeft = this.corners[x][y]; - final RenderCorner bottomRight = this.corners[x + 1][y]; - final RenderCorner topLeft = this.corners[x][y + 1]; - final RenderCorner topRight = this.corners[x + 1][y + 1]; - - return (bottomLeft.getRamp() != 0) && (topLeft.getRamp() != 0) && (bottomRight.getRamp() != 0) - && (topRight.getRamp() != 0) && !((bottomLeft.getLayerHeight() == topRight.getLayerHeight()) - && (topLeft.getLayerHeight() == bottomRight.getLayerHeight())); - } - - private static void loadWaterColor(final float[] out, final String prefix, final Element waterInfo) { - for (int i = 0; i < colorTags.length; i++) { - final String colorTag = colorTags[i]; - out[i] = waterInfo == null ? 0.0f : waterInfo.getFieldFloatValue(prefix + "_" + colorTag) / 255f; - } - } - - public short getVariation(final int groundTexture, final int variation) { - final GroundTexture texture = this.groundTextures.get(groundTexture); - - // Extended ? - if (texture.extended) { - if (variation <= 15) { - return (short) (16 + variation); - } else if (variation == 16) { - return 15; - } else { - return 0; - } - } else { - if (variation == 0) { - return 0; - } else { - return 15; - } - } - } - - public void update() { - this.waterIndex += this.waterIncreasePerFrame; - - if (this.waterIndex >= this.waterTextureCount) { - this.waterIndex = 0; - } - } - - public void renderGround(final DynamicShadowManager dynamicShadowManager) { - // Render tiles - - this.webGL.useShaderProgram(this.groundShader); - - final GL30 gl = Gdx.gl30; - gl.glEnable(GL20.GL_CULL_FACE); - gl.glDisable(GL30.GL_BLEND); - gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); - gl.glEnable(GL20.GL_DEPTH_TEST); - gl.glDepthMask(true); - - gl.glUniformMatrix4fv(this.groundShader.getUniformLocation("MVP"), 1, false, - this.camera.viewProjectionMatrix.val, 0); - gl.glUniform1i(this.groundShader.getUniformLocation("show_pathing_map"), this.viewer.renderPathing); - gl.glUniform1i(this.groundShader.getUniformLocation("show_lighting"), this.viewer.renderLighting); - gl.glUniform1i(this.groundShader.getUniformLocation("height_texture"), 0); - gl.glUniform1i(this.groundShader.getUniformLocation("height_cliff_texture"), 1); - gl.glUniform1i(this.groundShader.getUniformLocation("terrain_texture_list"), 2); - gl.glUniform1i(this.groundShader.getUniformLocation("shadowMap"), 20); - gl.glUniform1f(this.groundShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); - gl.glUniform1f(this.groundShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); - - final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); - final DataTexture unitLightsTexture = lightManager.getTerrainLightsTexture(); - - unitLightsTexture.bind(21); - gl.glUniform1i(this.groundShader.getUniformLocation("lightTexture"), 21); - gl.glUniform1f(this.groundShader.getUniformLocation("lightCount"), lightManager.getTerrainLightCount()); - gl.glUniform1f(this.groundShader.getUniformLocation("lightTextureHeight"), unitLightsTexture.getHeight()); - - gl.glUniformMatrix4fv(this.groundShader.getUniformLocation("DepthBiasMVP"), 1, false, - dynamicShadowManager.getDepthBiasMVP().val, 0); - - gl.glActiveTexture(GL30.GL_TEXTURE0); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); - - gl.glActiveTexture(GL30.GL_TEXTURE1); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); - - gl.glActiveTexture(GL30.GL_TEXTURE2); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); - - for (int i = 0; i < this.groundTextures.size(); i++) { - gl.glActiveTexture(GL30.GL_TEXTURE3 + i); - gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.groundTextures.get(i).id); - } + this.waveBuilder = new WaveBuilder(this.mapSize, this.waterTable, viewer, this.corners, this.centerOffset, + this.waterHeightOffset, w3eFile, w3iFile); + this.pathingGrid = new PathingGrid(terrainPathing, this.centerOffset); + } + + public void createWaves() { + this.waveBuilder.createWaves(this); + } + + private void updateGroundHeights(final Rectangle area) { + for (int j = (int) area.y; j < (area.y + area.height); j++) { + for (int i = (int) area.x; i < (area.x + area.width); i++) { + this.groundHeights[(j * this.columns) + i] = this.corners[i][j].getGroundHeight(); + + float rampHeight = 0f; + // Check if in one of the configurations the bottom_left is a ramp + XLoop: for (int xOffset = -1; xOffset <= 0; xOffset++) { + for (int yOffset = -1; yOffset <= 0; yOffset++) { + if (((i + xOffset) >= 0) && ((i + xOffset) < (this.columns - 1)) && ((j + yOffset) >= 0) + && ((j + yOffset) < (this.rows - 1))) { + final RenderCorner bottomLeft = this.corners[i + xOffset][j + yOffset]; + final RenderCorner bottomRight = this.corners[i + 1 + xOffset][j + yOffset]; + final RenderCorner topLeft = this.corners[i + xOffset][j + 1 + yOffset]; + final RenderCorner topRight = this.corners[i + 1 + xOffset][j + 1 + yOffset]; + + final int base = Math.min( + Math.min(bottomLeft.getLayerHeight(), bottomRight.getLayerHeight()), + Math.min(topLeft.getLayerHeight(), topRight.getLayerHeight())); + if (this.corners[i][j].getLayerHeight() != base) { + continue; + } + + if (isCornerRampEntrance(i + xOffset, j + yOffset)) { + rampHeight = 0.5f; + break XLoop; + } + } + } + } + + final RenderCorner corner = this.corners[i][j]; + final float newGroundCornerHeight = corner.computeFinalGroundHeight() + rampHeight; + this.groundCornerHeights[(j * this.columns) + i] = newGroundCornerHeight; + corner.depth = (corner.getWater() != 0) + ? (this.waterHeightOffset + corner.getWaterHeight()) - newGroundCornerHeight + : 0; + } + } + updateGroundHeights(); + updateCornerHeights(); + } + + private void updateGroundHeights() { + Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); + Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns, this.rows, GL30.GL_RED, GL30.GL_FLOAT, + RenderMathUtils.wrap(this.groundHeights)); + } + + private void updateCornerHeights() { + final FloatBuffer groundCornerHeightsWrapped = RenderMathUtils.wrap(this.groundCornerHeights); + Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); + Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns, this.rows, GL30.GL_RED, GL30.GL_FLOAT, + groundCornerHeightsWrapped); + Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeightLinear); + Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns, this.rows, GL30.GL_RED, GL30.GL_FLOAT, + groundCornerHeightsWrapped); + } + + /** + * calculateRamps() is copied from Riv whereas a lot of the rest of the terrain + * was copied from HiveWE + */ + private void calculateRamps() { + final int columns = this.mapSize[0]; + final int rows = this.mapSize[1]; + + final String[] ramps = { "AAHL", "AALH", "ABHL", "AHLA", "ALHA", "ALHB", "BALH", "BHLA", "HAAL", "HBAL", "HLAA", + "HLAB", "LAAH", "LABH", "LHAA", "LHBA" }; + + // Adjust terrain height inside ramps (set rampAdjust) + for (int y = 1; y < (rows - 1); ++y) { + for (int x = 1; x < (columns - 1); ++x) { + final RenderCorner o = this.corners[x][y]; + if (!o.isRamp()) { + continue; + } + final RenderCorner a = this.corners[x - 1][y - 1]; + final RenderCorner b = this.corners[x - 1][y]; + final RenderCorner c = this.corners[x - 1][y + 1]; + final RenderCorner d = this.corners[x][y + 1]; + final RenderCorner e = this.corners[x + 1][y + 1]; + final RenderCorner f = this.corners[x + 1][y]; + final RenderCorner g = this.corners[x + 1][y - 1]; + final RenderCorner h = this.corners[x][y - 1]; + final int base = o.getLayerHeight(); + if ((b.isRamp() && f.isRamp()) || (d.isRamp() && h.isRamp())) { + float adjust = 0; + if (b.isRamp() && f.isRamp()) { + adjust = Math.max(adjust, ((b.getLayerHeight() + f.getLayerHeight()) / 2) - base); + } + if (d.isRamp() && h.isRamp()) { + adjust = Math.max(adjust, ((d.getLayerHeight() + h.getLayerHeight()) / 2) - base); + } + if (a.isRamp() && e.isRamp()) { + adjust = Math.max(adjust, (((a.getLayerHeight() + e.getLayerHeight()) / 2) - base) / 2); + } + if (c.isRamp() && g.isRamp()) { + adjust = Math.max(adjust, (((c.getLayerHeight() + g.getLayerHeight()) / 2) - base) / 2); + } + o.rampAdjust = adjust; + } + } + } + } + + /// TODO clean + /// Function is a bit of a mess + /// Updates the cliff and ramp meshes for an area + private void updateCliffMeshes(final Rectangle area) throws IOException { + // Remove all existing cliff meshes in area + for (int i = this.cliffs.size(); i-- > 0;) { + final IVec3 pos = this.cliffs.get(i); + if (area.contains(pos.x, pos.y)) { + this.cliffs.remove(i); + } + } + + for (int i = (int) area.getX(); i < (int) (area.getX() + area.getWidth()); i++) { + for (int j = (int) area.getY(); j < (int) (area.getY() + area.getHeight()); j++) { + this.corners[i][j].romp = false; + } + } + + final Rectangle adjusted = new Rectangle(area.x - 2, area.y - 2, area.width + 4, area.height + 4); + final Rectangle rampArea = new Rectangle(); + Intersector.intersectRectangles(new Rectangle(0, 0, this.columns, this.rows), adjusted, rampArea); + + // Add new cliff meshes + final int xLimit = (int) ((rampArea.getX() + rampArea.getWidth()) - 1); + for (int i = (int) rampArea.getX(); i < xLimit; i++) { + final int yLimit = (int) ((rampArea.getY() + rampArea.getHeight()) - 1); + for (int j = (int) rampArea.getY(); j < yLimit; j++) { + final RenderCorner bottomLeft = this.corners[i][j]; + final RenderCorner bottomRight = this.corners[i + 1][j]; + final RenderCorner topLeft = this.corners[i][j + 1]; + final RenderCorner topRight = this.corners[i + 1][j + 1]; + + if (bottomLeft.cliff && !bottomLeft.hideCliff) { + final int base = Math.min(Math.min(bottomLeft.getLayerHeight(), bottomRight.getLayerHeight()), + Math.min(topLeft.getLayerHeight(), topRight.getLayerHeight())); + + final boolean facingDown = (topLeft.getLayerHeight() >= bottomLeft.getLayerHeight()) + && (topRight.getLayerHeight() >= bottomRight.getLayerHeight()); + final boolean facingLeft = (bottomRight.getLayerHeight() >= bottomLeft.getLayerHeight()) + && (topRight.getLayerHeight() >= topLeft.getLayerHeight()); + + int bottomLeftCliffTex = bottomLeft.getCliffTexture(); + if (bottomLeftCliffTex == 15) { + bottomLeftCliffTex -= 14; + } + if (!(facingDown && (j == 0)) && !(!facingDown && (j >= (this.rows - 2))) + && !(facingLeft && (i == 0)) && !(!facingLeft && (i >= (this.columns - 2)))) { + final boolean br = ((bottomLeft.getRamp() != 0) != (bottomRight.getRamp() != 0)) + && ((topLeft.getRamp() != 0) != (topRight.getRamp() != 0)) + && !this.corners[i + (bottomRight.getRamp() != 0 ? 1 : 0)][j + + (facingDown ? -1 : 1)].cliff; + + final boolean bo = ((bottomLeft.getRamp() != 0) != (topLeft.getRamp() != 0)) + && ((bottomRight.getRamp() != 0) != (topRight.getRamp() != 0)) + && !this.corners[i + (facingLeft ? -1 : 1)][j + (topLeft.getRamp() != 0 ? 1 : 0)].cliff; + + if (br || bo) { + String fileName = "" + (char) ((bottomLeft.getRamp() != 0 ? 'L' : 'A') + + ((bottomLeft.getLayerHeight() - base) * (bottomLeft.getRamp() != 0 ? -4 : 1))) + + (char) ((topLeft.getRamp() != 0 ? 'L' : 'A') + + ((topLeft.getLayerHeight() - base) * (topLeft.getRamp() != 0 ? -4 : 1))) + + (char) ((topRight.getRamp() != 0 ? 'L' : 'A') + + ((topRight.getLayerHeight() - base) * (topRight.getRamp() != 0 ? -4 : 1))) + + (char) ((bottomRight.getRamp() != 0 ? 'L' : 'A') + + ((bottomRight.getLayerHeight() - base) + * (bottomRight.getRamp() != 0 ? -4 : 1))); + + final String rampModelDir = this.cliffTextures.get(bottomLeftCliffTex).rampModelDir; + fileName = "Doodads\\Terrain\\" + rampModelDir + "\\" + rampModelDir + fileName + "0.mdx"; + + if (this.dataSource.has(fileName)) { + if (!this.pathToCliff.containsKey(fileName)) { + this.cliffMeshes.add(new CliffMesh(fileName, this.dataSource, Gdx.gl30)); + this.pathToCliff.put(fileName, this.cliffMeshes.size() - 1); + } + + for (int ji = this.cliffs.size(); ji-- > 0;) { + final IVec3 pos = this.cliffs.get(ji); + if ((pos.x == (i + ((bo ? 1 : 0) * (facingLeft ? 0 : 1)))) + && (pos.y == (j - ((br ? 1 : 0) * (facingDown ? 1 : 0))))) { + this.cliffs.remove(ji); + break; + } + } + + this.cliffs.add(new IVec3((i + ((bo ? 1 : 0) * (facingLeft ? 0 : 1))), + (j - ((br ? 1 : 0) * (facingDown ? 1 : 0))), this.pathToCliff.get(fileName))); + bottomLeft.romp = true; + + this.corners[i + ((facingLeft ? -1 : 1) * (bo ? 1 : 0))][j + + ((facingDown ? -1 : 1) * (br ? 1 : 0))].romp = true; + + continue; + } + } + } + + if (isCornerRampEntrance(i, j)) { + continue; + } + + // Ramps move 1 right/down in some cases and thus their area is one bigger to + // the top and left. + if (!area.contains(i, j)) { + continue; + } + + // Cliff model path + + String fileName = "" + (char) (('A' + bottomLeft.getLayerHeight()) - base) + + (char) (('A' + topLeft.getLayerHeight()) - base) + + (char) (('A' + topRight.getLayerHeight()) - base) + + (char) (('A' + bottomRight.getLayerHeight()) - base); + + if ("AAAA".equals(fileName)) { + continue; + } + + // Clamp to within max variations + + fileName = this.cliffTextures.get(bottomLeftCliffTex).cliffModelDir + fileName + + Variations.getCliffVariation(this.cliffTextures.get(bottomLeftCliffTex).cliffModelDir, + fileName, bottomLeft.getCliffVariation()); + if (!this.pathToCliff.containsKey(fileName)) { + throw new IllegalArgumentException("No such pathToCliff entry: " + fileName); + } + this.cliffs.add(new IVec3(i, j, this.pathToCliff.get(fileName))); + } + } + } + + } + + public void logRomp(final int x, final int y) { + System.out.println("romp: " + this.corners[x][y].romp); + System.out.println("ramp: " + this.corners[x][y].isRamp()); + System.out.println("cliff: " + this.corners[x][y].cliff); + } + + private void updateGroundTextures(final Rectangle area) { + final Rectangle adjusted = new Rectangle(area.x - 1, area.y - 1, area.width + 2, area.height + 2); + final Rectangle updateArea = new Rectangle(); + Intersector.intersectRectangles(new Rectangle(0, 0, this.columns - 1, this.rows - 1), adjusted, updateArea); + + for (int j = (int) (updateArea.getY()); j <= (int) ((updateArea.getY() + updateArea.getHeight()) - 1); j++) { + for (int i = (int) (updateArea.getX()); i <= (int) ((updateArea.getX() + updateArea.getWidth()) - 1); i++) { + getTextureVariations(i, j, this.groundTextureList, ((j * (this.columns - 1)) + i) * 4); + + if (this.corners[i][j].cliff || this.corners[i][j].romp) { + if (isCornerRampEntrance(i, j)) { + continue; + } + this.groundTextureList[(((j * (this.columns - 1)) + i) * 4) + 3] |= 0b1000000000000000; + } + } + } + + uploadGroundTexture(); + } + + public void removeTerrainCell(final int i, final int j) { + this.groundTextureList[(((j * (this.columns - 1)) + i) * 4) + 3] |= 0b1000000000000000; + this.corners[i][j].hideCliff = true; + uploadGroundTexture(); + try { + updateCliffMeshes(new Rectangle(i - 1, j - 1, 1, 1)); // TODO does this work? + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + + public void removeTerrainCellWithoutFlush(final int i, final int j) { + this.groundTextureList[(((j * (this.columns - 1)) + i) * 4) + 3] |= 0b1000000000000000; + this.corners[i][j].hideCliff = true; + } + + public void flushRemovedTerrainCells() { + uploadGroundTexture(); + try { + updateCliffMeshes(new Rectangle(0, 0, this.columns - 1, this.rows - 1)); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + + private void uploadGroundTexture() { + if (this.groundTextureData != -1) { + Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); + Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns - 1, this.rows - 1, GL30.GL_RGBA_INTEGER, + GL30.GL_UNSIGNED_SHORT, RenderMathUtils.wrapShort(this.groundTextureList)); + } + } + + /// The 4 ground textures of the tilepoint. First 5 bits are which texture array + /// to use and the next 5 bits are which subtexture to use + private void getTextureVariations(final int x, final int y, final short[] out, final int outStartOffset) { + final int bottomLeft = realTileTexture(x, y); + final int bottomRight = realTileTexture(x + 1, y); + final int topLeft = realTileTexture(x, y + 1); + final int topRight = realTileTexture(x + 1, y + 1); + + final TreeSet set = new TreeSet<>(); + set.add(bottomLeft); + set.add(bottomRight); + set.add(topLeft); + set.add(topRight); + Arrays.fill(out, outStartOffset, outStartOffset + 4, (short) 17); + int component = outStartOffset + 1; + + final Iterator iterator = set.iterator(); + iterator.hasNext(); + final short firstValue = iterator.next().shortValue(); + out[outStartOffset] = (short) (firstValue + + (getVariation(firstValue, this.corners[x][y].getGroundVariation()) << 5)); + + int index; + while (iterator.hasNext()) { + index = 0; + final int texture = iterator.next().intValue(); + index |= (bottomRight == texture ? 1 : 0) << 0; + index |= (bottomLeft == texture ? 1 : 0) << 1; + index |= (topRight == texture ? 1 : 0) << 2; + index |= (topLeft == texture ? 1 : 0) << 3; + + out[component++] = (short) (texture + (index << 5)); + } + } + + private int realTileTexture(final int x, final int y) { + ILoop: for (int i = -1; i < 1; i++) { + for (int j = -1; j < 1; j++) { + if (((x + i) >= 0) && ((x + i) < this.columns) && ((y + j) >= 0) && ((y + j) < this.rows)) { + if (this.corners[x + i][y + j].cliff) { + if (((x + i) < (this.columns - 1)) && ((y + j) < (this.rows - 1))) { + final RenderCorner bottomLeft = this.corners[x + i][y + j]; + final RenderCorner bottomRight = this.corners[x + i + 1][y + j]; + final RenderCorner topLeft = this.corners[x + i][y + j + 1]; + final RenderCorner topRight = this.corners[x + i + 1][y + j + 1]; + + if ((bottomLeft.getRamp() != 0) && (topLeft.getRamp() != 0) && (bottomRight.getRamp() != 0) + && (topRight.getRamp() != 0) && (!bottomLeft.romp) && (!bottomRight.romp) + && (!topLeft.romp) && (!topRight.romp)) { + break ILoop; + } + } + } + if (this.corners[x + i][y + j].romp || this.corners[x + i][y + j].cliff) { + int texture = this.corners[x + i][y + j].getCliffTexture(); + // Number 15 seems to be something + if (texture == 15) { + texture -= 14; + } + + return this.cliffToGroundTexture.get(texture); + } + } + } + } + + if (this.corners[x][y].getBlight() != 0) { + return this.blightTextureIndex; + } + + return this.corners[x][y].getGroundTexture(); + } + + private boolean isCornerRampEntrance(final int x, final int y) { + if ((x == this.columns) || (y == this.rows)) { + return false; + } + + final RenderCorner bottomLeft = this.corners[x][y]; + final RenderCorner bottomRight = this.corners[x + 1][y]; + final RenderCorner topLeft = this.corners[x][y + 1]; + final RenderCorner topRight = this.corners[x + 1][y + 1]; + + return (bottomLeft.getRamp() != 0) && (topLeft.getRamp() != 0) && (bottomRight.getRamp() != 0) + && (topRight.getRamp() != 0) && !((bottomLeft.getLayerHeight() == topRight.getLayerHeight()) + && (topLeft.getLayerHeight() == bottomRight.getLayerHeight())); + } + + private static void loadWaterColor(final float[] out, final String prefix, final Element waterInfo) { + for (int i = 0; i < colorTags.length; i++) { + final String colorTag = colorTags[i]; + out[i] = waterInfo == null ? 0.0f : waterInfo.getFieldFloatValue(prefix + "_" + colorTag) / 255f; + } + } + + public short getVariation(final int groundTexture, final int variation) { + final GroundTexture texture = this.groundTextures.get(groundTexture); + + // Extended ? + if (texture.extended) { + if (variation <= 15) { + return (short) (16 + variation); + } + else if (variation == 16) { + return 15; + } + else { + return 0; + } + } + else { + if (variation == 0) { + return 0; + } + else { + return 15; + } + } + } + + public void update() { + this.waterIndex += this.waterIncreasePerFrame; + + if (this.waterIndex >= this.waterTextureCount) { + this.waterIndex = 0; + } + } + + public void renderGround(final DynamicShadowManager dynamicShadowManager) { + // Render tiles + + this.webGL.useShaderProgram(this.groundShader); + + final GL30 gl = Gdx.gl30; + gl.glEnable(GL20.GL_CULL_FACE); + gl.glDisable(GL30.GL_BLEND); + gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); + gl.glEnable(GL20.GL_DEPTH_TEST); + gl.glDepthMask(true); + + gl.glUniformMatrix4fv(this.groundShader.getUniformLocation("MVP"), 1, false, + this.camera.viewProjectionMatrix.val, 0); + gl.glUniform1i(this.groundShader.getUniformLocation("show_pathing_map"), this.viewer.renderPathing); + gl.glUniform1i(this.groundShader.getUniformLocation("show_lighting"), this.viewer.renderLighting); + gl.glUniform1i(this.groundShader.getUniformLocation("height_texture"), 0); + gl.glUniform1i(this.groundShader.getUniformLocation("height_cliff_texture"), 1); + gl.glUniform1i(this.groundShader.getUniformLocation("terrain_texture_list"), 2); + gl.glUniform1i(this.groundShader.getUniformLocation("shadowMap"), 20); + gl.glUniform1f(this.groundShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); + gl.glUniform1f(this.groundShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); + + final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); + final DataTexture unitLightsTexture = lightManager.getTerrainLightsTexture(); + + unitLightsTexture.bind(21); + gl.glUniform1i(this.groundShader.getUniformLocation("lightTexture"), 21); + gl.glUniform1f(this.groundShader.getUniformLocation("lightCount"), lightManager.getTerrainLightCount()); + gl.glUniform1f(this.groundShader.getUniformLocation("lightTextureHeight"), unitLightsTexture.getHeight()); + + gl.glUniformMatrix4fv(this.groundShader.getUniformLocation("DepthBiasMVP"), 1, false, + dynamicShadowManager.getDepthBiasMVP().val, 0); + + gl.glActiveTexture(GL30.GL_TEXTURE0); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); + + gl.glActiveTexture(GL30.GL_TEXTURE1); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); + + gl.glActiveTexture(GL30.GL_TEXTURE2); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); + + for (int i = 0; i < this.groundTextures.size(); i++) { + gl.glActiveTexture(GL30.GL_TEXTURE3 + i); + gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.groundTextures.get(i).id); + } // gl.glActiveTexture(GL30.GL_TEXTURE20, /*pathingMap.getTextureStatic()*/); // gl.glActiveTexture(GL30.GL_TEXTURE21, /*pathingMap.getTextureDynamic()*/); - gl.glActiveTexture(GL30.GL_TEXTURE20); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); + gl.glActiveTexture(GL30.GL_TEXTURE20); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); // gl.glEnableVertexAttribArray(0); - gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, Shapes.INSTANCE.vertexBuffer); - gl.glVertexAttribPointer(0, 2, GL30.GL_FLOAT, false, 0, 0); + gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, Shapes.INSTANCE.vertexBuffer); + gl.glVertexAttribPointer(0, 2, GL30.GL_FLOAT, false, 0, 0); - gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, Shapes.INSTANCE.indexBuffer); - if (WIREFRAME_TERRAIN) { - Extensions.wireframeExtension.glPolygonMode(GL20.GL_FRONT_AND_BACK, Extensions.GL_LINE); - } - gl.glDrawElementsInstanced(GL30.GL_TRIANGLES, Shapes.INSTANCE.quadIndices.length * 3, GL30.GL_UNSIGNED_INT, 0, - (this.columns - 1) * (this.rows - 1)); - if (WIREFRAME_TERRAIN) { - Extensions.wireframeExtension.glPolygonMode(GL20.GL_FRONT_AND_BACK, Extensions.GL_FILL); - } + gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, Shapes.INSTANCE.indexBuffer); + if (WIREFRAME_TERRAIN) { + Extensions.wireframeExtension.glPolygonMode(GL20.GL_FRONT_AND_BACK, Extensions.GL_LINE); + } + gl.glDrawElementsInstanced(GL30.GL_TRIANGLES, Shapes.INSTANCE.quadIndices.length * 3, GL30.GL_UNSIGNED_INT, 0, + (this.columns - 1) * (this.rows - 1)); + if (WIREFRAME_TERRAIN) { + Extensions.wireframeExtension.glPolygonMode(GL20.GL_FRONT_AND_BACK, Extensions.GL_FILL); + } // gl.glDisableVertexAttribArray(0); - gl.glEnable(GL30.GL_BLEND); + gl.glEnable(GL30.GL_BLEND); - } + } - private GL30 renderGroundIntersectionMesh() { - if (true) { - throw new UnsupportedOperationException("No longer supported"); - } - this.webGL.useShaderProgram(this.testShader); + private GL30 renderGroundIntersectionMesh() { + if (true) { + throw new UnsupportedOperationException("No longer supported"); + } + this.webGL.useShaderProgram(this.testShader); - final GL30 gl = Gdx.gl30; - gl.glDisable(GL30.GL_CULL_FACE); - gl.glDisable(GL30.GL_BLEND); - this.testShader.setUniformMatrix("MVP", this.camera.viewProjectionMatrix); - gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.testArrayBuffer); - this.testShader.setVertexAttribute("vPosition", 3, GL30.GL_FLOAT, false, 12, 0); + final GL30 gl = Gdx.gl30; + gl.glDisable(GL30.GL_CULL_FACE); + gl.glDisable(GL30.GL_BLEND); + this.testShader.setUniformMatrix("MVP", this.camera.viewProjectionMatrix); + gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.testArrayBuffer); + this.testShader.setVertexAttribute("vPosition", 3, GL30.GL_FLOAT, false, 12, 0); - gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, this.testElementBuffer); - gl.glDrawElements(GL30.GL_LINES, this.softwareGroundMesh.indices.length, GL30.GL_UNSIGNED_SHORT, 0);// ); + gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, this.testElementBuffer); + gl.glDrawElements(GL30.GL_LINES, this.softwareGroundMesh.indices.length, GL30.GL_UNSIGNED_SHORT, 0);// ); - gl.glEnable(GL30.GL_BLEND); - return gl; - } + gl.glEnable(GL30.GL_BLEND); + return gl; + } - public void renderUberSplats() { - final GL30 gl = Gdx.gl30; - final WebGL webGL = this.webGL; - final ShaderProgram shader = this.uberSplatShader; + public void renderUberSplats() { + final GL30 gl = Gdx.gl30; + final WebGL webGL = this.webGL; + final ShaderProgram shader = this.uberSplatShader; - gl.glDepthMask(false); - gl.glEnable(GL30.GL_BLEND); - gl.glBlendFunc(GL30.GL_SRC_ALPHA, GL30.GL_ONE_MINUS_SRC_ALPHA); - gl.glBlendEquation(GL30.GL_FUNC_ADD); + gl.glDepthMask(false); + gl.glEnable(GL30.GL_BLEND); + gl.glBlendFunc(GL30.GL_SRC_ALPHA, GL30.GL_ONE_MINUS_SRC_ALPHA); + gl.glBlendEquation(GL30.GL_FUNC_ADD); - webGL.useShaderProgram(this.uberSplatShader); + webGL.useShaderProgram(this.uberSplatShader); - shader.setUniformMatrix("u_mvp", this.camera.viewProjectionMatrix); - shader.setUniformi("u_heightMap", 0); - sizeHeap[0] = this.columns - 1; - sizeHeap[1] = this.rows - 1; - shader.setUniform2fv("u_size", sizeHeap, 0, 2); - sizeHeap[0] = 1 / (float) this.columns; - sizeHeap[1] = 1 / (float) this.rows; - shader.setUniform2fv("u_pixel", sizeHeap, 0, 2); - shader.setUniform2fv("u_centerOffset", this.centerOffset, 0, 2); - shader.setUniformi("u_texture", 1); - shader.setUniformi("u_shadowMap", 2); + shader.setUniformMatrix("u_mvp", this.camera.viewProjectionMatrix); + shader.setUniformi("u_heightMap", 0); + sizeHeap[0] = this.columns - 1; + sizeHeap[1] = this.rows - 1; + shader.setUniform2fv("u_size", sizeHeap, 0, 2); + sizeHeap[0] = 1 / (float) this.columns; + sizeHeap[1] = 1 / (float) this.rows; + shader.setUniform2fv("u_pixel", sizeHeap, 0, 2); + shader.setUniform2fv("u_centerOffset", this.centerOffset, 0, 2); + shader.setUniformi("u_texture", 1); + shader.setUniformi("u_shadowMap", 2); - gl.glActiveTexture(GL30.GL_TEXTURE0); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeightLinear); + gl.glActiveTexture(GL30.GL_TEXTURE0); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeightLinear); - gl.glActiveTexture(GL30.GL_TEXTURE2); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); + gl.glActiveTexture(GL30.GL_TEXTURE2); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); - final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); - final DataTexture terrainLightsTexture = lightManager.getTerrainLightsTexture(); + final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); + final DataTexture terrainLightsTexture = lightManager.getTerrainLightsTexture(); - terrainLightsTexture.bind(21); - gl.glUniform1i(shader.getUniformLocation("u_lightTexture"), 21); - gl.glUniform1f(shader.getUniformLocation("u_lightCount"), lightManager.getTerrainLightCount()); - gl.glUniform1f(shader.getUniformLocation("u_lightTextureHeight"), terrainLightsTexture.getHeight()); + terrainLightsTexture.bind(21); + gl.glUniform1i(shader.getUniformLocation("u_lightTexture"), 21); + gl.glUniform1f(shader.getUniformLocation("u_lightCount"), lightManager.getTerrainLightCount()); + gl.glUniform1f(shader.getUniformLocation("u_lightTextureHeight"), terrainLightsTexture.getHeight()); - // Render the cliffs - for (final SplatModel splat : this.uberSplatModels.values()) { - splat.render(gl, shader); - } - } + // Render the cliffs + for (final SplatModel splat : this.uberSplatModels.values()) { + splat.render(gl, shader); + } + } - public void renderWater() { - // Render water - this.webGL.useShaderProgram(this.waterShader); + public void renderWater() { + // Render water + this.webGL.useShaderProgram(this.waterShader); - final GL30 gl = Gdx.gl30; - gl.glDepthMask(false); - gl.glDisable(GL30.GL_CULL_FACE); - gl.glEnable(GL30.GL_BLEND); - gl.glBlendFunc(GL30.GL_SRC_ALPHA, GL30.GL_ONE_MINUS_SRC_ALPHA); + final GL30 gl = Gdx.gl30; + gl.glDepthMask(false); + gl.glDisable(GL30.GL_CULL_FACE); + gl.glEnable(GL30.GL_BLEND); + gl.glBlendFunc(GL30.GL_SRC_ALPHA, GL30.GL_ONE_MINUS_SRC_ALPHA); - gl.glUniformMatrix4fv(0, 1, false, this.camera.viewProjectionMatrix.val, 0); - gl.glUniform4fv(1, 1, this.minShallowColor, 0); - gl.glUniform4fv(2, 1, this.maxShallowColor, 0); - gl.glUniform4fv(3, 1, this.minDeepColor, 0); - gl.glUniform4fv(4, 1, this.maxDeepColor, 0); - gl.glUniform1f(5, this.waterHeightOffset); - gl.glUniform1i(6, (int) this.waterIndex); - gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); - gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); - gl.glUniform4fv(11, 1, this.shaderMapBounds, 0); + gl.glUniformMatrix4fv(0, 1, false, this.camera.viewProjectionMatrix.val, 0); + gl.glUniform4fv(1, 1, this.minShallowColor, 0); + gl.glUniform4fv(2, 1, this.maxShallowColor, 0); + gl.glUniform4fv(3, 1, this.minDeepColor, 0); + gl.glUniform4fv(4, 1, this.maxDeepColor, 0); + gl.glUniform1f(5, this.waterHeightOffset); + gl.glUniform1i(6, (int) this.waterIndex); + gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); + gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); + gl.glUniform4fv(11, 1, this.shaderMapBounds, 0); - final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); - final DataTexture terrainLightsTexture = lightManager.getTerrainLightsTexture(); + final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); + final DataTexture terrainLightsTexture = lightManager.getTerrainLightsTexture(); - terrainLightsTexture.bind(3); - gl.glUniform1f(9, lightManager.getTerrainLightCount()); - gl.glUniform1f(10, terrainLightsTexture.getHeight()); + terrainLightsTexture.bind(3); + gl.glUniform1f(9, lightManager.getTerrainLightCount()); + gl.glUniform1f(10, terrainLightsTexture.getHeight()); - gl.glActiveTexture(GL30.GL_TEXTURE0); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight); - gl.glActiveTexture(GL30.GL_TEXTURE1); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); - gl.glActiveTexture(GL30.GL_TEXTURE2); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterExists); - gl.glActiveTexture(GL30.GL_TEXTURE4); - gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.waterTextureArray); + gl.glActiveTexture(GL30.GL_TEXTURE0); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight); + gl.glActiveTexture(GL30.GL_TEXTURE1); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); + gl.glActiveTexture(GL30.GL_TEXTURE2); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterExists); + gl.glActiveTexture(GL30.GL_TEXTURE4); + gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.waterTextureArray); - gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, Shapes.INSTANCE.vertexBuffer); - gl.glVertexAttribPointer(0, 2, GL30.GL_FLOAT, false, 0, 0); + gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, Shapes.INSTANCE.vertexBuffer); + gl.glVertexAttribPointer(0, 2, GL30.GL_FLOAT, false, 0, 0); - gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, Shapes.INSTANCE.indexBuffer); - gl.glDrawElementsInstanced(GL30.GL_TRIANGLES, Shapes.INSTANCE.quadIndices.length * 3, GL30.GL_UNSIGNED_INT, 0, - (this.columns - 1) * (this.rows - 1)); + gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, Shapes.INSTANCE.indexBuffer); + gl.glDrawElementsInstanced(GL30.GL_TRIANGLES, Shapes.INSTANCE.quadIndices.length * 3, GL30.GL_UNSIGNED_INT, 0, + (this.columns - 1) * (this.rows - 1)); - gl.glEnable(GL30.GL_BLEND); - } + gl.glEnable(GL30.GL_BLEND); + } - public void renderCliffs() { + public void renderCliffs() { - // Render cliffs - for (final IVec3 i : this.cliffs) { - final RenderCorner bottomLeft = this.corners[i.x][i.y]; - final RenderCorner bottomRight = this.corners[i.x + 1][i.y]; - final RenderCorner topLeft = this.corners[i.x][i.y + 1]; - final RenderCorner topRight = this.corners[i.x + 1][i.y + 1]; + // Render cliffs + for (final IVec3 i : this.cliffs) { + final RenderCorner bottomLeft = this.corners[i.x][i.y]; + final RenderCorner bottomRight = this.corners[i.x + 1][i.y]; + final RenderCorner topLeft = this.corners[i.x][i.y + 1]; + final RenderCorner topRight = this.corners[i.x + 1][i.y + 1]; - final float min = Math.min(Math.min(bottomLeft.getLayerHeight() - 2, bottomRight.getLayerHeight() - 2), - Math.min(topLeft.getLayerHeight() - 2, topRight.getLayerHeight() - 2)); + final float min = Math.min(Math.min(bottomLeft.getLayerHeight() - 2, bottomRight.getLayerHeight() - 2), + Math.min(topLeft.getLayerHeight() - 2, topRight.getLayerHeight() - 2)); - fourComponentHeap[0] = i.x; - fourComponentHeap[1] = i.y; - fourComponentHeap[2] = min; - fourComponentHeap[3] = bottomLeft.getCliffTexture(); - this.cliffMeshes.get(i.z).renderQueue(fourComponentHeap); - } + fourComponentHeap[0] = i.x; + fourComponentHeap[1] = i.y; + fourComponentHeap[2] = min; + fourComponentHeap[3] = bottomLeft.getCliffTexture(); + this.cliffMeshes.get(i.z).renderQueue(fourComponentHeap); + } - this.webGL.useShaderProgram(this.cliffShader); + this.webGL.useShaderProgram(this.cliffShader); - final GL30 gl = Gdx.gl30; - gl.glDisable(GL30.GL_BLEND); + final GL30 gl = Gdx.gl30; + gl.glDisable(GL30.GL_BLEND); - // WC3 models are 128x too large - tempMatrix.set(this.camera.viewProjectionMatrix); - gl.glUniformMatrix4fv(0, 1, false, tempMatrix.val, 0); - gl.glUniform1i(1, this.viewer.renderPathing); - gl.glUniform1i(2, this.viewer.renderLighting); + // WC3 models are 128x too large + tempMatrix.set(this.camera.viewProjectionMatrix); + gl.glUniformMatrix4fv(0, 1, false, tempMatrix.val, 0); + gl.glUniform1i(1, this.viewer.renderPathing); + gl.glUniform1i(2, this.viewer.renderLighting); - final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); - final DataTexture unitLightsTexture = lightManager.getTerrainLightsTexture(); + final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); + final DataTexture unitLightsTexture = lightManager.getTerrainLightsTexture(); - unitLightsTexture.bind(21); - gl.glUniform1i(this.cliffShader.getUniformLocation("lightTexture"), 21); - gl.glUniform1f(this.cliffShader.getUniformLocation("lightCount"), lightManager.getTerrainLightCount()); - gl.glUniform1f(this.cliffShader.getUniformLocation("lightTextureHeight"), unitLightsTexture.getHeight()); + unitLightsTexture.bind(21); + gl.glUniform1i(this.cliffShader.getUniformLocation("lightTexture"), 21); + gl.glUniform1f(this.cliffShader.getUniformLocation("lightCount"), lightManager.getTerrainLightCount()); + gl.glUniform1f(this.cliffShader.getUniformLocation("lightTextureHeight"), unitLightsTexture.getHeight()); - this.cliffShader.setUniformi("shadowMap", 2); - gl.glActiveTexture(GL30.GL_TEXTURE2); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); + this.cliffShader.setUniformi("shadowMap", 2); + gl.glActiveTexture(GL30.GL_TEXTURE2); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); - gl.glActiveTexture(GL30.GL_TEXTURE0); - gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.cliffTextureArray); - gl.glActiveTexture(GL30.GL_TEXTURE1); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); + gl.glActiveTexture(GL30.GL_TEXTURE0); + gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.cliffTextureArray); + gl.glActiveTexture(GL30.GL_TEXTURE1); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); // gl.glActiveTexture(GL30.GL_TEXTURE2); - for (final CliffMesh i : this.cliffMeshes) { - gl.glUniform1f(this.cliffShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); - gl.glUniform1f(this.cliffShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); - i.render(); - } - } + for (final CliffMesh i : this.cliffMeshes) { + gl.glUniform1f(this.cliffShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); + gl.glUniform1f(this.cliffShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); + i.render(); + } + } - public void addShadow(final String file, final float shadowX, final float shadowY) { - if (!this.shadows.containsKey(file)) { - final String path = "ReplaceableTextures\\Shadows\\" + file + ".blp"; - this.shadows.put(file, new ArrayList<>()); - this.shadowTextures.put(file, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null)); - } - this.shadows.get(file).add(new float[]{shadowX, shadowY}); - if (initShadowsFinished) { - final Texture texture = shadowTextures.get(file); + public BuildingShadow addShadow(final String file, final float shadowX, final float shadowY) { + if (!this.shadows.containsKey(file)) { + final String path = "ReplaceableTextures\\Shadows\\" + file + ".blp"; + this.shadows.put(file, new ArrayList<>()); + this.shadowTextures.put(file, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null)); + } + final List shadowList = this.shadows.get(file); + final float[] shadowPositionArray = new float[] { shadowX, shadowY }; + shadowList.add(shadowPositionArray); + if (this.initShadowsFinished) { + final Texture texture = this.shadowTextures.get(file); - final int columns = (this.columns - 1) * 4; - final int rows = (this.rows - 1) * 4; - blitShadowData(columns, rows, shadowX, shadowY, texture); - GL30 gl = Gdx.gl30; - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, columns, rows, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE, - RenderMathUtils.wrap(shadowData)); - } - } + final int columns = (this.columns - 1) * 4; + final int rows = (this.rows - 1) * 4; + blitShadowData(columns, rows, shadowX, shadowY, texture); + final GL30 gl = Gdx.gl30; + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, columns, rows, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE, + RenderMathUtils.wrap(this.shadowData)); + } + return new BuildingShadow() { + @Override + public void remove() { + shadowList.remove(shadowPositionArray); + reloadShadowDataToGPU(); + } - public void blitShadowData(int columns, int rows, float shadowX, float shadowY, Texture texture) { - final int width = texture.getWidth(); - final int height = texture.getHeight(); - final int ox = (int) Math.round(width * 0.3); - final int oy = (int) Math.round(height * 0.7); - blitShadowDataLocation(columns, rows, (RawOpenGLTextureResource) texture, width, height, ox, oy, centerOffset, shadowX, shadowY); - } + @Override + public void move(final float x, final float y) { + shadowPositionArray[0] = x; + shadowPositionArray[1] = y; + reloadShadowDataToGPU(); + } + }; + } - public void initShadows() throws IOException { - final GL30 gl = Gdx.gl30; - final float[] centerOffset = this.centerOffset; - final int columns = (this.columns - 1) * 4; - final int rows = (this.rows - 1) * 4; + public void blitShadowData(final int columns, final int rows, final float shadowX, final float shadowY, + final Texture texture) { + final int width = texture.getWidth(); + final int height = texture.getHeight(); + final int ox = (int) Math.round(width * 0.3); + final int oy = (int) Math.round(height * 0.7); + blitShadowDataLocation(columns, rows, (RawOpenGLTextureResource) texture, width, height, ox, oy, + this.centerOffset, shadowX, shadowY, this.shadowData); + } - final int shadowSize = columns * rows; - shadowData = new byte[columns * rows]; - if (this.viewer.mapMpq.has("war3map.shd")) { - final byte[] buffer; + public void initShadows() throws IOException { + final GL30 gl = Gdx.gl30; + final float[] centerOffset = this.centerOffset; + final int columns = (this.columns - 1) * 4; + final int rows = (this.rows - 1) * 4; - try (final InputStream shadowSource = this.viewer.mapMpq.getResourceAsStream("war3map.shd")) { - buffer = IOUtils.toByteArray(shadowSource); - } + final int shadowSize = columns * rows; + this.staticShadowData = new byte[columns * rows]; + this.shadowData = new byte[columns * rows]; + if (this.viewer.mapMpq.has("war3map.shd")) { + final byte[] buffer; - for (int i = 0; i < shadowSize; i++) { - shadowData[i] = (byte) ((buffer[i] & 0xFF) / 2f); - } - } + try (final InputStream shadowSource = this.viewer.mapMpq.getResourceAsStream("war3map.shd")) { + buffer = IOUtils.toByteArray(shadowSource); + } - for (final Map.Entry fileAndTexture : this.shadowTextures.entrySet()) { - final String file = fileAndTexture.getKey(); - final Texture texture = fileAndTexture.getValue(); + for (int i = 0; i < shadowSize; i++) { + this.staticShadowData[i] = (byte) ((buffer[i] & 0xFF) / 2f); + } + } - final int width = texture.getWidth(); - final int height = texture.getHeight(); - final int ox = (int) Math.round(width * 0.3); - final int oy = (int) Math.round(height * 0.7); - for (final float[] location : this.shadows.get(file)) { - blitShadowDataLocation(columns, rows, (RawOpenGLTextureResource) texture, width, height, ox, oy, centerOffset, location[0], location[1]); - } - } + final byte outsideArea = (byte) 204; + final int x0 = this.mapBounds[0] * 4, x1 = (this.mapSize[0] - this.mapBounds[1] - 1) * 4, + y0 = this.mapBounds[2] * 4, y1 = (this.mapSize[1] - this.mapBounds[3] - 1) * 4; + for (int y = y0; y < y1; ++y) { + for (int x = x0; x < x1; ++x) { + final RenderCorner c = this.corners[x >> 2][y >> 2]; + if (c.getBoundary() != 0) { + this.staticShadowData[(y * columns) + x] = outsideArea; + } + } + } + for (int y = 0; y < rows; ++y) { + for (int x = 0; x < x0; ++x) { + this.staticShadowData[(y * columns) + x] = outsideArea; + } + for (int x = x1; x < columns; ++x) { + this.staticShadowData[(y * columns) + x] = outsideArea; + } + } + for (int x = x0; x < x1; ++x) { + for (int y = 0; y < y0; ++y) { + this.staticShadowData[(y * columns) + x] = outsideArea; + } + for (int y = y1; y < rows; ++y) { + this.staticShadowData[(y * columns) + x] = outsideArea; + } + } + reloadShadowData(centerOffset, columns, rows); - final byte outsideArea = (byte) 204; - final int x0 = this.mapBounds[0] * 4, x1 = (this.mapSize[0] - this.mapBounds[1] - 1) * 4, - y0 = this.mapBounds[2] * 4, y1 = (this.mapSize[1] - this.mapBounds[3] - 1) * 4; - for (int y = y0; y < y1; ++y) { - for (int x = x0; x < x1; ++x) { - final RenderCorner c = this.corners[x >> 2][y >> 2]; - if (c.getBoundary() != 0) { - shadowData[(y * columns) + x] = outsideArea; - } - } - } - for (int y = 0; y < rows; ++y) { - for (int x = 0; x < x0; ++x) { - shadowData[(y * columns) + x] = outsideArea; - } - for (int x = x1; x < columns; ++x) { - shadowData[(y * columns) + x] = outsideArea; - } - } - for (int x = x0; x < x1; ++x) { - for (int y = 0; y < y0; ++y) { - shadowData[(y * columns) + x] = outsideArea; - } - for (int y = y1; y < rows; ++y) { - shadowData[(y * columns) + x] = outsideArea; - } - } + this.shadowMap = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, columns, rows, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE, + RenderMathUtils.wrap(this.shadowData)); + this.initShadowsFinished = true; + } - this.shadowMap = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, columns, rows, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE, - RenderMathUtils.wrap(shadowData)); - this.initShadowsFinished = true; - } + private void reloadShadowData(final float[] centerOffset, final int columns, final int rows) { + System.arraycopy(this.staticShadowData, 0, this.shadowData, 0, this.staticShadowData.length); + for (final Map.Entry fileAndTexture : this.shadowTextures.entrySet()) { + final String file = fileAndTexture.getKey(); + final Texture texture = fileAndTexture.getValue(); - public void blitShadowDataLocation(int columns, int rows, RawOpenGLTextureResource texture, int width, int height, int x01, int y01, float[] centerOffset, float v, float v2) { - final int x0 = (int) Math.floor((v - centerOffset[0]) / 32.0) - x01; - final int y0 = (int) Math.floor((v2 - centerOffset[1]) / 32.0) + y01; - for (int y = 0; y < height; ++y) { - if (((y0 - y) < 0) || ((y0 - y) >= rows)) { - continue; - } - for (int x = 0; x < width; ++x) { - if (((x0 + x) < 0) || ((x0 + x) >= columns)) { - continue; - } - if (texture.getData().get((((y * width) + x) * 4) + 3) != 0) { - shadowData[((y0 - y) * columns) + x0 + x] = (byte) 128; - } - } - } - } + final int width = texture.getWidth(); + final int height = texture.getHeight(); + final int ox = (int) Math.round(width * 0.3); + final int oy = (int) Math.round(height * 0.7); + for (final float[] location : this.shadows.get(file)) { + blitShadowDataLocation(columns, rows, (RawOpenGLTextureResource) texture, width, height, ox, oy, + centerOffset, location[0], location[1], this.shadowData); + } + } + } + + public void blitShadowDataLocation(final int columns, final int rows, final RawOpenGLTextureResource texture, + final int width, final int height, final int x01, final int y01, final float[] centerOffset, final float v, + final float v2, final byte[] shadowData) { + final int x0 = (int) Math.floor((v - centerOffset[0]) / 32.0) - x01; + final int y0 = (int) Math.floor((v2 - centerOffset[1]) / 32.0) + y01; + for (int y = 0; y < height; ++y) { + if (((y0 - y) < 0) || ((y0 - y) >= rows)) { + continue; + } + for (int x = 0; x < width; ++x) { + if (((x0 + x) < 0) || ((x0 + x) >= columns)) { + continue; + } + if (texture.getData().get((((y * width) + x) * 4) + 3) != 0) { + shadowData[((y0 - y) * columns) + x0 + x] = (byte) 128; + } + } + } + } // public Vector3 groundNormal(final Vector3 out, int x, int y) { // final float[] centerOffset = this.centerOffset; @@ -1250,180 +1291,204 @@ public class Terrain { // return out; // } - private final WaveBuilder waveBuilder; - public PathingGrid pathingGrid; - private final Rectangle shaderMapBoundsRectangle; - private final Rectangle entireMapRectangle; + private final WaveBuilder waveBuilder; + public PathingGrid pathingGrid; + private final Rectangle shaderMapBoundsRectangle; + private final Rectangle entireMapRectangle; - private static final class UnloadedTexture { - private final int width; - private final int height; - private final Buffer data; - private final String cliffModelDir; - private final String rampModelDir; + private static final class UnloadedTexture { + private final int width; + private final int height; + private final Buffer data; + private final String cliffModelDir; + private final String rampModelDir; - public UnloadedTexture(final int width, final int height, final Buffer data, final String cliffModelDir, - final String rampModelDir) { - this.width = width; - this.height = height; - this.data = data; - this.cliffModelDir = cliffModelDir; - this.rampModelDir = rampModelDir; - } + public UnloadedTexture(final int width, final int height, final Buffer data, final String cliffModelDir, + final String rampModelDir) { + this.width = width; + this.height = height; + this.data = data; + this.cliffModelDir = cliffModelDir; + this.rampModelDir = rampModelDir; + } - } + } - public float getGroundHeight(final float x, final float y) { - final float userCellSpaceX = (x - this.centerOffset[0]) / 128.0f; - final float userCellSpaceY = (y - this.centerOffset[1]) / 128.0f; - final int cellX = (int) userCellSpaceX; - final int cellY = (int) userCellSpaceY; + public float getGroundHeight(final float x, final float y) { + final float userCellSpaceX = (x - this.centerOffset[0]) / 128.0f; + final float userCellSpaceY = (y - this.centerOffset[1]) / 128.0f; + final int cellX = (int) userCellSpaceX; + final int cellY = (int) userCellSpaceY; - if ((cellX >= 0) && (cellX < (this.mapSize[0] - 1)) && (cellY >= 0) && (cellY < (this.mapSize[1] - 1))) { - final float bottomLeft = this.groundCornerHeights[(cellY * this.columns) + cellX]; - final float bottomRight = this.groundCornerHeights[(cellY * this.columns) + cellX + 1]; - final float topLeft = this.groundCornerHeights[((cellY + 1) * this.columns) + cellX]; - final float topRight = this.groundCornerHeights[((cellY + 1) * this.columns) + cellX + 1]; - final float sqX = userCellSpaceX - cellX; - final float sqY = userCellSpaceY - cellY; - float height; + if ((cellX >= 0) && (cellX < (this.mapSize[0] - 1)) && (cellY >= 0) && (cellY < (this.mapSize[1] - 1))) { + final float bottomLeft = this.groundCornerHeights[(cellY * this.columns) + cellX]; + final float bottomRight = this.groundCornerHeights[(cellY * this.columns) + cellX + 1]; + final float topLeft = this.groundCornerHeights[((cellY + 1) * this.columns) + cellX]; + final float topRight = this.groundCornerHeights[((cellY + 1) * this.columns) + cellX + 1]; + final float sqX = userCellSpaceX - cellX; + final float sqY = userCellSpaceY - cellY; + float height; - if ((sqX + sqY) < 1) { - height = bottomLeft + ((bottomRight - bottomLeft) * sqX) + ((topLeft - bottomLeft) * sqY); - } else { - height = topRight + ((bottomRight - topRight) * (1 - sqY)) + ((topLeft - topRight) * (1 - sqX)); - } + if ((sqX + sqY) < 1) { + height = bottomLeft + ((bottomRight - bottomLeft) * sqX) + ((topLeft - bottomLeft) * sqY); + } + else { + height = topRight + ((bottomRight - topRight) * (1 - sqY)) + ((topLeft - topRight) * (1 - sqX)); + } - return height * 128.0f; - } + return height * 128.0f; + } - return 0; - } + return 0; + } - public float getWaterHeight(final float x, final float y) { - final float userCellSpaceX = (x - this.centerOffset[0]) / 128.0f; - final float userCellSpaceY = (y - this.centerOffset[1]) / 128.0f; - final int cellX = (int) userCellSpaceX; - final int cellY = (int) userCellSpaceY; + public float getWaterHeight(final float x, final float y) { + final float userCellSpaceX = (x - this.centerOffset[0]) / 128.0f; + final float userCellSpaceY = (y - this.centerOffset[1]) / 128.0f; + final int cellX = (int) userCellSpaceX; + final int cellY = (int) userCellSpaceY; - if ((cellX >= 0) && (cellX < (this.mapSize[0] - 1)) && (cellY >= 0) && (cellY < (this.mapSize[1] - 1))) { - final float bottomLeft = this.waterHeights[(cellY * this.columns) + cellX]; - final float bottomRight = this.waterHeights[(cellY * this.columns) + cellX + 1]; - final float topLeft = this.waterHeights[((cellY + 1) * this.columns) + cellX]; - final float topRight = this.waterHeights[((cellY + 1) * this.columns) + cellX + 1]; - final float sqX = userCellSpaceX - cellX; - final float sqY = userCellSpaceY - cellY; - float height; + if ((cellX >= 0) && (cellX < (this.mapSize[0] - 1)) && (cellY >= 0) && (cellY < (this.mapSize[1] - 1))) { + final float bottomLeft = this.waterHeights[(cellY * this.columns) + cellX]; + final float bottomRight = this.waterHeights[(cellY * this.columns) + cellX + 1]; + final float topLeft = this.waterHeights[((cellY + 1) * this.columns) + cellX]; + final float topRight = this.waterHeights[((cellY + 1) * this.columns) + cellX + 1]; + final float sqX = userCellSpaceX - cellX; + final float sqY = userCellSpaceY - cellY; + float height; - if ((sqX + sqY) < 1) { - height = bottomLeft + ((bottomRight - bottomLeft) * sqX) + ((topLeft - bottomLeft) * sqY); - } else { - height = topRight + ((bottomRight - topRight) * (1 - sqY)) + ((topLeft - topRight) * (1 - sqX)); - } + if ((sqX + sqY) < 1) { + height = bottomLeft + ((bottomRight - bottomLeft) * sqX) + ((topLeft - bottomLeft) * sqY); + } + else { + height = topRight + ((bottomRight - topRight) * (1 - sqY)) + ((topLeft - topRight) * (1 - sqX)); + } - return ((height + this.waterHeightOffset) * 128.0f); - } + return ((height + this.waterHeightOffset) * 128.0f); + } - return this.waterHeightOffset * 128.0f; - } + return this.waterHeightOffset * 128.0f; + } - public static final class Splat { - public List locations = new ArrayList<>(); - public List> unitMapping = new ArrayList<>(); - public float opacity = 1; - } + public static final class Splat { + public List locations = new ArrayList<>(); + public List> unitMapping = new ArrayList<>(); + public float opacity = 1; + } - public void loadSplats() throws IOException { - for (final Map.Entry entry : this.splats.entrySet()) { - final String path = entry.getKey(); - final Splat splat = entry.getValue(); + public void loadSplats() throws IOException { + for (final Map.Entry entry : this.splats.entrySet()) { + final String path = entry.getKey(); + final Splat splat = entry.getValue(); - final SplatModel splatModel = new SplatModel(Gdx.gl30, - (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), splat.locations, this.centerOffset, - splat.unitMapping, false); - splatModel.color[3] = splat.opacity; - this.uberSplatModels.put(path, splatModel); - } - } + final SplatModel splatModel = new SplatModel(Gdx.gl30, + (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), splat.locations, this.centerOffset, + splat.unitMapping, false); + splatModel.color[3] = splat.opacity; + this.uberSplatModels.put(path, splatModel); + } + } - public void removeSplatBatchModel(String path) { - this.uberSplatModels.remove(path); - } + public void removeSplatBatchModel(final String path) { + this.uberSplatModels.remove(path); + } - public void addSplatBatchModel(String path, final SplatModel model) { - this.uberSplatModels.put(path, model); - } + public void addSplatBatchModel(final String path, final SplatModel model) { + this.uberSplatModels.put(path, model); + } - public void addUberSplat(String path, float x, float y, float z, float scale) { - SplatModel splatModel = this.uberSplatModels.get(path); - if (splatModel == null) { - splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), - new ArrayList<>(), centerOffset, null, false); - this.uberSplatModels.put(path, splatModel); - } - splatModel.add(x, y, z, scale, centerOffset); - } + public SplatMover addUberSplat(final String path, final float x, final float y, final float z, final float scale) { + SplatModel splatModel = this.uberSplatModels.get(path); + if (splatModel == null) { + splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), + new ArrayList<>(), this.centerOffset, null, false); + this.uberSplatModels.put(path, splatModel); + } + return splatModel.add(x - scale, y - scale, x + scale, y + scale, z, this.centerOffset); + } - public static final class SoftwareGroundMesh { - public final float[] vertices; - public final int[] indices; + public SplatMover addUnitShadowSplat(final String texture, final float x, final float y, final float x2, + final float y2, final float zDepthUpward, final float opacity) { + SplatModel splatModel = this.uberSplatModels.get(texture); + if (splatModel == null) { + splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(texture, PathSolver.DEFAULT, null), + new ArrayList<>(), this.centerOffset, null, false); + splatModel.color[3] = opacity; + this.uberSplatModels.put(texture, splatModel); + } + return splatModel.add(x, y, x2, y2, zDepthUpward, this.centerOffset); + } - private SoftwareGroundMesh(final float[] groundHeights, final float[] groundCornerHeights, - final float[] centerOffset, final int columns, final int rows) { - this.vertices = new float[(columns - 1) * (rows - 1) * Shapes.INSTANCE.quadVertices.length * 3]; - this.indices = new int[(columns - 1) * (rows - 1) * Shapes.INSTANCE.quadIndices.length * 3]; - for (int y = 0; y < (rows - 1); y++) { - for (int x = 0; x < (columns - 1); x++) { - final int instanceId = (y * (columns - 1)) + x; - for (int vertexId = 0; vertexId < Shapes.INSTANCE.quadVertices.length; vertexId++) { - final float vPositionX = Shapes.INSTANCE.quadVertices[vertexId][0]; - final float vPositionY = Shapes.INSTANCE.quadVertices[vertexId][1]; - final int groundCornerHeightIndex = (int) (((vPositionY + y) * (columns)) + (vPositionX + x)); - final float height = groundCornerHeights[groundCornerHeightIndex]; - this.vertices[(instanceId * 4 * 3) + (vertexId * 3)] = ((vPositionX + x) * 128f) - + centerOffset[0]; - this.vertices[(instanceId * 4 * 3) + (vertexId * 3) + 1] = ((vPositionY + y) * 128f) - + centerOffset[1]; - this.vertices[(instanceId * 4 * 3) + (vertexId * 3) + 2] = height * 128f; - } - for (int triangle = 0; triangle < Shapes.INSTANCE.quadIndices.length; triangle++) { - for (int vertexId = 0; vertexId < Shapes.INSTANCE.quadIndices[triangle].length; vertexId++) { - final int vertexIndex = Shapes.INSTANCE.quadIndices[triangle][vertexId]; - final int indexValue = (vertexIndex + (instanceId * 4)); - if ((indexValue * 3) >= this.vertices.length) { - throw new IllegalStateException(); - } - this.indices[(instanceId * 2 * 3) + (triangle * 3) + vertexId] = indexValue; - } - } - } - } - } - } + public static final class SoftwareGroundMesh { + public final float[] vertices; + public final int[] indices; - public boolean inPlayableArea(float x, float y) { - x = (x - this.centerOffset[0]) / 128.0f; - y = (y - this.centerOffset[1]) / 128.0f; - if (x < this.mapBounds[0]) { - return false; - } - if (x >= (this.mapSize[0] - this.mapBounds[1] - 1)) { - return false; - } - if (y < this.mapBounds[2]) { - return false; - } - if (y >= (this.mapSize[1] - this.mapBounds[3] - 1)) { - return false; - } // TODO why do we use floor if we can use int cast? - return this.corners[(int) Math.floor(x)][(int) Math.floor(y)].getBoundary() == 0; - } + private SoftwareGroundMesh(final float[] groundHeights, final float[] groundCornerHeights, + final float[] centerOffset, final int columns, final int rows) { + this.vertices = new float[(columns - 1) * (rows - 1) * Shapes.INSTANCE.quadVertices.length * 3]; + this.indices = new int[(columns - 1) * (rows - 1) * Shapes.INSTANCE.quadIndices.length * 3]; + for (int y = 0; y < (rows - 1); y++) { + for (int x = 0; x < (columns - 1); x++) { + final int instanceId = (y * (columns - 1)) + x; + for (int vertexId = 0; vertexId < Shapes.INSTANCE.quadVertices.length; vertexId++) { + final float vPositionX = Shapes.INSTANCE.quadVertices[vertexId][0]; + final float vPositionY = Shapes.INSTANCE.quadVertices[vertexId][1]; + final int groundCornerHeightIndex = (int) (((vPositionY + y) * (columns)) + (vPositionX + x)); + final float height = groundCornerHeights[groundCornerHeightIndex]; + this.vertices[(instanceId * 4 * 3) + (vertexId * 3)] = ((vPositionX + x) * 128f) + + centerOffset[0]; + this.vertices[(instanceId * 4 * 3) + (vertexId * 3) + 1] = ((vPositionY + y) * 128f) + + centerOffset[1]; + this.vertices[(instanceId * 4 * 3) + (vertexId * 3) + 2] = height * 128f; + } + for (int triangle = 0; triangle < Shapes.INSTANCE.quadIndices.length; triangle++) { + for (int vertexId = 0; vertexId < Shapes.INSTANCE.quadIndices[triangle].length; vertexId++) { + final int vertexIndex = Shapes.INSTANCE.quadIndices[triangle][vertexId]; + final int indexValue = (vertexIndex + (instanceId * 4)); + if ((indexValue * 3) >= this.vertices.length) { + throw new IllegalStateException(); + } + this.indices[(instanceId * 2 * 3) + (triangle * 3) + vertexId] = indexValue; + } + } + } + } + } + } - public Rectangle getPlayableMapArea() { - return this.shaderMapBoundsRectangle; - } + public boolean inPlayableArea(float x, float y) { + x = (x - this.centerOffset[0]) / 128.0f; + y = (y - this.centerOffset[1]) / 128.0f; + if (x < this.mapBounds[0]) { + return false; + } + if (x >= (this.mapSize[0] - this.mapBounds[1] - 1)) { + return false; + } + if (y < this.mapBounds[2]) { + return false; + } + if (y >= (this.mapSize[1] - this.mapBounds[3] - 1)) { + return false; + } // TODO why do we use floor if we can use int cast? + return this.corners[(int) Math.floor(x)][(int) Math.floor(y)].getBoundary() == 0; + } - public Rectangle getEntireMap() { - return this.entireMapRectangle; - } + public Rectangle getPlayableMapArea() { + return this.shaderMapBoundsRectangle; + } + + public Rectangle getEntireMap() { + return this.entireMapRectangle; + } + + private void reloadShadowDataToGPU() { + final int columns = (Terrain.this.columns - 1) * 4; + final int rows = (Terrain.this.rows - 1) * 4; + reloadShadowData(Terrain.this.centerOffset, columns, rows); + final GL30 gl = Gdx.gl30; + gl.glBindTexture(GL30.GL_TEXTURE_2D, Terrain.this.shadowMap); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, columns, rows, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE, + RenderMathUtils.wrap(Terrain.this.shadowData)); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 10f6bf9..1af84bb 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -52,8 +52,6 @@ public class RenderUnit { public SplatMover shadow; public SplatMover selectionCircle; - private float x; - private float y; private float facing; private boolean swimming; @@ -67,13 +65,17 @@ public class RenderUnit { private boolean corpse; private boolean boneCorpse; private final RenderUnitTypeData typeData; + public final MdxModel specialArtModel; + public SplatMover uberSplat; 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 portraitModel, final CUnit simulationUnit, final RenderUnitTypeData typeData, + final MdxModel specialArtModel) { this.portraitModel = portraitModel; this.simulationUnit = simulationUnit; this.typeData = typeData; + this.specialArtModel = specialArtModel; final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance(); this.location[0] = x; @@ -83,8 +85,6 @@ public class RenderUnit { this.facing = simulationUnit.getFacing(); final float angle = (float) Math.toRadians(this.facing); // instance.localRotation.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle); - this.x = simulationUnit.getX(); - this.y = simulationUnit.getY(); instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle)); this.playerIndex = playerIndex & 0xFFFF; instance.setTeamColor(this.playerIndex); @@ -139,9 +139,11 @@ public class RenderUnit { public void populateCommandCard(final CSimulation game, final CommandButtonListener commandButtonListener, final AbilityDataUI abilityDataUI, final int subMenuOrderId) { + final CommandCardPopulatingAbilityVisitor commandCardPopulatingVisitor = CommandCardPopulatingAbilityVisitor.INSTANCE + .reset(game, this.simulationUnit, commandButtonListener, abilityDataUI, subMenuOrderId, + this.simulationUnit.isConstructing()); for (final CAbility ability : this.simulationUnit.getAbilities()) { - ability.visit(CommandCardPopulatingAbilityVisitor.INSTANCE.reset(game, this.simulationUnit, - commandButtonListener, abilityDataUI, subMenuOrderId)); + ability.visit(commandCardPopulatingVisitor); } } @@ -161,48 +163,48 @@ public class RenderUnit { } else { this.instance.show(); + if (wasHidden) { + if (this.shadow != null) { + this.shadow.show(map.terrain.centerOffset); + } + } } + final float prevX = this.location[0]; + final float prevY = this.location[1]; final float simulationX = this.simulationUnit.getX(); final float simulationY = this.simulationUnit.getY(); - if (wasHidden) { - this.x = simulationX; - this.y = simulationY; - } final float deltaTime = Gdx.graphics.getDeltaTime(); - final float simDx = simulationX - this.x; - final float simDy = simulationY - this.y; + final float simDx = simulationX - this.location[0]; + final float simDy = simulationY - this.location[1]; final float distanceToSimulation = (float) Math.sqrt((simDx * simDx) + (simDy * simDy)); final int speed = this.simulationUnit.getSpeed(); final float speedDelta = speed * deltaTime; if ((distanceToSimulation > speedDelta) && (deltaTime < 1.0)) { // The 1.0 here says that after 1 second of lag, units just teleport to show // where they actually are - this.x += (speedDelta * simDx) / distanceToSimulation; - this.y += (speedDelta * simDy) / distanceToSimulation; + this.location[0] += (speedDelta * simDx) / distanceToSimulation; + this.location[1] += (speedDelta * simDy) / distanceToSimulation; } else { - this.x = simulationX; - this.y = simulationY; + this.location[0] = simulationX; + this.location[1] = simulationY; } - final float x = this.x; - final float dx = x - this.location[0]; - this.location[0] = x; - final float y = this.y; - final float dy = y - this.location[1]; - this.location[1] = y; + final float dx = this.location[0] - prevX; + final float dy = this.location[1] - prevY; final float groundHeight; final MovementType movementType = this.simulationUnit.getUnitType().getMovementType(); - final short terrainPathing = map.terrain.pathingGrid.getPathing(x, y); + final short terrainPathing = map.terrain.pathingGrid.getPathing(this.location[0], this.location[1]); boolean swimming = (movementType == MovementType.AMPHIBIOUS) && PathingGrid.isPathingFlag(terrainPathing, PathingGrid.PathingType.SWIMMABLE) && !PathingGrid.isPathingFlag(terrainPathing, PathingGrid.PathingType.WALKABLE); - final float groundHeightTerrain = map.terrain.getGroundHeight(x, y); + final float groundHeightTerrain = map.terrain.getGroundHeight(this.location[0], this.location[1]); float groundHeightTerrainAndWater; MdxComplexInstance currentWalkableUnder; final boolean standingOnWater = (swimming) || (movementType == MovementType.FLOAT) || (movementType == MovementType.FLY) || (movementType == MovementType.HOVER); if (standingOnWater) { - groundHeightTerrainAndWater = Math.max(groundHeightTerrain, map.terrain.getWaterHeight(x, y)); + groundHeightTerrainAndWater = Math.max(groundHeightTerrain, + map.terrain.getWaterHeight(this.location[0], this.location[1])); } else { // land units will have their feet pass under the surface of the water @@ -214,8 +216,8 @@ public class RenderUnit { currentWalkableUnder = null; } else { - currentWalkableUnder = map.getHighestWalkableUnder(x, y); - War3MapViewer.gdxRayHeap.set(x, y, 4096, 0, 0, -8192); + currentWalkableUnder = map.getHighestWalkableUnder(this.location[0], this.location[1]); + War3MapViewer.gdxRayHeap.set(this.location[0], this.location[1], 4096, 0, 0, -8192); if ((currentWalkableUnder != null) && currentWalkableUnder.intersectRayWithCollision(War3MapViewer.gdxRayHeap, War3MapViewer.intersectionHeap, true, true) @@ -244,6 +246,10 @@ public class RenderUnit { this.shadow.destroy(Gdx.gl30, map.terrain.centerOffset); this.shadow = null; } + if (this.uberSplat != null) { + this.uberSplat.destroy(Gdx.gl30, map.terrain.centerOffset); + this.uberSplat = null; + } if (this.selectionCircle != null) { this.selectionCircle.destroy(Gdx.gl30, map.terrain.centerOffset); this.selectionCircle = null; @@ -311,15 +317,15 @@ public class RenderUnit { final float maxRoll = this.typeData.getMaxRoll(); final float sampleRadius = this.typeData.getElevationSampleRadius(); float pitch, roll; - final float pitchSampleForwardX = x + (sampleRadius * (float) Math.cos(facingRadians)); - final float pitchSampleForwardY = y + (sampleRadius * (float) Math.sin(facingRadians)); - final float pitchSampleBackwardX = x - (sampleRadius * (float) Math.cos(facingRadians)); - final float pitchSampleBackwardY = y - (sampleRadius * (float) Math.sin(facingRadians)); + final float pitchSampleForwardX = this.location[0] + (sampleRadius * (float) Math.cos(facingRadians)); + final float pitchSampleForwardY = this.location[1] + (sampleRadius * (float) Math.sin(facingRadians)); + final float pitchSampleBackwardX = this.location[0] - (sampleRadius * (float) Math.cos(facingRadians)); + final float pitchSampleBackwardY = this.location[1] - (sampleRadius * (float) Math.sin(facingRadians)); final double leftOfFacingAngle = facingRadians + (Math.PI / 2); - final float rollSampleForwardX = x + (sampleRadius * (float) Math.cos(leftOfFacingAngle)); - final float rollSampleForwardY = y + (sampleRadius * (float) Math.sin(leftOfFacingAngle)); - final float rollSampleBackwardX = x - (sampleRadius * (float) Math.cos(leftOfFacingAngle)); - final float rollSampleBackwardY = y - (sampleRadius * (float) Math.sin(leftOfFacingAngle)); + final float rollSampleForwardX = this.location[0] + (sampleRadius * (float) Math.cos(leftOfFacingAngle)); + final float rollSampleForwardY = this.location[1] + (sampleRadius * (float) Math.sin(leftOfFacingAngle)); + final float rollSampleBackwardX = this.location[0] - (sampleRadius * (float) Math.cos(leftOfFacingAngle)); + final float rollSampleBackwardY = this.location[1] - (sampleRadius * (float) Math.sin(leftOfFacingAngle)); final float pitchSampleGroundHeight1; final float pitchSampleGroundHeight2; final float rollSampleGroundHeight1; @@ -504,4 +510,21 @@ public class RenderUnit { this.allowRarityVariations = allowRarityVariations; } } + + public void repositioned(final War3MapViewer map) { + final float prevX = this.location[0]; + final float prevY = this.location[1]; + final float simulationX = this.simulationUnit.getX(); + final float simulationY = this.simulationUnit.getY(); + final float dx = simulationX - prevX; + final float dy = simulationY - prevY; + if (this.shadow != null) { + this.shadow.move(dx, dy, map.terrain.centerOffset); + } + if (this.selectionCircle != null) { + this.selectionCircle.move(dx, dy, map.terrain.centerOffset); + } + this.location[0] = this.simulationUnit.getX(); + this.location[1] = this.simulationUnit.getY(); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java index a9b97a7..d136b3b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java @@ -1,6 +1,8 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import com.badlogic.gdx.graphics.Texture; @@ -26,8 +28,14 @@ public class AbilityDataUI { private static final War3ID UNIT_ICON_NORMAL_Y = War3ID.fromString("ubpy"); private static final War3ID UNIT_ICON_NORMAL = War3ID.fromString("uico"); + private static final War3ID UPGRADE_ICON_NORMAL_X = War3ID.fromString("gbpx"); + private static final War3ID UPGRADE_ICON_NORMAL_Y = War3ID.fromString("gbpy"); + private static final War3ID UPGRADE_ICON_NORMAL = War3ID.fromString("gar1"); + private static final War3ID UPGRADE_LEVELS = War3ID.fromString("glvl"); + private final Map rawcodeToUI = new HashMap<>(); private final Map rawcodeToUnitUI = new HashMap<>(); + private final Map> rawcodeToUpgradeUI = new HashMap<>(); private final IconUI moveUI; private final IconUI stopUI; private final IconUI holdPosUI; @@ -41,8 +49,10 @@ public class AbilityDataUI { private final IconUI buildNeutralUI; private final IconUI buildNagaUI; private final IconUI cancelUI; + private final IconUI cancelBuildUI; - public AbilityDataUI(final MutableObjectData abilityData, final MutableObjectData unitData, final GameUI gameUI) { + public AbilityDataUI(final MutableObjectData abilityData, final MutableObjectData unitData, + final MutableObjectData upgradeData, final GameUI gameUI) { final String disabledPrefix = gameUI.getSkinField("CommandButtonDisabledArtPath"); for (final War3ID alias : abilityData.keySet()) { final MutableGameObject abilityTypeData = abilityData.get(alias); @@ -75,6 +85,21 @@ public class AbilityDataUI { final Texture iconNormalDisabled = gameUI.loadTexture(disable(iconNormalPath, disabledPrefix)); this.rawcodeToUnitUI.put(alias, new IconUI(iconNormal, iconNormalDisabled, iconNormalX, iconNormalY)); } + for (final War3ID alias : upgradeData.keySet()) { + final MutableGameObject upgradeTypeData = upgradeData.get(alias); + final int upgradeLevels = upgradeTypeData.getFieldAsInteger(UPGRADE_LEVELS, 0); + final int iconNormalX = upgradeTypeData.getFieldAsInteger(UPGRADE_ICON_NORMAL_X, 0); + final int iconNormalY = upgradeTypeData.getFieldAsInteger(UPGRADE_ICON_NORMAL_Y, 0); + final List upgradeIconsByLevel = new ArrayList<>(); + for (int i = 0; i < upgradeLevels; i++) { + final String iconNormalPath = gameUI + .trySkinField(upgradeTypeData.getFieldAsString(UPGRADE_ICON_NORMAL, i)); + final Texture iconNormal = gameUI.loadTexture(iconNormalPath); + final Texture iconNormalDisabled = gameUI.loadTexture(disable(iconNormalPath, disabledPrefix)); + upgradeIconsByLevel.add(new IconUI(iconNormal, iconNormalDisabled, iconNormalX, iconNormalY)); + } + this.rawcodeToUpgradeUI.put(alias, upgradeIconsByLevel); + } this.moveUI = createBuiltInIconUI(gameUI, "CmdMove", disabledPrefix); this.stopUI = createBuiltInIconUI(gameUI, "CmdStop", disabledPrefix); this.holdPosUI = createBuiltInIconUI(gameUI, "CmdHoldPos", disabledPrefix); @@ -88,6 +113,7 @@ public class AbilityDataUI { this.buildNeutralUI = createBuiltInIconUI(gameUI, "CmdBuild", disabledPrefix); this.attackGroundUI = createBuiltInIconUI(gameUI, "CmdAttackGround", disabledPrefix); this.cancelUI = createBuiltInIconUI(gameUI, "CmdCancel", disabledPrefix); + this.cancelBuildUI = createBuiltInIconUI(gameUI, "CmdCancelBuild", disabledPrefix); } private IconUI createBuiltInIconUI(final GameUI gameUI, final String key, final String disabledPrefix) { @@ -108,6 +134,19 @@ public class AbilityDataUI { return this.rawcodeToUnitUI.get(rawcode); } + public IconUI getUpgradeUI(final War3ID rawcode, final int level) { + final List upgradeUI = this.rawcodeToUpgradeUI.get(rawcode); + if (upgradeUI != null) { + if (level < upgradeUI.size()) { + return upgradeUI.get(level); + } + else { + return upgradeUI.get(upgradeUI.size() - 1); + } + } + return null; + } + private static String disable(final String path, final String disabledPrefix) { final int slashIndex = path.lastIndexOf('\\'); String name = path; @@ -169,4 +208,8 @@ public class AbilityDataUI { return this.cancelUI; } + public IconUI getCancelBuildUI() { + return this.cancelBuildUI; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java index 6b350d8..fd3e865 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java @@ -11,6 +11,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityG import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.AbstractCAbilityBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityBuildInProgress; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityHumanBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNagaBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNeutralBuild; @@ -18,7 +19,9 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityOrcBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityUndeadBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor { public static final CommandCardPopulatingAbilityVisitor INSTANCE = new CommandCardPopulatingAbilityVisitor(); @@ -29,22 +32,24 @@ public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor(); this.newUnits = new ArrayList<>(); this.projectiles = new ArrayList<>(); @@ -118,14 +120,12 @@ public class CSimulation { } public CUnit createUnit(final War3ID typeId, final int playerIndex, final float x, final float y, - final float facing, final BufferedImage buildingPathingPixelMap) { + final float facing, final BufferedImage buildingPathingPixelMap, + final RemovablePathingMapInstance pathingInstance, final BuildingShadow buildingShadowInstance) { final CUnit unit = this.unitData.create(this, playerIndex, typeId, x, y, facing, buildingPathingPixelMap, - this.simulationRenderController, this.handleIdAllocator); + this.handleIdAllocator, pathingInstance, buildingShadowInstance); this.newUnits.add(unit); this.handleIdToUnit.put(unit.getHandleId(), unit); - for (final CAbility ability : unit.getAbilities()) { - this.handleIdToAbility.put(ability.getHandleId(), ability); - } this.worldCollision.addUnit(unit); return unit; } @@ -143,6 +143,10 @@ public class CSimulation { return this.handleIdToAbility.get(handleId); } + protected void onAbilityAddedToUnit(final CUnit unit, final CAbility ability) { + this.handleIdToAbility.put(ability.getHandleId(), ability); + } + public CAttackProjectile createProjectile(final CUnit source, final float launchX, final float launchY, final float launchFacing, final CUnitAttackMissile attack, final CWidget target, final float damage, final int bounceIndex) { @@ -223,7 +227,7 @@ public class CSimulation { this.simulationRenderController.spawnUnitDamageSound(damagedUnit, weaponSound, armorType); } - public void unitConstructedEvent(CUnit constructingUnit, CUnit constructedStructure) { + public void unitConstructedEvent(final CUnit constructingUnit, final CUnit constructedStructure) { this.simulationRenderController.spawnUnitConstructionSound(constructingUnit, constructedStructure); } @@ -235,7 +239,23 @@ public class CSimulation { return this.commandErrorListener; } - public void unitConstructFinishEvent(CUnit constructedStructure) { + public void unitConstructFinishEvent(final CUnit constructedStructure) { this.simulationRenderController.spawnUnitConstructionFinishSound(constructedStructure); - } + } + + public void createBuildingDeathEffect(final CUnit cUnit) { + this.simulationRenderController.spawnBuildingDeathEffect(cUnit); + } + + public HandleIdAllocator getHandleIdAllocator() { + return this.handleIdAllocator; + } + + public void unitTrainedEvent(final CUnit trainingUnit, final CUnit trainedUnit) { + this.simulationRenderController.spawnUnitReadySound(trainedUnit); + } + + public void unitRepositioned(final CUnit cUnit) { + this.simulationRenderController.unitRepositioned(cUnit); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index a2e10cc..c34de81 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -3,6 +3,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.EnumSet; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Queue; @@ -12,8 +13,11 @@ import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.BuildingShadow; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.RemovablePathingMapInstance; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitStateListener.CUnitStateNotifier; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityBuildInProgress; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorFollow; @@ -49,6 +53,8 @@ public class CUnit extends CWidget { private final CUnitType unitType; private Rectangle collisionRectangle; + private RemovablePathingMapInstance pathingInstance; + private BuildingShadow buildingShadowInstance; private final EnumSet classifications = EnumSet.noneOf(CUnitClassification.class); @@ -75,10 +81,13 @@ public class CUnit extends CWidget { private boolean hidden = false; private boolean updating = true; private CUnit workerInside; + private final War3ID[] buildQueue = new War3ID[WarsmashConstants.BUILD_QUEUE_SIZE]; + private final QueueItemType[] buildQueueTypes = new QueueItemType[WarsmashConstants.BUILD_QUEUE_SIZE]; public CUnit(final int handleId, final int playerIndex, final float x, final float y, final float life, final War3ID typeId, final float facing, final float mana, final int maximumLife, final int maximumMana, - final int speed, final int defense, final CUnitType unitType) { + final int speed, final int defense, final CUnitType unitType, + final RemovablePathingMapInstance pathingInstance, final BuildingShadow buildingShadowInstance) { super(handleId, x, y, life); this.playerIndex = playerIndex; this.typeId = typeId; @@ -88,6 +97,8 @@ public class CUnit extends CWidget { this.maximumMana = maximumMana; this.speed = speed; this.defense = defense; + this.pathingInstance = pathingInstance; + this.buildingShadowInstance = buildingShadowInstance; this.flyHeight = unitType.getDefaultFlyingHeight(); this.unitType = unitType; this.classifications.addAll(unitType.getClassifications()); @@ -107,6 +118,7 @@ public class CUnit extends CWidget { public void add(final CSimulation simulation, final CAbility ability) { this.abilities.add(ability); + simulation.onAbilityAddedToUnit(this, ability); ability.onAdd(simulation, this); } @@ -207,33 +219,87 @@ public class CUnit extends CWidget { else if (this.updating) { if (this.constructing) { this.constructionProgress += WarsmashConstants.SIMULATION_STEP_TIME; - if (this.constructionProgress >= this.unitType.getBuildTime()) { + final int buildTime = this.unitType.getBuildTime(); + final float healthGain = (WarsmashConstants.SIMULATION_STEP_TIME / buildTime) + * (this.maximumLife * (1.0f - WarsmashConstants.BUILDING_CONSTRUCT_START_LIFE)); + setLife(game, Math.min(this.life + healthGain, this.maximumLife)); + if (this.constructionProgress >= buildTime) { this.constructing = false; - if (this.workerInside != null) { - this.workerInside.setHidden(false); - this.workerInside.setUpdating(true); - this.workerInside.nudgeAround(game, this); - this.workerInside = null; + this.constructionProgress = 0; + popoutWorker(game); + final Iterator abilityIterator = this.abilities.iterator(); + while (abilityIterator.hasNext()) { + final CAbility ability = abilityIterator.next(); + if (ability instanceof CAbilityBuildInProgress) { + abilityIterator.remove(); + } } game.unitConstructFinishEvent(this); this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); } } - else if (this.currentBehavior != null) { - final CBehavior lastBehavior = this.currentBehavior; - this.currentBehavior = this.currentBehavior.update(game); - if (this.currentBehavior.getHighlightOrderId() != lastBehavior.getHighlightOrderId()) { - this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); - } - } else { - // check to auto acquire targets - autoAcquireAttackTargets(game); + final War3ID queuedRawcode = this.buildQueue[0]; + if (queuedRawcode != null) { + // queue step forward + this.constructionProgress += WarsmashConstants.SIMULATION_STEP_TIME; + if (this.buildQueueTypes[0] == QueueItemType.UNIT) { + final CUnitType trainedUnitType = game.getUnitData().getUnitType(queuedRawcode); + if (this.constructionProgress >= trainedUnitType.getBuildTime()) { + this.constructionProgress = 0; + final CUnit trainedUnit = game.createUnit(queuedRawcode, this.playerIndex, getX(), getY(), + game.getGameplayConstants().getBuildingAngle()); + // nudge the trained unit out around us + trainedUnit.nudgeAround(game, this); + game.unitTrainedEvent(this, trainedUnit); + for (int i = 0; i < (this.buildQueue.length - 1); i++) { + this.buildQueue[i] = this.buildQueue[i + 1]; + this.buildQueueTypes[i] = this.buildQueueTypes[i + 1]; + } + this.buildQueue[this.buildQueue.length - 1] = null; + this.buildQueueTypes[this.buildQueue.length - 1] = null; + this.stateNotifier.queueChanged(); + } + } + else if (this.buildQueueTypes[0] == QueueItemType.RESEARCH) { + final CUnitType trainedUnitType = game.getUnitData().getUnitType(queuedRawcode); + if (this.constructionProgress >= trainedUnitType.getBuildTime()) { + this.constructionProgress = 0; + for (int i = 0; i < (this.buildQueue.length - 1); i++) { + this.buildQueue[i] = this.buildQueue[i + 1]; + this.buildQueueTypes[i] = this.buildQueueTypes[i + 1]; + } + this.buildQueue[this.buildQueue.length - 1] = null; + this.buildQueueTypes[this.buildQueue.length - 1] = null; + this.stateNotifier.queueChanged(); + } + } + } + if (this.currentBehavior != null) { + final CBehavior lastBehavior = this.currentBehavior; + this.currentBehavior = this.currentBehavior.update(game); + if (this.currentBehavior.getHighlightOrderId() != lastBehavior.getHighlightOrderId()) { + this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); + } + } + else { + // check to auto acquire targets + autoAcquireAttackTargets(game); + } } } return false; } + private void popoutWorker(final CSimulation game) { + if (this.workerInside != null) { + this.workerInside.setHidden(false); + this.workerInside.setUpdating(true); + this.workerInside.nudgeAround(game, this); + this.workerInside = null; + } + } + public void autoAcquireAttackTargets(final CSimulation game) { if (!this.unitType.getAttacks().isEmpty()) { if (this.collisionRectangle != null) { @@ -452,7 +518,7 @@ public class CUnit extends CWidget { simulation.unitDamageEvent(this, weaponType, this.unitType.getArmorType()); this.stateNotifier.lifeChanged(); if (isDead()) { - if (!wasDead && !this.unitType.isBuilding()) { + if (!wasDead) { kill(simulation); } } @@ -474,7 +540,21 @@ public class CUnit extends CWidget { private void kill(final CSimulation simulation) { this.currentBehavior = null; this.orderQueue.clear(); - this.deathTurnTick = simulation.getGameTurnTick(); + if (this.constructing) { + simulation.createBuildingDeathEffect(this); + } + else { + this.deathTurnTick = simulation.getGameTurnTick(); + } + if (this.pathingInstance != null) { + this.pathingInstance.remove(); + this.pathingInstance = null; + } + if (this.buildingShadowInstance != null) { + this.buildingShadowInstance.remove(); + this.buildingShadowInstance = null; + } + popoutWorker(simulation); } public boolean canReach(final CWidget target, final float range) { @@ -753,5 +833,62 @@ public class CUnit extends CWidget { } setX(x, simulation.getWorldCollision()); setY(y, simulation.getWorldCollision()); + simulation.unitRepositioned(this); + } + + @Override + public void setLife(final CSimulation simulation, final float life) { + final boolean wasDead = isDead(); + super.setLife(simulation, life); + if (isDead() && !wasDead) { + kill(simulation); + } + this.stateNotifier.lifeChanged(); + } + + private void queue(final War3ID rawcode, final QueueItemType queueItemType) { + for (int i = 0; i < this.buildQueue.length; i++) { + if (this.buildQueue[i] == null) { + this.buildQueue[i] = rawcode; + this.buildQueueTypes[i] = queueItemType; + break; + } + } + } + + public War3ID[] getBuildQueue() { + return this.buildQueue; + } + + public QueueItemType[] getBuildQueueTypes() { + return this.buildQueueTypes; + } + + public float getBuildQueueTimeRemaining(final CSimulation simulation) { + if (this.buildQueueTypes[0] == null) { + return 0; + } + switch (this.buildQueueTypes[0]) { + case RESEARCH: + return 999; // TODO + case UNIT: + final CUnitType trainedUnitType = simulation.getUnitData().getUnitType(this.buildQueue[0]); + return trainedUnitType.getBuildTime(); + default: + return 0; + } + } + + public void queueTrainingUnit(final War3ID rawcode) { + queue(rawcode, QueueItemType.UNIT); + } + + public void queueResearch(final War3ID rawcode) { + queue(rawcode, QueueItemType.RESEARCH); + } + + public static enum QueueItemType { + UNIT, + RESEARCH; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitFilterFunction.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitFilterFunction.java index 6cbc073..03722f5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitFilterFunction.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitFilterFunction.java @@ -9,4 +9,11 @@ public interface CUnitFilterFunction { return true; } }; + + CUnitFilterFunction ACCEPT_ALL_LIVING = new CUnitFilterFunction() { + @Override + public boolean call(final CUnit unit) { + return !unit.isDead(); + } + }; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java index 94b974c..b036dcf 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java @@ -7,6 +7,8 @@ public interface CUnitStateListener { void ordersChanged(int abilityHandleId, int orderId); + void queueChanged(); + public static final class CUnitStateNotifier extends SubscriberSetNotifier implements CUnitStateListener { @Override @@ -22,5 +24,12 @@ public interface CUnitStateListener { listener.ordersChanged(abilityHandleId, orderId); } } + + @Override + public void queueChanged() { + for (final CUnitStateListener listener : set) { + listener.queueChanged(); + } + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java index 5606115..1e752da 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java @@ -38,6 +38,8 @@ public class CUnitType { private final float defaultAcquisitionRange; private final float minimumAttackRange; private final List structuresBuilt; + private final List unitsTrained; + private final List researchesAvailable; private final CUnitRace unitRace; private final int goldCost; private final int lumberCost; @@ -49,7 +51,8 @@ public class CUnitType { final boolean raise, final boolean decay, final CDefenseType defenseType, final float impactZ, final BufferedImage buildingPathingPixelMap, final float deathTime, final EnumSet targetedAs, final float defaultAcquisitionRange, final float minimumAttackRange, final List structuresBuilt, - final CUnitRace unitRace, final int goldCost, final int lumberCost, final int buildTime) { + final List unitsTrained, final List researchesAvailable, final CUnitRace unitRace, + final int goldCost, final int lumberCost, final int buildTime) { this.name = name; this.building = isBldg; this.movementType = movementType; @@ -68,6 +71,8 @@ public class CUnitType { this.defaultAcquisitionRange = defaultAcquisitionRange; this.minimumAttackRange = minimumAttackRange; this.structuresBuilt = structuresBuilt; + this.unitsTrained = unitsTrained; + this.researchesAvailable = researchesAvailable; this.unitRace = unitRace; this.goldCost = goldCost; this.lumberCost = lumberCost; @@ -146,6 +151,14 @@ public class CUnitType { return this.structuresBuilt; } + public List getUnitsTrained() { + return this.unitsTrained; + } + + public List getResearchesAvailable() { + return this.researchesAvailable; + } + public CUnitRace getRace() { return this.unitRace; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java index a78217d..e047d7f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java @@ -42,7 +42,7 @@ public abstract class CWidget { this.y = y; } - public void setLife(final float life) { + public void setLife(final CSimulation simulation, final float life) { this.life = life; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java index 5d3a7c5..2aa8c0d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityBuildInProgress; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityHumanBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNagaBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNeutralBuild; @@ -7,6 +8,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityOrcBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityUndeadBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; /** * A visitor for the lowest level inherent types of an ability. It's a bit of a @@ -36,4 +38,8 @@ public interface CAbilityVisitor { T accept(CAbilityNagaBuild ability); T accept(CAbilityNeutralBuild ability); + + T accept(CAbilityBuildInProgress ability); + + T accept(CAbilityQueue ability); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java index 487a78f..cf8d1b1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java @@ -33,22 +33,29 @@ public abstract class AbstractCAbilityBuild extends AbstractCAbility implements @Override public void checkCanUse(final CSimulation game, final CUnit unit, final int orderId, final AbilityActivationReceiver receiver) { - final CUnitType unitType = game.getUnitData().getUnitType(new War3ID(orderId)); - if (unitType != null) { - final CPlayer player = game.getPlayer(unit.getPlayerIndex()); - if (player.getGold() >= unitType.getGoldCost()) { - if (player.getLumber() >= unitType.getLumberCost()) { - receiver.useOk(); + final War3ID orderIdAsRawtype = new War3ID(orderId); + if (this.structuresBuilt.contains(orderIdAsRawtype)) { + final CUnitType unitType = game.getUnitData().getUnitType(orderIdAsRawtype); + if (unitType != null) { + final CPlayer player = game.getPlayer(unit.getPlayerIndex()); + if (player.getGold() >= unitType.getGoldCost()) { + if (player.getLumber() >= unitType.getLumberCost()) { + receiver.useOk(); + } + else { + receiver.notEnoughResources(ResourceType.LUMBER); + } } else { - receiver.notEnoughResources(ResourceType.LUMBER); + receiver.notEnoughResources(ResourceType.GOLD); } } else { - receiver.notEnoughResources(ResourceType.GOLD); + receiver.useOk(); } } else { + /// ??? receiver.useOk(); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java new file mode 100644 index 0000000..1b5a906 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java @@ -0,0 +1,85 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; + +public class CAbilityBuildInProgress extends AbstractCAbility { + + public CAbilityBuildInProgress(final int handleId) { + super(handleId); + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + + } + + @Override + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + caster.setLife(game, 0); + return false; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + return null; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + return null; + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + return null; + } + + @Override + public void checkCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { + receiver.useOk(); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final Vector2 target, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityTargetCheckReceiver receiver) { + if (orderId == OrderIds.cancel) { + receiver.targetOk(null); + } + else { + receiver.orderIdNotAccepted(); + } + } + + @Override + public T visit(final CAbilityVisitor visitor) { + return visitor.accept(this); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java new file mode 100644 index 0000000..2f1af60 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java @@ -0,0 +1,133 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.util.War3ID; +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.CUnitType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; + +public final class CAbilityQueue extends AbstractCAbility { + private final Set unitsTrained; + private final Set researchesAvailable; + + public CAbilityQueue(final int handleId, final List unitsTrained, final List researchesAvailable) { + super(handleId); + this.unitsTrained = new LinkedHashSet<>(unitsTrained); + this.researchesAvailable = new LinkedHashSet<>(researchesAvailable); + } + + public Set getUnitsTrained() { + return this.unitsTrained; + } + + public Set getResearchesAvailable() { + return this.researchesAvailable; + } + + @Override + public void checkCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { + final War3ID orderIdAsRawtype = new War3ID(orderId); + if (this.unitsTrained.contains(orderIdAsRawtype) || this.researchesAvailable.contains(orderIdAsRawtype)) { + final CUnitType unitType = game.getUnitData().getUnitType(orderIdAsRawtype); + if (unitType != null) { + final CPlayer player = game.getPlayer(unit.getPlayerIndex()); + if (player.getGold() >= unitType.getGoldCost()) { + if (player.getLumber() >= unitType.getLumberCost()) { + receiver.useOk(); + } + else { + receiver.notEnoughResources(ResourceType.LUMBER); + } + } + else { + receiver.notEnoughResources(ResourceType.GOLD); + } + } + else { + receiver.useOk(); + } + } + else { + /// ??? + receiver.useOk(); + } + } + + @Override + public final void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public final void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final Vector2 target, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public final void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityTargetCheckReceiver receiver) { + if (this.unitsTrained.contains(new War3ID(orderId)) || this.researchesAvailable.contains(new War3ID(orderId))) { + receiver.targetOk(null); + } + else { + receiver.orderIdNotAccepted(); + } + } + + @Override + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + return true; + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + return null; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + return null; + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + final War3ID rawcode = new War3ID(orderId); + if (this.unitsTrained.contains(rawcode)) { + caster.queueTrainingUnit(rawcode); + } + else if (this.researchesAvailable.contains(rawcode)) { + caster.queueResearch(rawcode); + } + return null; + } + + @Override + public T visit(final CAbilityVisitor visitor) { + return visitor.accept(this); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/upgrade/CAbilityUpgrade.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/upgrade/CAbilityUpgrade.java new file mode 100644 index 0000000..fb52922 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/upgrade/CAbilityUpgrade.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.upgrade; + +public class CAbilityUpgrade { + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java index 9a43644..140aca2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java @@ -1,9 +1,11 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.build; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.util.WarsmashConstants; 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.CUnitType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityBuildInProgress; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CAbstractRangedPointTargetBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; @@ -39,6 +41,9 @@ public class CBehaviorOrcBuild extends CAbstractRangedPointTargetBehavior { this.targetY, simulation.getGameplayConstants().getBuildingAngle()); constructedStructure.setConstructing(true); constructedStructure.setWorkerInside(this.unit); + constructedStructure.setLife(simulation, + constructedStructure.getMaximumLife() * WarsmashConstants.BUILDING_CONSTRUCT_START_LIFE); + constructedStructure.add(simulation, new CAbilityBuildInProgress(simulation.getHandleIdAllocator().createId())); this.unit.setHidden(true); this.unit.setUpdating(false); simulation.unitConstructedEvent(this.unit, constructedStructure); 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 037deec..510371a 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 @@ -10,12 +10,15 @@ import java.util.Map; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.BuildingShadow; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.RemovablePathingMapInstance; 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.CUnitType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.HandleIdAllocator; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityHumanBuild; @@ -24,6 +27,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNightElfBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityOrcBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityUndeadBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; @@ -128,6 +132,8 @@ public class CUnitData { private static final War3ID ABILITIES_NORMAL = War3ID.fromString("uabi"); private static final War3ID STRUCTURES_BUILT = War3ID.fromString("ubui"); + private static final War3ID UNITS_TRAINED = War3ID.fromString("utra"); + private static final War3ID RESEARCHES_AVAILABLE = War3ID.fromString("ures"); private static final War3ID UNIT_RACE = War3ID.fromString("urac"); private static final War3ID GOLD_COST = War3ID.fromString("ugol"); @@ -137,17 +143,19 @@ public class CUnitData { private final MutableObjectData unitData; private final Map unitIdToUnitType = new HashMap<>(); private final CAbilityData abilityData; - private SimulationRenderController simulationRenderController; + private final SimulationRenderController simulationRenderController; - public CUnitData(final MutableObjectData unitData, final CAbilityData abilityData) { + public CUnitData(final MutableObjectData unitData, final CAbilityData abilityData, + final SimulationRenderController simulationRenderController) { this.unitData = unitData; this.abilityData = abilityData; + this.simulationRenderController = simulationRenderController; } public CUnit create(final CSimulation simulation, final int playerIndex, final War3ID typeId, final float x, final float y, final float facing, final BufferedImage buildingPathingPixelMap, - final SimulationRenderController simulationRenderController, final HandleIdAllocator handleIdAllocator) { - this.simulationRenderController = simulationRenderController; + final HandleIdAllocator handleIdAllocator, final RemovablePathingMapInstance pathingInstance, + final BuildingShadow buildingShadowInstance) { final MutableGameObject unitType = this.unitData.get(typeId); final int handleId = handleIdAllocator.createId(); final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); @@ -160,7 +168,7 @@ public class CUnitData { 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); + speed, defense, unitTypeInstance, pathingInstance, buildingShadowInstance); if (speed > 0) { unit.add(simulation, new CAbilityMove(handleIdAllocator.createId())); } @@ -193,9 +201,17 @@ public class CUnitData { break; } } + final List unitsTrained = unitTypeInstance.getUnitsTrained(); + final List researchesAvailable = unitTypeInstance.getResearchesAvailable(); + if (!unitsTrained.isEmpty() || !researchesAvailable.isEmpty()) { + unit.add(simulation, new CAbilityQueue(handleIdAllocator.createId(), unitsTrained, researchesAvailable)); + } for (final String ability : abilityList.split(",")) { if ((ability.length() > 0) && !"_".equals(ability)) { - unit.add(simulation, this.abilityData.createAbility(ability, handleIdAllocator.createId())); + final CAbility createAbility = this.abilityData.createAbility(ability, handleIdAllocator.createId()); + if (createAbility != null) { + unit.add(simulation, createAbility); + } } } return unit; @@ -338,6 +354,24 @@ public class CUnitData { final int lumberCost = unitType.getFieldAsInteger(LUMBER_COST, 0); final int buildTime = unitType.getFieldAsInteger(BUILD_TIME, 0); + final String unitsTrainedString = unitType.getFieldAsString(UNITS_TRAINED, 0); + final String[] unitsTrainedStringItems = unitsTrainedString.trim().split(","); + final List unitsTrained = new ArrayList<>(); + for (final String unitsTrainedStringItem : unitsTrainedStringItems) { + if (unitsTrainedStringItem.length() == 4) { + unitsTrained.add(War3ID.fromString(unitsTrainedStringItem)); + } + } + + final String researchesAvailableString = unitType.getFieldAsString(RESEARCHES_AVAILABLE, 0); + final String[] researchesAvailableStringItems = researchesAvailableString.trim().split(","); + final List researchesAvailable = new ArrayList<>(); + for (final String researchesAvailableStringItem : researchesAvailableStringItems) { + if (researchesAvailableStringItem.length() == 4) { + researchesAvailable.add(War3ID.fromString(researchesAvailableStringItem)); + } + } + final String structuresBuiltString = unitType.getFieldAsString(STRUCTURES_BUILT, 0); final String[] structuresBuiltStringItems = structuresBuiltString.split(","); final List structuresBuilt = new ArrayList<>(); @@ -352,8 +386,8 @@ public class CUnitData { unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications, attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, - targetedAs, acquisitionRange, minimumAttackRange, structuresBuilt, unitRace, goldCost, lumberCost, - buildTime); + targetedAs, acquisitionRange, minimumAttackRange, structuresBuilt, unitsTrained, + researchesAvailable, unitRace, goldCost, lumberCost, buildTime); this.unitIdToUnitType.put(typeId, unitTypeInstance); } return unitTypeInstance; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java index 93425ef..98747ea 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java @@ -1,7 +1,10 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.util.WarsmashConstants; public class CPlayer { @@ -15,6 +18,7 @@ public class CPlayer { private int gold = 5000; private int lumber = 5000; private final EnumSet[] alliances = new EnumSet[WarsmashConstants.MAX_PLAYERS]; + private final Map rawcodeToTechtreeUnlocked = new HashMap<>(); public CPlayer(final int id, final CMapControl controlType, final String name, final CRace race, final float[] startLocation) { @@ -106,4 +110,12 @@ public class CPlayer { public void setColorIndex(final int colorIndex) { this.colorIndex = colorIndex; } + + public int getTechtreeUnlocked(final War3ID rawcode) { + final Integer techtreeUnlocked = this.rawcodeToTechtreeUnlocked.get(rawcode); + if (techtreeUnlocked == null) { + return 0; + } + return techtreeUnlocked; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java index eeb1f73..876a1a2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java @@ -28,4 +28,10 @@ public interface SimulationRenderController { BufferedImage getBuildingPathingPixelMap(War3ID rawcode); void spawnUnitConstructionFinishSound(CUnit constructedStructure); + + void spawnBuildingDeathEffect(CUnit cUnit); + + void spawnUnitReadySound(CUnit trainedUnit); + + void unitRepositioned(CUnit cUnit); } 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 59140e0..03499c6 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 @@ -57,6 +57,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataU import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.IconUI; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.CommandButtonListener; 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; @@ -111,11 +112,19 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private final Rectangle tempRect = new Rectangle(); private final Vector2 projectionTemp1 = new Vector2(); private final Vector2 projectionTemp2 = new Vector2(); + private UIFrame simpleInfoPanelUnitDetail; private StringFrame simpleNameValue; private StringFrame simpleClassValue; private StringFrame simpleBuildingActionLabel; private SimpleStatusBarFrame simpleBuildTimeIndicator; + + private UIFrame simpleInfoPanelBuildingDetail; + private StringFrame simpleBuildingNameValue; + private StringFrame simpleBuildingDescriptionValue; + private StringFrame simpleBuildingBuildingActionLabel; + private SimpleStatusBarFrame simpleBuildingBuildTimeIndicator; + private UIFrame attack1Icon; private TextureFrame attack1IconBackdrop; private StringFrame attack1InfoPanelIconValue; @@ -282,12 +291,15 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.unitLifeText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitHitPointText", 0); this.unitManaText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitManaPointText", 0); + // Create Simple Info Unit Detail this.simpleInfoPanelUnitDetail = this.rootFrame.createSimpleFrame("SimpleInfoPanelUnitDetail", this.consoleUI, 0); this.simpleInfoPanelUnitDetail .addAnchor(new AnchorDefinition(FramePoint.BOTTOM, 0, GameUI.convertY(this.uiViewport, 0.0f))); - this.simpleInfoPanelUnitDetail.setWidth(GameUI.convertY(this.uiViewport, 0.180f)); - this.simpleInfoPanelUnitDetail.setHeight(GameUI.convertY(this.uiViewport, 0.105f)); + final float infoPanelUnitDetailWidth = GameUI.convertY(this.uiViewport, 0.180f); + this.simpleInfoPanelUnitDetail.setWidth(infoPanelUnitDetailWidth); + final float infoPanelUnitDetailHeight = GameUI.convertY(this.uiViewport, 0.105f); + this.simpleInfoPanelUnitDetail.setHeight(infoPanelUnitDetailHeight); this.simpleNameValue = (StringFrame) this.rootFrame.getFrameByName("SimpleNameValue", 0); this.simpleClassValue = (StringFrame) this.rootFrame.getFrameByName("SimpleClassValue", 0); this.simpleBuildingActionLabel = (StringFrame) this.rootFrame.getFrameByName("SimpleBuildingActionLabel", 0); @@ -297,8 +309,33 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma simpleBuildTimeIndicatorBar.setTexture("SimpleBuildTimeIndicator", this.rootFrame); final TextureFrame simpleBuildTimeIndicatorBorder = this.simpleBuildTimeIndicator.getBorderFrame(); simpleBuildTimeIndicatorBorder.setTexture("SimpleBuildTimeIndicatorBorder", this.rootFrame); - this.simpleBuildTimeIndicator.setWidth(GameUI.convertX(this.uiViewport, 0.10538f)); - this.simpleBuildTimeIndicator.setHeight(GameUI.convertY(this.uiViewport, 0.0103f)); + final float buildTimeIndicatorWidth = GameUI.convertX(this.uiViewport, 0.10538f); + final float buildTimeIndicatorHeight = GameUI.convertY(this.uiViewport, 0.0103f); + this.simpleBuildTimeIndicator.setWidth(buildTimeIndicatorWidth); + this.simpleBuildTimeIndicator.setHeight(buildTimeIndicatorHeight); + + // Create Simple Info Panel Building Detail + this.simpleInfoPanelBuildingDetail = this.rootFrame.createSimpleFrame("SimpleInfoPanelBuildingDetail", + this.consoleUI, 0); + this.simpleInfoPanelBuildingDetail + .addAnchor(new AnchorDefinition(FramePoint.BOTTOM, 0, GameUI.convertY(this.uiViewport, 0.0f))); + this.simpleInfoPanelBuildingDetail.setWidth(infoPanelUnitDetailWidth); + this.simpleInfoPanelBuildingDetail.setHeight(infoPanelUnitDetailHeight); + this.simpleBuildingNameValue = (StringFrame) this.rootFrame.getFrameByName("SimpleBuildingNameValue", 0); + this.simpleBuildingDescriptionValue = (StringFrame) this.rootFrame + .getFrameByName("SimpleBuildingDescriptionValue", 0); + this.simpleBuildingBuildingActionLabel = (StringFrame) this.rootFrame + .getFrameByName("SimpleBuildingActionLabel", 0); + this.simpleBuildingBuildTimeIndicator = (SimpleStatusBarFrame) this.rootFrame + .getFrameByName("SimpleBuildTimeIndicator", 0); + final TextureFrame simpleBuildingBuildTimeIndicatorBar = this.simpleBuildingBuildTimeIndicator.getBarFrame(); + simpleBuildingBuildTimeIndicatorBar.setTexture("SimpleBuildTimeIndicator", this.rootFrame); + final TextureFrame simpleBuildingBuildTimeIndicatorBorder = this.simpleBuildingBuildTimeIndicator + .getBorderFrame(); + simpleBuildingBuildTimeIndicatorBorder.setTexture("SimpleBuildTimeIndicatorBorder", this.rootFrame); + this.simpleBuildingBuildTimeIndicator.setWidth(buildTimeIndicatorWidth); + this.simpleBuildingBuildTimeIndicator.setHeight(buildTimeIndicatorHeight); + this.simpleInfoPanelBuildingDetail.setVisible(false); this.attack1Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, 0); @@ -494,7 +531,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.war3MapViewer.mapPathSolver, this.war3MapViewer.solverParams); this.cursorModelInstance = (MdxComplexInstance) model.addInstance(); this.cursorModelInstance.setVertexColor(new float[] { 1, 1, 1, 0.5f }); - this.cursorModelInstance.setTeamColor(activeCommandUnit.getSimulationUnit().getPlayerIndex()); + this.cursorModelInstance.setTeamColor(this.activeCommandUnit.getSimulationUnit().getPlayerIndex()); this.cursorModelInstance.rotate(RenderUnit.tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, this.war3MapViewer.simulation.getGameplayConstants().getBuildingAngle())); this.cursorModelInstance.setAnimationSpeed(0f); @@ -570,6 +607,14 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma .setValue(Math.min(this.selectedUnit.getSimulationUnit().getConstructionProgress() / this.selectedUnit.getSimulationUnit().getUnitType().getBuildTime(), 0.99f)); } + if (this.simpleBuildingBuildTimeIndicator.isVisible() && (this.selectedUnit != null)) { + this.simpleBuildingBuildTimeIndicator + .setValue(Math.min( + this.selectedUnit.getSimulationUnit().getConstructionProgress() / this.selectedUnit + .getSimulationUnit().getBuildQueueTimeRemaining(this.war3MapViewer.simulation), + 0.99f)); + } + final float groundHeight = Math.max( this.war3MapViewer.terrain.getGroundHeight(this.cameraManager.target.x, this.cameraManager.target.y), this.war3MapViewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)); @@ -687,83 +732,110 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } private void reloadSelectedUnitUI(final RenderUnit unit) { - 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(); - } - } - if (classText != null) { - this.simpleClassValue.setText(classText); - } - else { - this.simpleClassValue.setText(""); - } - this.unitLifeText.setText(FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getLife()) + " / " - + unit.getSimulationUnit().getMaximumLife()); - final int maximumMana = unit.getSimulationUnit().getMaximumMana(); + final CUnit simulationUnit = unit.getSimulationUnit(); + this.unitLifeText.setText( + FastNumberFormat.formatWholeNumber(simulationUnit.getLife()) + " / " + simulationUnit.getMaximumLife()); + final int maximumMana = simulationUnit.getMaximumMana(); if (maximumMana > 0) { - this.unitManaText.setText( - FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getMana()) + " / " + maximumMana); + this.unitManaText + .setText(FastNumberFormat.formatWholeNumber(simulationUnit.getMana()) + " / " + maximumMana); } else { this.unitManaText.setText(""); } - this.simpleBuildingActionLabel.setText(""); + if (simulationUnit.getBuildQueue()[0] != null) { + this.simpleInfoPanelBuildingDetail.setVisible(true); + this.simpleInfoPanelUnitDetail.setVisible(false); + this.simpleBuildingNameValue.setText(simulationUnit.getUnitType().getName()); + this.simpleBuildingDescriptionValue.setText(""); - final boolean anyAttacks = unit.getSimulationUnit().getUnitType().getAttacks().size() > 0; - final boolean constructing = unit.getSimulationUnit().isConstructing(); - final UIFrame localArmorIcon = this.armorIcon; - final TextureFrame localArmorIconBackdrop = this.armorIconBackdrop; - final StringFrame localArmorInfoPanelIconValue = this.armorInfoPanelIconValue; - if (anyAttacks && !constructing) { - final CUnitAttack attackOne = unit.getSimulationUnit().getUnitType().getAttacks().get(0); - this.attack1Icon.setVisible(attackOne.isShowUI()); - this.attack1IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackOne.getAttackType())); - this.attack1InfoPanelIconValue.setText(attackOne.getMinDamage() + " - " + attackOne.getMaxDamage()); - if (unit.getSimulationUnit().getUnitType().getAttacks().size() > 1) { - final CUnitAttack attackTwo = unit.getSimulationUnit().getUnitType().getAttacks().get(1); - this.attack2Icon.setVisible(attackTwo.isShowUI()); - this.attack2IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackTwo.getAttackType())); - this.attack2InfoPanelIconValue.setText(attackTwo.getMinDamage() + " - " + attackTwo.getMaxDamage()); + this.simpleBuildingBuildTimeIndicator.setVisible(true); + this.simpleBuildTimeIndicator.setVisible(false); + if (simulationUnit.getBuildQueueTypes()[0] == QueueItemType.UNIT) { + this.simpleBuildingBuildingActionLabel + .setText(this.rootFrame.getTemplates().getDecoratedString("TRAINING")); } else { - this.attack2Icon.setVisible(false); + this.simpleBuildingBuildingActionLabel + .setText(this.rootFrame.getTemplates().getDecoratedString("RESEARCHING")); } - - this.armorIcon - .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); - this.armorIcon.positionBounds(this.uiViewport); - } - else { this.attack1Icon.setVisible(false); this.attack2Icon.setVisible(false); + this.armorIcon.setVisible(false); + } + else { + this.simpleInfoPanelBuildingDetail.setVisible(false); + this.simpleInfoPanelUnitDetail.setVisible(true); + this.simpleNameValue.setText(simulationUnit.getUnitType().getName()); + String classText = null; + for (final CUnitClassification classification : simulationUnit.getClassifications()) { + if ((classification == CUnitClassification.MECHANICAL) && simulationUnit.getUnitType().isBuilding()) { + // buildings dont display MECHANICAL + continue; + } + if (classification.getDisplayName() != null) { + classText = classification.getDisplayName(); + } + } + if (classText != null) { + this.simpleClassValue.setText(classText); + } + else { + this.simpleClassValue.setText(""); + } - this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, - FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); - this.armorIcon.positionBounds(this.uiViewport); - } + final boolean anyAttacks = simulationUnit.getUnitType().getAttacks().size() > 0; + final boolean constructing = simulationUnit.isConstructing(); + final UIFrame localArmorIcon = this.armorIcon; + final TextureFrame localArmorIconBackdrop = this.armorIconBackdrop; + final StringFrame localArmorInfoPanelIconValue = this.armorInfoPanelIconValue; + if (anyAttacks && !constructing) { + final CUnitAttack attackOne = simulationUnit.getUnitType().getAttacks().get(0); + this.attack1Icon.setVisible(attackOne.isShowUI()); + this.attack1IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackOne.getAttackType())); + this.attack1InfoPanelIconValue.setText(attackOne.getMinDamage() + " - " + attackOne.getMaxDamage()); + if (simulationUnit.getUnitType().getAttacks().size() > 1) { + final CUnitAttack attackTwo = simulationUnit.getUnitType().getAttacks().get(1); + this.attack2Icon.setVisible(attackTwo.isShowUI()); + this.attack2IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackTwo.getAttackType())); + this.attack2InfoPanelIconValue.setText(attackTwo.getMinDamage() + " - " + attackTwo.getMaxDamage()); + } + else { + this.attack2Icon.setVisible(false); + } - localArmorIcon.setVisible(!constructing); - this.simpleBuildTimeIndicator.setVisible(constructing); - if (constructing) { - this.simpleBuildingActionLabel.setText(this.rootFrame.getTemplates().getDecoratedString("CONSTRUCTING")); + this.armorIcon.addSetPoint( + new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); + this.armorIcon.positionBounds(this.uiViewport); + } + else { + this.attack1Icon.setVisible(false); + this.attack2Icon.setVisible(false); + + this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, + FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); + this.armorIcon.positionBounds(this.uiViewport); + } + + localArmorIcon.setVisible(!constructing); + this.simpleBuildTimeIndicator.setVisible(constructing); + this.simpleBuildingBuildTimeIndicator.setVisible(false); + if (constructing) { + this.simpleBuildingActionLabel + .setText(this.rootFrame.getTemplates().getDecoratedString("CONSTRUCTING")); + } + else { + this.simpleBuildingActionLabel.setText(""); + } + final Texture defenseTexture = this.defenseBackdrops + .getTexture(simulationUnit.getUnitType().getDefenseType()); + if (defenseTexture == null) { + throw new RuntimeException(simulationUnit.getUnitType().getDefenseType() + " can't find texture!"); + } + localArmorIconBackdrop.setTexture(defenseTexture); + localArmorInfoPanelIconValue.setText(Integer.toString(simulationUnit.getDefense())); } - final Texture defenseTexture = this.defenseBackdrops - .getTexture(unit.getSimulationUnit().getUnitType().getDefenseType()); - if (defenseTexture == null) { - throw new RuntimeException( - unit.getSimulationUnit().getUnitType().getDefenseType() + " can't find texture!"); - } - localArmorIconBackdrop.setTexture(defenseTexture); - localArmorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense())); clearAndRepopulateCommandCard(); } @@ -779,8 +851,15 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma public void commandButton(final int buttonPositionX, final int buttonPositionY, final Texture icon, final int abilityHandleId, final int orderId, final int autoCastId, final boolean active, final boolean autoCastActive, final boolean menuButton) { - final int x = Math.max(0, Math.min(COMMAND_CARD_WIDTH - 1, buttonPositionX)); - final int y = Math.max(0, Math.min(COMMAND_CARD_HEIGHT - 1, buttonPositionY)); + int x = Math.max(0, Math.min(COMMAND_CARD_WIDTH - 1, buttonPositionX)); + int y = Math.max(0, Math.min(COMMAND_CARD_HEIGHT - 1, buttonPositionY)); + while (this.commandCard[y][x].isVisible() && (y < COMMAND_CARD_HEIGHT) && (x < COMMAND_CARD_WIDTH)) { + x++; + if (x >= COMMAND_CARD_WIDTH) { + x = 0; + y++; + } + } this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, autoCastId, active, autoCastActive, menuButton); } @@ -877,6 +956,11 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma reloadSelectedUnitUI(this.selectedUnit); } + @Override + public void queueChanged() { + reloadSelectedUnitUI(this.selectedUnit); + } + private void clearAndRepopulateCommandCard() { clearCommandCard(); final AbilityDataUI abilityDataUI = this.war3MapViewer.getAbilityDataUI(); @@ -885,9 +969,6 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final IconUI cancelUI = abilityDataUI.getCancelUI(); this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, menuOrderId, 0, false, false, true); - } - else if (this.selectedUnit.getSimulationUnit().isConstructing()) { - } else { if (menuOrderId != 0) {