diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index c296465..2bdc4dd 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -446,4 +446,8 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { super.add(childFrame); this.nameToFrame.put(childFrame.getName(), childFrame); } + + public Scene getUiScene() { + return uiScene; + } } 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 fa89ac6..fa37dfb 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java @@ -225,6 +225,11 @@ 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}); + compact(Gdx.gl30, centerOffset); + } + private static final class Batch { private final int uvsOffset; private final int vertexBuffer; 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 abca5c8..c148071 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -96,1405 +96,1402 @@ import mpq.MPQArchive; import mpq.MPQException; public class War3MapViewer extends ModelViewer { - private static final War3ID UNIT_FILE = War3ID.fromString("umdl"); - 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"); - private static final War3ID UNIT_SHADOW_Y = War3ID.fromString("ushy"); - private static final War3ID UNIT_SHADOW_W = War3ID.fromString("ushw"); - private static final War3ID UNIT_SHADOW_H = War3ID.fromString("ushh"); - private static final War3ID BUILDING_SHADOW = War3ID.fromString("ushb"); - public static final War3ID UNIT_SELECT_SCALE = War3ID.fromString("ussc"); - private static final War3ID UNIT_SELECT_HEIGHT = War3ID.fromString("uslz"); - private static final War3ID UNIT_SOUNDSET = War3ID.fromString("usnd"); - private static final War3ID ITEM_FILE = War3ID.fromString("ifil"); - private static final War3ID UNIT_PATHING = War3ID.fromString("upat"); - private static final War3ID DESTRUCTABLE_PATHING = War3ID.fromString("bptx"); - private static final War3ID ELEVATION_SAMPLE_RADIUS = War3ID.fromString("uerd"); - private static final War3ID MAX_PITCH = War3ID.fromString("umxp"); - private static final War3ID MAX_ROLL = War3ID.fromString("umxr"); - private static final War3ID sloc = War3ID.fromString("sloc"); - private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); - private static final float[] rayHeap = new float[6]; - public static final Ray gdxRayHeap = new Ray(); - private static final Vector2 mousePosHeap = new Vector2(); - private static final Vector3 normalHeap = new Vector3(); - public static final Vector3 intersectionHeap = new Vector3(); - private static final Rectangle rectangleHeap = new Rectangle(); - public static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); - - public PathSolver wc3PathSolver = PathSolver.DEFAULT; - public SolverParams solverParams = new SolverParams(); - public WorldScene worldScene; - public boolean anyReady; - public MappedData terrainData = new MappedData(); - public MappedData cliffTypesData = new MappedData(); - public MappedData waterData = new MappedData(); - public boolean terrainReady; - public boolean cliffsReady; - public boolean doodadsAndDestructiblesLoaded; - public MappedData doodadsData = new MappedData(); - public MappedData doodadMetaData = new MappedData(); - public MappedData destructableMetaData = new MappedData(); - public List doodads = new ArrayList<>(); - public List terrainDoodads = new ArrayList<>(); - public boolean doodadsReady; - public boolean unitsAndItemsLoaded; - public MappedData unitsData = new MappedData(); - public MappedData unitMetaData = new MappedData(); - public List units = new ArrayList<>(); - public List items = new ArrayList<>(); - public List projectiles = new ArrayList<>(); - public boolean unitsReady; - public War3Map mapMpq; - public PathSolver mapPathSolver = PathSolver.DEFAULT; - - private final DataSource gameDataSource; - - public Terrain terrain; - public int renderPathing = 0; - public int renderLighting = 1; - - public List selModels = new ArrayList<>(); - public List selected = new ArrayList<>(); - private DataTable unitAckSoundsTable; - private DataTable unitCombatSoundsTable; - public DataTable miscData; - private DataTable unitGlobalStrings; - public DataTable uiSoundsTable; - private MdxComplexInstance confirmationInstance; - public MdxComplexInstance dncUnit; - public MdxComplexInstance dncUnitDay; - public MdxComplexInstance dncTerrain; - public MdxComplexInstance dncTarget; - public CSimulation simulation; - private float updateTime; - - // for World Editor, I think - public Vector2[] startLocations = new Vector2[WarsmashConstants.MAX_PLAYERS]; - - private final DynamicShadowManager dynamicShadowManager = new DynamicShadowManager(); - - private final Random seededRandom = new Random(1337L); - - private final Map filePathToPathingMap = new HashMap<>(); - - private final List selectionCircleSizes = new ArrayList<>(); - - private final Map unitToRenderPeer = new HashMap<>(); - private final Map unitIdToTypeData = new HashMap<>(); - private GameUI gameUI; - private Vector3 lightDirection; - - private Quadtree walkableObjectsTree; - private final QuadtreeIntersectorFindsWalkableRenderHeight walkablesIntersector = new QuadtreeIntersectorFindsWalkableRenderHeight(); - private final QuadtreeIntersectorFindsHitPoint walkablesIntersectionFinder = new QuadtreeIntersectorFindsHitPoint(); - private final QuadtreeIntersectorFindsHighestWalkable intersectorFindsHighestWalkable = new QuadtreeIntersectorFindsHighestWalkable(); - - private KeyedSounds uiSounds; - - public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { - super(dataSource, canvas); - this.gameDataSource = dataSource; - - final WebGL webGL = this.webGL; - - this.addHandler(new MdxHandler()); - - this.wc3PathSolver = PathSolver.DEFAULT; - - this.worldScene = this.addWorldScene(); - - if (!this.dynamicShadowManager.setup(webGL)) { - throw new IllegalStateException("FrameBuffer setup failed"); - } - } - - public void loadSLKs(final WorldEditStrings worldEditStrings) throws IOException { - final GenericResource terrain = this.loadMapGeneric("TerrainArt\\Terrain.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource cliffTypes = this.loadMapGeneric("TerrainArt\\CliffTypes.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource water = this.loadMapGeneric("TerrainArt\\Water.slk", FetchDataTypeName.SLK, - stringDataCallback); - - // == when loaded, which is always in our system == - this.terrainData.load(terrain.data.toString()); - this.cliffTypesData.load(cliffTypes.data.toString()); - this.waterData.load(water.data.toString()); - // emit terrain loaded?? - - final GenericResource doodads = this.loadMapGeneric("Doodads\\Doodads.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource doodadMetaData = this.loadMapGeneric("Doodads\\DoodadMetaData.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource destructableData = this.loadMapGeneric("Units\\DestructableData.slk", - FetchDataTypeName.SLK, stringDataCallback); - final GenericResource destructableMetaData = this.loadMapGeneric("Units\\DestructableMetaData.slk", - FetchDataTypeName.SLK, stringDataCallback); - - // == when loaded, which is always in our system == - this.doodadsAndDestructiblesLoaded = true; - this.doodadsData.load(doodads.data.toString()); - this.doodadMetaData.load(doodadMetaData.data.toString()); - this.doodadsData.load(destructableData.data.toString()); - this.destructableMetaData.load(destructableData.data.toString()); - // emit doodads loaded - - final GenericResource unitData = this.loadMapGeneric("Units\\UnitData.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource unitUi = this.loadMapGeneric("Units\\unitUI.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource itemData = this.loadMapGeneric("Units\\ItemData.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource unitMetaData = this.loadMapGeneric("Units\\UnitMetaData.slk", FetchDataTypeName.SLK, - stringDataCallback); - - // == when loaded, which is always in our system == - this.unitsAndItemsLoaded = true; - this.unitsData.load(unitData.data.toString()); - this.unitsData.load(unitUi.data.toString()); - this.unitsData.load(itemData.data.toString()); - this.unitMetaData.load(unitMetaData.data.toString()); - // emit loaded - - this.unitAckSoundsTable = new DataTable(worldEditStrings); - try (InputStream terrainSlkStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UnitAckSounds.slk")) { - this.unitAckSoundsTable.readSLK(terrainSlkStream); - } - this.unitCombatSoundsTable = new DataTable(worldEditStrings); - try (InputStream terrainSlkStream = this.dataSource - .getResourceAsStream("UI\\SoundInfo\\UnitCombatSounds.slk")) { - this.unitCombatSoundsTable.readSLK(terrainSlkStream); - } - this.miscData = new DataTable(worldEditStrings); - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\MiscData.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscData.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscGame.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\MiscData.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - if (this.dataSource.has("war3mapMisc.txt")) { - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("war3mapMisc.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - } - final Element light = this.miscData.get("Light"); - final float lightX = light.getFieldFloatValue("Direction", 0); - final float lightY = light.getFieldFloatValue("Direction", 1); - final float lightZ = light.getFieldFloatValue("Direction", 2); - this.lightDirection = new Vector3(lightX, lightY, lightZ).nor(); - this.unitGlobalStrings = new DataTable(worldEditStrings); - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) { - this.unitGlobalStrings.readTXT(miscDataTxtStream, true); - } - final Element categories = this.unitGlobalStrings.get("Categories"); - for (final CUnitClassification unitClassification : CUnitClassification.values()) { - if (unitClassification.getLocaleKey() != null) { - final String displayName = categories.getField(unitClassification.getLocaleKey()); - unitClassification.setDisplayName(displayName); - } - } - this.selectionCircleSizes.clear(); - final Element selectionCircleData = this.miscData.get("SelectionCircle"); - final int selectionCircleNumSizes = selectionCircleData.getFieldValue("NumSizes"); - for (int i = 0; i < selectionCircleNumSizes; i++) { - final String indexString = i < 10 ? "0" + i : Integer.toString(i); - final float size = selectionCircleData.getFieldFloatValue("Size" + indexString); - final String texture = selectionCircleData.getField("Texture" + indexString); - final String textureDotted = selectionCircleData.getField("TextureDotted" + indexString); - this.selectionCircleSizes.add(new SelectionCircleSize(size, texture, textureDotted)); - } - this.selectionCircleScaleFactor = selectionCircleData.getFieldFloatValue("ScaleFactor"); - - this.uiSoundsTable = new DataTable(worldEditStrings); - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UISounds.slk")) { - this.uiSoundsTable.readSLK(miscDataTxtStream); - } - } - - public GenericResource loadMapGeneric(final String path, final FetchDataTypeName dataType, - final LoadGenericCallback callback) { - if (this.mapMpq == null) { - return loadGeneric(path, dataType, callback); - } - else { - return loadGeneric(path, dataType, callback, this.dataSource); - } - } - - public void loadMap(final String mapFilePath) throws IOException { - final War3Map war3Map = new War3Map(this.gameDataSource, mapFilePath); - - this.mapMpq = war3Map; - - final PathSolver wc3PathSolver = this.wc3PathSolver; - - char tileset = 'A'; - - final War3MapW3i w3iFile = this.mapMpq.readMapInformation(); - - tileset = w3iFile.getTileset(); - - DataSource tilesetSource; - try { - // Slightly complex. Here's the theory: - // 1.) Copy map into RAM - // 2.) Setup a Data Source that will read assets - // from either the map or the game, giving the map priority. - SeekableByteChannel sbc; - final CompoundDataSource compoundDataSource = war3Map.getCompoundDataSource(); - try (InputStream mapStream = compoundDataSource.getResourceAsStream(tileset + ".mpq")) { - if (mapStream == null) { - tilesetSource = new CompoundDataSource(Arrays.asList(compoundDataSource, - new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), - new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); - } - else { - final byte[] mapData = IOUtils.toByteArray(mapStream); - sbc = new SeekableInMemoryByteChannel(mapData); - final DataSource internalMpqContentsDataSource = new MpqDataSource(new MPQArchive(sbc), sbc); - tilesetSource = new CompoundDataSource( - Arrays.asList(compoundDataSource, internalMpqContentsDataSource)); - } - } - catch (final IOException exc) { - tilesetSource = new CompoundDataSource( - Arrays.asList(compoundDataSource, new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), - new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); - } - } - catch (final MPQException e) { - throw new RuntimeException(e); - } - setDataSource(tilesetSource); - this.worldEditStrings = new WorldEditStrings(this.dataSource); - loadSLKs(this.worldEditStrings); - - this.solverParams.tileset = Character.toLowerCase(tileset); - - final War3MapW3e terrainData = this.mapMpq.readEnvironment(); - - final War3MapWpm terrainPathing = this.mapMpq.readPathing(); - - final StandardObjectData standardObjectData = new StandardObjectData(this.dataSource); - this.worldEditData = standardObjectData.getWorldEditData(); - - this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, - this.worldEditStrings, this, this.worldEditData); - - final float[] centerOffset = terrainData.getCenterOffset(); - final int[] mapSize = terrainData.getMapSize(); - - this.terrainReady = true; - this.anyReady = true; - this.cliffsReady = true; - - // Override the grid based on the map. - this.worldScene.grid = new Grid(centerOffset[0], centerOffset[1], (mapSize[0] * 128) - 128, - (mapSize[1] * 128) - 128, 16 * 128, 16 * 128); - - final MdxModel confirmation = (MdxModel) load("UI\\Feedback\\Confirmation\\Confirmation.mdx", - PathSolver.DEFAULT, null); - this.confirmationInstance = (MdxComplexInstance) confirmation.addInstance(); - this.confirmationInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE); - this.confirmationInstance.setSequence(0); - this.confirmationInstance.setScene(this.worldScene); - - this.allObjectData = this.mapMpq.readModifications(); - this.simulation = new CSimulation(this.miscData, this.allObjectData.getUnits(), - this.allObjectData.getAbilities(), new SimulationRenderController() { - private final Map keyToCombatSound = new HashMap<>(); - - @Override - public CAttackProjectile createAttackProjectile(final CSimulation simulation, final float launchX, - final float launchY, final float launchFacing, final CUnit source, - final CUnitAttackMissile unitAttack, final CWidget target, final float damage, - final int bounceIndex) { - final War3ID typeId = source.getTypeId(); - final int projectileSpeed = unitAttack.getProjectileSpeed(); - final float projectileArc = unitAttack.getProjectileArc(); - String missileArt = unitAttack.getProjectileArt(); - final float projectileLaunchX = simulation.getUnitData().getProjectileLaunchX(typeId); - final float projectileLaunchY = simulation.getUnitData().getProjectileLaunchY(typeId); - final float projectileLaunchZ = simulation.getUnitData().getProjectileLaunchZ(typeId); - - missileArt = mdx(missileArt); - final float facing = launchFacing; - final float sinFacing = (float) Math.sin(facing); - final float cosFacing = (float) Math.cos(facing); - final float x = (launchX + (projectileLaunchY * cosFacing)) + (projectileLaunchX * sinFacing); - final float y = (launchY + (projectileLaunchY * sinFacing)) - (projectileLaunchX * cosFacing); - - final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight() - + projectileLaunchZ; - final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y, - projectileSpeed, target, source, damage, unitAttack, bounceIndex); - - final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, - War3MapViewer.this.solverParams); - final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); - modelInstance.setTeamColor(source.getPlayerIndex()); - modelInstance.setScene(War3MapViewer.this.worldScene); - if (bounceIndex == 0) { - SequenceUtils.randomBirthSequence(modelInstance); - } - else { - SequenceUtils.randomStandSequence(modelInstance); - } - modelInstance.setLocation(x, y, height); - final RenderAttackProjectile renderAttackProjectile = new RenderAttackProjectile( - simulationAttackProjectile, modelInstance, height, projectileArc, War3MapViewer.this); - - War3MapViewer.this.projectiles.add(renderAttackProjectile); - - return simulationAttackProjectile; - } - - @Override - public void createInstantAttackEffect(final CSimulation cSimulation, final CUnit source, - final CUnitAttackInstant unitAttack, final CWidget target) { - final War3ID typeId = source.getTypeId(); - - String missileArt = unitAttack.getProjectileArt(); - final float projectileLaunchX = War3MapViewer.this.simulation.getUnitData() - .getProjectileLaunchX(typeId); - final float projectileLaunchY = War3MapViewer.this.simulation.getUnitData() - .getProjectileLaunchY(typeId); - missileArt = mdx(missileArt); - final float facing = (float) Math.toRadians(source.getFacing()); - final float sinFacing = (float) Math.sin(facing); - final float cosFacing = (float) Math.cos(facing); - final float x = (source.getX() + (projectileLaunchY * cosFacing)) - + (projectileLaunchX * sinFacing); - final float y = (source.getY() + (projectileLaunchY * sinFacing)) - - (projectileLaunchX * cosFacing); - - final float targetX = target.getX(); - final float targetY = target.getY(); - final float angleToTarget = (float) Math.atan2(targetY - y, targetX - x); - - final float height = War3MapViewer.this.terrain.getGroundHeight(targetX, targetY) - + target.getFlyHeight() + target.getImpactZ(); - - final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, - War3MapViewer.this.solverParams); - final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); - modelInstance.setTeamColor(source.getPlayerIndex()); - modelInstance.setScene(War3MapViewer.this.worldScene); - SequenceUtils.randomBirthSequence(modelInstance); - modelInstance.setLocation(targetX, targetY, height); - War3MapViewer.this.projectiles - .add(new RenderAttackInstant(modelInstance, War3MapViewer.this, angleToTarget)); - } - - @Override - public void spawnUnitDamageSound(final CUnit damagedUnit, final String weaponSound, - final String armorType) { - final String key = weaponSound + armorType; - UnitSound combatSound = this.keyToCombatSound.get(key); - if (combatSound == null) { - combatSound = UnitSound.create(War3MapViewer.this.dataSource, - War3MapViewer.this.unitCombatSoundsTable, weaponSound, armorType); - this.keyToCombatSound.put(key, combatSound); - } - combatSound.play(War3MapViewer.this.worldScene.audioContext, damagedUnit.getX(), - damagedUnit.getY()); - } - - @Override - public void removeUnit(final CUnit unit) { - final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.remove(unit); - War3MapViewer.this.units.remove(renderUnit); - War3MapViewer.this.worldScene.removeInstance(renderUnit.instance); - } - - @Override - public BufferedImage getBuildingPathingPixelMap(final War3ID rawcode) { - return War3MapViewer.this - .getBuildingPathingPixelMap(War3MapViewer.this.allObjectData.getUnits().get(rawcode)); - } - - @Override - public CUnit createUnit(final CSimulation simulation, final War3ID typeId, final int playerIndex, - final float x, final float y, final float facing) { - return createNewUnit(War3MapViewer.this.allObjectData, typeId, x, y, 0f, playerIndex, - (float) Math.toRadians(facing)); - } - }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, w3iFile.getPlayers()); - - this.walkableObjectsTree = new Quadtree<>(this.terrain.getEntireMap()); - if (this.doodadsAndDestructiblesLoaded) { - this.loadDoodadsAndDestructibles(this.allObjectData); - } - else { - throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); - } - - loadSounds(); - - this.terrain.createWaves(); - - loadLightsAndShading(tileset); - } - - private void loadSounds() { - this.uiSounds = new KeyedSounds(this.uiSoundsTable, this.mapMpq); - } - - /** - * Loads the map information that should be loaded after UI, such as units, who - * need to be able to setup their UI counterparts (icons, etc) for their - * abilities while loading. This allows the dynamic creation of units while the - * game is playing to better share code with the startup sequence's creation of - * units. - * - * @throws IOException - */ - public void loadAfterUI() throws IOException { - if (this.unitsAndItemsLoaded) { - this.loadUnitsAndItems(this.allObjectData); - } - else { - throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); - } - - // After we finish loading units, we need to update & create the stored shadow - // information for all unit shadows - this.terrain.initShadows(); - } - - private void loadLightsAndShading(final char tileset) { - // TODO this should be set by the war3map.j actually, not by the tileset, so the - // call to set day night models is just for testing to make the test look pretty - final Element defaultTerrainLights = this.worldEditData.get("TerrainLights"); - final Element defaultUnitLights = this.worldEditData.get("UnitLights"); - setDayNightModels(defaultTerrainLights.getField(Character.toString(tileset)), - defaultUnitLights.getField(Character.toString(tileset))); - - } - - private void loadDoodadsAndDestructibles(final Warcraft3MapObjectData modifications) throws IOException { - this.applyModificationFile(this.doodadsData, this.doodadMetaData, modifications.getDoodads(), - WorldEditorDataType.DOODADS); - this.applyModificationFile(this.doodadsData, this.destructableMetaData, modifications.getDestructibles(), - WorldEditorDataType.DESTRUCTIBLES); - - final War3MapDoo doo = this.mapMpq.readDoodads(); - - for (final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad : doo.getDoodads()) { - WorldEditorDataType type = WorldEditorDataType.DOODADS; - MutableGameObject row = modifications.getDoodads().get(doodad.getId()); - if (row == null) { - row = modifications.getDestructibles().get(doodad.getId()); - type = WorldEditorDataType.DESTRUCTIBLES; - } - if (row != null) { - String file = row.readSLKTag("file"); - final int numVar = row.readSLKTagInt("numVar"); - - if (file.endsWith(".mdx") || file.endsWith(".mdl")) { - file = file.substring(0, file.length() - 4); - } - - String fileVar = file; - - file += ".mdx"; - - if (numVar > 1) { - fileVar += Math.min(doodad.getVariation(), numVar - 1); - } - - fileVar += ".mdx"; - - final float maxPitch = row.readSLKTagFloat("maxPitch"); - final float maxRoll = row.readSLKTagFloat("maxRoll"); - if (type == WorldEditorDataType.DESTRUCTIBLES) { - final String shadowString = row.readSLKTag("shadow"); - if ((shadowString != null) && (shadowString.length() > 0) && !"_".equals(shadowString)) { - this.terrain.addShadow(shadowString, doodad.getLocation()[0], doodad.getLocation()[1]); - } - - final String pathingTexture = row.readSLKTag("pathTex"); - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - - BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (bufferedImage == null) { - if (this.mapMpq.has(pathingTexture)) { - try { - bufferedImage = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage); - } - catch (final Exception exc) { - exc.printStackTrace(); - } - } - } - if (bufferedImage != null) { - this.terrain.pathingGrid.blitPathingOverlayTexture(doodad.getLocation()[0], - doodad.getLocation()[1], (int) Math.toDegrees(doodad.getAngle()), bufferedImage); - } - } - } - // First see if the model is local. - // Doodads referring to local models may have invalid variations, so if the - // variation doesn't exist, try without a variation. - - String path; - if (this.mapMpq.has(fileVar)) { - path = fileVar; - } - else { - path = file; - } - MdxModel model; - if (this.mapMpq.has(path)) { - model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); - } - else { - model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams); - } - - if (type == WorldEditorDataType.DESTRUCTIBLES) { - final RenderDestructable renderDestructable = new RenderDestructable(this, model, row, doodad, type, - maxPitch, maxRoll, doodad.getLife()); - if (row.readSLKTagBoolean("walkable")) { - final float x = doodad.getLocation()[0]; - final float y = doodad.getLocation()[1]; - final BoundingBox boundingBox = model.bounds.getBoundingBox(); - final float minX = boundingBox.min.x + x; - final float minY = boundingBox.min.y + y; - final Rectangle renderDestructableBounds = new Rectangle(minX, minY, boundingBox.getWidth(), - boundingBox.getHeight()); - this.walkableObjectsTree.add((MdxComplexInstance) renderDestructable.instance, - renderDestructableBounds); - } - this.doodads.add(renderDestructable); - } - else { - this.doodads.add(new RenderDoodad(this, model, row, doodad, type, maxPitch, maxRoll)); - } - } - } - - // Cliff/Terrain doodads. - for (final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad : doo.getTerrainDoodads()) { - final MutableGameObject row = modifications.getDoodads().get(doodad.getId()); - String file = row.readSLKTag("file");// - if ("".equals(file)) { - final String blaBla = row.readSLKTag("file"); - System.out.println("bla"); - } - if (file.toLowerCase().endsWith(".mdl")) { - file = file.substring(0, file.length() - 4); - } - if (!file.toLowerCase().endsWith(".mdx")) { - file += ".mdx"; - } - final MdxModel model = (MdxModel) this.load(file, this.mapPathSolver, this.solverParams); - - final String pathingTexture = row.readSLKTag("pathTex"); - BufferedImage pathingTextureImage; - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - - pathingTextureImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (pathingTextureImage == null) { - if (this.mapMpq.has(pathingTexture)) { - try { - pathingTextureImage = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), pathingTextureImage); - } - catch (final Exception exc) { - exc.printStackTrace(); - } - } - } - } - else { - pathingTextureImage = null; - } - if (pathingTextureImage != null) { - // blit out terrain cells under this TerrainDoodad - final int textureWidth = pathingTextureImage.getWidth(); - final int textureHeight = pathingTextureImage.getHeight(); - final int textureWidthTerrainCells = textureWidth / 4; - final int textureHeightTerrainCells = textureHeight / 4; - final int minCellX = ((int) doodad.getLocation()[0]); - final int minCellY = ((int) doodad.getLocation()[1]); - final int maxCellX = (minCellX + textureWidthTerrainCells) - 1; - final int maxCellY = (minCellY + textureHeightTerrainCells) - 1; - for (int j = minCellY; j <= maxCellY; j++) { - for (int i = minCellX; i <= maxCellX; i++) { - this.terrain.removeTerrainCellWithoutFlush(i, j); - } - } - this.terrain.flushRemovedTerrainCells(); - } - - System.out.println("Loading terrain doodad: " + file); - this.terrainDoodads.add(new TerrainDoodad(this, model, row, doodad, pathingTextureImage)); - } - - this.doodadsReady = true; - this.anyReady = true; - } - - private void applyModificationFile(final MappedData doodadsData2, final MappedData doodadMetaData2, - final MutableObjectData destructibles, final WorldEditorDataType dataType) { - // TODO condense ported MappedData from Ghostwolf and MutableObjectData from - // Retera - - } - - private void loadUnitsAndItems(final Warcraft3MapObjectData modifications) throws IOException { - final War3Map mpq = this.mapMpq; - - this.soundsetNameToSoundset = new HashMap<>(); - - if (this.dataSource.has("war3mapUnits.doo")) { - final War3MapUnitsDoo dooFile = mpq.readUnits(); - - // Collect the units and items data. - for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) { - final War3ID unitId = unit.getId(); - final float unitX = unit.getLocation()[0]; - final float unitY = unit.getLocation()[1]; - final float unitZ = unit.getLocation()[2]; - final int playerIndex = unit.getPlayer(); - final float unitAngle = unit.getAngle(); - - createNewUnit(modifications, unitId, unitX, unitY, unitZ, playerIndex, unitAngle); - } - } - - this.terrain.loadSplats(); - - this.unitsReady = true; - this.anyReady = true; - } - - private CUnit createNewUnit(final Warcraft3MapObjectData modifications, final War3ID unitId, float unitX, - float unitY, final float unitZ, final int playerIndex, final float unitAngle) { - UnitSoundset soundset = null; - MutableGameObject row = null; - String path = null; - Splat unitShadowSplat = null; - BufferedImage buildingPathingPixelMap = null; - final float unitVertexScale = 1.0f; - - // Hardcoded? - WorldEditorDataType type = null; - if (sloc.equals(unitId)) { + private static final War3ID UNIT_FILE = War3ID.fromString("umdl"); + 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"); + private static final War3ID UNIT_SHADOW_Y = War3ID.fromString("ushy"); + private static final War3ID UNIT_SHADOW_W = War3ID.fromString("ushw"); + private static final War3ID UNIT_SHADOW_H = War3ID.fromString("ushh"); + private static final War3ID BUILDING_SHADOW = War3ID.fromString("ushb"); + public static final War3ID UNIT_SELECT_SCALE = War3ID.fromString("ussc"); + private static final War3ID UNIT_SELECT_HEIGHT = War3ID.fromString("uslz"); + private static final War3ID UNIT_SOUNDSET = War3ID.fromString("usnd"); + private static final War3ID ITEM_FILE = War3ID.fromString("ifil"); + private static final War3ID UNIT_PATHING = War3ID.fromString("upat"); + private static final War3ID DESTRUCTABLE_PATHING = War3ID.fromString("bptx"); + private static final War3ID ELEVATION_SAMPLE_RADIUS = War3ID.fromString("uerd"); + private static final War3ID MAX_PITCH = War3ID.fromString("umxp"); + private static final War3ID MAX_ROLL = War3ID.fromString("umxr"); + private static final War3ID sloc = War3ID.fromString("sloc"); + private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); + private static final float[] rayHeap = new float[6]; + public static final Ray gdxRayHeap = new Ray(); + private static final Vector2 mousePosHeap = new Vector2(); + private static final Vector3 normalHeap = new Vector3(); + public static final Vector3 intersectionHeap = new Vector3(); + private static final Rectangle rectangleHeap = new Rectangle(); + public static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); + + public PathSolver wc3PathSolver = PathSolver.DEFAULT; + public SolverParams solverParams = new SolverParams(); + public WorldScene worldScene; + public boolean anyReady; + public MappedData terrainData = new MappedData(); + public MappedData cliffTypesData = new MappedData(); + public MappedData waterData = new MappedData(); + public boolean terrainReady; + public boolean cliffsReady; + public boolean doodadsAndDestructiblesLoaded; + public MappedData doodadsData = new MappedData(); + public MappedData doodadMetaData = new MappedData(); + public MappedData destructableMetaData = new MappedData(); + public List doodads = new ArrayList<>(); + public List terrainDoodads = new ArrayList<>(); + public boolean doodadsReady; + public boolean unitsAndItemsLoaded; + public MappedData unitsData = new MappedData(); + public MappedData unitMetaData = new MappedData(); + public List units = new ArrayList<>(); + public List items = new ArrayList<>(); + public List projectiles = new ArrayList<>(); + public boolean unitsReady; + public War3Map mapMpq; + public PathSolver mapPathSolver = PathSolver.DEFAULT; + + private final DataSource gameDataSource; + + public Terrain terrain; + public int renderPathing = 0; + public int renderLighting = 1; + + public List selModels = new ArrayList<>(); + public List selected = new ArrayList<>(); + private DataTable unitAckSoundsTable; + private DataTable unitCombatSoundsTable; + public DataTable miscData; + private DataTable unitGlobalStrings; + public DataTable uiSoundsTable; + private MdxComplexInstance confirmationInstance; + public MdxComplexInstance dncUnit; + public MdxComplexInstance dncUnitDay; + public MdxComplexInstance dncTerrain; + public MdxComplexInstance dncTarget; + public CSimulation simulation; + private float updateTime; + + // for World Editor, I think + public Vector2[] startLocations = new Vector2[WarsmashConstants.MAX_PLAYERS]; + + private final DynamicShadowManager dynamicShadowManager = new DynamicShadowManager(); + + private final Random seededRandom = new Random(1337L); + + private final Map filePathToPathingMap = new HashMap<>(); + + private final List selectionCircleSizes = new ArrayList<>(); + + private final Map unitToRenderPeer = new HashMap<>(); + private final Map unitIdToTypeData = new HashMap<>(); + private GameUI gameUI; + private Vector3 lightDirection; + + private Quadtree walkableObjectsTree; + private final QuadtreeIntersectorFindsWalkableRenderHeight walkablesIntersector = new QuadtreeIntersectorFindsWalkableRenderHeight(); + private final QuadtreeIntersectorFindsHitPoint walkablesIntersectionFinder = new QuadtreeIntersectorFindsHitPoint(); + private final QuadtreeIntersectorFindsHighestWalkable intersectorFindsHighestWalkable = new QuadtreeIntersectorFindsHighestWalkable(); + + private KeyedSounds uiSounds; + + public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { + super(dataSource, canvas); + this.gameDataSource = dataSource; + + final WebGL webGL = this.webGL; + + this.addHandler(new MdxHandler()); + + this.wc3PathSolver = PathSolver.DEFAULT; + + this.worldScene = this.addWorldScene(); + + if (!this.dynamicShadowManager.setup(webGL)) { + throw new IllegalStateException("FrameBuffer setup failed"); + } + } + + public void loadSLKs(final WorldEditStrings worldEditStrings) throws IOException { + final GenericResource terrain = this.loadMapGeneric("TerrainArt\\Terrain.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource cliffTypes = this.loadMapGeneric("TerrainArt\\CliffTypes.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource water = this.loadMapGeneric("TerrainArt\\Water.slk", FetchDataTypeName.SLK, + stringDataCallback); + + // == when loaded, which is always in our system == + this.terrainData.load(terrain.data.toString()); + this.cliffTypesData.load(cliffTypes.data.toString()); + this.waterData.load(water.data.toString()); + // emit terrain loaded?? + + final GenericResource doodads = this.loadMapGeneric("Doodads\\Doodads.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource doodadMetaData = this.loadMapGeneric("Doodads\\DoodadMetaData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource destructableData = this.loadMapGeneric("Units\\DestructableData.slk", + FetchDataTypeName.SLK, stringDataCallback); + final GenericResource destructableMetaData = this.loadMapGeneric("Units\\DestructableMetaData.slk", + FetchDataTypeName.SLK, stringDataCallback); + + // == when loaded, which is always in our system == + this.doodadsAndDestructiblesLoaded = true; + this.doodadsData.load(doodads.data.toString()); + this.doodadMetaData.load(doodadMetaData.data.toString()); + this.doodadsData.load(destructableData.data.toString()); + this.destructableMetaData.load(destructableData.data.toString()); + // emit doodads loaded + + final GenericResource unitData = this.loadMapGeneric("Units\\UnitData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource unitUi = this.loadMapGeneric("Units\\unitUI.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource itemData = this.loadMapGeneric("Units\\ItemData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource unitMetaData = this.loadMapGeneric("Units\\UnitMetaData.slk", FetchDataTypeName.SLK, + stringDataCallback); + + // == when loaded, which is always in our system == + this.unitsAndItemsLoaded = true; + this.unitsData.load(unitData.data.toString()); + this.unitsData.load(unitUi.data.toString()); + this.unitsData.load(itemData.data.toString()); + this.unitMetaData.load(unitMetaData.data.toString()); + // emit loaded + + this.unitAckSoundsTable = new DataTable(worldEditStrings); + try (InputStream terrainSlkStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UnitAckSounds.slk")) { + this.unitAckSoundsTable.readSLK(terrainSlkStream); + } + this.unitCombatSoundsTable = new DataTable(worldEditStrings); + try (InputStream terrainSlkStream = this.dataSource + .getResourceAsStream("UI\\SoundInfo\\UnitCombatSounds.slk")) { + this.unitCombatSoundsTable.readSLK(terrainSlkStream); + } + this.miscData = new DataTable(worldEditStrings); + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscGame.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + if (this.dataSource.has("war3mapMisc.txt")) { + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("war3mapMisc.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + } + final Element light = this.miscData.get("Light"); + final float lightX = light.getFieldFloatValue("Direction", 0); + final float lightY = light.getFieldFloatValue("Direction", 1); + final float lightZ = light.getFieldFloatValue("Direction", 2); + this.lightDirection = new Vector3(lightX, lightY, lightZ).nor(); + this.unitGlobalStrings = new DataTable(worldEditStrings); + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) { + this.unitGlobalStrings.readTXT(miscDataTxtStream, true); + } + final Element categories = this.unitGlobalStrings.get("Categories"); + for (final CUnitClassification unitClassification : CUnitClassification.values()) { + if (unitClassification.getLocaleKey() != null) { + final String displayName = categories.getField(unitClassification.getLocaleKey()); + unitClassification.setDisplayName(displayName); + } + } + this.selectionCircleSizes.clear(); + final Element selectionCircleData = this.miscData.get("SelectionCircle"); + final int selectionCircleNumSizes = selectionCircleData.getFieldValue("NumSizes"); + for (int i = 0; i < selectionCircleNumSizes; i++) { + final String indexString = i < 10 ? "0" + i : Integer.toString(i); + final float size = selectionCircleData.getFieldFloatValue("Size" + indexString); + final String texture = selectionCircleData.getField("Texture" + indexString); + final String textureDotted = selectionCircleData.getField("TextureDotted" + indexString); + this.selectionCircleSizes.add(new SelectionCircleSize(size, texture, textureDotted)); + } + this.selectionCircleScaleFactor = selectionCircleData.getFieldFloatValue("ScaleFactor"); + + this.uiSoundsTable = new DataTable(worldEditStrings); + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UISounds.slk")) { + this.uiSoundsTable.readSLK(miscDataTxtStream); + } + } + + public GenericResource loadMapGeneric(final String path, final FetchDataTypeName dataType, + final LoadGenericCallback callback) { + if (this.mapMpq == null) { + return loadGeneric(path, dataType, callback); + } else { + return loadGeneric(path, dataType, callback, this.dataSource); + } + } + + public void loadMap(final String mapFilePath) throws IOException { + final War3Map war3Map = new War3Map(this.gameDataSource, mapFilePath); + + this.mapMpq = war3Map; + + final PathSolver wc3PathSolver = this.wc3PathSolver; + + char tileset = 'A'; + + final War3MapW3i w3iFile = this.mapMpq.readMapInformation(); + + tileset = w3iFile.getTileset(); + + DataSource tilesetSource; + try { + // Slightly complex. Here's the theory: + // 1.) Copy map into RAM + // 2.) Setup a Data Source that will read assets + // from either the map or the game, giving the map priority. + SeekableByteChannel sbc; + final CompoundDataSource compoundDataSource = war3Map.getCompoundDataSource(); + try (InputStream mapStream = compoundDataSource.getResourceAsStream(tileset + ".mpq")) { + if (mapStream == null) { + tilesetSource = new CompoundDataSource(Arrays.asList(compoundDataSource, + new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), + new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); + } else { + final byte[] mapData = IOUtils.toByteArray(mapStream); + sbc = new SeekableInMemoryByteChannel(mapData); + final DataSource internalMpqContentsDataSource = new MpqDataSource(new MPQArchive(sbc), sbc); + tilesetSource = new CompoundDataSource( + Arrays.asList(compoundDataSource, internalMpqContentsDataSource)); + } + } catch (final IOException exc) { + tilesetSource = new CompoundDataSource( + Arrays.asList(compoundDataSource, new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), + new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); + } + } catch (final MPQException e) { + throw new RuntimeException(e); + } + setDataSource(tilesetSource); + this.worldEditStrings = new WorldEditStrings(this.dataSource); + loadSLKs(this.worldEditStrings); + + this.solverParams.tileset = Character.toLowerCase(tileset); + + final War3MapW3e terrainData = this.mapMpq.readEnvironment(); + + final War3MapWpm terrainPathing = this.mapMpq.readPathing(); + + final StandardObjectData standardObjectData = new StandardObjectData(this.dataSource); + this.worldEditData = standardObjectData.getWorldEditData(); + + this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, + this.worldEditStrings, this, this.worldEditData); + + final float[] centerOffset = terrainData.getCenterOffset(); + final int[] mapSize = terrainData.getMapSize(); + + this.terrainReady = true; + this.anyReady = true; + this.cliffsReady = true; + + // Override the grid based on the map. + this.worldScene.grid = new Grid(centerOffset[0], centerOffset[1], (mapSize[0] * 128) - 128, + (mapSize[1] * 128) - 128, 16 * 128, 16 * 128); + + final MdxModel confirmation = (MdxModel) load("UI\\Feedback\\Confirmation\\Confirmation.mdx", + PathSolver.DEFAULT, null); + this.confirmationInstance = (MdxComplexInstance) confirmation.addInstance(); + this.confirmationInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE); + this.confirmationInstance.setSequence(0); + this.confirmationInstance.setScene(this.worldScene); + + this.allObjectData = this.mapMpq.readModifications(); + this.simulation = new CSimulation(this.miscData, this.allObjectData.getUnits(), + this.allObjectData.getAbilities(), new SimulationRenderController() { + private final Map keyToCombatSound = new HashMap<>(); + + @Override + public CAttackProjectile createAttackProjectile(final CSimulation simulation, final float launchX, + final float launchY, final float launchFacing, final CUnit source, + final CUnitAttackMissile unitAttack, final CWidget target, final float damage, + final int bounceIndex) { + final War3ID typeId = source.getTypeId(); + final int projectileSpeed = unitAttack.getProjectileSpeed(); + final float projectileArc = unitAttack.getProjectileArc(); + String missileArt = unitAttack.getProjectileArt(); + final float projectileLaunchX = simulation.getUnitData().getProjectileLaunchX(typeId); + final float projectileLaunchY = simulation.getUnitData().getProjectileLaunchY(typeId); + final float projectileLaunchZ = simulation.getUnitData().getProjectileLaunchZ(typeId); + + missileArt = mdx(missileArt); + final float facing = launchFacing; + final float sinFacing = (float) Math.sin(facing); + final float cosFacing = (float) Math.cos(facing); + final float x = (launchX + (projectileLaunchY * cosFacing)) + (projectileLaunchX * sinFacing); + final float y = (launchY + (projectileLaunchY * sinFacing)) - (projectileLaunchX * cosFacing); + + final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight() + + projectileLaunchZ; + final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y, + projectileSpeed, target, source, damage, unitAttack, bounceIndex); + + final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, + War3MapViewer.this.solverParams); + final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); + modelInstance.setTeamColor(source.getPlayerIndex()); + modelInstance.setScene(War3MapViewer.this.worldScene); + if (bounceIndex == 0) { + SequenceUtils.randomBirthSequence(modelInstance); + } else { + SequenceUtils.randomStandSequence(modelInstance); + } + modelInstance.setLocation(x, y, height); + final RenderAttackProjectile renderAttackProjectile = new RenderAttackProjectile( + simulationAttackProjectile, modelInstance, height, projectileArc, War3MapViewer.this); + + War3MapViewer.this.projectiles.add(renderAttackProjectile); + + return simulationAttackProjectile; + } + + @Override + public void createInstantAttackEffect(final CSimulation cSimulation, final CUnit source, + final CUnitAttackInstant unitAttack, final CWidget target) { + final War3ID typeId = source.getTypeId(); + + String missileArt = unitAttack.getProjectileArt(); + final float projectileLaunchX = War3MapViewer.this.simulation.getUnitData() + .getProjectileLaunchX(typeId); + final float projectileLaunchY = War3MapViewer.this.simulation.getUnitData() + .getProjectileLaunchY(typeId); + missileArt = mdx(missileArt); + final float facing = (float) Math.toRadians(source.getFacing()); + final float sinFacing = (float) Math.sin(facing); + final float cosFacing = (float) Math.cos(facing); + final float x = (source.getX() + (projectileLaunchY * cosFacing)) + + (projectileLaunchX * sinFacing); + final float y = (source.getY() + (projectileLaunchY * sinFacing)) + - (projectileLaunchX * cosFacing); + + final float targetX = target.getX(); + final float targetY = target.getY(); + final float angleToTarget = (float) Math.atan2(targetY - y, targetX - x); + + final float height = War3MapViewer.this.terrain.getGroundHeight(targetX, targetY) + + target.getFlyHeight() + target.getImpactZ(); + + final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, + War3MapViewer.this.solverParams); + final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); + modelInstance.setTeamColor(source.getPlayerIndex()); + modelInstance.setScene(War3MapViewer.this.worldScene); + SequenceUtils.randomBirthSequence(modelInstance); + modelInstance.setLocation(targetX, targetY, height); + War3MapViewer.this.projectiles + .add(new RenderAttackInstant(modelInstance, War3MapViewer.this, angleToTarget)); + } + + @Override + public void spawnUnitDamageSound(final CUnit damagedUnit, final String weaponSound, + final String armorType) { + final String key = weaponSound + armorType; + UnitSound combatSound = this.keyToCombatSound.get(key); + if (combatSound == null) { + combatSound = UnitSound.create(War3MapViewer.this.dataSource, + War3MapViewer.this.unitCombatSoundsTable, weaponSound, armorType); + this.keyToCombatSound.put(key, combatSound); + } + combatSound.play(War3MapViewer.this.worldScene.audioContext, damagedUnit.getX(), + damagedUnit.getY()); + } + + @Override + public void spawnUnitConstructionSound(CUnit constructingUnit, CUnit constructedStructure) { + UnitSound constructingBuilding = uiSounds.getSound(gameUI.getSkinField("ConstructingBuilding")); + if (constructingBuilding != null) { + constructingBuilding.play(worldScene.audioContext, constructedStructure.getX(), constructedStructure.getY()); + } + } + + @Override + public void removeUnit(final CUnit unit) { + final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.remove(unit); + War3MapViewer.this.units.remove(renderUnit); + War3MapViewer.this.worldScene.removeInstance(renderUnit.instance); + } + + @Override + public BufferedImage getBuildingPathingPixelMap(final War3ID rawcode) { + return War3MapViewer.this + .getBuildingPathingPixelMap(War3MapViewer.this.allObjectData.getUnits().get(rawcode)); + } + + @Override + public void spawnUnitConstructionFinishSound(CUnit constructedStructure) { + UnitSound constructingBuilding = uiSounds.getSound(gameUI.getSkinField("JobDoneSound")); + if (constructingBuilding != null) { + constructingBuilding.play(worldScene.audioContext, constructedStructure.getX(), constructedStructure.getY()); + } + } + + @Override + public CUnit createUnit(final CSimulation simulation, final War3ID typeId, final int playerIndex, + final float x, final float y, final float facing) { + return createNewUnit(War3MapViewer.this.allObjectData, typeId, x, y, 0f, playerIndex, + (float) Math.toRadians(facing)); + } + }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, w3iFile.getPlayers()); + + this.walkableObjectsTree = new Quadtree<>(this.terrain.getEntireMap()); + if (this.doodadsAndDestructiblesLoaded) { + this.loadDoodadsAndDestructibles(this.allObjectData); + } else { + throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); + } + + loadSounds(); + + this.terrain.createWaves(); + + loadLightsAndShading(tileset); + } + + private void loadSounds() { + this.uiSounds = new KeyedSounds(this.uiSoundsTable, this.mapMpq); + } + + /** + * Loads the map information that should be loaded after UI, such as units, who + * need to be able to setup their UI counterparts (icons, etc) for their + * abilities while loading. This allows the dynamic creation of units while the + * game is playing to better share code with the startup sequence's creation of + * units. + * + * @throws IOException + */ + public void loadAfterUI() throws IOException { + if (this.unitsAndItemsLoaded) { + this.loadUnitsAndItems(this.allObjectData); + } else { + throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); + } + + // After we finish loading units, we need to update & create the stored shadow + // information for all unit shadows + this.terrain.initShadows(); + } + + private void loadLightsAndShading(final char tileset) { + // TODO this should be set by the war3map.j actually, not by the tileset, so the + // call to set day night models is just for testing to make the test look pretty + final Element defaultTerrainLights = this.worldEditData.get("TerrainLights"); + final Element defaultUnitLights = this.worldEditData.get("UnitLights"); + setDayNightModels(defaultTerrainLights.getField(Character.toString(tileset)), + defaultUnitLights.getField(Character.toString(tileset))); + + } + + private void loadDoodadsAndDestructibles(final Warcraft3MapObjectData modifications) throws IOException { + this.applyModificationFile(this.doodadsData, this.doodadMetaData, modifications.getDoodads(), + WorldEditorDataType.DOODADS); + this.applyModificationFile(this.doodadsData, this.destructableMetaData, modifications.getDestructibles(), + WorldEditorDataType.DESTRUCTIBLES); + + final War3MapDoo doo = this.mapMpq.readDoodads(); + + for (final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad : doo.getDoodads()) { + WorldEditorDataType type = WorldEditorDataType.DOODADS; + MutableGameObject row = modifications.getDoodads().get(doodad.getId()); + if (row == null) { + row = modifications.getDestructibles().get(doodad.getId()); + type = WorldEditorDataType.DESTRUCTIBLES; + } + if (row != null) { + String file = row.readSLKTag("file"); + final int numVar = row.readSLKTagInt("numVar"); + + if (file.endsWith(".mdx") || file.endsWith(".mdl")) { + file = file.substring(0, file.length() - 4); + } + + String fileVar = file; + + file += ".mdx"; + + if (numVar > 1) { + fileVar += Math.min(doodad.getVariation(), numVar - 1); + } + + fileVar += ".mdx"; + + final float maxPitch = row.readSLKTagFloat("maxPitch"); + final float maxRoll = row.readSLKTagFloat("maxRoll"); + if (type == WorldEditorDataType.DESTRUCTIBLES) { + final String shadowString = row.readSLKTag("shadow"); + if ((shadowString != null) && (shadowString.length() > 0) && !"_".equals(shadowString)) { + this.terrain.addShadow(shadowString, doodad.getLocation()[0], doodad.getLocation()[1]); + } + + final String pathingTexture = row.readSLKTag("pathTex"); + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + + BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (bufferedImage == null) { + if (this.mapMpq.has(pathingTexture)) { + try { + bufferedImage = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage); + } catch (final Exception exc) { + exc.printStackTrace(); + } + } + } + if (bufferedImage != null) { + this.terrain.pathingGrid.blitPathingOverlayTexture(doodad.getLocation()[0], + doodad.getLocation()[1], (int) Math.toDegrees(doodad.getAngle()), bufferedImage); + } + } + } + // First see if the model is local. + // Doodads referring to local models may have invalid variations, so if the + // variation doesn't exist, try without a variation. + + String path; + if (this.mapMpq.has(fileVar)) { + path = fileVar; + } else { + path = file; + } + MdxModel model; + if (this.mapMpq.has(path)) { + model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); + } else { + model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams); + } + + if (type == WorldEditorDataType.DESTRUCTIBLES) { + final RenderDestructable renderDestructable = new RenderDestructable(this, model, row, doodad, type, + maxPitch, maxRoll, doodad.getLife()); + if (row.readSLKTagBoolean("walkable")) { + final float x = doodad.getLocation()[0]; + final float y = doodad.getLocation()[1]; + final BoundingBox boundingBox = model.bounds.getBoundingBox(); + final float minX = boundingBox.min.x + x; + final float minY = boundingBox.min.y + y; + final Rectangle renderDestructableBounds = new Rectangle(minX, minY, boundingBox.getWidth(), + boundingBox.getHeight()); + this.walkableObjectsTree.add((MdxComplexInstance) renderDestructable.instance, + renderDestructableBounds); + } + this.doodads.add(renderDestructable); + } else { + this.doodads.add(new RenderDoodad(this, model, row, doodad, type, maxPitch, maxRoll)); + } + } + } + + // Cliff/Terrain doodads. + for (final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad : doo.getTerrainDoodads()) { + final MutableGameObject row = modifications.getDoodads().get(doodad.getId()); + String file = row.readSLKTag("file");// + if ("".equals(file)) { + final String blaBla = row.readSLKTag("file"); + System.out.println("bla"); + } + if (file.toLowerCase().endsWith(".mdl")) { + file = file.substring(0, file.length() - 4); + } + if (!file.toLowerCase().endsWith(".mdx")) { + file += ".mdx"; + } + final MdxModel model = (MdxModel) this.load(file, this.mapPathSolver, this.solverParams); + + final String pathingTexture = row.readSLKTag("pathTex"); + BufferedImage pathingTextureImage; + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + + pathingTextureImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (pathingTextureImage == null) { + if (this.mapMpq.has(pathingTexture)) { + try { + pathingTextureImage = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), pathingTextureImage); + } catch (final Exception exc) { + exc.printStackTrace(); + } + } + } + } else { + pathingTextureImage = null; + } + if (pathingTextureImage != null) { + // blit out terrain cells under this TerrainDoodad + final int textureWidth = pathingTextureImage.getWidth(); + final int textureHeight = pathingTextureImage.getHeight(); + final int textureWidthTerrainCells = textureWidth / 4; + final int textureHeightTerrainCells = textureHeight / 4; + final int minCellX = ((int) doodad.getLocation()[0]); + final int minCellY = ((int) doodad.getLocation()[1]); + final int maxCellX = (minCellX + textureWidthTerrainCells) - 1; + final int maxCellY = (minCellY + textureHeightTerrainCells) - 1; + for (int j = minCellY; j <= maxCellY; j++) { + for (int i = minCellX; i <= maxCellX; i++) { + this.terrain.removeTerrainCellWithoutFlush(i, j); + } + } + this.terrain.flushRemovedTerrainCells(); + } + + System.out.println("Loading terrain doodad: " + file); + this.terrainDoodads.add(new TerrainDoodad(this, model, row, doodad, pathingTextureImage)); + } + + this.doodadsReady = true; + this.anyReady = true; + } + + private void applyModificationFile(final MappedData doodadsData2, final MappedData doodadMetaData2, + final MutableObjectData destructibles, final WorldEditorDataType dataType) { + // TODO condense ported MappedData from Ghostwolf and MutableObjectData from + // Retera + + } + + private void loadUnitsAndItems(final Warcraft3MapObjectData modifications) throws IOException { + final War3Map mpq = this.mapMpq; + this.unitsReady = false; + + this.soundsetNameToSoundset = new HashMap<>(); + + if (this.dataSource.has("war3mapUnits.doo")) { + final War3MapUnitsDoo dooFile = mpq.readUnits(); + + // Collect the units and items data. + for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) { + final War3ID unitId = unit.getId(); + final float unitX = unit.getLocation()[0]; + final float unitY = unit.getLocation()[1]; + final float unitZ = unit.getLocation()[2]; + final int playerIndex = unit.getPlayer(); + final float unitAngle = unit.getAngle(); + + createNewUnit(modifications, unitId, unitX, unitY, unitZ, playerIndex, unitAngle); + } + } + + this.terrain.loadSplats(); + + this.unitsReady = true; + this.anyReady = true; + } + + private CUnit createNewUnit(final Warcraft3MapObjectData modifications, final War3ID unitId, float unitX, + float unitY, final float unitZ, final int playerIndex, final float unitAngle) { + UnitSoundset soundset = null; + MutableGameObject row = null; + String path = null; + Splat unitShadowSplat = null; + BufferedImage buildingPathingPixelMap = null; + final float unitVertexScale = 1.0f; + + // Hardcoded? + WorldEditorDataType type = null; + if (sloc.equals(unitId)) { // path = "Objects\\StartLocation\\StartLocation.mdx"; - type = null; /// ?????? - this.startLocations[playerIndex] = new Vector2(unitX, unitY); - } - else { - row = modifications.getUnits().get(unitId); - if (row == null) { - row = modifications.getItems().get(unitId); - if (row != null) { - type = WorldEditorDataType.ITEM; - path = row.getFieldAsString(ITEM_FILE, 0); + type = null; /// ?????? + this.startLocations[playerIndex] = new Vector2(unitX, unitY); + } else { + row = modifications.getUnits().get(unitId); + if (row == null) { + row = modifications.getItems().get(unitId); + if (row != null) { + type = WorldEditorDataType.ITEM; + path = row.getFieldAsString(ITEM_FILE, 0); - if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { - path = path.substring(0, path.length() - 4); - } + if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { + path = path.substring(0, path.length() - 4); + } - final String unitShadow = "Shadow"; - if ((unitShadow != null) && !"_".equals(unitShadow)) { - final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; - final float shadowX = 50; - final float shadowY = 50; - final float shadowWidth = 128; - final float shadowHeight = 128; - 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); - } + Element misc = miscData.get("Misc"); + String itemShadowFile = misc.getField("ItemShadowFile"); + int itemShadowWidth = misc.getFieldValue("ItemShadowSize", 0); + int itemShadowHeight = misc.getFieldValue("ItemShadowSize", 1); + int itemShadowX = misc.getFieldValue("ItemShadowOffset", 0); + int itemShadowY = misc.getFieldValue("ItemShadowOffset", 1); + if ((itemShadowFile != null) && !"_".equals(itemShadowFile)) { + final String texture = "ReplaceableTextures\\Shadows\\" + itemShadowFile + ".blp"; + final float shadowX = itemShadowX; + final float shadowY = itemShadowY; + final float shadowWidth = itemShadowWidth; + final float shadowHeight = itemShadowHeight; + 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); + } - path += ".mdx"; - } - } - else { - type = WorldEditorDataType.UNITS; - path = getUnitModelPath(row); + path += ".mdx"; + } + } else { + type = WorldEditorDataType.UNITS; + path = getUnitModelPath(row); - buildingPathingPixelMap = getBuildingPathingPixelMap(row); - 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); - } + buildingPathingPixelMap = getBuildingPathingPixelMap(row); + 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); + } - final String uberSplat = row.getFieldAsString(UBER_SPLAT, 0); - if (uberSplat != null) { - final Element uberSplatInfo = this.terrain.uberSplatTable.get(uberSplat); - if (uberSplatInfo != null) { - final String texturePath = uberSplatInfo.getField("Dir") + "\\" + uberSplatInfo.getField("file") - + ".blp"; - if (!this.terrain.splats.containsKey(texturePath)) { - this.terrain.splats.put(texturePath, new Splat()); - } - final float x = unitX; - final float y = unitY; - final float s = uberSplatInfo.getFieldFloatValue("Scale"); - this.terrain.splats.get(texturePath).locations - .add(new float[] { x - s, y - s, x + s, y + s, 1 }); - } - } + final String uberSplat = row.getFieldAsString(UBER_SPLAT, 0); + if (uberSplat != null) { + final Element uberSplatInfo = this.terrain.uberSplatTable.get(uberSplat); + if (uberSplatInfo != null) { + final String texturePath = uberSplatInfo.getField("Dir") + "\\" + uberSplatInfo.getField("file") + + ".blp"; + final float s = uberSplatInfo.getFieldFloatValue("Scale"); + if (unitsReady) { + this.terrain.addUberSplat(texturePath, unitX, unitY, 1, s); + } else { + if (!this.terrain.splats.containsKey(texturePath)) { + this.terrain.splats.put(texturePath, new Splat()); + } + 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}); + } + } + } - final String unitShadow = row.getFieldAsString(UNIT_SHADOW, 0); - if ((unitShadow != null) && !"_".equals(unitShadow)) { - final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; - final float shadowX = row.getFieldAsFloat(UNIT_SHADOW_X, 0); - final float shadowY = row.getFieldAsFloat(UNIT_SHADOW_Y, 0); - 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); - } - } + final String unitShadow = row.getFieldAsString(UNIT_SHADOW, 0); + if ((unitShadow != null) && !"_".equals(unitShadow)) { + final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; + final float shadowX = row.getFieldAsFloat(UNIT_SHADOW_X, 0); + final float shadowY = row.getFieldAsFloat(UNIT_SHADOW_Y, 0); + 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); + } + } - final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); - if ((buildingShadow != null) && !"_".equals(buildingShadow)) { - this.terrain.addShadow(buildingShadow, unitX, unitY); - } + final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); + if ((buildingShadow != null) && !"_".equals(buildingShadow)) { + this.terrain.addShadow(buildingShadow, unitX, unitY); + } - final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); - UnitSoundset unitSoundset = this.soundsetNameToSoundset.get(soundName); - if (unitSoundset == null) { - unitSoundset = new UnitSoundset(this.dataSource, this.unitAckSoundsTable, soundName); - this.soundsetNameToSoundset.put(soundName, unitSoundset); - } - soundset = unitSoundset; + final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); + UnitSoundset unitSoundset = this.soundsetNameToSoundset.get(soundName); + if (unitSoundset == null) { + unitSoundset = new UnitSoundset(this.dataSource, this.unitAckSoundsTable, soundName); + this.soundsetNameToSoundset.put(soundName, unitSoundset); + } + soundset = unitSoundset; - } - } + } + } - if (path != 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"; - if (this.dataSource.has(portraitPath)) { - portraitModel = (MdxModel) this.load(portraitPath, this.mapPathSolver, this.solverParams); - } - else { - portraitModel = model; - } - if (type == WorldEditorDataType.UNITS) { - final float angle = (float) Math.toDegrees(unitAngle); - final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), playerIndex, unitX, unitY, - angle, buildingPathingPixelMap); - final RenderUnitTypeData typeData = getUnitTypeData(unitId, row); - final RenderUnit renderUnit = new RenderUnit(this, model, row, unitX, unitY, unitZ, playerIndex, - soundset, portraitModel, simulationUnit, typeData); - this.unitToRenderPeer.put(simulationUnit, renderUnit); - this.units.add(renderUnit); - if (unitShadowSplat != null) { - unitShadowSplat.unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { - renderUnit.shadow = t; - } - }); - } - return simulationUnit; - } - else { - this.items - .add(new RenderItem(this, model, row, unitX, unitY, unitZ, unitAngle, soundset, portraitModel)); // TODO - // store - // somewhere - if (unitShadowSplat != null) { - unitShadowSplat.unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { + if (path != 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"; + if (this.dataSource.has(portraitPath)) { + portraitModel = (MdxModel) this.load(portraitPath, this.mapPathSolver, this.solverParams); + } else { + portraitModel = model; + } + if (type == WorldEditorDataType.UNITS) { + final float angle = (float) Math.toDegrees(unitAngle); + final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), playerIndex, unitX, unitY, + angle, buildingPathingPixelMap); + final RenderUnitTypeData typeData = getUnitTypeData(unitId, row); + final RenderUnit renderUnit = new RenderUnit(this, model, row, unitX, unitY, unitZ, playerIndex, + soundset, portraitModel, simulationUnit, typeData); + this.unitToRenderPeer.put(simulationUnit, renderUnit); + this.units.add(renderUnit); + if (unitShadowSplat != null) { + unitShadowSplat.unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + renderUnit.shadow = t; + } + }); + } + return simulationUnit; + } else { + this.items + .add(new RenderItem(this, model, row, unitX, unitY, unitZ, unitAngle, soundset, portraitModel)); // TODO + // store + // somewhere + if (unitShadowSplat != null) { + unitShadowSplat.unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { - } - }); - } - } - } - else { - System.err.println("Unknown unit ID: " + unitId); - } - return null; - } + } + }); + } + } + } else { + System.err.println("Unknown unit ID: " + unitId); + } + return null; + } - public String getUnitModelPath(final MutableGameObject row) { - String path; - path = row.getFieldAsString(UNIT_FILE, 0); + public String getUnitModelPath(final MutableGameObject row) { + String path; + path = row.getFieldAsString(UNIT_FILE, 0); - if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { - path = path.substring(0, path.length() - 4); - } - if ((row.readSLKTagInt("fileVerFlags") == 2) && this.dataSource.has(path + "_V1.mdx")) { - path += "_V1"; - } + if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { + path = path.substring(0, path.length() - 4); + } + if ((row.readSLKTagInt("fileVerFlags") == 2) && this.dataSource.has(path + "_V1.mdx")) { + path += "_V1"; + } - path += ".mdx"; - return path; - } + path += ".mdx"; + return path; + } - private BufferedImage getBuildingPathingPixelMap(final MutableGameObject row) { - BufferedImage buildingPathingPixelMap = null; - final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0); - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - buildingPathingPixelMap = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (buildingPathingPixelMap == null) { - try { - if (pathingTexture.toLowerCase().endsWith(".tga")) { - buildingPathingPixelMap = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - } - else { - try (InputStream stream = this.mapMpq.getResourceAsStream(pathingTexture)) { - buildingPathingPixelMap = ImageIO.read(stream); - System.out.println("LOADING BLP PATHING: " + pathingTexture); - } - } - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), buildingPathingPixelMap); - } - catch (final IOException exc) { - System.err.println("Failure to get pathing: " + exc.getClass() + ":" + exc.getMessage()); - } - } - } - return buildingPathingPixelMap; - } + private BufferedImage getBuildingPathingPixelMap(final MutableGameObject row) { + BufferedImage buildingPathingPixelMap = null; + final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0); + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + buildingPathingPixelMap = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (buildingPathingPixelMap == null) { + try { + if (pathingTexture.toLowerCase().endsWith(".tga")) { + buildingPathingPixelMap = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + } else { + try (InputStream stream = this.mapMpq.getResourceAsStream(pathingTexture)) { + buildingPathingPixelMap = ImageIO.read(stream); + System.out.println("LOADING BLP PATHING: " + pathingTexture); + } + } + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), buildingPathingPixelMap); + } catch (final IOException exc) { + System.err.println("Failure to get pathing: " + exc.getClass() + ":" + exc.getMessage()); + } + } + } + return buildingPathingPixelMap; + } - public RenderUnitTypeData getUnitTypeData(final War3ID key, final MutableGameObject row) { - RenderUnitTypeData unitTypeData = this.unitIdToTypeData.get(key); - if (unitTypeData == null) { - unitTypeData = new RenderUnitTypeData(row.getFieldAsFloat(MAX_PITCH, 0), row.getFieldAsFloat(MAX_ROLL, 0), - row.getFieldAsFloat(ELEVATION_SAMPLE_RADIUS, 0)); - this.unitIdToTypeData.put(key, unitTypeData); - } - return unitTypeData; - } + public RenderUnitTypeData getUnitTypeData(final War3ID key, final MutableGameObject row) { + RenderUnitTypeData unitTypeData = this.unitIdToTypeData.get(key); + if (unitTypeData == null) { + unitTypeData = new RenderUnitTypeData(row.getFieldAsFloat(MAX_PITCH, 0), row.getFieldAsFloat(MAX_ROLL, 0), + row.getFieldAsFloat(ELEVATION_SAMPLE_RADIUS, 0)); + this.unitIdToTypeData.put(key, unitTypeData); + } + return unitTypeData; + } - @Override - public void update() { - if (this.anyReady) { - this.terrain.update(); + @Override + public void update() { + if (this.anyReady) { + this.terrain.update(); - super.update(); + super.update(); - for (final RenderUnit unit : this.units) { - unit.updateAnimations(this); - } - final Iterator projectileIterator = this.projectiles.iterator(); - while (projectileIterator.hasNext()) { - final RenderEffect projectile = projectileIterator.next(); - if (projectile.updateAnimations(this, Gdx.graphics.getDeltaTime())) { - projectileIterator.remove(); - } - } - for (final RenderItem item : this.items) { - final MdxComplexInstance instance = item.instance; - final MdxComplexInstance mdxComplexInstance = instance; - if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { - SequenceUtils.randomStandSequence(mdxComplexInstance); - } - } - for (final RenderDoodad item : this.doodads) { - final ModelInstance instance = item.instance; - if (instance instanceof MdxComplexInstance) { - final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance; - if ((mdxComplexInstance.sequence == -1) || (mdxComplexInstance.sequenceEnded - && ((item.getAnimation() != AnimationTokens.PrimaryTag.DEATH) - || (((MdxModel) mdxComplexInstance.model).sequences.get(mdxComplexInstance.sequence) - .getFlags() == 0)))) { - SequenceUtils.randomSequence(mdxComplexInstance, item.getAnimation(), SequenceUtils.EMPTY, - true); + for (final RenderUnit unit : this.units) { + unit.updateAnimations(this); + } + final Iterator projectileIterator = this.projectiles.iterator(); + while (projectileIterator.hasNext()) { + final RenderEffect projectile = projectileIterator.next(); + if (projectile.updateAnimations(this, Gdx.graphics.getDeltaTime())) { + projectileIterator.remove(); + } + } + for (final RenderItem item : this.items) { + final MdxComplexInstance instance = item.instance; + final MdxComplexInstance mdxComplexInstance = instance; + if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { + SequenceUtils.randomStandSequence(mdxComplexInstance); + } + } + for (final RenderDoodad item : this.doodads) { + final ModelInstance instance = item.instance; + if (instance instanceof MdxComplexInstance) { + final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance; + if ((mdxComplexInstance.sequence == -1) || (mdxComplexInstance.sequenceEnded + && ((item.getAnimation() != AnimationTokens.PrimaryTag.DEATH) + || (((MdxModel) mdxComplexInstance.model).sequences.get(mdxComplexInstance.sequence) + .getFlags() == 0)))) { + SequenceUtils.randomSequence(mdxComplexInstance, item.getAnimation(), SequenceUtils.EMPTY, + true); - } - } - } + } + } + } - final float rawDeltaTime = Gdx.graphics.getRawDeltaTime(); - this.updateTime += rawDeltaTime; - while (this.updateTime >= WarsmashConstants.SIMULATION_STEP_TIME) { - this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME; - this.simulation.update(); - } - this.dncTerrain.setFrameByRatio( - this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); - this.dncTerrain.update(rawDeltaTime, null); - this.dncUnit.setFrameByRatio( - this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); - this.dncUnit.update(rawDeltaTime, null); - this.dncUnitDay.setFrameByRatio(0.5f); - this.dncUnitDay.update(rawDeltaTime, null); - this.dncTarget.setFrameByRatio( - this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); - this.dncTarget.update(rawDeltaTime, null); - } - } + final float rawDeltaTime = Gdx.graphics.getRawDeltaTime(); + this.updateTime += rawDeltaTime; + while (this.updateTime >= WarsmashConstants.SIMULATION_STEP_TIME) { + this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME; + this.simulation.update(); + } + this.dncTerrain.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncTerrain.update(rawDeltaTime, null); + this.dncUnit.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncUnit.update(rawDeltaTime, null); + this.dncUnitDay.setFrameByRatio(0.5f); + this.dncUnitDay.update(rawDeltaTime, null); + this.dncTarget.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncTarget.update(rawDeltaTime, null); + } + } - @Override - public void render() { - if (this.anyReady) { - final Scene worldScene = this.worldScene; + @Override + public void render() { + if (this.anyReady) { + final Scene worldScene = this.worldScene; - startFrame(); - worldScene.startFrame(); - worldScene.renderOpaque(this.dynamicShadowManager, this.webGL); - this.terrain.renderGround(this.dynamicShadowManager); - this.terrain.renderCliffs(); - worldScene.renderOpaque(); - this.terrain.renderUberSplats(); - this.terrain.renderWater(); - worldScene.renderTranslucent(); + startFrame(); + worldScene.startFrame(); + worldScene.renderOpaque(this.dynamicShadowManager, this.webGL); + this.terrain.renderGround(this.dynamicShadowManager); + this.terrain.renderCliffs(); + worldScene.renderOpaque(); + this.terrain.renderUberSplats(); + this.terrain.renderWater(); + worldScene.renderTranslucent(); - final List scenes = this.scenes; - for (final Scene scene : scenes) { - if (scene != worldScene) { - scene.startFrame(); - scene.renderOpaque(); - scene.renderTranslucent(); - } - } - } - } + final List scenes = this.scenes; + for (final Scene scene : scenes) { + if (scene != worldScene) { + scene.startFrame(); + scene.renderOpaque(); + scene.renderTranslucent(); + } + } + } + } - public void deselect() { - if (!this.selModels.isEmpty()) { - for (final SplatModel model : this.selModels) { - this.terrain.removeSplatBatchModel(model); - } - this.selModels.clear(); - for (final RenderUnit unit : this.selected) { - unit.selectionCircle = null; - } - } - this.selected.clear(); - } + public void deselect() { + if (!this.selModels.isEmpty()) { + for (final SplatModel model : this.selModels) { + this.terrain.removeSplatBatchModel("selection"); + } + this.selModels.clear(); + for (final RenderUnit unit : this.selected) { + unit.selectionCircle = null; + } + } + this.selected.clear(); + } - public void doSelectUnit(final List units) { - deselect(); - if (units.isEmpty()) { - return; - } + public void doSelectUnit(final List units) { + deselect(); + if (units.isEmpty()) { + return; + } - final Map splats = new HashMap(); - for (final RenderUnit unit : units) { - if (unit.row != null) { - if (unit.selectionScale > 0) { - final float selectionSize = unit.selectionScale * this.selectionCircleScaleFactor; - String path = null; - for (int i = 0; i < this.selectionCircleSizes.size(); i++) { - final SelectionCircleSize selectionCircleSize = this.selectionCircleSizes.get(i); - if ((selectionSize < selectionCircleSize.size) - || (i == (this.selectionCircleSizes.size() - 1))) { - path = selectionCircleSize.texture; - break; - } - } - if (!path.toLowerCase().endsWith(".blp")) { - path += ".blp"; - } - if (!splats.containsKey(path)) { - splats.put(path, new Splat()); - } - final float x = unit.location[0]; - final float y = unit.location[1]; - System.out.println("Selecting a unit at " + x + "," + y); - final float z = unit.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); - splats.get(path).locations.add(new float[] { x - (selectionSize / 2), y - (selectionSize / 2), - x + (selectionSize / 2), y + (selectionSize / 2), z + 5 }); - splats.get(path).unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { - unit.selectionCircle = t; - } - }); - } - this.selected.add(unit); - } - } - this.selModels.clear(); - for (final Map.Entry entry : splats.entrySet()) { - final String path = entry.getKey(); - final Splat locations = entry.getValue(); - final SplatModel model = new SplatModel(Gdx.gl30, (Texture) load(path, PathSolver.DEFAULT, null), - locations.locations, this.terrain.centerOffset, locations.unitMapping, true); - model.color[0] = 0; - model.color[1] = 1; - model.color[2] = 0; - model.color[3] = 1; - this.selModels.add(model); - this.terrain.addSplatBatchModel(model); - } - } + final Map splats = new HashMap(); + for (final RenderUnit unit : units) { + if (unit.row != null) { + if (unit.selectionScale > 0) { + final float selectionSize = unit.selectionScale * this.selectionCircleScaleFactor; + String path = null; + for (int i = 0; i < this.selectionCircleSizes.size(); i++) { + final SelectionCircleSize selectionCircleSize = this.selectionCircleSizes.get(i); + if ((selectionSize < selectionCircleSize.size) + || (i == (this.selectionCircleSizes.size() - 1))) { + path = selectionCircleSize.texture; + break; + } + } + if (!path.toLowerCase().endsWith(".blp")) { + path += ".blp"; + } + if (!splats.containsKey(path)) { + splats.put(path, new Splat()); + } + final float x = unit.location[0]; + final float y = unit.location[1]; + System.out.println("Selecting a unit at " + x + "," + y); + final float z = unit.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); + splats.get(path).locations.add(new float[]{x - (selectionSize / 2), y - (selectionSize / 2), + x + (selectionSize / 2), y + (selectionSize / 2), z + 5}); + splats.get(path).unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + unit.selectionCircle = t; + } + }); + } + this.selected.add(unit); + } + } + this.selModels.clear(); + for (final Map.Entry entry : splats.entrySet()) { + final String path = entry.getKey(); + final Splat locations = entry.getValue(); + final SplatModel model = new SplatModel(Gdx.gl30, (Texture) load(path, PathSolver.DEFAULT, null), + locations.locations, this.terrain.centerOffset, locations.unitMapping, true); + model.color[0] = 0; + model.color[1] = 1; + model.color[2] = 0; + model.color[3] = 1; + this.selModels.add(model); + this.terrain.addSplatBatchModel("selection", model); + } + } - public void getClickLocation(final Vector3 out, final int screenX, final int screenY) { - final float[] ray = rayHeap; - mousePosHeap.set(screenX, screenY); - 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 - RenderMathUtils.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices, - this.terrain.softwareGroundMesh.indices, 3, out); - rectangleHeap.set(Math.min(out.x, gdxRayHeap.origin.x), Math.min(out.y, gdxRayHeap.origin.y), - Math.abs(out.x - gdxRayHeap.origin.x), Math.abs(out.y - gdxRayHeap.origin.y)); - this.walkableObjectsTree.intersect(rectangleHeap, this.walkablesIntersectionFinder.reset(gdxRayHeap)); - if (this.walkablesIntersectionFinder.found) { - out.set(this.walkablesIntersectionFinder.intersection); - } - else { - out.z = Math.max(getWalkableRenderHeight(out.x, out.y), this.terrain.getGroundHeight(out.x, out.y)); - } - } + public void getClickLocation(final Vector3 out, final int screenX, final int screenY) { + final float[] ray = rayHeap; + mousePosHeap.set(screenX, screenY); + 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 + RenderMathUtils.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices, + this.terrain.softwareGroundMesh.indices, 3, out); + rectangleHeap.set(Math.min(out.x, gdxRayHeap.origin.x), Math.min(out.y, gdxRayHeap.origin.y), + Math.abs(out.x - gdxRayHeap.origin.x), Math.abs(out.y - gdxRayHeap.origin.y)); + this.walkableObjectsTree.intersect(rectangleHeap, this.walkablesIntersectionFinder.reset(gdxRayHeap)); + if (this.walkablesIntersectionFinder.found) { + out.set(this.walkablesIntersectionFinder.intersection); + } else { + out.z = Math.max(getWalkableRenderHeight(out.x, out.y), this.terrain.getGroundHeight(out.x, out.y)); + } + } - public void showConfirmation(final Vector3 position, final float red, final float green, final float blue) { - this.confirmationInstance.show(); - this.confirmationInstance.setSequence(0); - this.confirmationInstance.setLocation(position); - this.worldScene.instanceMoved(this.confirmationInstance, position.x, position.y); - this.confirmationInstance.vertexColor[0] = red; - this.confirmationInstance.vertexColor[1] = green; - this.confirmationInstance.vertexColor[2] = blue; - } + public void showConfirmation(final Vector3 position, final float red, final float green, final float blue) { + this.confirmationInstance.show(); + this.confirmationInstance.setSequence(0); + this.confirmationInstance.setLocation(position); + this.worldScene.instanceMoved(this.confirmationInstance, position.x, position.y); + this.confirmationInstance.vertexColor[0] = red; + this.confirmationInstance.vertexColor[1] = green; + this.confirmationInstance.vertexColor[2] = blue; + } - 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 + 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; - } - } - } - List sel; - if (entity != null) { - if (toggle) { - sel = new ArrayList<>(this.selected); - final int idx = sel.indexOf(entity); - if (idx >= 0) { - sel.remove(idx); - } - else { - sel.add(entity); - } - } - else { - sel = Arrays.asList(entity); - } - } - else { - sel = Collections.emptyList(); - } - this.doSelectUnit(sel); - return sel; - } + 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; + } + } + } + List sel; + if (entity != null) { + if (toggle) { + sel = new ArrayList<>(this.selected); + final int idx = sel.indexOf(entity); + if (idx >= 0) { + sel.remove(idx); + } else { + sel.add(entity); + } + } else { + sel = Arrays.asList(entity); + } + } else { + sel = Collections.emptyList(); + } + this.doSelectUnit(sel); + return sel; + } - public RenderUnit rayPickUnit(final float x, final float y) { - return rayPickUnit(x, y, CUnitFilterFunction.ACCEPT_ALL); - } + public RenderUnit rayPickUnit(final float x, final float y) { + return rayPickUnit(x, y, CUnitFilterFunction.ACCEPT_ALL); + } - public RenderUnit rayPickUnit(final float x, final float y, final CUnitFilterFunction filter) { - final float[] ray = rayHeap; - mousePosHeap.set(x, y); - this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); - gdxRayHeap.direction.nor();// needed for libgdx + public RenderUnit rayPickUnit(final float x, final float y, final CUnitFilterFunction filter) { + final float[] ray = rayHeap; + mousePosHeap.set(x, y); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx - RenderUnit entity = null; - for (final RenderUnit unit : this.units) { - final MdxComplexInstance instance = unit.instance; - if (instance.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)) { - entity = unit; - } - } - } - } - return entity; - } + 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 (filter.call(unit.getSimulationUnit()) && (intersectionHeap.z > this.terrain + .getGroundHeight(intersectionHeap.x, intersectionHeap.y))) { + if ((entity == null) || (entity.instance.depth > instance.depth)) { + entity = unit; + } + } + } + } + return entity; + } - private static final class MappedDataCallbackImplementation implements LoadGenericCallback { - @Override - public Object call(final InputStream data) { - final StringBuilder stringBuilder = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { - String line; - while ((line = reader.readLine()) != null) { - stringBuilder.append(line); - stringBuilder.append("\n"); - } - } - catch (final UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - catch (final IOException e) { - throw new RuntimeException(e); - } - return new MappedData(stringBuilder.toString()); - } - } + private static final class MappedDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + final StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append("\n"); + } + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } catch (final IOException e) { + throw new RuntimeException(e); + } + return new MappedData(stringBuilder.toString()); + } + } - private static final class StringDataCallbackImplementation implements LoadGenericCallback { - @Override - public Object call(final InputStream data) { - if (data == null) { - System.err.println("data null"); - } - final StringBuilder stringBuilder = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { - String line; - while ((line = reader.readLine()) != null) { - stringBuilder.append(line); - stringBuilder.append("\n"); - } - } - catch (final UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - catch (final IOException e) { - throw new RuntimeException(e); - } - return stringBuilder.toString(); - } - } + private static final class StringDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + if (data == null) { + System.err.println("data null"); + } + final StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append("\n"); + } + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } catch (final IOException e) { + throw new RuntimeException(e); + } + return stringBuilder.toString(); + } + } - private static final class StreamDataCallbackImplementation implements LoadGenericCallback { - @Override - public Object call(final InputStream data) { - return data; - } - } + private static final class StreamDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + return data; + } + } - public static final class SolverParams { - public char tileset; - public boolean reforged; - public boolean hd; - } + public static final class SolverParams { + public char tileset; + public boolean reforged; + public boolean hd; + } - public static final class CliffInfo { - public List locations = new ArrayList<>(); - public List textures = new ArrayList<>(); - } + public static final class CliffInfo { + public List locations = new ArrayList<>(); + public List textures = new ArrayList<>(); + } - private static final int MAXIMUM_ACCEPTED = 1 << 30; - private float selectionCircleScaleFactor; - private DataTable worldEditData; - private WorldEditStrings worldEditStrings; - private Warcraft3MapObjectData allObjectData; - private AbilityDataUI abilityDataUI; - private Map soundsetNameToSoundset; + private static final int MAXIMUM_ACCEPTED = 1 << 30; + private float selectionCircleScaleFactor; + private DataTable worldEditData; + private WorldEditStrings worldEditStrings; + private Warcraft3MapObjectData allObjectData; + private AbilityDataUI abilityDataUI; + private Map soundsetNameToSoundset; - /** - * Returns a power of two size for the given target capacity. - */ - private static final int pow2GreaterThan(final int capacity) { - int numElements = capacity - 1; - numElements |= numElements >>> 1; - numElements |= numElements >>> 2; - numElements |= numElements >>> 4; - numElements |= numElements >>> 8; - numElements |= numElements >>> 16; - return (numElements < 0) ? 1 : (numElements >= MAXIMUM_ACCEPTED) ? MAXIMUM_ACCEPTED : numElements + 1; - } + /** + * Returns a power of two size for the given target capacity. + */ + private static final int pow2GreaterThan(final int capacity) { + int numElements = capacity - 1; + numElements |= numElements >>> 1; + numElements |= numElements >>> 2; + numElements |= numElements >>> 4; + numElements |= numElements >>> 8; + numElements |= numElements >>> 16; + return (numElements < 0) ? 1 : (numElements >= MAXIMUM_ACCEPTED) ? MAXIMUM_ACCEPTED : numElements + 1; + } - public void standOnRepeat(final MdxComplexInstance instance) { - instance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - SequenceUtils.randomStandSequence(instance); - } + public void standOnRepeat(final MdxComplexInstance instance) { + instance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + SequenceUtils.randomStandSequence(instance); + } - private static final class SelectionCircleSize { - private final float size; - private final String texture; - private final String textureDotted; + private static final class SelectionCircleSize { + private final float size; + private final String texture; + private final String textureDotted; - public SelectionCircleSize(final float size, final String texture, final String textureDotted) { - this.size = size; - this.texture = texture; - this.textureDotted = textureDotted; - } - } + public SelectionCircleSize(final float size, final String texture, final String textureDotted) { + this.size = size; + this.texture = texture; + this.textureDotted = textureDotted; + } + } - public void setDayNightModels(final String terrainDNCFile, final String unitDNCFile) { - final MdxModel terrainDNCModel = (MdxModel) load(mdx(terrainDNCFile), PathSolver.DEFAULT, null); - this.dncTerrain = (MdxComplexInstance) terrainDNCModel.addInstance(); - this.dncTerrain.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncTerrain.setSequence(0); - final MdxModel unitDNCModel = (MdxModel) load(mdx(unitDNCFile), PathSolver.DEFAULT, null); - this.dncUnit = (MdxComplexInstance) unitDNCModel.addInstance(); - this.dncUnit.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncUnit.setSequence(0); - this.dncUnitDay = (MdxComplexInstance) unitDNCModel.addInstance(); - this.dncUnitDay.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncUnitDay.setSequence(0); - final MdxModel targetDNCModel = (MdxModel) load( - mdx("Environment\\DNC\\DNCLordaeron\\DNCLordaeronTarget\\DNCLordaeronTarget.mdl"), PathSolver.DEFAULT, - null); - this.dncTarget = (MdxComplexInstance) targetDNCModel.addInstance(); - this.dncTarget.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncTarget.setSequence(0); - } + public void setDayNightModels(final String terrainDNCFile, final String unitDNCFile) { + final MdxModel terrainDNCModel = (MdxModel) load(mdx(terrainDNCFile), PathSolver.DEFAULT, null); + this.dncTerrain = (MdxComplexInstance) terrainDNCModel.addInstance(); + this.dncTerrain.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncTerrain.setSequence(0); + final MdxModel unitDNCModel = (MdxModel) load(mdx(unitDNCFile), PathSolver.DEFAULT, null); + this.dncUnit = (MdxComplexInstance) unitDNCModel.addInstance(); + this.dncUnit.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncUnit.setSequence(0); + this.dncUnitDay = (MdxComplexInstance) unitDNCModel.addInstance(); + this.dncUnitDay.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncUnitDay.setSequence(0); + final MdxModel targetDNCModel = (MdxModel) load( + mdx("Environment\\DNC\\DNCLordaeron\\DNCLordaeronTarget\\DNCLordaeronTarget.mdl"), PathSolver.DEFAULT, + null); + this.dncTarget = (MdxComplexInstance) targetDNCModel.addInstance(); + this.dncTarget.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncTarget.setSequence(0); + } - private static String mdx(String mdxPath) { - if (mdxPath.toLowerCase().endsWith(".mdl")) { - mdxPath = mdxPath.substring(0, mdxPath.length() - 4); - } - if (!mdxPath.toLowerCase().endsWith(".mdx")) { - mdxPath += ".mdx"; - } - return mdxPath; - } + private static String mdx(String mdxPath) { + if (mdxPath.toLowerCase().endsWith(".mdl")) { + mdxPath = mdxPath.substring(0, mdxPath.length() - 4); + } + if (!mdxPath.toLowerCase().endsWith(".mdx")) { + mdxPath += ".mdx"; + } + return mdxPath; + } - @Override - public SceneLightManager createLightManager(final boolean simple) { - if (simple) { - return new W3xScenePortraitLightManager(this, this.lightDirection); - } - else { - return new W3xSceneWorldLightManager(this); - } - } + @Override + public SceneLightManager createLightManager(final boolean simple) { + if (simple) { + return new W3xScenePortraitLightManager(this, this.lightDirection); + } else { + return new W3xSceneWorldLightManager(this); + } + } - public WorldEditStrings getWorldEditStrings() { - return this.worldEditStrings; - } + public WorldEditStrings getWorldEditStrings() { + return this.worldEditStrings; + } - public void setGameUI(final GameUI gameUI) { - this.gameUI = gameUI; - this.abilityDataUI = new AbilityDataUI(this.allObjectData.getAbilities(), this.allObjectData.getUnits(), - gameUI); - } + public void setGameUI(final GameUI gameUI) { + this.gameUI = gameUI; + this.abilityDataUI = new AbilityDataUI(this.allObjectData.getAbilities(), this.allObjectData.getUnits(), + gameUI); + } - public GameUI getGameUI() { - return this.gameUI; - } + public GameUI getGameUI() { + return this.gameUI; + } - public AbilityDataUI getAbilityDataUI() { - return this.abilityDataUI; - } + public AbilityDataUI getAbilityDataUI() { + return this.abilityDataUI; + } - public KeyedSounds getUiSounds() { - return this.uiSounds; - } + public KeyedSounds getUiSounds() { + return this.uiSounds; + } - public Warcraft3MapObjectData getAllObjectData() { - return this.allObjectData; - } + public Warcraft3MapObjectData getAllObjectData() { + return this.allObjectData; + } - public float getWalkableRenderHeight(final float x, final float y) { - this.walkableObjectsTree.intersect(x, y, this.walkablesIntersector.reset(x, y)); - return this.walkablesIntersector.z; - } + public float getWalkableRenderHeight(final float x, final float y) { + this.walkableObjectsTree.intersect(x, y, this.walkablesIntersector.reset(x, y)); + return this.walkablesIntersector.z; + } - public MdxComplexInstance getHighestWalkableUnder(final float x, final float y) { - this.walkableObjectsTree.intersect(x, y, this.intersectorFindsHighestWalkable.reset(x, y)); - return this.intersectorFindsHighestWalkable.highestInstance; - } + public MdxComplexInstance getHighestWalkableUnder(final float x, final float y) { + this.walkableObjectsTree.intersect(x, y, this.intersectorFindsHighestWalkable.reset(x, y)); + return this.intersectorFindsHighestWalkable.highestInstance; + } - private static final class QuadtreeIntersectorFindsWalkableRenderHeight - implements QuadtreeIntersector { - private float z; - private final Ray ray = new Ray(); - private final Vector3 intersection = new Vector3(); + private static final class QuadtreeIntersectorFindsWalkableRenderHeight + implements QuadtreeIntersector { + private float z; + private final Ray ray = new Ray(); + private final Vector3 intersection = new Vector3(); - private QuadtreeIntersectorFindsWalkableRenderHeight reset(final float x, final float y) { - this.z = -Float.MAX_VALUE; - this.ray.set(x, y, 4096, 0, 0, -8192); - return this; - } + private QuadtreeIntersectorFindsWalkableRenderHeight reset(final float x, final float y) { + this.z = -Float.MAX_VALUE; + this.ray.set(x, y, 4096, 0, 0, -8192); + return this; + } - @Override - public boolean onIntersect(final MdxComplexInstance intersectingObject) { - if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { - this.z = Math.max(this.z, this.intersection.z); - } - return false; - } - } + @Override + public boolean onIntersect(final MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { + this.z = Math.max(this.z, this.intersection.z); + } + return false; + } + } - private static final class QuadtreeIntersectorFindsHighestWalkable - implements QuadtreeIntersector { - private float z; - private final Ray ray = new Ray(); - private final Vector3 intersection = new Vector3(); - private MdxComplexInstance highestInstance; + private static final class QuadtreeIntersectorFindsHighestWalkable + implements QuadtreeIntersector { + private float z; + private final Ray ray = new Ray(); + private final Vector3 intersection = new Vector3(); + private MdxComplexInstance highestInstance; - private QuadtreeIntersectorFindsHighestWalkable reset(final float x, final float y) { - this.z = -Float.MAX_VALUE; - this.ray.set(x, y, 4096, 0, 0, -8192); - this.highestInstance = null; - return this; - } + private QuadtreeIntersectorFindsHighestWalkable reset(final float x, final float y) { + this.z = -Float.MAX_VALUE; + this.ray.set(x, y, 4096, 0, 0, -8192); + this.highestInstance = null; + return this; + } - @Override - public boolean onIntersect(final MdxComplexInstance intersectingObject) { - if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { - if (this.intersection.z > this.z) { - this.z = this.intersection.z; - this.highestInstance = intersectingObject; - } - } - return false; - } - } + @Override + public boolean onIntersect(final MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { + if (this.intersection.z > this.z) { + this.z = this.intersection.z; + this.highestInstance = intersectingObject; + } + } + return false; + } + } - private static final class QuadtreeIntersectorFindsHitPoint implements QuadtreeIntersector { - private Ray ray; - private final Vector3 intersection = new Vector3(); - private boolean found; + private static final class QuadtreeIntersectorFindsHitPoint implements QuadtreeIntersector { + private Ray ray; + private final Vector3 intersection = new Vector3(); + private boolean found; - private QuadtreeIntersectorFindsHitPoint reset(final Ray ray) { - this.ray = ray; - this.found = false; - return this; - } + private QuadtreeIntersectorFindsHitPoint reset(final Ray ray) { + this.ray = ray; + this.found = false; + return this; + } - @Override - public boolean onIntersect(final MdxComplexInstance intersectingObject) { - if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { - this.found = true; - return true; - } - return false; - } - } + @Override + public boolean onIntersect(final MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { + this.found = true; + return true; + } + return false; + } + } } 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 c5b6b40..471801b 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,13 +5,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.Buffer; import java.nio.FloatBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.TreeSet; +import java.util.*; import java.util.function.Consumer; import javax.imageio.ImageIO; @@ -54,1148 +48,1167 @@ 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 List 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 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; - 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 ArrayList<>(); - 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) { - 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 x, final float y) { - 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[] { x, y }); - } + 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 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; + 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 shadowSize = columns * rows; - final byte[] shadowData = new byte[columns * rows]; - if (this.viewer.mapMpq.has("war3map.shd")) { - final byte[] buffer; + 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); + } - try (final InputStream shadowSource = this.viewer.mapMpq.getResourceAsStream("war3map.shd")) { - buffer = IOUtils.toByteArray(shadowSource); - } + 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; - for (int i = 0; i < shadowSize; i++) { - shadowData[i] = (byte) ((buffer[i] & 0xFF) / 2f); - } - } + final int shadowSize = columns * rows; + shadowData = new byte[columns * rows]; + if (this.viewer.mapMpq.has("war3map.shd")) { + final byte[] buffer; - for (final Map.Entry fileAndTexture : this.shadowTextures.entrySet()) { - final String file = fileAndTexture.getKey(); - final Texture texture = fileAndTexture.getValue(); + try (final InputStream shadowSource = this.viewer.mapMpq.getResourceAsStream("war3map.shd")) { + buffer = IOUtils.toByteArray(shadowSource); + } - 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)) { - final int x0 = (int) Math.floor((location[0] - centerOffset[0]) / 32.0) - ox; - final int y0 = (int) Math.floor((location[1] - centerOffset[1]) / 32.0) + oy; - 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 (((RawOpenGLTextureResource) texture).getData().get((((y * width) + x) * 4) + 3) != 0) { - shadowData[((y0 - y) * columns) + x0 + x] = (byte) 128; - } - } - } - } - } + for (int i = 0; i < shadowSize; i++) { + shadowData[i] = (byte) ((buffer[i] & 0xFF) / 2f); + } + } - 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; - } - } + for (final Map.Entry fileAndTexture : this.shadowTextures.entrySet()) { + final String file = fileAndTexture.getKey(); + final Texture texture = fileAndTexture.getValue(); - 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)); - } + 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) { + 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(shadowData)); + this.initShadowsFinished = true; + } + + 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; + } + } + } + } // public Vector3 groundNormal(final Vector3 out, int x, int y) { // final float[] centerOffset = this.centerOffset; @@ -1237,172 +1250,180 @@ 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.add(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(final SplatModel model) { - this.uberSplatModels.remove(model); - } + public void removeSplatBatchModel(String path) { + this.uberSplatModels.remove(path); + } - public void addSplatBatchModel(final SplatModel model) { - this.uberSplatModels.add(model); - } + public void addSplatBatchModel(String path, final SplatModel model) { + this.uberSplatModels.put(path, model); + } - public static final class SoftwareGroundMesh { - public final float[] vertices; - public final int[] indices; + 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); + } - 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; + } } 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 e6368ba..8affe9f 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 @@ -12,13 +12,10 @@ import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; -import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; +import com.etheller.warsmash.viewer5.handlers.w3x.*; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; -import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; -import com.etheller.warsmash.viewer5.handlers.w3x.UnitSoundset; -import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; @@ -39,6 +36,7 @@ public class RenderUnit { private static final War3ID ORIENTATION_INTERPOLATION = War3ID.fromString("uori"); private static final War3ID ANIM_PROPS = War3ID.fromString("uani"); private static final War3ID BLEND_TIME = War3ID.fromString("uble"); + private static final War3ID BUILD_SOUND_LABEL = War3ID.fromString("ubsl"); private static final float[] heapZ = new float[3]; public final MdxComplexInstance instance; public final MutableGameObject row; @@ -66,6 +64,7 @@ public class RenderUnit { private boolean corpse; private boolean boneCorpse; private final RenderUnitTypeData typeData; + public UnitSound buildSound; 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, @@ -128,6 +127,7 @@ public class RenderUnit { final float blendTime = row.getFieldAsFloat(BLEND_TIME, 0); instance.setBlendTime(blendTime * 1000.0f); + buildSound = map.getUiSounds().getSound(row.getFieldAsString(BUILD_SOUND_LABEL, 0)); } this.instance = instance; @@ -350,6 +350,9 @@ public class RenderUnit { this.selectionCircle.move(dx, dy, map.terrain.centerOffset); } this.unitAnimationListenerImpl.update(); + if(!dead && simulationUnit.isConstructing()) { + instance.setFrameByRatio(simulationUnit.getConstructionProgress() / simulationUnit.getUnitType().getBuildTime()); + } } private float getGroundHeightSample(final float groundHeight, final MdxComplexInstance currentWalkableUnder, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 2e29414..bbd3378 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -223,6 +223,10 @@ public class CSimulation { this.simulationRenderController.spawnUnitDamageSound(damagedUnit, weaponSound, armorType); } + public void unitConstructedEvent(CUnit constructingUnit, CUnit constructedStructure) { + this.simulationRenderController.spawnUnitConstructionSound(constructingUnit, constructedStructure); + } + public CPlayer getPlayer(final int index) { return this.players.get(index); } @@ -230,4 +234,8 @@ public class CSimulation { public CommandErrorListener getCommandErrorListener() { return this.commandErrorListener; } + + public void unitConstructFinishEvent(CUnit constructedStructure) { + this.simulationRenderController.spawnUnitConstructionFinishSound(constructedStructure); + } } 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 8eb6864..e69812b 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 @@ -70,6 +70,8 @@ public class CUnit extends CWidget { private transient CBehaviorFollow followBehavior; private transient CBehaviorPatrol patrolBehavior; private transient CBehaviorStop stopBehavior; + private boolean constructing = false; + private float constructionProgress; 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, @@ -199,6 +201,14 @@ public class CUnit extends CWidget { return true; } } + else if (constructing) { + constructionProgress += WarsmashConstants.SIMULATION_STEP_TIME; + if (constructionProgress >= unitType.getBuildTime()) { + constructing = false; + game.unitConstructFinishEvent(this); + this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); + } + } else if (this.currentBehavior != null) { final CBehavior lastBehavior = this.currentBehavior; this.currentBehavior = this.currentBehavior.update(game); @@ -670,4 +680,22 @@ public class CUnit extends CWidget { return getCurrentBehavior() instanceof CBehaviorMove; } + public void setConstructing(boolean constructing) { + this.constructing = constructing; + if (constructing) { + unitAnimationListener.playAnimation(true, PrimaryTag.BIRTH, SequenceUtils.EMPTY, 0.0f, true); + } + } + + public void setConstructionProgress(float constructionProgress) { + this.constructionProgress = constructionProgress; + } + + public boolean isConstructing() { + return constructing; + } + + public float getConstructionProgress() { + return constructionProgress; + } } 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 38dd56d..83e7b3d 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 @@ -35,8 +35,10 @@ public class CBehaviorOrcBuild extends CAbstractRangedPointTargetBehavior { @Override protected CBehavior update(final CSimulation simulation, final boolean withinRange) { - simulation.createUnit(this.orderId, this.unit.getPlayerIndex(), this.targetX, this.targetY, + CUnit constructedStructure = simulation.createUnit(this.orderId, this.unit.getPlayerIndex(), this.targetX, this.targetY, simulation.getGameplayConstants().getBuildingAngle()); + constructedStructure.setConstructing(true); + simulation.unitConstructedEvent(this.unit, constructedStructure); return this.unit.pollNextOrderBehavior(simulation); } 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 0052226..eeb1f73 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 @@ -21,7 +21,11 @@ public interface SimulationRenderController { void spawnUnitDamageSound(CUnit damagedUnit, final String weaponSound, String armorType); + void spawnUnitConstructionSound(CUnit constructingUnit, CUnit constructedStructure); + void removeUnit(CUnit unit); BufferedImage getBuildingPathingPixelMap(War3ID rawcode); + + void spawnUnitConstructionFinishSound(CUnit constructedStructure); } 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 0f72e55..0b0a887 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 @@ -79,1050 +79,1037 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgAbili import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandCardCommandListener; public class MeleeUI implements CUnitStateListener, CommandButtonListener, CommandCardCommandListener { - public static final float DEFAULT_COMMAND_CARD_ICON_WIDTH = 0.039f; - public static final float DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH = 0.037f; - private static final int COMMAND_CARD_WIDTH = 4; - private static final int COMMAND_CARD_HEIGHT = 3; - - private static final Vector2 screenCoordsVector = new Vector2(); - private static final Vector3 clickLocationTemp = new Vector3(); - private static final Vector2 clickLocationTemp2 = new Vector2(); - private final DataSource dataSource; - private final ExtendViewport uiViewport; - private final FreeTypeFontGenerator fontGenerator; - private final Scene uiScene; - private final Scene portraitScene; - private final GameCameraManager cameraManager; - private final War3MapViewer war3MapViewer; - private final RootFrameListener rootFrameListener; - private GameUI rootFrame; - private UIFrame consoleUI; - private UIFrame resourceBar; - private StringFrame resourceBarGoldText; - private StringFrame resourceBarLumberText; - private StringFrame resourceBarSupplyText; - private StringFrame resourceBarUpkeepText; - private SpriteFrame timeIndicator; - private UIFrame unitPortrait; - private StringFrame unitLifeText; - private StringFrame unitManaText; - private Portrait portrait; - 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 UIFrame attack1Icon; - private TextureFrame attack1IconBackdrop; - private StringFrame attack1InfoPanelIconValue; - private StringFrame attack1InfoPanelIconLevel; - private UIFrame attack2Icon; - private TextureFrame attack2IconBackdrop; - private StringFrame attack2InfoPanelIconValue; - private StringFrame attack2InfoPanelIconLevel; - private UIFrame armorIcon; - private TextureFrame armorIconBackdrop; - private StringFrame armorInfoPanelIconValue; - private StringFrame armorInfoPanelIconLevel; - private InfoPanelIconBackdrops damageBackdrops; - private InfoPanelIconBackdrops defenseBackdrops; - - private final CommandCardIcon[][] commandCard = new CommandCardIcon[COMMAND_CARD_HEIGHT][COMMAND_CARD_WIDTH]; - - private RenderUnit selectedUnit; - private final List subMenuOrderIdStack = new ArrayList<>(); - - // TODO remove this & replace with FDF - private final Texture activeButtonTexture; - private UIFrame inventoryCover; - private SpriteFrame cursorFrame; - private MeleeUIMinimap meleeUIMinimap; - private final CPlayerUnitOrderListener unitOrderListener; - private StringFrame errorMessageFrame; - - private CAbilityView activeCommand; - private int activeCommandOrderId; - private RenderUnit activeCommandUnit; - private MdxComplexInstance cursorModelInstance = null; - private BufferedImage cursorModelPathing; - - private int selectedSoundCount = 0; - private final ActiveCommandUnitTargetFilter activeCommandUnitTargetFilter; - - // TODO these corrections are used for old hardcoded UI stuff, we should - // probably remove them later - private final float widthRatioCorrection; - private final float heightRatioCorrection; - private CommandCardIcon mouseDownUIFrame; - - public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, - final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, - final CameraPreset[] cameraPresets, final CameraRates cameraRates, final War3MapViewer war3MapViewer, - final RootFrameListener rootFrameListener, final CPlayerUnitOrderListener unitOrderListener) { - this.dataSource = dataSource; - this.uiViewport = uiViewport; - this.fontGenerator = fontGenerator; - this.uiScene = uiScene; - this.portraitScene = portraitScene; - this.war3MapViewer = war3MapViewer; - this.rootFrameListener = rootFrameListener; - this.unitOrderListener = unitOrderListener; - - this.cameraManager = new GameCameraManager(cameraPresets, cameraRates); - - this.cameraManager.setupCamera(war3MapViewer.worldScene); - if (this.war3MapViewer.startLocations[0] != null) { - this.cameraManager.target.x = this.war3MapViewer.startLocations[0].x; - this.cameraManager.target.y = this.war3MapViewer.startLocations[0].y; - } - - this.activeButtonTexture = ImageUtils.getBLPTexture(war3MapViewer.mapMpq, - "UI\\Widgets\\Console\\Human\\CommandButton\\human-activebutton.blp"); - this.activeCommandUnitTargetFilter = new ActiveCommandUnitTargetFilter(); - this.widthRatioCorrection = this.uiViewport.getMinWorldWidth() / 1600f; - this.heightRatioCorrection = this.uiViewport.getMinWorldHeight() / 1200f; - - } - - private MeleeUIMinimap createMinimap(final War3MapViewer war3MapViewer) { - final Rectangle minimapDisplayArea = new Rectangle(18.75f * this.widthRatioCorrection, - 13.75f * this.heightRatioCorrection, 278.75f * this.widthRatioCorrection, - 276.25f * this.heightRatioCorrection); - Texture minimapTexture = null; - if (war3MapViewer.dataSource.has("war3mapMap.tga")) { - try { - minimapTexture = ImageUtils.getTextureNoColorCorrection(TgaFile.readTGA("war3mapMap.tga", - war3MapViewer.dataSource.getResourceAsStream("war3mapMap.tga"))); - } - catch (final IOException e) { - System.err.println("Could not load minimap TGA file"); - e.printStackTrace(); - } - } - else if (war3MapViewer.dataSource.has("war3mapMap.blp")) { - try { - minimapTexture = ImageUtils - .getTexture(ImageIO.read(war3MapViewer.dataSource.getResourceAsStream("war3mapMap.blp"))); - } - catch (final IOException e) { - System.err.println("Could not load minimap BLP file"); - e.printStackTrace(); - } - } - final Texture[] teamColors = new Texture[WarsmashConstants.MAX_PLAYERS]; - for (int i = 0; i < teamColors.length; i++) { - teamColors[i] = ImageUtils.getBLPTexture(war3MapViewer.dataSource, - "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(i) + ".blp"); - } - final Rectangle playableMapArea = war3MapViewer.terrain.getPlayableMapArea(); - return new MeleeUIMinimap(minimapDisplayArea, playableMapArea, minimapTexture, teamColors); - } - - /** - * Called "main" because this was originally written in JASS so that maps could - * override it, and I may convert it back to the JASS at some point. - */ - public void main() { - // ================================= - // Load skins and templates - // ================================= - this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 0), this.uiViewport, - this.fontGenerator, this.uiScene, this.war3MapViewer); - this.rootFrameListener.onCreate(this.rootFrame); - try { - this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); - } - catch (final IOException exc) { - throw new IllegalStateException("Unable to load FrameDef.toc", exc); - } - try { - this.rootFrame.loadTOCFile("UI\\FrameDef\\SmashFrameDef.toc"); - } - catch (final IOException exc) { - throw new IllegalStateException("Unable to load SmashFrameDef.toc", exc); - } - this.damageBackdrops = new InfoPanelIconBackdrops(CAttackType.values(), this.rootFrame, "Damage", "Neutral"); - this.defenseBackdrops = new InfoPanelIconBackdrops(CDefenseType.values(), this.rootFrame, "Armor", "Neutral"); - - // ================================= - // Load major UI components - // ================================= - // Console UI is the background with the racial theme - this.consoleUI = this.rootFrame.createSimpleFrame("ConsoleUI", this.rootFrame, 0); - this.consoleUI.setSetAllPoints(true); - - // Resource bar is a 3 part bar with Gold, Lumber, and Food. - // Its template does not specify where to put it, so we must - // put it in the "TOPRIGHT" corner. - this.resourceBar = this.rootFrame.createSimpleFrame("ResourceBarFrame", this.consoleUI, 0); - this.resourceBar.addSetPoint(new SetPoint(FramePoint.TOPRIGHT, this.consoleUI, FramePoint.TOPRIGHT, 0, 0)); - this.resourceBarGoldText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarGoldText", 0); - this.resourceBarGoldText.setText("500"); - this.resourceBarLumberText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarLumberText", 0); - this.resourceBarLumberText.setText("150"); - this.resourceBarSupplyText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarSupplyText", 0); - this.resourceBarSupplyText.setText("12/100"); - this.resourceBarUpkeepText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarUpkeepText", 0); - this.resourceBarUpkeepText.setText("No Upkeep"); - this.resourceBarUpkeepText.setColor(Color.GREEN); - - // Create the Time Indicator (clock) - this.timeIndicator = (SpriteFrame) this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0); - this.timeIndicator.setSequence(0); // play the stand - this.timeIndicator.setAnimationSpeed(0.0f); // do not advance automatically - - // Create the unit portrait stuff - this.portrait = new Portrait(this.war3MapViewer, this.portraitScene); - positionPortrait(); - this.unitPortrait = this.rootFrame.createSimpleFrame("UnitPortrait", this.consoleUI, 0); - this.unitLifeText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitHitPointText", 0); - this.unitManaText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitManaPointText", 0); - - 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)); - this.simpleNameValue = (StringFrame) this.rootFrame.getFrameByName("SimpleNameValue", 0); - this.simpleClassValue = (StringFrame) this.rootFrame.getFrameByName("SimpleClassValue", 0); - this.simpleBuildingActionLabel = (StringFrame) this.rootFrame.getFrameByName("SimpleBuildingActionLabel", 0); - - this.attack1Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, - 0); - this.attack1Icon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, - FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); - this.attack1IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); - this.attack1InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); - this.attack1InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); - - this.attack2Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, - 1); - this.attack2Icon - .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0.1f), GameUI.convertY(this.uiViewport, -0.030125f))); - this.attack2IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 1); - this.attack2InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 1); - this.attack2InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 1); - - this.armorIcon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconArmor", this.simpleInfoPanelUnitDetail, - 1); - this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); - this.armorIconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); - this.armorInfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); - this.armorInfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); - - this.inventoryCover = this.rootFrame.createSimpleFrame("SmashConsoleInventoryCover", this.rootFrame, 0); - - this.errorMessageFrame = this.rootFrame.createStringFrame("SmashErrorMessageFrame", this.consoleUI, - new Color(0xFFFFCC00), TextJustify.LEFT, TextJustify.MIDDLE, 0.014f); - this.errorMessageFrame.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, - GameUI.convertX(this.uiViewport, 0.275f), GameUI.convertY(this.uiViewport, 0.275f))); - this.errorMessageFrame.setWidth(GameUI.convertX(this.uiViewport, 0.25f)); - - int commandButtonIndex = 0; - for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { - for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { - final CommandCardIcon commandCardIcon = new CommandCardIcon("SmashCommandButton_" + commandButtonIndex, - this.rootFrame, this); - this.rootFrame.add(commandCardIcon); - final TextureFrame iconFrame = this.rootFrame.createTextureFrame( - "SmashCommandButton_" + (commandButtonIndex) + "_Icon", this.rootFrame, false, null); - final TextureFrame activeHighlightFrame = this.rootFrame.createTextureFrame( - "SmashCommandButton_" + (commandButtonIndex) + "_ActiveHighlight", this.rootFrame, true, null, - FilterMode.ADDALPHA); - final SpriteFrame cooldownFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", - "SmashCommandButton_" + (commandButtonIndex) + "_Cooldown", this.rootFrame, "", 0); - final SpriteFrame autocastFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", - "SmashCommandButton_" + (commandButtonIndex) + "_Autocast", this.rootFrame, "", 0); - commandCardIcon.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, - GameUI.convertX(this.uiViewport, 0.6175f + (0.0434f * i)), - GameUI.convertY(this.uiViewport, 0.095f - (0.044f * j)))); - commandCardIcon.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - commandCardIcon.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - iconFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); - iconFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - iconFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - iconFrame.setTexture(ImageUtils.DEFAULT_ICON_PATH, this.rootFrame); - activeHighlightFrame - .addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); - activeHighlightFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - activeHighlightFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - activeHighlightFrame.setTexture("CommandButtonActiveHighlight", this.rootFrame); - cooldownFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); - this.rootFrame.setSpriteFrameModel(cooldownFrame, this.rootFrame.getSkinField("CommandButtonCooldown")); - cooldownFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - cooldownFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - autocastFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); - this.rootFrame.setSpriteFrameModel(autocastFrame, this.rootFrame.getSkinField("CommandButtonAutocast")); - autocastFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - autocastFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - commandCardIcon.set(iconFrame, activeHighlightFrame, cooldownFrame, autocastFrame); - this.commandCard[j][i] = commandCardIcon; - commandCardIcon.setCommandButton(null); - commandButtonIndex++; - } - } - - this.cursorFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashCursorFrame", this.rootFrame, - "", 0); - this.rootFrame.setSpriteFrameModel(this.cursorFrame, this.rootFrame.getSkinField("Cursor")); - this.cursorFrame.setSequence("Normal"); - this.cursorFrame.setZDepth(1.0f); - Gdx.input.setCursorCatched(true); - - this.meleeUIMinimap = createMinimap(this.war3MapViewer); - - this.rootFrame.positionBounds(this.uiViewport); - selectUnit(null); - } - - @Override - public void startUsingAbility(final int abilityHandleId, final int orderId, final boolean rightClick) { - // TODO not O(N) - CAbilityView abilityToUse = null; - for (final CAbility ability : this.selectedUnit.getSimulationUnit().getAbilities()) { - if (ability.getHandleId() == abilityHandleId) { - abilityToUse = ability; - break; - } - } - if (abilityToUse != null) { - final StringMsgAbilityActivationReceiver stringMsgActivationReceiver = StringMsgAbilityActivationReceiver - .getInstance().reset(); - abilityToUse.checkCanUse(this.war3MapViewer.simulation, this.selectedUnit.getSimulationUnit(), orderId, - stringMsgActivationReceiver); - if (!stringMsgActivationReceiver.isUseOk()) { - showCommandError(stringMsgActivationReceiver.getMessage()); - } - else { - final BooleanAbilityTargetCheckReceiver noTargetReceiver = BooleanAbilityTargetCheckReceiver - .getInstance().reset(); - abilityToUse.checkCanTargetNoTarget(this.war3MapViewer.simulation, - this.selectedUnit.getSimulationUnit(), orderId, noTargetReceiver); - if (noTargetReceiver.isTargetable()) { - this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), - abilityHandleId, orderId, isShiftDown()); - } - else { - this.activeCommand = abilityToUse; - this.activeCommandOrderId = orderId; - this.activeCommandUnit = this.selectedUnit; - clearAndRepopulateCommandCard(); - } - } - } - else { - this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), - abilityHandleId, orderId, isShiftDown()); - } - if (rightClick) { - this.war3MapViewer.getUiSounds().getSound("AutoCastButtonClick").play(this.uiScene.audioContext, 0, 0); - } - } - - @Override - public void openMenu(final int orderId) { - if (orderId == 0) { - this.subMenuOrderIdStack.clear(); - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - } - else { - this.subMenuOrderIdStack.add(orderId); - } - clearAndRepopulateCommandCard(); - } - - public void showCommandError(final String message) { - this.errorMessageFrame.setText(message); - } - - public void update(final float deltaTime) { - this.portrait.update(); - - final int baseMouseX = Gdx.input.getX(); - int mouseX = baseMouseX; - final int baseMouseY = Gdx.input.getY(); - int mouseY = baseMouseY; - final int minX = this.uiViewport.getScreenX(); - final int maxX = minX + this.uiViewport.getScreenWidth(); - final int minY = this.uiViewport.getScreenY(); - final int maxY = minY + this.uiViewport.getScreenHeight(); - final boolean left = mouseX <= (minX + 3); - final boolean right = mouseX >= (maxX - 3); - final boolean up = mouseY <= (minY + 3); - final boolean down = mouseY >= (maxY - 3); - this.cameraManager.applyVelocity(deltaTime, up, down, left, right); - - mouseX = Math.max(minX, Math.min(maxX, mouseX)); - mouseY = Math.max(minY, Math.min(maxY, mouseY)); - if (Gdx.input.isCursorCatched()) { - Gdx.input.setCursorPosition(mouseX, mouseY); - } - - screenCoordsVector.set(mouseX, mouseY); - this.uiViewport.unproject(screenCoordsVector); - this.cursorFrame.setFramePointX(FramePoint.LEFT, screenCoordsVector.x); - this.cursorFrame.setFramePointY(FramePoint.BOTTOM, screenCoordsVector.y); - - if (this.activeCommand != null) { - if (this.activeCommand instanceof AbstractCAbilityBuild) { - boolean justLoaded = false; - if (this.cursorModelInstance == null) { - final MutableObjectData unitData = this.war3MapViewer.getAllObjectData().getUnits(); - final War3ID buildingTypeId = new War3ID(this.activeCommandOrderId); - final String unitModelPath = this.war3MapViewer.getUnitModelPath(unitData.get(buildingTypeId)); - final MdxModel model = (MdxModel) this.war3MapViewer.load(unitModelPath, - this.war3MapViewer.mapPathSolver, this.war3MapViewer.solverParams); - this.cursorModelInstance = (MdxComplexInstance) model.addInstance(); - this.cursorModelInstance.setVertexColor(new float[] { 1, 1, 1, 0.5f }); - this.cursorModelInstance.rotate(RenderUnit.tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, - this.war3MapViewer.simulation.getGameplayConstants().getBuildingAngle())); - this.cursorModelInstance.setAnimationSpeed(0f); - justLoaded = true; - final CUnitType buildingUnitType = this.war3MapViewer.simulation.getUnitData() - .getUnitType(buildingTypeId); - this.cursorModelPathing = buildingUnitType.getBuildingPathingPixelMap(); - } - this.war3MapViewer.getClickLocation(clickLocationTemp, baseMouseX, - Gdx.graphics.getHeight() - baseMouseY); - if (this.cursorModelPathing != null) { - clickLocationTemp.x = Math.round(clickLocationTemp.x / 64f) * 64f; - clickLocationTemp.y = Math.round(clickLocationTemp.y / 64f) * 64f; - clickLocationTemp.z = this.war3MapViewer.terrain.getGroundHeight(clickLocationTemp.x, - clickLocationTemp.y); - } - this.cursorModelInstance.setLocation(clickLocationTemp); - SequenceUtils.randomSequence(this.cursorModelInstance, PrimaryTag.STAND); - this.cursorFrame.setVisible(false); - if (justLoaded) { - this.cursorModelInstance.setScene(this.war3MapViewer.worldScene); - } - } - else { - if (this.cursorModelInstance != null) { - this.cursorModelInstance.detach(); - this.cursorModelInstance = null; - this.cursorFrame.setVisible(true); - } - this.cursorFrame.setSequence("Target"); - } - } - else { - if (this.cursorModelInstance != null) { - this.cursorModelInstance.detach(); - this.cursorModelInstance = null; - this.cursorFrame.setVisible(true); - } - if (down) { - if (left) { - this.cursorFrame.setSequence("Scroll Down Left"); - } - else if (right) { - this.cursorFrame.setSequence("Scroll Down Right"); - } - else { - this.cursorFrame.setSequence("Scroll Down"); - } - } - else if (up) { - if (left) { - this.cursorFrame.setSequence("Scroll Up Left"); - } - else if (right) { - this.cursorFrame.setSequence("Scroll Up Right"); - } - else { - this.cursorFrame.setSequence("Scroll Up"); - } - } - else if (left) { - this.cursorFrame.setSequence("Scroll Left"); - } - else if (right) { - this.cursorFrame.setSequence("Scroll Right"); - } - else { - this.cursorFrame.setSequence("Normal"); - } - } - 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)); - this.cameraManager.updateTargetZ(groundHeight); - this.cameraManager.updateCamera(); - } - - public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) { - this.rootFrame.render(batch, font20, glyphLayout); - if (this.selectedUnit != null) { - font20.setColor(Color.WHITE); - - } - - this.meleeUIMinimap.render(batch, this.war3MapViewer.units); - this.timeIndicator.setFrameByRatio(this.war3MapViewer.simulation.getGameTimeOfDay() - / this.war3MapViewer.simulation.getGameplayConstants().getGameDayHours()); - } - - public void portraitTalk() { - this.portrait.talk(); - } - - private final class ActiveCommandUnitTargetFilter implements CUnitFilterFunction { - @Override - public boolean call(final CUnit unit) { - final BooleanAbilityTargetCheckReceiver targetReceiver = BooleanAbilityTargetCheckReceiver - .getInstance(); - MeleeUI.this.activeCommand.checkCanTarget(MeleeUI.this.war3MapViewer.simulation, - MeleeUI.this.activeCommandUnit.getSimulationUnit(), MeleeUI.this.activeCommandOrderId, unit, - targetReceiver); - return targetReceiver.isTargetable(); - } - } - - private static final class Portrait { - private MdxComplexInstance modelInstance; - private final PortraitCameraManager portraitCameraManager; - private final Scene portraitScene; - - public Portrait(final War3MapViewer war3MapViewer, final Scene portraitScene) { - this.portraitScene = portraitScene; - this.portraitCameraManager = new PortraitCameraManager(); - this.portraitCameraManager.setupCamera(this.portraitScene); - this.portraitScene.camera.viewport(new Rectangle(100, 0, 6400, 48)); - } - - public void update() { - this.portraitCameraManager.updateCamera(); - if ((this.modelInstance != null) - && (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1))) { - SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.EMPTY, true); - } - } - - public void talk() { - SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.TALK, true); - } - - public void setSelectedUnit(final RenderUnit unit) { - if (unit == null) { - if (this.modelInstance != null) { - this.portraitScene.removeInstance(this.modelInstance); - } - this.modelInstance = null; - this.portraitCameraManager.setModelInstance(null, null); - } - else { - final MdxModel portraitModel = unit.portraitModel; - if (portraitModel != null) { - if (this.modelInstance != null) { - this.portraitScene.removeInstance(this.modelInstance); - } - this.modelInstance = (MdxComplexInstance) portraitModel.addInstance(); - this.portraitCameraManager.setModelInstance(this.modelInstance, portraitModel); - this.modelInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); - this.modelInstance.setScene(this.portraitScene); - this.modelInstance.setVertexColor(unit.instance.vertexColor); - this.modelInstance.setTeamColor(unit.playerIndex); - } - } - } - } - - public void selectUnit(RenderUnit unit) { - this.subMenuOrderIdStack.clear(); - if ((unit != null) && unit.getSimulationUnit().isDead()) { - unit = null; - } - if (this.selectedUnit != null) { - this.selectedUnit.getSimulationUnit().removeStateListener(this); - } - this.portrait.setSelectedUnit(unit); - this.selectedUnit = unit; - clearCommandCard(); - if (unit == null) { - this.simpleNameValue.setText(""); - this.unitLifeText.setText(""); - this.unitManaText.setText(""); - this.simpleClassValue.setText(""); - this.simpleBuildingActionLabel.setText(""); - this.attack1Icon.setVisible(false); - this.attack2Icon.setVisible(false); - this.attack1InfoPanelIconLevel.setText(""); - this.attack2InfoPanelIconLevel.setText(""); - this.armorIcon.setVisible(false); - this.armorInfoPanelIconLevel.setText(""); - } - else { - unit.getSimulationUnit().addStateListener(this); - 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(); - if (maximumMana > 0) { - this.unitManaText.setText( - FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getMana()) + " / " + maximumMana); - } - else { - this.unitManaText.setText(""); - } - this.simpleBuildingActionLabel.setText(""); - - final boolean anyAttacks = unit.getSimulationUnit().getUnitType().getAttacks().size() > 0; - final UIFrame localArmorIcon = this.armorIcon; - final TextureFrame localArmorIconBackdrop = this.armorIconBackdrop; - final StringFrame localArmorInfoPanelIconValue = this.armorInfoPanelIconValue; - if (anyAttacks) { - 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()); - } - else { - this.attack2Icon.setVisible(false); - } - - 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(true); - final Texture defenseTexture = this.defenseBackdrops - .getTexture(unit.getSimulationUnit().getUnitType().getDefenseType()); - if (defenseTexture == null) { - throw new RuntimeException( - unit.getSimulationUnit().getUnitType().getDefenseType() + " can't find texture!"); - } - localArmorIconBackdrop.setTexture(defenseTexture); - localArmorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense())); - unit.populateCommandCard(this.war3MapViewer.simulation, this, this.war3MapViewer.getAbilityDataUI(), - getSubMenuOrderId()); - } - } - - private void clearCommandCard() { - for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { - for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { - this.commandCard[j][i].setCommandButton(null); - } - } - } - - @Override - 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)); - this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, autoCastId, active, autoCastActive, - menuButton); - } - - public void resize(final Rectangle viewport) { - this.cameraManager.resize(viewport); - positionPortrait(); - } - - public void positionPortrait() { - this.projectionTemp1.x = 422 * this.widthRatioCorrection; - this.projectionTemp1.y = 57 * this.heightRatioCorrection; - this.projectionTemp2.x = (422 + 167) * this.widthRatioCorrection; - this.projectionTemp2.y = (57 + 170) * this.heightRatioCorrection; - this.uiViewport.project(this.projectionTemp1); - this.uiViewport.project(this.projectionTemp2); - - this.tempRect.x = this.projectionTemp1.x + this.uiViewport.getScreenX(); - this.tempRect.y = this.projectionTemp1.y + this.uiViewport.getScreenY(); - this.tempRect.width = this.projectionTemp2.x - this.projectionTemp1.x; - this.tempRect.height = this.projectionTemp2.y - this.projectionTemp1.y; - this.portrait.portraitScene.camera.viewport(this.tempRect); - } - - private static final class InfoPanelIconBackdrops { - private final Texture[] damageBackdropTextures; - - public InfoPanelIconBackdrops(final CodeKeyType[] attackTypes, final GameUI gameUI, final String prefix, - final String suffix) { - this.damageBackdropTextures = new Texture[attackTypes.length]; - for (int index = 0; index < attackTypes.length; index++) { - final CodeKeyType attackType = attackTypes[index]; - String skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey() + suffix; - final Texture suffixTexture = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); - if (suffixTexture != null) { - this.damageBackdropTextures[index] = suffixTexture; - } - else { - skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey(); - this.damageBackdropTextures[index] = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); - } - } - } - - public Texture getTexture(final CodeKeyType attackType) { - if (attackType != null) { - final int ordinal = attackType.ordinal(); - if ((ordinal >= 0) && (ordinal < this.damageBackdropTextures.length)) { - return this.damageBackdropTextures[ordinal]; - } - } - return this.damageBackdropTextures[0]; - } - - private static String getSuffix(final CAttackType attackType) { - switch (attackType) { - case CHAOS: - return "Chaos"; - case HERO: - return "Hero"; - case MAGIC: - return "Magic"; - case NORMAL: - return "Normal"; - case PIERCE: - return "Pierce"; - case SIEGE: - return "Siege"; - case SPELLS: - return "Magic"; - case UNKNOWN: - return "Unknown"; - default: - throw new IllegalArgumentException("Unknown attack type: " + attackType); - } - - } - } - - @Override - public void lifeChanged() { - if (this.selectedUnit.getSimulationUnit().isDead()) { - selectUnit(null); - } - else { - this.unitLifeText - .setText(FastNumberFormat.formatWholeNumber(this.selectedUnit.getSimulationUnit().getLife()) + " / " - + this.selectedUnit.getSimulationUnit().getMaximumLife()); - } - } - - @Override - public void ordersChanged(final int abilityHandleId, final int orderId) { - clearAndRepopulateCommandCard(); - } - - private void clearAndRepopulateCommandCard() { - clearCommandCard(); - final AbilityDataUI abilityDataUI = this.war3MapViewer.getAbilityDataUI(); - final int menuOrderId = getSubMenuOrderId(); - if (this.activeCommand != null) { - final IconUI cancelUI = abilityDataUI.getCancelUI(); - this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, - menuOrderId, 0, false, false, true); - } - else { - if (menuOrderId != 0) { - final int exitOrderId = this.subMenuOrderIdStack.size() > 1 - ? this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 2) - : 0; - final IconUI cancelUI = abilityDataUI.getCancelUI(); - this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, - exitOrderId, 0, false, false, true); - } - this.selectedUnit.populateCommandCard(this.war3MapViewer.simulation, this, abilityDataUI, menuOrderId); - } - } - - private int getSubMenuOrderId() { - return this.subMenuOrderIdStack.isEmpty() ? 0 - : this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 1); - } - - public RenderUnit getSelectedUnit() { - return this.selectedUnit; - } - - public boolean keyDown(final int keycode) { - return this.cameraManager.keyDown(keycode); - } - - public boolean keyUp(final int keycode) { - return this.cameraManager.keyUp(keycode); - } - - public void scrolled(final int amount) { - this.cameraManager.scrolled(amount); - } - - public boolean touchDown(final int screenX, final int screenY, final float worldScreenY, final int button) { - screenCoordsVector.set(screenX, screenY); - this.uiViewport.unproject(screenCoordsVector); - if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { - final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenCoordsVector.x, - screenCoordsVector.y); - this.cameraManager.target.x = worldPoint.x; - this.cameraManager.target.y = worldPoint.y; - return true; - } - final UIFrame clickedUIFrame = this.rootFrame.touchDown(screenCoordsVector.x, screenCoordsVector.y, button); - if (clickedUIFrame == null) { - // try to interact with world - if (this.activeCommand != null) { - if (button == Input.Buttons.RIGHT) { - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - clearAndRepopulateCommandCard(); - } - else { - final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY, - this.activeCommandUnitTargetFilter); - final boolean shiftDown = isShiftDown(); - if (rayPickUnit != null) { - this.unitOrderListener.issueTargetOrder( - this.activeCommandUnit.getSimulationUnit().getHandleId(), - this.activeCommand.getHandleId(), this.activeCommandOrderId, - rayPickUnit.getSimulationUnit().getHandleId(), shiftDown); - if (getSelectedUnit().soundset.yesAttack - .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - if (!shiftDown) { - this.subMenuOrderIdStack.clear(); - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - clearAndRepopulateCommandCard(); - } - } - else { - this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); - clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); - - this.activeCommand.checkCanTarget(this.war3MapViewer.simulation, - this.activeCommandUnit.getSimulationUnit(), this.activeCommandOrderId, - clickLocationTemp2, PointAbilityTargetCheckReceiver.INSTANCE); - final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (target != null) { - if (this.activeCommand instanceof CAbilityAttack) { - this.war3MapViewer.showConfirmation(clickLocationTemp, 1, 0, 0); - } - else { - this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); - } - this.unitOrderListener.issuePointOrder( - this.activeCommandUnit.getSimulationUnit().getHandleId(), - this.activeCommand.getHandleId(), this.activeCommandOrderId, clickLocationTemp2.x, - clickLocationTemp2.y, shiftDown); - if (getSelectedUnit().soundset.yes - .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - if (this.activeCommand instanceof AbstractCAbilityBuild) { - this.war3MapViewer.getUiSounds().getSound("PlaceBuildingDefault") - .play(this.uiScene.audioContext, 0, 0); - } - if (!shiftDown) { - this.subMenuOrderIdStack.clear(); - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - clearAndRepopulateCommandCard(); - } - - } - } - } - } - else { - if (button == Input.Buttons.RIGHT) { - if (getSelectedUnit() != null) { - final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY); - if ((rayPickUnit != null) && !rayPickUnit.getSimulationUnit().isDead()) { - boolean ordered = false; - for (final RenderUnit unit : this.war3MapViewer.selected) { - for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { - ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), - OrderIds.smart, rayPickUnit.getSimulationUnit(), - CWidgetAbilityTargetCheckReceiver.INSTANCE); - final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (targetWidget != null) { - this.unitOrderListener.issueTargetOrder(unit.getSimulationUnit().getHandleId(), - ability.getHandleId(), OrderIds.smart, targetWidget.getHandleId(), - isShiftDown()); - ordered = true; - } - } - - } - if (ordered) { - if (getSelectedUnit().soundset.yesAttack.playUnitResponse( - this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - } - } - else { - this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); - this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); - clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); - - boolean ordered = false; - for (final RenderUnit unit : this.war3MapViewer.selected) { - for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { - ability.checkCanUse(this.war3MapViewer.simulation, unit.getSimulationUnit(), - OrderIds.smart, BooleanAbilityActivationReceiver.INSTANCE); - if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { - ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), - OrderIds.smart, clickLocationTemp2, - PointAbilityTargetCheckReceiver.INSTANCE); - final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (target != null) { - this.unitOrderListener.issuePointOrder( - unit.getSimulationUnit().getHandleId(), ability.getHandleId(), - OrderIds.smart, clickLocationTemp2.x, clickLocationTemp2.y, - isShiftDown()); - ordered = true; - } - } - } - - } - - if (ordered) { - if (getSelectedUnit().soundset.yes.playUnitResponse( - this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - } - } - } - } - else { - final List selectedUnits = this.war3MapViewer.selectUnit(screenX, worldScreenY, false); - if (!selectedUnits.isEmpty()) { - final RenderUnit unit = selectedUnits.get(0); - final boolean selectionChanged = getSelectedUnit() != unit; - boolean playedNewSound = false; - if (selectionChanged) { - this.selectedSoundCount = 0; - } - if (unit.soundset != null) { - UnitSound ackSoundToPlay = unit.soundset.what; - final int pissedSoundCount = unit.soundset.pissed.getSoundCount(); - int soundIndex; - if ((this.selectedSoundCount >= 3) && (pissedSoundCount > 0)) { - soundIndex = this.selectedSoundCount - 3; - ackSoundToPlay = unit.soundset.pissed; - } - else { - soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); - } - if (ackSoundToPlay.playUnitResponse(this.war3MapViewer.worldScene.audioContext, unit, - soundIndex)) { - this.selectedSoundCount++; - if ((this.selectedSoundCount - 3) >= pissedSoundCount) { - this.selectedSoundCount = 0; - } - playedNewSound = true; - } - } - if (selectionChanged) { - selectUnit(unit); - } - if (playedNewSound) { - portraitTalk(); - } - } - else { - selectUnit(null); - } - } - } - } - else { - if (clickedUIFrame instanceof CommandCardIcon) { - this.mouseDownUIFrame = (CommandCardIcon) clickedUIFrame; - this.mouseDownUIFrame.mouseDown(this.uiViewport); - } - } - return false; - } - - public boolean touchUp(final int screenX, final int screenY, final float worldScreenY, final int button) { - screenCoordsVector.set(screenX, screenY); - this.uiViewport.unproject(screenCoordsVector); - final UIFrame clickedUIFrame = this.rootFrame.touchUp(screenCoordsVector.x, screenCoordsVector.y, button); - if (this.mouseDownUIFrame != null) { - if (clickedUIFrame == this.mouseDownUIFrame) { - this.mouseDownUIFrame.onClick(button); - this.war3MapViewer.getUiSounds().getSound("InterfaceClick").play(this.uiScene.audioContext, 0, 0); - } - this.mouseDownUIFrame.mouseUp(this.uiViewport); - } - this.mouseDownUIFrame = null; - return false; - } - - private static boolean isShiftDown() { - return Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) || Gdx.input.isKeyPressed(Input.Keys.SHIFT_RIGHT); - } - - public boolean touchDragged(final int screenX, final int screenY, final float worldScreenY, final int pointer) { - screenCoordsVector.set(screenX, screenY); - this.uiViewport.unproject(screenCoordsVector); - - if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { - final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenCoordsVector.x, - screenCoordsVector.y); - this.cameraManager.target.x = worldPoint.x; - this.cameraManager.target.y = worldPoint.y; - } - return false; - } - - public float getHeightRatioCorrection() { - return this.heightRatioCorrection; - } + public static final float DEFAULT_COMMAND_CARD_ICON_WIDTH = 0.039f; + public static final float DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH = 0.037f; + private static final int COMMAND_CARD_WIDTH = 4; + private static final int COMMAND_CARD_HEIGHT = 3; + + private static final Vector2 screenCoordsVector = new Vector2(); + private static final Vector3 clickLocationTemp = new Vector3(); + private static final Vector2 clickLocationTemp2 = new Vector2(); + private final DataSource dataSource; + private final ExtendViewport uiViewport; + private final FreeTypeFontGenerator fontGenerator; + private final Scene uiScene; + private final Scene portraitScene; + private final GameCameraManager cameraManager; + private final War3MapViewer war3MapViewer; + private final RootFrameListener rootFrameListener; + private GameUI rootFrame; + private UIFrame consoleUI; + private UIFrame resourceBar; + private StringFrame resourceBarGoldText; + private StringFrame resourceBarLumberText; + private StringFrame resourceBarSupplyText; + private StringFrame resourceBarUpkeepText; + private SpriteFrame timeIndicator; + private UIFrame unitPortrait; + private StringFrame unitLifeText; + private StringFrame unitManaText; + private Portrait portrait; + 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 UIFrame attack1Icon; + private TextureFrame attack1IconBackdrop; + private StringFrame attack1InfoPanelIconValue; + private StringFrame attack1InfoPanelIconLevel; + private UIFrame attack2Icon; + private TextureFrame attack2IconBackdrop; + private StringFrame attack2InfoPanelIconValue; + private StringFrame attack2InfoPanelIconLevel; + private UIFrame armorIcon; + private TextureFrame armorIconBackdrop; + private StringFrame armorInfoPanelIconValue; + private StringFrame armorInfoPanelIconLevel; + private InfoPanelIconBackdrops damageBackdrops; + private InfoPanelIconBackdrops defenseBackdrops; + private SpriteFrame buildTimeIndicator; + + private final CommandCardIcon[][] commandCard = new CommandCardIcon[COMMAND_CARD_HEIGHT][COMMAND_CARD_WIDTH]; + + private RenderUnit selectedUnit; + private final List subMenuOrderIdStack = new ArrayList<>(); + + // TODO remove this & replace with FDF + private final Texture activeButtonTexture; + private UIFrame inventoryCover; + private SpriteFrame cursorFrame; + private MeleeUIMinimap meleeUIMinimap; + private final CPlayerUnitOrderListener unitOrderListener; + private StringFrame errorMessageFrame; + + private CAbilityView activeCommand; + private int activeCommandOrderId; + private RenderUnit activeCommandUnit; + private MdxComplexInstance cursorModelInstance = null; + private BufferedImage cursorModelPathing; + + private int selectedSoundCount = 0; + private final ActiveCommandUnitTargetFilter activeCommandUnitTargetFilter; + + // TODO these corrections are used for old hardcoded UI stuff, we should + // probably remove them later + private final float widthRatioCorrection; + private final float heightRatioCorrection; + private CommandCardIcon mouseDownUIFrame; + + public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, + final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, + final CameraPreset[] cameraPresets, final CameraRates cameraRates, final War3MapViewer war3MapViewer, + final RootFrameListener rootFrameListener, final CPlayerUnitOrderListener unitOrderListener) { + this.dataSource = dataSource; + this.uiViewport = uiViewport; + this.fontGenerator = fontGenerator; + this.uiScene = uiScene; + this.portraitScene = portraitScene; + this.war3MapViewer = war3MapViewer; + this.rootFrameListener = rootFrameListener; + this.unitOrderListener = unitOrderListener; + + this.cameraManager = new GameCameraManager(cameraPresets, cameraRates); + + this.cameraManager.setupCamera(war3MapViewer.worldScene); + if (this.war3MapViewer.startLocations[0] != null) { + this.cameraManager.target.x = this.war3MapViewer.startLocations[0].x; + this.cameraManager.target.y = this.war3MapViewer.startLocations[0].y; + } + + this.activeButtonTexture = ImageUtils.getBLPTexture(war3MapViewer.mapMpq, + "UI\\Widgets\\Console\\Human\\CommandButton\\human-activebutton.blp"); + this.activeCommandUnitTargetFilter = new ActiveCommandUnitTargetFilter(); + this.widthRatioCorrection = this.uiViewport.getMinWorldWidth() / 1600f; + this.heightRatioCorrection = this.uiViewport.getMinWorldHeight() / 1200f; + + } + + private MeleeUIMinimap createMinimap(final War3MapViewer war3MapViewer) { + final Rectangle minimapDisplayArea = new Rectangle(18.75f * this.widthRatioCorrection, + 13.75f * this.heightRatioCorrection, 278.75f * this.widthRatioCorrection, + 276.25f * this.heightRatioCorrection); + Texture minimapTexture = null; + if (war3MapViewer.dataSource.has("war3mapMap.tga")) { + try { + minimapTexture = ImageUtils.getTextureNoColorCorrection(TgaFile.readTGA("war3mapMap.tga", + war3MapViewer.dataSource.getResourceAsStream("war3mapMap.tga"))); + } catch (final IOException e) { + System.err.println("Could not load minimap TGA file"); + e.printStackTrace(); + } + } else if (war3MapViewer.dataSource.has("war3mapMap.blp")) { + try { + minimapTexture = ImageUtils + .getTexture(ImageIO.read(war3MapViewer.dataSource.getResourceAsStream("war3mapMap.blp"))); + } catch (final IOException e) { + System.err.println("Could not load minimap BLP file"); + e.printStackTrace(); + } + } + final Texture[] teamColors = new Texture[WarsmashConstants.MAX_PLAYERS]; + for (int i = 0; i < teamColors.length; i++) { + teamColors[i] = ImageUtils.getBLPTexture(war3MapViewer.dataSource, + "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(i) + ".blp"); + } + final Rectangle playableMapArea = war3MapViewer.terrain.getPlayableMapArea(); + return new MeleeUIMinimap(minimapDisplayArea, playableMapArea, minimapTexture, teamColors); + } + + /** + * Called "main" because this was originally written in JASS so that maps could + * override it, and I may convert it back to the JASS at some point. + */ + public void main() { + // ================================= + // Load skins and templates + // ================================= + this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 0), this.uiViewport, + this.fontGenerator, this.uiScene, this.war3MapViewer); + this.rootFrameListener.onCreate(this.rootFrame); + try { + this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); + } catch (final IOException exc) { + throw new IllegalStateException("Unable to load FrameDef.toc", exc); + } + try { + this.rootFrame.loadTOCFile("UI\\FrameDef\\SmashFrameDef.toc"); + } catch (final IOException exc) { + throw new IllegalStateException("Unable to load SmashFrameDef.toc", exc); + } + this.damageBackdrops = new InfoPanelIconBackdrops(CAttackType.values(), this.rootFrame, "Damage", "Neutral"); + this.defenseBackdrops = new InfoPanelIconBackdrops(CDefenseType.values(), this.rootFrame, "Armor", "Neutral"); + + // ================================= + // Load major UI components + // ================================= + // Console UI is the background with the racial theme + this.consoleUI = this.rootFrame.createSimpleFrame("ConsoleUI", this.rootFrame, 0); + this.consoleUI.setSetAllPoints(true); + + // Resource bar is a 3 part bar with Gold, Lumber, and Food. + // Its template does not specify where to put it, so we must + // put it in the "TOPRIGHT" corner. + this.resourceBar = this.rootFrame.createSimpleFrame("ResourceBarFrame", this.consoleUI, 0); + this.resourceBar.addSetPoint(new SetPoint(FramePoint.TOPRIGHT, this.consoleUI, FramePoint.TOPRIGHT, 0, 0)); + this.resourceBarGoldText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarGoldText", 0); + this.resourceBarGoldText.setText("500"); + this.resourceBarLumberText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarLumberText", 0); + this.resourceBarLumberText.setText("150"); + this.resourceBarSupplyText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarSupplyText", 0); + this.resourceBarSupplyText.setText("12/100"); + this.resourceBarUpkeepText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarUpkeepText", 0); + this.resourceBarUpkeepText.setText("No Upkeep"); + this.resourceBarUpkeepText.setColor(Color.GREEN); + + // Create the Time Indicator (clock) + this.timeIndicator = (SpriteFrame) this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0); + this.timeIndicator.setSequence(0); // play the stand + this.timeIndicator.setAnimationSpeed(0.0f); // do not advance automatically + + // Create the unit portrait stuff + this.portrait = new Portrait(this.war3MapViewer, this.portraitScene); + positionPortrait(); + this.unitPortrait = this.rootFrame.createSimpleFrame("UnitPortrait", this.consoleUI, 0); + this.unitLifeText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitHitPointText", 0); + this.unitManaText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitManaPointText", 0); + + 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)); + this.simpleNameValue = (StringFrame) this.rootFrame.getFrameByName("SimpleNameValue", 0); + this.simpleClassValue = (StringFrame) this.rootFrame.getFrameByName("SimpleClassValue", 0); + this.simpleBuildingActionLabel = (StringFrame) this.rootFrame.getFrameByName("SimpleBuildingActionLabel", 0); + + this.attack1Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, + 0); + this.attack1Icon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, + FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); + this.attack1IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); + this.attack1InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); + this.attack1InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); + + this.attack2Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, + 1); + this.attack2Icon + .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0.1f), GameUI.convertY(this.uiViewport, -0.030125f))); + this.attack2IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 1); + this.attack2InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 1); + this.attack2InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 1); + + this.armorIcon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconArmor", this.simpleInfoPanelUnitDetail, + 1); + this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); + this.armorIconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); + this.armorInfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); + this.armorInfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); + + this.inventoryCover = this.rootFrame.createSimpleFrame("SmashConsoleInventoryCover", this.rootFrame, 0); + + this.errorMessageFrame = this.rootFrame.createStringFrame("SmashErrorMessageFrame", this.consoleUI, + new Color(0xFFFFCC00), TextJustify.LEFT, TextJustify.MIDDLE, 0.014f); + this.errorMessageFrame.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, + GameUI.convertX(this.uiViewport, 0.275f), GameUI.convertY(this.uiViewport, 0.275f))); + this.errorMessageFrame.setWidth(GameUI.convertX(this.uiViewport, 0.25f)); + + int commandButtonIndex = 0; + for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { + for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { + final CommandCardIcon commandCardIcon = new CommandCardIcon("SmashCommandButton_" + commandButtonIndex, + this.rootFrame, this); + this.rootFrame.add(commandCardIcon); + final TextureFrame iconFrame = this.rootFrame.createTextureFrame( + "SmashCommandButton_" + (commandButtonIndex) + "_Icon", this.rootFrame, false, null); + final TextureFrame activeHighlightFrame = this.rootFrame.createTextureFrame( + "SmashCommandButton_" + (commandButtonIndex) + "_ActiveHighlight", this.rootFrame, true, null, + FilterMode.ADDALPHA); + final SpriteFrame cooldownFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", + "SmashCommandButton_" + (commandButtonIndex) + "_Cooldown", this.rootFrame, "", 0); + final SpriteFrame autocastFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", + "SmashCommandButton_" + (commandButtonIndex) + "_Autocast", this.rootFrame, "", 0); + commandCardIcon.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, + GameUI.convertX(this.uiViewport, 0.6175f + (0.0434f * i)), + GameUI.convertY(this.uiViewport, 0.095f - (0.044f * j)))); + commandCardIcon.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + commandCardIcon.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + iconFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + iconFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + iconFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + iconFrame.setTexture(ImageUtils.DEFAULT_ICON_PATH, this.rootFrame); + activeHighlightFrame + .addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + activeHighlightFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + activeHighlightFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + activeHighlightFrame.setTexture("CommandButtonActiveHighlight", this.rootFrame); + cooldownFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + this.rootFrame.setSpriteFrameModel(cooldownFrame, this.rootFrame.getSkinField("CommandButtonCooldown")); + cooldownFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + cooldownFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + autocastFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + this.rootFrame.setSpriteFrameModel(autocastFrame, this.rootFrame.getSkinField("CommandButtonAutocast")); + autocastFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + autocastFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + commandCardIcon.set(iconFrame, activeHighlightFrame, cooldownFrame, autocastFrame); + this.commandCard[j][i] = commandCardIcon; + commandCardIcon.setCommandButton(null); + commandButtonIndex++; + } + } + buildTimeIndicator = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", + "SmashBuildProgressBar", this.rootFrame, "", 0); + buildTimeIndicator.addSetPoint(new SetPoint(FramePoint.CENTER, simpleClassValue, FramePoint.CENTER, + GameUI.convertX(this.uiViewport, -0.04f), + GameUI.convertY(this.uiViewport, -0.025f))); + this.rootFrame.setSpriteFrameModel(buildTimeIndicator, this.rootFrame.getSkinField("BuildTimeIndicator")); + buildTimeIndicator.setAnimationSpeed(0); + + this.cursorFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashCursorFrame", this.rootFrame, + "", 0); + this.rootFrame.setSpriteFrameModel(this.cursorFrame, this.rootFrame.getSkinField("Cursor")); + this.cursorFrame.setSequence("Normal"); + this.cursorFrame.setZDepth(1.0f); + Gdx.input.setCursorCatched(true); + + this.meleeUIMinimap = createMinimap(this.war3MapViewer); + + this.rootFrame.positionBounds(this.uiViewport); + selectUnit(null); + } + + @Override + public void startUsingAbility(final int abilityHandleId, final int orderId, final boolean rightClick) { + // TODO not O(N) + CAbilityView abilityToUse = null; + for (final CAbility ability : this.selectedUnit.getSimulationUnit().getAbilities()) { + if (ability.getHandleId() == abilityHandleId) { + abilityToUse = ability; + break; + } + } + if (abilityToUse != null) { + final StringMsgAbilityActivationReceiver stringMsgActivationReceiver = StringMsgAbilityActivationReceiver + .getInstance().reset(); + abilityToUse.checkCanUse(this.war3MapViewer.simulation, this.selectedUnit.getSimulationUnit(), orderId, + stringMsgActivationReceiver); + if (!stringMsgActivationReceiver.isUseOk()) { + showCommandError(stringMsgActivationReceiver.getMessage()); + } else { + final BooleanAbilityTargetCheckReceiver noTargetReceiver = BooleanAbilityTargetCheckReceiver + .getInstance().reset(); + abilityToUse.checkCanTargetNoTarget(this.war3MapViewer.simulation, + this.selectedUnit.getSimulationUnit(), orderId, noTargetReceiver); + if (noTargetReceiver.isTargetable()) { + this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), + abilityHandleId, orderId, isShiftDown()); + } else { + this.activeCommand = abilityToUse; + this.activeCommandOrderId = orderId; + this.activeCommandUnit = this.selectedUnit; + clearAndRepopulateCommandCard(); + } + } + } else { + this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), + abilityHandleId, orderId, isShiftDown()); + } + if (rightClick) { + this.war3MapViewer.getUiSounds().getSound("AutoCastButtonClick").play(this.uiScene.audioContext, 0, 0); + } + } + + @Override + public void openMenu(final int orderId) { + if (orderId == 0) { + this.subMenuOrderIdStack.clear(); + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + } else { + this.subMenuOrderIdStack.add(orderId); + } + clearAndRepopulateCommandCard(); + } + + public void showCommandError(final String message) { + this.errorMessageFrame.setText(message); + } + + public void update(final float deltaTime) { + this.portrait.update(); + + final int baseMouseX = Gdx.input.getX(); + int mouseX = baseMouseX; + final int baseMouseY = Gdx.input.getY(); + int mouseY = baseMouseY; + final int minX = this.uiViewport.getScreenX(); + final int maxX = minX + this.uiViewport.getScreenWidth(); + final int minY = this.uiViewport.getScreenY(); + final int maxY = minY + this.uiViewport.getScreenHeight(); + final boolean left = mouseX <= (minX + 3); + final boolean right = mouseX >= (maxX - 3); + final boolean up = mouseY <= (minY + 3); + final boolean down = mouseY >= (maxY - 3); + this.cameraManager.applyVelocity(deltaTime, up, down, left, right); + + mouseX = Math.max(minX, Math.min(maxX, mouseX)); + mouseY = Math.max(minY, Math.min(maxY, mouseY)); + if (Gdx.input.isCursorCatched()) { + Gdx.input.setCursorPosition(mouseX, mouseY); + } + + screenCoordsVector.set(mouseX, mouseY); + this.uiViewport.unproject(screenCoordsVector); + this.cursorFrame.setFramePointX(FramePoint.LEFT, screenCoordsVector.x); + this.cursorFrame.setFramePointY(FramePoint.BOTTOM, screenCoordsVector.y); + + if (this.activeCommand != null) { + if (this.activeCommand instanceof AbstractCAbilityBuild) { + boolean justLoaded = false; + if (this.cursorModelInstance == null) { + final MutableObjectData unitData = this.war3MapViewer.getAllObjectData().getUnits(); + final War3ID buildingTypeId = new War3ID(this.activeCommandOrderId); + final String unitModelPath = this.war3MapViewer.getUnitModelPath(unitData.get(buildingTypeId)); + final MdxModel model = (MdxModel) this.war3MapViewer.load(unitModelPath, + this.war3MapViewer.mapPathSolver, this.war3MapViewer.solverParams); + this.cursorModelInstance = (MdxComplexInstance) model.addInstance(); + this.cursorModelInstance.setVertexColor(new float[]{1, 1, 1, 0.5f}); + this.cursorModelInstance.rotate(RenderUnit.tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, + this.war3MapViewer.simulation.getGameplayConstants().getBuildingAngle())); + this.cursorModelInstance.setAnimationSpeed(0f); + justLoaded = true; + final CUnitType buildingUnitType = this.war3MapViewer.simulation.getUnitData() + .getUnitType(buildingTypeId); + this.cursorModelPathing = buildingUnitType.getBuildingPathingPixelMap(); + } + this.war3MapViewer.getClickLocation(clickLocationTemp, baseMouseX, + Gdx.graphics.getHeight() - baseMouseY); + if (this.cursorModelPathing != null) { + clickLocationTemp.x = Math.round(clickLocationTemp.x / 64f) * 64f; + clickLocationTemp.y = Math.round(clickLocationTemp.y / 64f) * 64f; + clickLocationTemp.z = this.war3MapViewer.terrain.getGroundHeight(clickLocationTemp.x, + clickLocationTemp.y); + } + this.cursorModelInstance.setLocation(clickLocationTemp); + SequenceUtils.randomSequence(this.cursorModelInstance, PrimaryTag.STAND); + this.cursorFrame.setVisible(false); + if (justLoaded) { + this.cursorModelInstance.setScene(this.war3MapViewer.worldScene); + } + } else { + if (this.cursorModelInstance != null) { + this.cursorModelInstance.detach(); + this.cursorModelInstance = null; + this.cursorFrame.setVisible(true); + } + this.cursorFrame.setSequence("Target"); + } + } else { + if (this.cursorModelInstance != null) { + this.cursorModelInstance.detach(); + this.cursorModelInstance = null; + this.cursorFrame.setVisible(true); + } + if (down) { + if (left) { + this.cursorFrame.setSequence("Scroll Down Left"); + } else if (right) { + this.cursorFrame.setSequence("Scroll Down Right"); + } else { + this.cursorFrame.setSequence("Scroll Down"); + } + } else if (up) { + if (left) { + this.cursorFrame.setSequence("Scroll Up Left"); + } else if (right) { + this.cursorFrame.setSequence("Scroll Up Right"); + } else { + this.cursorFrame.setSequence("Scroll Up"); + } + } else if (left) { + this.cursorFrame.setSequence("Scroll Left"); + } else if (right) { + this.cursorFrame.setSequence("Scroll Right"); + } else { + this.cursorFrame.setSequence("Normal"); + } + } + if (this.buildTimeIndicator.isVisible() && this.selectedUnit != null) { + buildTimeIndicator.setFrameByRatio(Math.min(this.selectedUnit.getSimulationUnit().getConstructionProgress() / + this.selectedUnit.getSimulationUnit().getUnitType().getBuildTime(), 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)); + this.cameraManager.updateTargetZ(groundHeight); + this.cameraManager.updateCamera(); + } + + public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) { + this.rootFrame.render(batch, font20, glyphLayout); + if (this.selectedUnit != null) { + font20.setColor(Color.WHITE); + + } + + this.meleeUIMinimap.render(batch, this.war3MapViewer.units); + this.timeIndicator.setFrameByRatio(this.war3MapViewer.simulation.getGameTimeOfDay() + / this.war3MapViewer.simulation.getGameplayConstants().getGameDayHours()); + } + + public void portraitTalk() { + this.portrait.talk(); + } + + private final class ActiveCommandUnitTargetFilter implements CUnitFilterFunction { + @Override + public boolean call(final CUnit unit) { + final BooleanAbilityTargetCheckReceiver targetReceiver = BooleanAbilityTargetCheckReceiver + .getInstance(); + MeleeUI.this.activeCommand.checkCanTarget(MeleeUI.this.war3MapViewer.simulation, + MeleeUI.this.activeCommandUnit.getSimulationUnit(), MeleeUI.this.activeCommandOrderId, unit, + targetReceiver); + return targetReceiver.isTargetable(); + } + } + + private static final class Portrait { + private MdxComplexInstance modelInstance; + private final PortraitCameraManager portraitCameraManager; + private final Scene portraitScene; + + public Portrait(final War3MapViewer war3MapViewer, final Scene portraitScene) { + this.portraitScene = portraitScene; + this.portraitCameraManager = new PortraitCameraManager(); + this.portraitCameraManager.setupCamera(this.portraitScene); + this.portraitScene.camera.viewport(new Rectangle(100, 0, 6400, 48)); + } + + public void update() { + this.portraitCameraManager.updateCamera(); + if ((this.modelInstance != null) + && (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1))) { + SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.EMPTY, true); + } + } + + public void talk() { + SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.TALK, true); + } + + public void setSelectedUnit(final RenderUnit unit) { + if (unit == null) { + if (this.modelInstance != null) { + this.portraitScene.removeInstance(this.modelInstance); + } + this.modelInstance = null; + this.portraitCameraManager.setModelInstance(null, null); + } else { + final MdxModel portraitModel = unit.portraitModel; + if (portraitModel != null) { + if (this.modelInstance != null) { + this.portraitScene.removeInstance(this.modelInstance); + } + this.modelInstance = (MdxComplexInstance) portraitModel.addInstance(); + this.portraitCameraManager.setModelInstance(this.modelInstance, portraitModel); + this.modelInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); + this.modelInstance.setScene(this.portraitScene); + this.modelInstance.setVertexColor(unit.instance.vertexColor); + this.modelInstance.setTeamColor(unit.playerIndex); + } + } + } + } + + public void selectUnit(RenderUnit unit) { + this.subMenuOrderIdStack.clear(); + if ((unit != null) && unit.getSimulationUnit().isDead()) { + unit = null; + } + if (this.selectedUnit != null) { + this.selectedUnit.getSimulationUnit().removeStateListener(this); + } + this.portrait.setSelectedUnit(unit); + this.selectedUnit = unit; + if (unit == null) { + clearCommandCard(); + this.simpleNameValue.setText(""); + this.unitLifeText.setText(""); + this.unitManaText.setText(""); + this.simpleClassValue.setText(""); + this.simpleBuildingActionLabel.setText(""); + this.attack1Icon.setVisible(false); + this.attack2Icon.setVisible(false); + this.attack1InfoPanelIconLevel.setText(""); + this.attack2InfoPanelIconLevel.setText(""); + this.armorIcon.setVisible(false); + this.armorInfoPanelIconLevel.setText(""); + this.buildTimeIndicator.setVisible(false); + } else { + unit.getSimulationUnit().addStateListener(this); + 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(); + if (maximumMana > 0) { + this.unitManaText.setText( + FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getMana()) + " / " + maximumMana); + } else { + this.unitManaText.setText(""); + } + this.simpleBuildingActionLabel.setText(""); + + final boolean anyAttacks = unit.getSimulationUnit().getUnitType().getAttacks().size() > 0; + 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()); + } else { + this.attack2Icon.setVisible(false); + } + + 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); + buildTimeIndicator.setVisible(constructing); + if (constructing) { + buildTimeIndicator.setSequence(0); + } + 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(); + } + } + + private void clearCommandCard() { + for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { + for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { + this.commandCard[j][i].setCommandButton(null); + } + } + } + + @Override + 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)); + this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, autoCastId, active, autoCastActive, + menuButton); + } + + public void resize(final Rectangle viewport) { + this.cameraManager.resize(viewport); + positionPortrait(); + } + + public void positionPortrait() { + this.projectionTemp1.x = 422 * this.widthRatioCorrection; + this.projectionTemp1.y = 57 * this.heightRatioCorrection; + this.projectionTemp2.x = (422 + 167) * this.widthRatioCorrection; + this.projectionTemp2.y = (57 + 170) * this.heightRatioCorrection; + this.uiViewport.project(this.projectionTemp1); + this.uiViewport.project(this.projectionTemp2); + + this.tempRect.x = this.projectionTemp1.x + this.uiViewport.getScreenX(); + this.tempRect.y = this.projectionTemp1.y + this.uiViewport.getScreenY(); + this.tempRect.width = this.projectionTemp2.x - this.projectionTemp1.x; + this.tempRect.height = this.projectionTemp2.y - this.projectionTemp1.y; + this.portrait.portraitScene.camera.viewport(this.tempRect); + } + + private static final class InfoPanelIconBackdrops { + private final Texture[] damageBackdropTextures; + + public InfoPanelIconBackdrops(final CodeKeyType[] attackTypes, final GameUI gameUI, final String prefix, + final String suffix) { + this.damageBackdropTextures = new Texture[attackTypes.length]; + for (int index = 0; index < attackTypes.length; index++) { + final CodeKeyType attackType = attackTypes[index]; + String skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey() + suffix; + final Texture suffixTexture = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); + if (suffixTexture != null) { + this.damageBackdropTextures[index] = suffixTexture; + } else { + skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey(); + this.damageBackdropTextures[index] = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); + } + } + } + + public Texture getTexture(final CodeKeyType attackType) { + if (attackType != null) { + final int ordinal = attackType.ordinal(); + if ((ordinal >= 0) && (ordinal < this.damageBackdropTextures.length)) { + return this.damageBackdropTextures[ordinal]; + } + } + return this.damageBackdropTextures[0]; + } + + private static String getSuffix(final CAttackType attackType) { + switch (attackType) { + case CHAOS: + return "Chaos"; + case HERO: + return "Hero"; + case MAGIC: + return "Magic"; + case NORMAL: + return "Normal"; + case PIERCE: + return "Pierce"; + case SIEGE: + return "Siege"; + case SPELLS: + return "Magic"; + case UNKNOWN: + return "Unknown"; + default: + throw new IllegalArgumentException("Unknown attack type: " + attackType); + } + + } + } + + @Override + public void lifeChanged() { + if (this.selectedUnit.getSimulationUnit().isDead()) { + selectUnit(null); + } else { + this.unitLifeText + .setText(FastNumberFormat.formatWholeNumber(this.selectedUnit.getSimulationUnit().getLife()) + " / " + + this.selectedUnit.getSimulationUnit().getMaximumLife()); + } + } + + @Override + public void ordersChanged(final int abilityHandleId, final int orderId) { + selectUnit(selectedUnit); + } + + private void clearAndRepopulateCommandCard() { + clearCommandCard(); + final AbilityDataUI abilityDataUI = this.war3MapViewer.getAbilityDataUI(); + final int menuOrderId = getSubMenuOrderId(); + if (this.activeCommand != null) { + 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) { + final int exitOrderId = this.subMenuOrderIdStack.size() > 1 + ? this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 2) + : 0; + final IconUI cancelUI = abilityDataUI.getCancelUI(); + this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, + exitOrderId, 0, false, false, true); + } + this.selectedUnit.populateCommandCard(this.war3MapViewer.simulation, this, abilityDataUI, menuOrderId); + } + } + + private int getSubMenuOrderId() { + return this.subMenuOrderIdStack.isEmpty() ? 0 + : this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 1); + } + + public RenderUnit getSelectedUnit() { + return this.selectedUnit; + } + + public boolean keyDown(final int keycode) { + return this.cameraManager.keyDown(keycode); + } + + public boolean keyUp(final int keycode) { + return this.cameraManager.keyUp(keycode); + } + + public void scrolled(final int amount) { + this.cameraManager.scrolled(amount); + } + + public boolean touchDown(final int screenX, final int screenY, final float worldScreenY, final int button) { + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { + final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenCoordsVector.x, + screenCoordsVector.y); + this.cameraManager.target.x = worldPoint.x; + this.cameraManager.target.y = worldPoint.y; + return true; + } + final UIFrame clickedUIFrame = this.rootFrame.touchDown(screenCoordsVector.x, screenCoordsVector.y, button); + if (clickedUIFrame == null) { + // try to interact with world + if (this.activeCommand != null) { + if (button == Input.Buttons.RIGHT) { + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + clearAndRepopulateCommandCard(); + } else { + final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY, + this.activeCommandUnitTargetFilter); + final boolean shiftDown = isShiftDown(); + if (rayPickUnit != null) { + this.unitOrderListener.issueTargetOrder( + this.activeCommandUnit.getSimulationUnit().getHandleId(), + this.activeCommand.getHandleId(), this.activeCommandOrderId, + rayPickUnit.getSimulationUnit().getHandleId(), shiftDown); + if (getSelectedUnit().soundset.yesAttack + .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + if (!shiftDown) { + this.subMenuOrderIdStack.clear(); + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + clearAndRepopulateCommandCard(); + } + } else { + this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); + clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); + + this.activeCommand.checkCanTarget(this.war3MapViewer.simulation, + this.activeCommandUnit.getSimulationUnit(), this.activeCommandOrderId, + clickLocationTemp2, PointAbilityTargetCheckReceiver.INSTANCE); + final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (target != null) { + if (this.activeCommand instanceof CAbilityAttack) { + this.war3MapViewer.showConfirmation(clickLocationTemp, 1, 0, 0); + } else { + this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); + } + this.unitOrderListener.issuePointOrder( + this.activeCommandUnit.getSimulationUnit().getHandleId(), + this.activeCommand.getHandleId(), this.activeCommandOrderId, clickLocationTemp2.x, + clickLocationTemp2.y, shiftDown); + if (getSelectedUnit().soundset.yes + .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + if (this.activeCommand instanceof AbstractCAbilityBuild) { + this.war3MapViewer.getUiSounds().getSound("PlaceBuildingDefault") + .play(this.uiScene.audioContext, 0, 0); + } + if (!shiftDown) { + this.subMenuOrderIdStack.clear(); + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + clearAndRepopulateCommandCard(); + } + + } + } + } + } else { + if (button == Input.Buttons.RIGHT) { + if (getSelectedUnit() != null) { + final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY); + if ((rayPickUnit != null) && !rayPickUnit.getSimulationUnit().isDead()) { + boolean ordered = false; + for (final RenderUnit unit : this.war3MapViewer.selected) { + for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { + ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), + OrderIds.smart, rayPickUnit.getSimulationUnit(), + CWidgetAbilityTargetCheckReceiver.INSTANCE); + final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (targetWidget != null) { + this.unitOrderListener.issueTargetOrder(unit.getSimulationUnit().getHandleId(), + ability.getHandleId(), OrderIds.smart, targetWidget.getHandleId(), + isShiftDown()); + ordered = true; + } + } + + } + if (ordered) { + if (getSelectedUnit().soundset.yesAttack.playUnitResponse( + this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + } + } else { + this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); + this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); + clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); + + boolean ordered = false; + for (final RenderUnit unit : this.war3MapViewer.selected) { + for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { + ability.checkCanUse(this.war3MapViewer.simulation, unit.getSimulationUnit(), + OrderIds.smart, BooleanAbilityActivationReceiver.INSTANCE); + if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { + ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), + OrderIds.smart, clickLocationTemp2, + PointAbilityTargetCheckReceiver.INSTANCE); + final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (target != null) { + this.unitOrderListener.issuePointOrder( + unit.getSimulationUnit().getHandleId(), ability.getHandleId(), + OrderIds.smart, clickLocationTemp2.x, clickLocationTemp2.y, + isShiftDown()); + ordered = true; + } + } + } + + } + + if (ordered) { + if (getSelectedUnit().soundset.yes.playUnitResponse( + this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + } + } + } + } else { + final List selectedUnits = this.war3MapViewer.selectUnit(screenX, worldScreenY, false); + if (!selectedUnits.isEmpty()) { + final RenderUnit unit = selectedUnits.get(0); + final boolean selectionChanged = getSelectedUnit() != unit; + boolean playedNewSound = false; + if (selectionChanged) { + this.selectedSoundCount = 0; + } + if (unit.soundset != null) { + UnitSound ackSoundToPlay = unit.soundset.what; + int soundIndex; + final int pissedSoundCount = unit.soundset.pissed.getSoundCount(); + if (unit.getSimulationUnit().isConstructing()) { + ackSoundToPlay = unit.buildSound; + soundIndex = 0; + } else { + if ((this.selectedSoundCount >= 3) && (pissedSoundCount > 0)) { + soundIndex = this.selectedSoundCount - 3; + ackSoundToPlay = unit.soundset.pissed; + } else { + soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); + } + } + if (ackSoundToPlay != null && ackSoundToPlay.playUnitResponse(this.war3MapViewer.worldScene.audioContext, unit, + soundIndex)) { + this.selectedSoundCount++; + if ((this.selectedSoundCount - 3) >= pissedSoundCount) { + this.selectedSoundCount = 0; + } + playedNewSound = true; + } + } + if (selectionChanged) { + selectUnit(unit); + } + if (playedNewSound) { + portraitTalk(); + } + } else { + selectUnit(null); + } + } + } + } else { + if (clickedUIFrame instanceof CommandCardIcon) { + this.mouseDownUIFrame = (CommandCardIcon) clickedUIFrame; + this.mouseDownUIFrame.mouseDown(this.uiViewport); + } + } + return false; + } + + public boolean touchUp(final int screenX, final int screenY, final float worldScreenY, final int button) { + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + final UIFrame clickedUIFrame = this.rootFrame.touchUp(screenCoordsVector.x, screenCoordsVector.y, button); + if (this.mouseDownUIFrame != null) { + if (clickedUIFrame == this.mouseDownUIFrame) { + this.mouseDownUIFrame.onClick(button); + this.war3MapViewer.getUiSounds().getSound("InterfaceClick").play(this.uiScene.audioContext, 0, 0); + } + this.mouseDownUIFrame.mouseUp(this.uiViewport); + } + this.mouseDownUIFrame = null; + return false; + } + + private static boolean isShiftDown() { + return Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) || Gdx.input.isKeyPressed(Input.Keys.SHIFT_RIGHT); + } + + public boolean touchDragged(final int screenX, final int screenY, final float worldScreenY, final int pointer) { + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + + if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { + final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenCoordsVector.x, + screenCoordsVector.y); + this.cameraManager.target.x = worldPoint.x; + this.cameraManager.target.y = worldPoint.y; + } + return false; + } + + public float getHeightRatioCorrection() { + return this.heightRatioCorrection; + } }