From 238015f21df9be052d1e8a928ed2946eb962352c Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 31 Oct 2020 15:08:00 -0400 Subject: [PATCH] Highlight proper movement icons, improve sequence filter --- .../w3x/SecondaryTagSequenceComparator.java | 41 + .../viewer5/handlers/w3x/SequenceUtils.java | 388 +-- .../viewer5/handlers/w3x/War3MapViewer.java | 2501 ++++++++--------- .../w3x/environment/TerrainShaders.java | 2 +- .../handlers/w3x/rendersim/RenderUnit.java | 3 - .../CommandCardPopulatingAbilityVisitor.java | 124 +- .../handlers/w3x/simulation/CUnit.java | 17 +- .../simulation/abilities/CAbilityAttack.java | 4 +- .../abilities/CAbilityColdArrows.java | 2 +- .../simulation/abilities/CAbilityMove.java | 4 +- .../w3x/simulation/behaviors/CBehavior.java | 2 + .../simulation/behaviors/CBehaviorAttack.java | 10 +- .../simulation/behaviors/CBehaviorFollow.java | 10 +- .../simulation/behaviors/CBehaviorMove.java | 39 +- .../simulation/behaviors/CBehaviorPatrol.java | 6 + .../simulation/behaviors/CBehaviorStop.java | 7 + .../w3x/simulation/data/CUnitData.java | 748 ++--- 17 files changed, 1998 insertions(+), 1910 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/SecondaryTagSequenceComparator.java diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SecondaryTagSequenceComparator.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SecondaryTagSequenceComparator.java new file mode 100644 index 0000000..1a5ab6b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SecondaryTagSequenceComparator.java @@ -0,0 +1,41 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +import java.util.Comparator; +import java.util.EnumSet; + +public class SecondaryTagSequenceComparator implements Comparator { + private final StandSequenceComparator standSequenceComparator; + private EnumSet ignoredTags; + + public SecondaryTagSequenceComparator(StandSequenceComparator standSequenceComparator) { + this.standSequenceComparator = standSequenceComparator; + } + + public SecondaryTagSequenceComparator reset(EnumSet ignoredTags) { + this.ignoredTags = ignoredTags; + return this; + } + + @Override + public int compare(final IndexedSequence a, final IndexedSequence b) { + EnumSet secondaryTagsA = a.sequence.getSecondaryTags(); + EnumSet secondaryTagsB = b.sequence.getSecondaryTags(); + int secondaryTagsAOrdinal = getTagsOrdinal(secondaryTagsA, ignoredTags); + int secondaryTagsBOrdinal = getTagsOrdinal(secondaryTagsB, ignoredTags); + if (secondaryTagsAOrdinal != secondaryTagsBOrdinal) { + return secondaryTagsBOrdinal - secondaryTagsAOrdinal; + } + return standSequenceComparator.compare(a, b); + } + + public static int getTagsOrdinal(EnumSet secondaryTagsA, EnumSet ignoredTags) { + int secondaryTagsBOrdinal = Integer.MAX_VALUE; + for (AnimationTokens.SecondaryTag secondaryTag : secondaryTagsA) { + if (secondaryTag.ordinal() < secondaryTagsBOrdinal && !ignoredTags.contains(secondaryTag)) { + secondaryTagsBOrdinal = secondaryTag.ordinal(); + } + } + return secondaryTagsBOrdinal; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java index eb727b8..cfd9e99 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java @@ -1,6 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x; import java.util.ArrayList; +import java.util.Comparator; import java.util.EnumSet; import java.util.List; @@ -11,229 +12,244 @@ import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; public class SequenceUtils { - public static final EnumSet EMPTY = EnumSet.noneOf(SecondaryTag.class); - public static final EnumSet READY = EnumSet.of(SecondaryTag.READY); - public static final EnumSet FLESH = EnumSet.of(SecondaryTag.FLESH); - public static final EnumSet BONE = EnumSet.of(SecondaryTag.BONE); + public static final EnumSet EMPTY = EnumSet.noneOf(SecondaryTag.class); + public static final EnumSet READY = EnumSet.of(SecondaryTag.READY); + public static final EnumSet FLESH = EnumSet.of(SecondaryTag.FLESH); + public static final EnumSet BONE = EnumSet.of(SecondaryTag.BONE); - private static final StandSequenceComparator STAND_SEQUENCE_COMPARATOR = new StandSequenceComparator(); + private static final StandSequenceComparator STAND_SEQUENCE_COMPARATOR = new StandSequenceComparator(); + private static final SecondaryTagSequenceComparator SECONDARY_TAG_SEQUENCE_COMPARATOR = new SecondaryTagSequenceComparator(STAND_SEQUENCE_COMPARATOR); - public static List filterSequences(final String type, final List sequences) { - final List filtered = new ArrayList<>(); + public static List filterSequences(final String type, final List sequences) { + final List filtered = new ArrayList<>(); - for (int i = 0, l = sequences.size(); i < l; i++) { - final Sequence sequence = sequences.get(i); - final String name = sequence.getName().split("-")[0].trim().toLowerCase(); + for (int i = 0, l = sequences.size(); i < l; i++) { + final Sequence sequence = sequences.get(i); + final String name = sequence.getName().split("-")[0].trim().toLowerCase(); - if (name.equals(type)) { - filtered.add(new IndexedSequence(sequence, i)); - } - } + if (name.equals(type)) { + filtered.add(new IndexedSequence(sequence, i)); + } + } - return filtered; - } + return filtered; + } - private static List filterSequences(final PrimaryTag type, final EnumSet tags, - final List sequences) { - final List filtered = new ArrayList<>(); + private static List filterSequences(final PrimaryTag type, final EnumSet tags, + final List sequences) { + final List filtered = new ArrayList<>(); - for (int i = 0, l = sequences.size(); i < l; i++) { - final Sequence sequence = sequences.get(i); - if (sequence.getPrimaryTags().contains(type) && sequence.getSecondaryTags().containsAll(tags) - && tags.containsAll(sequence.getSecondaryTags())) { - for (final AnimationTokens.SecondaryTag secondaryTag : sequence.getSecondaryTags()) { - if (tags.contains(secondaryTag)) { - filtered.add(new IndexedSequence(sequence, i)); - } - } - } - } - if (filtered.isEmpty()) { - for (int i = 0, l = sequences.size(); i < l; i++) { - final Sequence sequence = sequences.get(i); - if (sequence.getPrimaryTags().contains(type) && sequence.getSecondaryTags().containsAll(tags) - && tags.containsAll(sequence.getSecondaryTags())) { - filtered.add(new IndexedSequence(sequence, i)); - } - } - } + for (int i = 0, l = sequences.size(); i < l; i++) { + final Sequence sequence = sequences.get(i); + if (sequence.getPrimaryTags().contains(type) && (sequence.getSecondaryTags().containsAll(tags) + && tags.containsAll(sequence.getSecondaryTags()))) { + filtered.add(new IndexedSequence(sequence, i)); + } + } - return filtered; - } + return filtered; + } - public static IndexedSequence selectSequence(final String type, final List sequences) { - final List filtered = filterSequences(type, sequences); + public static IndexedSequence selectSequence(final String type, final List sequences) { + final List filtered = filterSequences(type, sequences); - filtered.sort(STAND_SEQUENCE_COMPARATOR); + filtered.sort(STAND_SEQUENCE_COMPARATOR); - int i = 0; - final double randomRoll = Math.random() * 100; - for (final int l = filtered.size(); i < l; i++) { - final Sequence sequence = filtered.get(i).sequence; - final float rarity = sequence.getRarity(); + int i = 0; + final double randomRoll = Math.random() * 100; + for (final int l = filtered.size(); i < l; i++) { + final Sequence sequence = filtered.get(i).sequence; + final float rarity = sequence.getRarity(); - if (rarity == 0) { - break; - } + if (rarity == 0) { + break; + } - if (randomRoll < (10 - rarity)) { - return filtered.get(i); - } - } + if (randomRoll < (10 - rarity)) { + return filtered.get(i); + } + } - final int sequencesLeft = filtered.size() - i; - final int random = (int) (i + Math.floor(Math.random() * sequencesLeft)); - if (sequencesLeft <= 0) { - return null; // new IndexedSequence(null, 0); - } - final IndexedSequence sequence = filtered.get(random); + final int sequencesLeft = filtered.size() - i; + final int random = (int) (i + Math.floor(Math.random() * sequencesLeft)); + if (sequencesLeft <= 0) { + return null; // new IndexedSequence(null, 0); + } + final IndexedSequence sequence = filtered.get(random); - return sequence; - } + return sequence; + } - public static IndexedSequence selectSequence(final AnimationTokens.PrimaryTag type, - final EnumSet tags, final List sequences, - final boolean allowRarityVariations) { - final List filtered = filterSequences(type, tags, sequences); + public static IndexedSequence selectSequence(final AnimationTokens.PrimaryTag type, + final EnumSet tags, final List sequences, + final boolean allowRarityVariations) { + List filtered = filterSequences(type, tags, sequences); + Comparator sequenceComparator = STAND_SEQUENCE_COMPARATOR; - filtered.sort(STAND_SEQUENCE_COMPARATOR); + if (filtered.isEmpty() && !tags.isEmpty()) { + filtered = filterSequences(type, EMPTY, sequences); + } + if (filtered.isEmpty()) { + // find tags + EnumSet fallbackTags = null; + for (int i = 0, l = sequences.size(); i < l; i++) { + final Sequence sequence = sequences.get(i); + if (sequence.getPrimaryTags().contains(type)) { + if (fallbackTags == null || sequence.getSecondaryTags().size() < fallbackTags.size() + || (sequence.getSecondaryTags().size() == fallbackTags.size() && SecondaryTagSequenceComparator.getTagsOrdinal(sequence.getSecondaryTags(), tags) + > SecondaryTagSequenceComparator.getTagsOrdinal(fallbackTags, tags)) + ) { + fallbackTags = sequence.getSecondaryTags(); + } + } + } + if (fallbackTags != null) { + filtered = filterSequences(type, fallbackTags, sequences); + } + } - int i = 0; - final double randomRoll = Math.random() * 100; - for (final int l = filtered.size(); i < l; i++) { - final Sequence sequence = filtered.get(i).sequence; - final float rarity = sequence.getRarity(); + filtered.sort(sequenceComparator); - if (rarity == 0) { - break; - } + int i = 0; + final double randomRoll = Math.random() * 100; + for (final int l = filtered.size(); i < l; i++) { + final Sequence sequence = filtered.get(i).sequence; + final float rarity = sequence.getRarity(); - if ((randomRoll < (10 - rarity)) && allowRarityVariations) { - return filtered.get(i); - } - } + if (rarity == 0) { + break; + } - final int sequencesLeft = filtered.size() - i; - final int random = (int) (i + Math.floor(Math.random() * sequencesLeft)); - if (sequencesLeft <= 0) { - return null; // new IndexedSequence(null, 0); - } - final IndexedSequence sequence = filtered.get(random); + if ((randomRoll < (10 - rarity)) && allowRarityVariations) { + return filtered.get(i); + } + } - return sequence; - } + final int sequencesLeft = filtered.size() - i; + final int random = (int) (i + Math.floor(Math.random() * sequencesLeft)); + if (sequencesLeft <= 0) { + return null; // new IndexedSequence(null, 0); + } + final IndexedSequence sequence = filtered.get(random); - public static void randomStandSequence(final MdxComplexInstance target) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence("stand", sequences); + return sequence; + } - if (sequence != null) { - target.setSequence(sequence.index); - } - else { - target.setSequence(0); - } - } + public static void randomStandSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("stand", sequences); - public static void randomDeathSequence(final MdxComplexInstance target) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence("death", sequences); + if (sequence != null) { + target.setSequence(sequence.index); + } else { + target.setSequence(0); + } + } - if (sequence != null) { - target.setSequence(sequence.index); - } - else { - target.setSequence(0); - } - } + public static void randomDeathSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("death", sequences); - public static void randomWalkSequence(final MdxComplexInstance target) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence("walk", sequences); + if (sequence != null) { + target.setSequence(sequence.index); + } else { + target.setSequence(0); + } + } - if (sequence != null) { - target.setSequence(sequence.index); - } - else { - randomStandSequence(target); - } - } + public static void randomWalkSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("walk", sequences); - public static void randomBirthSequence(final MdxComplexInstance target) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence("birth", sequences); + if (sequence != null) { + target.setSequence(sequence.index); + } else { + randomStandSequence(target); + } + } - if (sequence != null) { - target.setSequence(sequence.index); - } - else { - randomStandSequence(target); - } - } + public static void randomBirthSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("birth", sequences); - public static void randomPortraitSequence(final MdxComplexInstance target) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence("portrait", sequences); + if (sequence != null) { + target.setSequence(sequence.index); + } else { + randomStandSequence(target); + } + } - if (sequence != null) { - target.setSequence(sequence.index); - } - else { - randomStandSequence(target); - } - } + public static void randomPortraitSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("portrait", sequences); - public static void randomPortraitTalkSequence(final MdxComplexInstance target) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence("portrait talk", sequences); + if (sequence != null) { + target.setSequence(sequence.index); + } else { + randomStandSequence(target); + } + } - if (sequence != null) { - target.setSequence(sequence.index); - } - else { - randomPortraitSequence(target); - } - } + public static void randomPortraitTalkSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("portrait talk", sequences); - public static void randomSequence(final MdxComplexInstance target, final String sequenceName) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence(sequenceName, sequences); + if (sequence != null) { + target.setSequence(sequence.index); + } else { + randomPortraitSequence(target); + } + } - if (sequence != null) { - target.setSequence(sequence.index); - } - else { - randomStandSequence(target); - } - } + public static void randomSequence(final MdxComplexInstance target, final String sequenceName) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence(sequenceName, sequences); - public static Sequence randomSequence(final MdxComplexInstance target, final PrimaryTag animationName, - final EnumSet secondaryAnimationTags, final boolean allowRarityVariations) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence(animationName, secondaryAnimationTags, sequences, - allowRarityVariations); + if (sequence != null) { + target.setSequence(sequence.index); + } else { + randomStandSequence(target); + } + } - if (sequence != null) { - target.setSequence(sequence.index); - return sequence.sequence; - } - else { - if (!secondaryAnimationTags.isEmpty()) { - return randomSequence(target, animationName, EMPTY, allowRarityVariations); - } - return null; - } - } + public static Sequence randomSequence(final MdxComplexInstance target, final PrimaryTag animationName, + final boolean allowRarityVariations) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence(animationName, null, sequences, + allowRarityVariations); - public static Sequence randomSequence(final MdxComplexInstance target, final PrimaryTag animationName) { - return randomSequence(target, animationName, EMPTY, false); - } + if (sequence != null) { + target.setSequence(sequence.index); + return sequence.sequence; + } else { + return null; + } + } + + public static Sequence randomSequence(final MdxComplexInstance target, final PrimaryTag animationName, + final EnumSet secondaryAnimationTags, final boolean allowRarityVariations) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence(animationName, secondaryAnimationTags, sequences, + allowRarityVariations); + + if (sequence != null) { + target.setSequence(sequence.index); + return sequence.sequence; + } else { + return null; + } + } + + public static Sequence randomSequence(final MdxComplexInstance target, final PrimaryTag animationName) { + return randomSequence(target, animationName, EMPTY, false); + } } 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 82ab54f..0376097 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -95,1328 +95,1301 @@ 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; - 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(); - - 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"); - } - - 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); - } - }, 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"); - } - - this.terrain.createWaves(); - - loadLightsAndShading(tileset); - } - - /** - * 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; - - if (this.dataSource.has("war3mapUnits.doo")) { - final War3MapUnitsDoo dooFile = mpq.readUnits(); - - final Map soundsetNameToSoundset = new HashMap<>(); - - // Collect the units and items data. - UnitSoundset soundset = null; - for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) { - MutableGameObject row = null; - String path = null; - Splat unitShadowSplat = null; - BufferedImage buildingPathingPixelMap = null; - - // Hardcoded? - WorldEditorDataType type = null; - if (sloc.equals(unit.getId())) { + 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; + 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(); + + 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"); + } + + 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); + } + }, 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"); + } + + this.terrain.createWaves(); + + loadLightsAndShading(tileset); + } + + /** + * 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; + + if (this.dataSource.has("war3mapUnits.doo")) { + final War3MapUnitsDoo dooFile = mpq.readUnits(); + + final Map soundsetNameToSoundset = new HashMap<>(); + + // Collect the units and items data. + UnitSoundset soundset = null; + for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) { + MutableGameObject row = null; + String path = null; + Splat unitShadowSplat = null; + BufferedImage buildingPathingPixelMap = null; + + // Hardcoded? + WorldEditorDataType type = null; + if (sloc.equals(unit.getId())) { // path = "Objects\\StartLocation\\StartLocation.mdx"; - type = null; /// ?????? - this.startLocations[unit.getPlayer()] = new Vector2(unit.getLocation()[0], unit.getLocation()[1]); - } - else { - row = modifications.getUnits().get(unit.getId()); - if (row == null) { - row = modifications.getItems().get(unit.getId()); - if (row != null) { - type = WorldEditorDataType.ITEM; - path = row.getFieldAsString(ITEM_FILE, 0); + type = null; /// ?????? + this.startLocations[unit.getPlayer()] = new Vector2(unit.getLocation()[0], unit.getLocation()[1]); + } else { + row = modifications.getUnits().get(unit.getId()); + if (row == null) { + row = modifications.getItems().get(unit.getId()); + 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 = unit.getLocation()[0] - shadowX; - final float y = unit.getLocation()[1] - 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 = "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 = unit.getLocation()[0] - shadowX; + final float y = unit.getLocation()[1] - 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 = row.getFieldAsString(UNIT_FILE, 0); + path += ".mdx"; + } + } else { + type = WorldEditorDataType.UNITS; + 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"; + path += ".mdx"; - 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 = unit.getLocation()[0]; - final float y = unit.getLocation()[1]; - 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"; + if (!this.terrain.splats.containsKey(texturePath)) { + this.terrain.splats.put(texturePath, new Splat()); + } + final float x = unit.getLocation()[0]; + final float y = unit.getLocation()[1]; + 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 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 = unit.getLocation()[0] - shadowX; - final float y = unit.getLocation()[1] - 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 = unit.getLocation()[0] - shadowX; + final float y = unit.getLocation()[1] - 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, unit.getLocation()[0], unit.getLocation()[1]); - } - 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) { - 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); - } - this.terrain.pathingGrid.blitPathingOverlayTexture(unit.getLocation()[0], - unit.getLocation()[1], (int) Math.toDegrees(unit.getAngle()), - buildingPathingPixelMap); - } + final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); + if ((buildingShadow != null) && !"_".equals(buildingShadow)) { + this.terrain.addShadow(buildingShadow, unit.getLocation()[0], unit.getLocation()[1]); + } + 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) { + 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); + } + this.terrain.pathingGrid.blitPathingOverlayTexture(unit.getLocation()[0], + unit.getLocation()[1], (int) Math.toDegrees(unit.getAngle()), + buildingPathingPixelMap); + } - final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); - UnitSoundset unitSoundset = soundsetNameToSoundset.get(soundName); - if (unitSoundset == null) { - unitSoundset = new UnitSoundset(this.dataSource, this.unitAckSoundsTable, soundName); - soundsetNameToSoundset.put(soundName, unitSoundset); - } - soundset = unitSoundset; - } - } + final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); + UnitSoundset unitSoundset = soundsetNameToSoundset.get(soundName); + if (unitSoundset == null) { + unitSoundset = new UnitSoundset(this.dataSource, this.unitAckSoundsTable, soundName); + 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(unit.getAngle()); - final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), unit.getPlayer(), - unit.getLocation()[0], unit.getLocation()[1], angle, buildingPathingPixelMap); - final RenderUnitTypeData typeData = getUnitTypeData(unit.getId(), row); - final RenderUnit renderUnit = new RenderUnit(this, model, row, unit, 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; - } - }); - } - } - else { - this.items.add(new RenderItem(this, model, row, unit, 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(unit.getAngle()); + final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), unit.getPlayer(), + unit.getLocation()[0], unit.getLocation()[1], angle, buildingPathingPixelMap); + final RenderUnitTypeData typeData = getUnitTypeData(unit.getId(), row); + final RenderUnit renderUnit = new RenderUnit(this, model, row, unit, 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; + } + }); + } + } else { + this.items.add(new RenderItem(this, model, row, unit, 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: " + unit.getId()); - } - } - } + } + }); + } + } + } else { + System.err.println("Unknown unit ID: " + unit.getId()); + } + } + } - this.terrain.loadSplats(); + this.terrain.loadSplats(); - this.unitsReady = true; - this.anyReady = true; - } + this.unitsReady = true; + this.anyReady = true; + } - private 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; - } + private 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/* - * && (((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); - } - } + } + } + } - @Override - public void render() { - if (this.anyReady) { - final Scene worldScene = this.worldScene; + 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); + } + } - 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(); + @Override + public void render() { + if (this.anyReady) { + final Scene worldScene = this.worldScene; - final List scenes = this.scenes; - for (final Scene scene : scenes) { - if (scene != worldScene) { - scene.startFrame(); - scene.renderOpaque(); - scene.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(); - 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(); - } + final List scenes = this.scenes; + for (final Scene scene : scenes) { + if (scene != worldScene) { + scene.startFrame(); + scene.renderOpaque(); + scene.renderTranslucent(); + } + } + } + } - public void doSelectUnit(final List units) { - deselect(); - if (units.isEmpty()) { - return; - } + 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(); + } - 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); - } - } + public void doSelectUnit(final List units) { + deselect(); + if (units.isEmpty()) { + return; + } - 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)); - } - } + 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); + } + } - 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 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 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 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; + } - 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 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 RenderUnit rayPickUnit(final float x, final float y) { - return rayPickUnit(x, y, CUnitFilterFunction.ACCEPT_ALL); - } + 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, 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) { + return rayPickUnit(x, y, CUnitFilterFunction.ACCEPT_ALL); + } - 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())) { - if ((entity == null) || (entity.instance.depth > instance.depth)) { - entity = unit; - } - } - } - } - return entity; - } + 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 - 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()); - } - } + 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())) { + if ((entity == null) || (entity.instance.depth > instance.depth)) { + entity = unit; + } + } + } + } + return entity; + } - 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 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 StreamDataCallbackImplementation implements LoadGenericCallback { - @Override - public Object call(final InputStream data) { - return data; - } - } + 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(); + } + } - public static final class SolverParams { - public char tileset; - public boolean reforged; - public boolean hd; - } + private static final class StreamDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + return data; + } + } - public static final class CliffInfo { - public List locations = new ArrayList<>(); - public List textures = new ArrayList<>(); - } + public static final class SolverParams { + public char tileset; + public boolean reforged; + public boolean hd; + } - private static final int MAXIMUM_ACCEPTED = 1 << 30; - private float selectionCircleScaleFactor; - private DataTable worldEditData; - private WorldEditStrings worldEditStrings; - private Warcraft3MapObjectData allObjectData; - private AbilityDataUI abilityDataUI; + public static final class CliffInfo { + public List locations = new ArrayList<>(); + public List textures = new ArrayList<>(); + } - /** - * 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; - } + private static final int MAXIMUM_ACCEPTED = 1 << 30; + private float selectionCircleScaleFactor; + private DataTable worldEditData; + private WorldEditStrings worldEditStrings; + private Warcraft3MapObjectData allObjectData; + private AbilityDataUI abilityDataUI; - public void standOnRepeat(final MdxComplexInstance instance) { - instance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - SequenceUtils.randomStandSequence(instance); - } + /** + * 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; + } - private static final class SelectionCircleSize { - private final float size; - private final String texture; - private final String textureDotted; + public void standOnRepeat(final MdxComplexInstance instance) { + instance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + SequenceUtils.randomStandSequence(instance); + } - public SelectionCircleSize(final float size, final String texture, final String textureDotted) { - this.size = size; - this.texture = texture; - this.textureDotted = textureDotted; - } - } + private static final class SelectionCircleSize { + private final float size; + private final String texture; + private final String 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 SelectionCircleSize(final float size, final String texture, final String textureDotted) { + this.size = size; + this.texture = texture; + this.textureDotted = textureDotted; + } + } - 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; - } + 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); + } - @Override - public SceneLightManager createLightManager(final boolean simple) { - if (simple) { - return new W3xScenePortraitLightManager(this, this.lightDirection); - } - else { - return new W3xSceneWorldLightManager(this); - } - } + 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; + } - public WorldEditStrings getWorldEditStrings() { - return this.worldEditStrings; - } + @Override + public SceneLightManager createLightManager(final boolean simple) { + if (simple) { + return new W3xScenePortraitLightManager(this, this.lightDirection); + } else { + return new W3xSceneWorldLightManager(this); + } + } - public void setGameUI(final GameUI gameUI) { - this.gameUI = gameUI; - this.abilityDataUI = new AbilityDataUI(this.allObjectData.getAbilities(), gameUI); - } + public WorldEditStrings getWorldEditStrings() { + return this.worldEditStrings; + } - public GameUI getGameUI() { - return this.gameUI; - } + public void setGameUI(final GameUI gameUI) { + this.gameUI = gameUI; + this.abilityDataUI = new AbilityDataUI(this.allObjectData.getAbilities(), gameUI); + } - public AbilityDataUI getAbilityDataUI() { - return this.abilityDataUI; - } + public GameUI getGameUI() { + return this.gameUI; + } - public float getWalkableRenderHeight(final float x, final float y) { - this.walkableObjectsTree.intersect(x, y, this.walkablesIntersector.reset(x, y)); - return this.walkablesIntersector.z; - } + public AbilityDataUI getAbilityDataUI() { + return this.abilityDataUI; + } - public MdxComplexInstance getHighestWalkableUnder(final float x, final float y) { - this.walkableObjectsTree.intersect(x, y, this.intersectorFindsHighestWalkable.reset(x, y)); - return this.intersectorFindsHighestWalkable.highestInstance; - } + public float getWalkableRenderHeight(final float x, final float y) { + this.walkableObjectsTree.intersect(x, y, this.walkablesIntersector.reset(x, y)); + return this.walkablesIntersector.z; + } - private static final class QuadtreeIntersectorFindsWalkableRenderHeight - implements QuadtreeIntersector { - private float z; - private final Ray ray = new Ray(); - private final Vector3 intersection = new Vector3(); + public MdxComplexInstance getHighestWalkableUnder(final float x, final float y) { + this.walkableObjectsTree.intersect(x, y, this.intersectorFindsHighestWalkable.reset(x, y)); + return this.intersectorFindsHighestWalkable.highestInstance; + } - 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 static final class QuadtreeIntersectorFindsWalkableRenderHeight + implements QuadtreeIntersector { + private float z; + private final Ray ray = new Ray(); + private final Vector3 intersection = new Vector3(); - @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 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 static final class QuadtreeIntersectorFindsHighestWalkable - implements QuadtreeIntersector { - private float z; - private final Ray ray = new Ray(); - private final Vector3 intersection = new Vector3(); - private MdxComplexInstance highestInstance; + @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 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 static final class QuadtreeIntersectorFindsHighestWalkable + implements QuadtreeIntersector { + private float z; + private final Ray ray = new Ray(); + private final Vector3 intersection = new Vector3(); + private MdxComplexInstance highestInstance; - @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 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 static final class QuadtreeIntersectorFindsHitPoint implements QuadtreeIntersector { - private Ray ray; - private final Vector3 intersection = new Vector3(); - private boolean found; + @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 QuadtreeIntersectorFindsHitPoint reset(final Ray ray) { - this.ray = ray; - this.found = false; - return this; - } + private static final class QuadtreeIntersectorFindsHitPoint implements QuadtreeIntersector { + private Ray ray; + private final Vector3 intersection = new Vector3(); + private boolean found; - @Override - public boolean onIntersect(final MdxComplexInstance intersectingObject) { - if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { - this.found = true; - return true; - } - return false; - } - } + 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; + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java index 4509e1e..7164836 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java @@ -61,7 +61,7 @@ public class TerrainShaders { " vec3 terrain_normal = normalize(vNormal);//vec3(hL - hR, hD - hU, 2.0)+vNormal);\r\n" + // "\r\n" + // " Normal = terrain_normal;\r\n" + // - Shaders.lightSystem("Normal", "myposition.xyz", "lightTexture", "lightTextureHeight", "lightCount", + Shaders.lightSystem("terrain_normal", "myposition.xyz", "lightTexture", "lightTextureHeight", "lightCount", true) + "\r\n" + // " shadeColor = clamp(lightFactor, 0.0, 1.0);\r\n" + // 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 12704ea..1afb33c 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 @@ -194,9 +194,6 @@ public class RenderUnit { } else { currentWalkableUnder = map.getHighestWalkableUnder(x, y); - if (currentWalkableUnder != null) { - System.out.println("WALKABLE UNDER"); - } War3MapViewer.gdxRayHeap.set(x, y, 4096, 0, 0, -8192); if ((currentWalkableUnder != null) && currentWalkableUnder.intersectRayWithCollision(War3MapViewer.gdxRayHeap, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java index eac054d..e4544bc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java @@ -13,75 +13,73 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityV import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor { - public static final CommandCardPopulatingAbilityVisitor INSTANCE = new CommandCardPopulatingAbilityVisitor(); + public static final CommandCardPopulatingAbilityVisitor INSTANCE = new CommandCardPopulatingAbilityVisitor(); - private CSimulation game; - private CUnit unit; + private CSimulation game; + private CUnit unit; - private CommandButtonListener commandButtonListener; - private AbilityDataUI abilityDataUI; - private boolean hasStop; + private CommandButtonListener commandButtonListener; + private AbilityDataUI abilityDataUI; + private boolean hasStop; - public CommandCardPopulatingAbilityVisitor reset(final CSimulation game, final CUnit unit, - final CommandButtonListener commandButtonListener, final AbilityDataUI abilityDataUI) { - this.game = game; - this.unit = unit; - this.commandButtonListener = commandButtonListener; - this.abilityDataUI = abilityDataUI; - this.hasStop = false; - return this; - } + public CommandCardPopulatingAbilityVisitor reset(final CSimulation game, final CUnit unit, + final CommandButtonListener commandButtonListener, final AbilityDataUI abilityDataUI) { + this.game = game; + this.unit = unit; + this.commandButtonListener = commandButtonListener; + this.abilityDataUI = abilityDataUI; + this.hasStop = false; + return this; + } - @Override - public Void accept(final CAbilityAttack ability) { - addCommandButton(ability, this.abilityDataUI.getAttackUI(), ability.getHandleId(), OrderIds.attack, 0, false); - if (!this.hasStop) { - this.hasStop = true; - addCommandButton(null, this.abilityDataUI.getStopUI(), 0, OrderIds.stop, 0, false); - } - return null; - } + @Override + public Void accept(final CAbilityAttack ability) { + addCommandButton(ability, this.abilityDataUI.getAttackUI(), ability.getHandleId(), OrderIds.attack, 0, false); + if (!this.hasStop) { + this.hasStop = true; + addCommandButton(null, this.abilityDataUI.getStopUI(), 0, OrderIds.stop, 0, false); + } + return null; + } - @Override - public Void accept(final CAbilityMove ability) { - addCommandButton(ability, this.abilityDataUI.getMoveUI(), ability.getHandleId(), OrderIds.move, 0, false); - addCommandButton(ability, this.abilityDataUI.getHoldPosUI(), ability.getHandleId(), OrderIds.holdposition, 0, - false); - addCommandButton(ability, this.abilityDataUI.getPatrolUI(), ability.getHandleId(), OrderIds.patrol, 0, false); - if (!this.hasStop) { - this.hasStop = true; - addCommandButton(null, this.abilityDataUI.getStopUI(), 0, OrderIds.stop, 0, false); - } - return null; - } + @Override + public Void accept(final CAbilityMove ability) { + addCommandButton(ability, this.abilityDataUI.getMoveUI(), ability.getHandleId(), OrderIds.move, 0, false); + addCommandButton(ability, this.abilityDataUI.getHoldPosUI(), ability.getHandleId(), OrderIds.holdposition, 0, + false); + addCommandButton(ability, this.abilityDataUI.getPatrolUI(), ability.getHandleId(), OrderIds.patrol, 0, false); + if (!this.hasStop) { + this.hasStop = true; + addCommandButton(null, this.abilityDataUI.getStopUI(), 0, OrderIds.stop, 0, false); + } + return null; + } - @Override - public Void accept(final CAbilityGeneric ability) { - addCommandButton(ability, this.abilityDataUI.getUI(ability.getRawcode()).getOnIconUI(), ability.getHandleId(), - OrderIds.channel, 0, false); - return null; - } + @Override + public Void accept(final CAbilityGeneric ability) { + addCommandButton(ability, this.abilityDataUI.getUI(ability.getRawcode()).getOnIconUI(), ability.getHandleId(), + OrderIds.channel, 0, false); + return null; + } - @Override - public Void accept(final CAbilityColdArrows ability) { - final boolean autoCastActive = ability.isAutoCastActive(); - int autoCastId; - if (autoCastActive) { - autoCastId = OrderIds.coldarrows; - } - else { - autoCastId = OrderIds.uncoldarrows; - } - addCommandButton(ability, this.abilityDataUI.getUI(ability.getRawcode()).getOnIconUI(), ability.getHandleId(), - OrderIds.coldarrowstarg, autoCastId, autoCastActive); - return null; - } + @Override + public Void accept(final CAbilityColdArrows ability) { + final boolean autoCastActive = ability.isAutoCastActive(); + int autoCastId; + if (autoCastActive) { + autoCastId = OrderIds.coldarrows; + } else { + autoCastId = OrderIds.uncoldarrows; + } + addCommandButton(ability, this.abilityDataUI.getUI(ability.getRawcode()).getOnIconUI(), ability.getHandleId(), + OrderIds.coldarrowstarg, autoCastId, autoCastActive); + return null; + } - private void addCommandButton(final CAbility ability, final IconUI iconUI, final int handleId, final int orderId, - final int autoCastOrderId, final boolean autoCastActive) { - final boolean active = ((handleId == this.unit.getCurrentAbilityHandleId()) - && (orderId == this.unit.getCurrentOrderId())); - this.commandButtonListener.commandButton(iconUI.getButtonPositionX(), iconUI.getButtonPositionY(), - iconUI.getIcon(), handleId, orderId, autoCastOrderId, active, autoCastActive); - } + private void addCommandButton(final CAbility ability, final IconUI iconUI, final int handleId, final int orderId, + final int autoCastOrderId, final boolean autoCastActive) { + final boolean active = (this.unit.getCurrentBehavior() != null && orderId == this.unit.getCurrentBehavior().getHighlightOrderId()); + this.commandButtonListener.commandButton(iconUI.getButtonPositionX(), iconUI.getButtonPositionY(), + iconUI.getIcon(), handleId, orderId, autoCastOrderId, active, autoCastActive); + } } 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 75de310..3392af1 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 @@ -6,6 +6,7 @@ import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import java.util.Queue; +import java.util.concurrent.CyclicBarrier; import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.util.War3ID; @@ -200,7 +201,11 @@ public class CUnit extends CWidget { } } else if (this.currentBehavior != null) { + CBehavior lastBehavior = this.currentBehavior; this.currentBehavior = this.currentBehavior.update(game); + if(this.currentBehavior.getHighlightOrderId() != lastBehavior.getHighlightOrderId()) { + this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); + } } else { // check to auto acquire targets @@ -243,15 +248,15 @@ public class CUnit extends CWidget { else { this.currentBehavior = beginOrder(game, order); this.orderQueue.clear(); + final boolean omitNotify = (this.currentOrder == null) && (order == null); + if (!omitNotify) { + this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); + } } } private CBehavior beginOrder(final CSimulation game, final COrder order) { - final boolean omitNotify = (this.currentOrder == null) && (order == null); this.currentOrder = order; - if (!omitNotify) { - this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); - } CBehavior nextBehavior; if (order != null) { nextBehavior = order.begin(game, this); @@ -426,7 +431,7 @@ public class CUnit extends CWidget { CAllianceType.PASSIVE)) { for (final CUnitAttack attack : this.unitType.getAttacks()) { if (source.canBeTargetedBy(simulation, this, attack.getTargetsAllowed())) { - this.currentBehavior = getAttackBehavior().reset(attack, source); + this.currentBehavior = getAttackBehavior().reset(OrderIds.attack, attack, source); break; } } @@ -590,7 +595,7 @@ public class CUnit extends CWidget { if (this.source.canReach(unit, this.source.acquisitionRange) && unit.canBeTargetedBy(this.game, this.source, attack.getTargetsAllowed()) && (this.source.distance(unit) >= this.source.getUnitType().getMinimumAttackRange())) { - this.source.currentBehavior = this.source.getAttackBehavior().reset(attack, unit); + this.source.currentBehavior = this.source.getAttackBehavior().reset(OrderIds.attack, attack, unit); return true; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java index f7e57d8..a58c30f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java @@ -109,12 +109,12 @@ public class CAbilityAttack implements CAbility { CBehavior behavior = null; for (final CUnitAttack attack : caster.getUnitType().getAttacks()) { if (target.canBeTargetedBy(game, caster, attack.getTargetsAllowed())) { - behavior = caster.getAttackBehavior().reset(attack, target); + behavior = caster.getAttackBehavior().reset(OrderIds.attack, attack, target); break; } } if (behavior == null) { - behavior = caster.getMoveBehavior().reset(target.getX(), target.getY()); + behavior = caster.getMoveBehavior().reset(OrderIds.attack, target.getX(), target.getY()); } return behavior; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityColdArrows.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityColdArrows.java index 5efb110..61152bf 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityColdArrows.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityColdArrows.java @@ -94,7 +94,7 @@ public class CAbilityColdArrows implements CAbility { CBehavior behavior = null; for (final CUnitAttack attack : caster.getUnitType().getAttacks()) { if (target.canBeTargetedBy(game, caster, attack.getTargetsAllowed())) { - behavior = caster.getAttackBehavior().reset(attack, target); + behavior = caster.getAttackBehavior().reset(OrderIds.coldarrowstarg, attack, target); break; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java index 8c34b8e..fb1fc05 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java @@ -87,7 +87,7 @@ public class CAbilityMove implements CAbility { @Override public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { - return caster.getFollowBehavior().reset((CUnit) target); + return caster.getFollowBehavior().reset(OrderIds.move, (CUnit) target); } @Override @@ -96,7 +96,7 @@ public class CAbilityMove implements CAbility { return caster.getPatrolBehavior().reset(point); } else { - return caster.getMoveBehavior().reset(point.x, point.y); + return caster.getMoveBehavior().reset(OrderIds.move, point.x, point.y); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehavior.java index f848b67..91f12ed 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehavior.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehavior.java @@ -10,4 +10,6 @@ public interface CBehavior { * @return */ CBehavior update(CSimulation game); + + int getHighlightOrderId(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java index 6004fd5..8837938 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java @@ -10,6 +10,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUni public class CBehaviorAttack extends CAbstractRangedBehavior { + private int highlightOrderId; + public CBehaviorAttack(final CUnit unit) { super(unit); } @@ -19,7 +21,8 @@ public class CBehaviorAttack extends CAbstractRangedBehavior { private int backSwingTime; private int thisOrderCooldownEndTime; - public CBehaviorAttack reset(final CUnitAttack unitAttack, final CWidget target) { + public CBehaviorAttack reset(int highlightOrderId, final CUnitAttack unitAttack, final CWidget target) { + this.highlightOrderId = highlightOrderId; super.innerReset(target); this.unitAttack = unitAttack; this.damagePointLaunchTime = 0; @@ -28,6 +31,11 @@ public class CBehaviorAttack extends CAbstractRangedBehavior { return this; } + @Override + public int getHighlightOrderId() { + return highlightOrderId; + } + @Override public boolean isWithinRange(final CSimulation simulation) { float range = this.unitAttack.getRange(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java index ee80015..785195e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java @@ -7,14 +7,22 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; public class CBehaviorFollow extends CAbstractRangedBehavior { + private int higlightOrderId; + public CBehaviorFollow(final CUnit unit) { super(unit); } - public CBehavior reset(final CUnit target) { + public CBehavior reset(int higlightOrderId, final CUnit target) { + this.higlightOrderId = higlightOrderId; return innerReset(target); } + @Override + public int getHighlightOrderId() { + return higlightOrderId; + } + @Override public boolean isWithinRange(final CSimulation simulation) { return this.unit.canReach(this.target, this.unit.getAcquisitionRange()); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java index 5171364..b9e322f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java @@ -18,6 +18,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindin public class CBehaviorMove implements CBehavior { private static final Rectangle tempRect = new Rectangle(); private final CUnit unit; + private int highlightOrderId; public CBehaviorMove(final CUnit unit) { this.unit = unit; @@ -31,25 +32,34 @@ public class CBehaviorMove implements CBehavior { private CUnit followUnit; private CRangedBehavior rangedBehavior; - public CBehaviorMove reset(final float targetX, final float targetY) { - return reset(targetX, targetY, null); + public CBehaviorMove reset(int highlightOrderId, final float targetX, final float targetY) { + internalResetMove(highlightOrderId, targetX, targetY); + this.rangedBehavior = null; + return this; } - public CBehaviorMove reset(final float targetX, final float targetY, final CRangedBehavior rangedBehavior) { + private void internalResetMove(int highlightOrderId, final float targetX, final float targetY) { + this.highlightOrderId = highlightOrderId; this.wasWithinPropWindow = false; this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( this.unit.getUnitType().getCollisionSize()) ? CPathfindingProcessor.GridMapping.CORNERS - : CPathfindingProcessor.GridMapping.CELLS; + : CPathfindingProcessor.GridMapping.CELLS; this.target = new Point2D.Float(targetX, targetY); this.path = null; this.searchCycles = 0; this.followUnit = null; + } + + public CBehaviorMove reset(final float targetX, final float targetY, final CRangedBehavior rangedBehavior) { + internalResetMove(rangedBehavior.getHighlightOrderId(), targetX, targetY); this.rangedBehavior = rangedBehavior; return this; } - public CBehaviorMove reset(final CUnit followUnit) { - return reset(followUnit, null); + public CBehaviorMove reset(int highlightOrderId, final CUnit followUnit) { + internalResetMove(highlightOrderId, followUnit); + this.rangedBehavior = null; + return this; } public CBehaviorMove reset(final CUnit followUnit, final CRangedBehavior rangedBehavior) { @@ -65,6 +75,23 @@ public class CBehaviorMove implements CBehavior { return this; } + private void internalResetMove(int highlightOrderId, CUnit followUnit) { + this.highlightOrderId = highlightOrderId; + this.wasWithinPropWindow = false; + this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( + this.unit.getUnitType().getCollisionSize()) ? CPathfindingProcessor.GridMapping.CORNERS + : CPathfindingProcessor.GridMapping.CELLS; + this.target = new Float(followUnit.getX(), followUnit.getY()); + this.path = null; + this.searchCycles = 0; + this.followUnit = followUnit; + } + + @Override + public int getHighlightOrderId() { + return highlightOrderId; + } + @Override public CBehavior update(final CSimulation simulation) { if ((this.rangedBehavior != null) && this.rangedBehavior.isWithinRange(simulation)) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java index 947fbf3..f2d6c09 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java @@ -3,6 +3,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; public class CBehaviorPatrol implements CRangedBehavior { @@ -20,6 +21,11 @@ public class CBehaviorPatrol implements CRangedBehavior { return this; } + @Override + public int getHighlightOrderId() { + return OrderIds.patrol; + } + @Override public boolean isWithinRange(final CSimulation simulation) { return this.unit.distance(this.target.x, this.target.y) <= simulation.getGameplayConstants() diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorStop.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorStop.java index f884891..7845bfb 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorStop.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorStop.java @@ -4,6 +4,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; public class CBehaviorStop implements CBehavior { @@ -13,8 +14,14 @@ public class CBehaviorStop implements CBehavior { this.unit = unit; } + @Override + public int getHighlightOrderId() { + return OrderIds.stop; + } + @Override public CBehavior update(final CSimulation game) { + this.unit.autoAcquireAttackTargets(game); this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f, true); return this.unit.pollNextOrderBehavior(game); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index 37f184c..2baa1ab 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -32,410 +32,410 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUni import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; public class CUnitData { - private static final War3ID MANA_INITIAL_AMOUNT = War3ID.fromString("umpi"); - private static final War3ID MANA_MAXIMUM = War3ID.fromString("umpm"); - private static final War3ID HIT_POINT_MAXIMUM = War3ID.fromString("uhpm"); - private static final War3ID MOVEMENT_SPEED_BASE = War3ID.fromString("umvs"); - private static final War3ID PROPULSION_WINDOW = War3ID.fromString("uprw"); - private static final War3ID TURN_RATE = War3ID.fromString("umvr"); - private static final War3ID IS_BLDG = War3ID.fromString("ubdg"); - private static final War3ID NAME = War3ID.fromString("unam"); - private static final War3ID PROJECTILE_LAUNCH_X = War3ID.fromString("ulpx"); - private static final War3ID PROJECTILE_LAUNCH_Y = War3ID.fromString("ulpy"); - private static final War3ID PROJECTILE_LAUNCH_Z = War3ID.fromString("ulpz"); - private static final War3ID ATTACKS_ENABLED = War3ID.fromString("uaen"); - private static final War3ID ATTACK1_BACKSWING_POINT = War3ID.fromString("ubs1"); - private static final War3ID ATTACK1_DAMAGE_POINT = War3ID.fromString("udp1"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua1f"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua1h"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua1q"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua1p"); - private static final War3ID ATTACK1_ATTACK_TYPE = War3ID.fromString("ua1t"); - private static final War3ID ATTACK1_COOLDOWN = War3ID.fromString("ua1c"); - private static final War3ID ATTACK1_DMG_BASE = War3ID.fromString("ua1b"); - private static final War3ID ATTACK1_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd1"); - private static final War3ID ATTACK1_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd1"); - private static final War3ID ATTACK1_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl1"); - private static final War3ID ATTACK1_DMG_DICE = War3ID.fromString("ua1d"); - private static final War3ID ATTACK1_DMG_SIDES_PER_DIE = War3ID.fromString("ua1s"); - private static final War3ID ATTACK1_DMG_SPILL_DIST = War3ID.fromString("usd1"); - private static final War3ID ATTACK1_DMG_SPILL_RADIUS = War3ID.fromString("usr1"); - private static final War3ID ATTACK1_DMG_UPGRADE_AMT = War3ID.fromString("udu1"); - private static final War3ID ATTACK1_TARGET_COUNT = War3ID.fromString("utc1"); - private static final War3ID ATTACK1_PROJECTILE_ARC = War3ID.fromString("uma1"); - private static final War3ID ATTACK1_MISSILE_ART = War3ID.fromString("ua1m"); - private static final War3ID ATTACK1_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh1"); - private static final War3ID ATTACK1_PROJECTILE_SPEED = War3ID.fromString("ua1z"); - private static final War3ID ATTACK1_RANGE = War3ID.fromString("ua1r"); - private static final War3ID ATTACK1_RANGE_MOTION_BUFFER = War3ID.fromString("urb1"); - private static final War3ID ATTACK1_SHOW_UI = War3ID.fromString("uwu1"); - private static final War3ID ATTACK1_TARGETS_ALLOWED = War3ID.fromString("ua1g"); - private static final War3ID ATTACK1_WEAPON_SOUND = War3ID.fromString("ucs1"); - private static final War3ID ATTACK1_WEAPON_TYPE = War3ID.fromString("ua1w"); + private static final War3ID MANA_INITIAL_AMOUNT = War3ID.fromString("umpi"); + private static final War3ID MANA_MAXIMUM = War3ID.fromString("umpm"); + private static final War3ID HIT_POINT_MAXIMUM = War3ID.fromString("uhpm"); + private static final War3ID MOVEMENT_SPEED_BASE = War3ID.fromString("umvs"); + private static final War3ID PROPULSION_WINDOW = War3ID.fromString("uprw"); + private static final War3ID TURN_RATE = War3ID.fromString("umvr"); + private static final War3ID IS_BLDG = War3ID.fromString("ubdg"); + private static final War3ID NAME = War3ID.fromString("unam"); + private static final War3ID PROJECTILE_LAUNCH_X = War3ID.fromString("ulpx"); + private static final War3ID PROJECTILE_LAUNCH_Y = War3ID.fromString("ulpy"); + private static final War3ID PROJECTILE_LAUNCH_Z = War3ID.fromString("ulpz"); + private static final War3ID ATTACKS_ENABLED = War3ID.fromString("uaen"); + private static final War3ID ATTACK1_BACKSWING_POINT = War3ID.fromString("ubs1"); + private static final War3ID ATTACK1_DAMAGE_POINT = War3ID.fromString("udp1"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua1f"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua1h"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua1q"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua1p"); + private static final War3ID ATTACK1_ATTACK_TYPE = War3ID.fromString("ua1t"); + private static final War3ID ATTACK1_COOLDOWN = War3ID.fromString("ua1c"); + private static final War3ID ATTACK1_DMG_BASE = War3ID.fromString("ua1b"); + private static final War3ID ATTACK1_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd1"); + private static final War3ID ATTACK1_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd1"); + private static final War3ID ATTACK1_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl1"); + private static final War3ID ATTACK1_DMG_DICE = War3ID.fromString("ua1d"); + private static final War3ID ATTACK1_DMG_SIDES_PER_DIE = War3ID.fromString("ua1s"); + private static final War3ID ATTACK1_DMG_SPILL_DIST = War3ID.fromString("usd1"); + private static final War3ID ATTACK1_DMG_SPILL_RADIUS = War3ID.fromString("usr1"); + private static final War3ID ATTACK1_DMG_UPGRADE_AMT = War3ID.fromString("udu1"); + private static final War3ID ATTACK1_TARGET_COUNT = War3ID.fromString("utc1"); + private static final War3ID ATTACK1_PROJECTILE_ARC = War3ID.fromString("uma1"); + private static final War3ID ATTACK1_MISSILE_ART = War3ID.fromString("ua1m"); + private static final War3ID ATTACK1_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh1"); + private static final War3ID ATTACK1_PROJECTILE_SPEED = War3ID.fromString("ua1z"); + private static final War3ID ATTACK1_RANGE = War3ID.fromString("ua1r"); + private static final War3ID ATTACK1_RANGE_MOTION_BUFFER = War3ID.fromString("urb1"); + private static final War3ID ATTACK1_SHOW_UI = War3ID.fromString("uwu1"); + private static final War3ID ATTACK1_TARGETS_ALLOWED = War3ID.fromString("ua1g"); + private static final War3ID ATTACK1_WEAPON_SOUND = War3ID.fromString("ucs1"); + private static final War3ID ATTACK1_WEAPON_TYPE = War3ID.fromString("ua1w"); - private static final War3ID ATTACK2_BACKSWING_POINT = War3ID.fromString("ubs2"); - private static final War3ID ATTACK2_DAMAGE_POINT = War3ID.fromString("udp2"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua2f"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua2h"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua2q"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua2p"); - private static final War3ID ATTACK2_ATTACK_TYPE = War3ID.fromString("ua2t"); - private static final War3ID ATTACK2_COOLDOWN = War3ID.fromString("ua2c"); - private static final War3ID ATTACK2_DMG_BASE = War3ID.fromString("ua2b"); - private static final War3ID ATTACK2_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd2"); - private static final War3ID ATTACK2_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd2"); - private static final War3ID ATTACK2_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl2"); - private static final War3ID ATTACK2_DMG_DICE = War3ID.fromString("ua2d"); - private static final War3ID ATTACK2_DMG_SIDES_PER_DIE = War3ID.fromString("ua2s"); - private static final War3ID ATTACK2_DMG_SPILL_DIST = War3ID.fromString("usd2"); - private static final War3ID ATTACK2_DMG_SPILL_RADIUS = War3ID.fromString("usr2"); - private static final War3ID ATTACK2_DMG_UPGRADE_AMT = War3ID.fromString("udu2"); - private static final War3ID ATTACK2_TARGET_COUNT = War3ID.fromString("utc2"); - private static final War3ID ATTACK2_PROJECTILE_ARC = War3ID.fromString("uma2"); - private static final War3ID ATTACK2_MISSILE_ART = War3ID.fromString("ua2m"); - private static final War3ID ATTACK2_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh2"); - private static final War3ID ATTACK2_PROJECTILE_SPEED = War3ID.fromString("ua2z"); - private static final War3ID ATTACK2_RANGE = War3ID.fromString("ua2r"); - private static final War3ID ATTACK2_RANGE_MOTION_BUFFER = War3ID.fromString("urb2"); - private static final War3ID ATTACK2_SHOW_UI = War3ID.fromString("uwu2"); - private static final War3ID ATTACK2_TARGETS_ALLOWED = War3ID.fromString("ua2g"); - private static final War3ID ATTACK2_WEAPON_SOUND = War3ID.fromString("ucs2"); - private static final War3ID ATTACK2_WEAPON_TYPE = War3ID.fromString("ua2w"); + private static final War3ID ATTACK2_BACKSWING_POINT = War3ID.fromString("ubs2"); + private static final War3ID ATTACK2_DAMAGE_POINT = War3ID.fromString("udp2"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua2f"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua2h"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua2q"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua2p"); + private static final War3ID ATTACK2_ATTACK_TYPE = War3ID.fromString("ua2t"); + private static final War3ID ATTACK2_COOLDOWN = War3ID.fromString("ua2c"); + private static final War3ID ATTACK2_DMG_BASE = War3ID.fromString("ua2b"); + private static final War3ID ATTACK2_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd2"); + private static final War3ID ATTACK2_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd2"); + private static final War3ID ATTACK2_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl2"); + private static final War3ID ATTACK2_DMG_DICE = War3ID.fromString("ua2d"); + private static final War3ID ATTACK2_DMG_SIDES_PER_DIE = War3ID.fromString("ua2s"); + private static final War3ID ATTACK2_DMG_SPILL_DIST = War3ID.fromString("usd2"); + private static final War3ID ATTACK2_DMG_SPILL_RADIUS = War3ID.fromString("usr2"); + private static final War3ID ATTACK2_DMG_UPGRADE_AMT = War3ID.fromString("udu2"); + private static final War3ID ATTACK2_TARGET_COUNT = War3ID.fromString("utc2"); + private static final War3ID ATTACK2_PROJECTILE_ARC = War3ID.fromString("uma2"); + private static final War3ID ATTACK2_MISSILE_ART = War3ID.fromString("ua2m"); + private static final War3ID ATTACK2_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh2"); + private static final War3ID ATTACK2_PROJECTILE_SPEED = War3ID.fromString("ua2z"); + private static final War3ID ATTACK2_RANGE = War3ID.fromString("ua2r"); + private static final War3ID ATTACK2_RANGE_MOTION_BUFFER = War3ID.fromString("urb2"); + private static final War3ID ATTACK2_SHOW_UI = War3ID.fromString("uwu2"); + private static final War3ID ATTACK2_TARGETS_ALLOWED = War3ID.fromString("ua2g"); + private static final War3ID ATTACK2_WEAPON_SOUND = War3ID.fromString("ucs2"); + private static final War3ID ATTACK2_WEAPON_TYPE = War3ID.fromString("ua2w"); - private static final War3ID ACQUISITION_RANGE = War3ID.fromString("uacq"); - private static final War3ID MINIMUM_ATTACK_RANGE = War3ID.fromString("uamn"); + private static final War3ID ACQUISITION_RANGE = War3ID.fromString("uacq"); + private static final War3ID MINIMUM_ATTACK_RANGE = War3ID.fromString("uamn"); - private static final War3ID PROJECTILE_IMPACT_Z = War3ID.fromString("uimz"); + private static final War3ID PROJECTILE_IMPACT_Z = War3ID.fromString("uimz"); - private static final War3ID DEATH_TYPE = War3ID.fromString("udea"); - private static final War3ID ARMOR_TYPE = War3ID.fromString("uarm"); + private static final War3ID DEATH_TYPE = War3ID.fromString("udea"); + private static final War3ID ARMOR_TYPE = War3ID.fromString("uarm"); - private static final War3ID DEFENSE = War3ID.fromString("udef"); - private static final War3ID DEFENSE_TYPE = War3ID.fromString("udty"); - private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); - private static final War3ID MOVE_TYPE = War3ID.fromString("umvt"); - private static final War3ID COLLISION_SIZE = War3ID.fromString("ucol"); - private static final War3ID CLASSIFICATION = War3ID.fromString("utyp"); - private static final War3ID DEATH_TIME = War3ID.fromString("udtm"); - private static final War3ID TARGETED_AS = War3ID.fromString("utar"); + private static final War3ID DEFENSE = War3ID.fromString("udef"); + private static final War3ID DEFENSE_TYPE = War3ID.fromString("udty"); + private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); + private static final War3ID MOVE_TYPE = War3ID.fromString("umvt"); + private static final War3ID COLLISION_SIZE = War3ID.fromString("ucol"); + private static final War3ID CLASSIFICATION = War3ID.fromString("utyp"); + private static final War3ID DEATH_TIME = War3ID.fromString("udtm"); + private static final War3ID TARGETED_AS = War3ID.fromString("utar"); - private static final War3ID ABILITIES_NORMAL = War3ID.fromString("uabi"); - private final MutableObjectData unitData; - private final Map unitIdToUnitType = new HashMap<>(); - private final CAbilityData abilityData; + private static final War3ID ABILITIES_NORMAL = War3ID.fromString("uabi"); + private final MutableObjectData unitData; + private final Map unitIdToUnitType = new HashMap<>(); + private final CAbilityData abilityData; - public CUnitData(final MutableObjectData unitData, final CAbilityData abilityData) { - this.unitData = unitData; - this.abilityData = abilityData; - } + public CUnitData(final MutableObjectData unitData, final CAbilityData abilityData) { + this.unitData = unitData; + this.abilityData = abilityData; + } - public CUnit create(final CSimulation simulation, final int playerIndex, final War3ID typeId, final float x, - final float y, final float facing, final BufferedImage buildingPathingPixelMap, - final SimulationRenderController simulationRenderController, final HandleIdAllocator handleIdAllocator) { - final MutableGameObject unitType = this.unitData.get(typeId); - final int handleId = handleIdAllocator.createId(); - final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); - final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); - final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); - final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); - final int defense = unitType.getFieldAsInteger(DEFENSE, 0); - final String abilityList = unitType.getFieldAsString(ABILITIES_NORMAL, 0); + public CUnit create(final CSimulation simulation, final int playerIndex, final War3ID typeId, final float x, + final float y, final float facing, final BufferedImage buildingPathingPixelMap, + final SimulationRenderController simulationRenderController, final HandleIdAllocator handleIdAllocator) { + final MutableGameObject unitType = this.unitData.get(typeId); + final int handleId = handleIdAllocator.createId(); + final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); + final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); + final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); + final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); + final int defense = unitType.getFieldAsInteger(DEFENSE, 0); + final String abilityList = unitType.getFieldAsString(ABILITIES_NORMAL, 0); - final CUnitType unitTypeInstance = getUnitTypeInstance(typeId, buildingPathingPixelMap, unitType); + final CUnitType unitTypeInstance = getUnitTypeInstance(typeId, buildingPathingPixelMap, unitType); - final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, - speed, defense, unitTypeInstance); - if (speed > 0) { - unit.add(simulation, new CAbilityMove(handleIdAllocator.createId())); - } - if (!unitTypeInstance.getAttacks().isEmpty()) { - unit.add(simulation, new CAbilityAttack(handleIdAllocator.createId())); - } - for (final String ability : abilityList.split(",")) { - unit.add(simulation, this.abilityData.createAbility(ability, handleIdAllocator.createId())); - } - return unit; - } + final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, + speed, defense, unitTypeInstance); + if (speed > 0) { + unit.add(simulation, new CAbilityMove(handleIdAllocator.createId())); + } + if (!unitTypeInstance.getAttacks().isEmpty()) { + unit.add(simulation, new CAbilityAttack(handleIdAllocator.createId())); + } + for (final String ability : abilityList.split(",")) { + if (ability.length() > 0 && !"_".equals(ability)) { + unit.add(simulation, this.abilityData.createAbility(ability, handleIdAllocator.createId())); + } + } + return unit; + } - private CUnitType getUnitTypeInstance(final War3ID typeId, final BufferedImage buildingPathingPixelMap, - final MutableGameObject unitType) { - CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId); - if (unitTypeInstance == null) { - final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); - final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); - final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); - final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); - final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); - final String unitName = unitType.getFieldAsString(NAME, 0); - final float acquisitionRange = unitType.getFieldAsFloat(ACQUISITION_RANGE, 0); - final float minimumAttackRange = unitType.getFieldAsFloat(MINIMUM_ATTACK_RANGE, 0); - final EnumSet targetedAs = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0)); - final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0); - final EnumSet classifications = EnumSet.noneOf(CUnitClassification.class); - if (classificationString != null) { - final String[] classificationValues = classificationString.split(","); - for (final String unitEditorKey : classificationValues) { - final CUnitClassification unitClassification = CUnitClassification - .parseUnitClassification(unitEditorKey); - if (unitClassification != null) { - classifications.add(unitClassification); - } - } - } - final List attacks = new ArrayList<>(); - final int attacksEnabled = unitType.getFieldAsInteger(ATTACKS_ENABLED, 0); - if ((attacksEnabled & 0x1) != 0) { - try { - // attack one - final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK1_BACKSWING_POINT, 0); - final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK1_DAMAGE_POINT, 0); - final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_FULL_DMG, 0); - final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_HALF_DMG, 0); - final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_QUARTER_DMG, - 0); - final EnumSet areaOfEffectTargets = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_AREA_OF_EFFECT_TARGETS, 0)); - final CAttackType attackType = CAttackType - .parseAttackType(unitType.getFieldAsString(ATTACK1_ATTACK_TYPE, 0)); - final float cooldownTime = unitType.getFieldAsFloat(ATTACK1_COOLDOWN, 0); - final int damageBase = unitType.getFieldAsInteger(ATTACK1_DMG_BASE, 0); - final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_HALF, 0); - final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_QUARTER, 0); - final float damageLossFactor = unitType.getFieldAsFloat(ATTACK1_DAMAGE_LOSS_FACTOR, 0); - final int damageDice = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); - final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0); - final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_DIST, 0); - final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_RADIUS, 0); - final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0); - final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0); - final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); - final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); - final boolean projectileHomingEnabled = unitType - .getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED, 0); - final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); - final int range = unitType.getFieldAsInteger(ATTACK1_RANGE, 0); - final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK1_RANGE_MOTION_BUFFER, 0); - final boolean showUI = unitType.getFieldAsBoolean(ATTACK1_SHOW_UI, 0); - final EnumSet targetsAllowed = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_TARGETS_ALLOWED, 0)); - final String weaponSound = unitType.getFieldAsString(ATTACK1_WEAPON_SOUND, 0); - final CWeaponType weaponType = CWeaponType - .parseWeaponType(unitType.getFieldAsString(ATTACK1_WEAPON_TYPE, 0)); - attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, - areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, - cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, - damageDice, damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, - maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, - projectileSpeed, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, - weaponType)); - } - catch (final Exception exc) { - System.err.println("Attack 1 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); - } - } - if ((attacksEnabled & 0x2) != 0) { - try { - // attack two - final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK2_BACKSWING_POINT, 0); - final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK2_DAMAGE_POINT, 0); - final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_FULL_DMG, 0); - final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_HALF_DMG, 0); - final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_QUARTER_DMG, - 0); - final EnumSet areaOfEffectTargets = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_AREA_OF_EFFECT_TARGETS, 0)); - final CAttackType attackType = CAttackType - .parseAttackType(unitType.getFieldAsString(ATTACK2_ATTACK_TYPE, 0)); - final float cooldownTime = unitType.getFieldAsFloat(ATTACK2_COOLDOWN, 0); - final int damageBase = unitType.getFieldAsInteger(ATTACK2_DMG_BASE, 0); - final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_HALF, 0); - final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_QUARTER, 0); - final float damageLossFactor = unitType.getFieldAsFloat(ATTACK2_DAMAGE_LOSS_FACTOR, 0); - final int damageDice = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); - final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0); - final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_DIST, 0); - final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_RADIUS, 0); - final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0); - final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0); - final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); - final String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); - final boolean projectileHomingEnabled = unitType - .getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED, 0); - final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); - final int range = unitType.getFieldAsInteger(ATTACK2_RANGE, 0); - final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK2_RANGE_MOTION_BUFFER, 0); - final boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0); - final EnumSet targetsAllowed = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_TARGETS_ALLOWED, 0)); - final String weaponSound = unitType.getFieldAsString(ATTACK2_WEAPON_SOUND, 0); - final CWeaponType weaponType = CWeaponType - .parseWeaponType(unitType.getFieldAsString(ATTACK2_WEAPON_TYPE, 0)); - attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, - areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, - cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, - damageDice, damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, - maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, - projectileSpeed, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, - weaponType)); - } - catch (final Exception exc) { - System.err.println("Attack 2 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); - } - } - final int deathType = unitType.getFieldAsInteger(DEATH_TYPE, 0); - final boolean raise = (deathType & 0x1) != 0; - final boolean decay = (deathType & 0x2) != 0; - final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0); - final float impactZ = unitType.getFieldAsFloat(PROJECTILE_IMPACT_Z, 0); - final CDefenseType defenseType = CDefenseType.parseDefenseType(unitType.getFieldAsString(DEFENSE_TYPE, 0)); - final float deathTime = unitType.getFieldAsFloat(DEATH_TIME, 0); - unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications, - attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, - targetedAs, acquisitionRange, minimumAttackRange); - this.unitIdToUnitType.put(typeId, unitTypeInstance); - } - return unitTypeInstance; - } + private CUnitType getUnitTypeInstance(final War3ID typeId, final BufferedImage buildingPathingPixelMap, + final MutableGameObject unitType) { + CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId); + if (unitTypeInstance == null) { + final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); + final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); + final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); + final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); + final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); + final String unitName = unitType.getFieldAsString(NAME, 0); + final float acquisitionRange = unitType.getFieldAsFloat(ACQUISITION_RANGE, 0); + final float minimumAttackRange = unitType.getFieldAsFloat(MINIMUM_ATTACK_RANGE, 0); + final EnumSet targetedAs = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0)); + final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0); + final EnumSet classifications = EnumSet.noneOf(CUnitClassification.class); + if (classificationString != null) { + final String[] classificationValues = classificationString.split(","); + for (final String unitEditorKey : classificationValues) { + final CUnitClassification unitClassification = CUnitClassification + .parseUnitClassification(unitEditorKey); + if (unitClassification != null) { + classifications.add(unitClassification); + } + } + } + final List attacks = new ArrayList<>(); + final int attacksEnabled = unitType.getFieldAsInteger(ATTACKS_ENABLED, 0); + if ((attacksEnabled & 0x1) != 0) { + try { + // attack one + final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK1_BACKSWING_POINT, 0); + final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK1_DAMAGE_POINT, 0); + final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_FULL_DMG, 0); + final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_HALF_DMG, 0); + final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_QUARTER_DMG, + 0); + final EnumSet areaOfEffectTargets = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_AREA_OF_EFFECT_TARGETS, 0)); + final CAttackType attackType = CAttackType + .parseAttackType(unitType.getFieldAsString(ATTACK1_ATTACK_TYPE, 0)); + final float cooldownTime = unitType.getFieldAsFloat(ATTACK1_COOLDOWN, 0); + final int damageBase = unitType.getFieldAsInteger(ATTACK1_DMG_BASE, 0); + final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_HALF, 0); + final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_QUARTER, 0); + final float damageLossFactor = unitType.getFieldAsFloat(ATTACK1_DAMAGE_LOSS_FACTOR, 0); + final int damageDice = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); + final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0); + final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_DIST, 0); + final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_RADIUS, 0); + final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0); + final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0); + final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); + final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); + final boolean projectileHomingEnabled = unitType + .getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED, 0); + final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); + final int range = unitType.getFieldAsInteger(ATTACK1_RANGE, 0); + final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK1_RANGE_MOTION_BUFFER, 0); + final boolean showUI = unitType.getFieldAsBoolean(ATTACK1_SHOW_UI, 0); + final EnumSet targetsAllowed = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_TARGETS_ALLOWED, 0)); + final String weaponSound = unitType.getFieldAsString(ATTACK1_WEAPON_SOUND, 0); + final CWeaponType weaponType = CWeaponType + .parseWeaponType(unitType.getFieldAsString(ATTACK1_WEAPON_TYPE, 0)); + attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, + areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, + cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, + damageDice, damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, + maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, + weaponType)); + } catch (final Exception exc) { + System.err.println("Attack 1 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); + } + } + if ((attacksEnabled & 0x2) != 0) { + try { + // attack two + final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK2_BACKSWING_POINT, 0); + final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK2_DAMAGE_POINT, 0); + final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_FULL_DMG, 0); + final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_HALF_DMG, 0); + final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_QUARTER_DMG, + 0); + final EnumSet areaOfEffectTargets = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_AREA_OF_EFFECT_TARGETS, 0)); + final CAttackType attackType = CAttackType + .parseAttackType(unitType.getFieldAsString(ATTACK2_ATTACK_TYPE, 0)); + final float cooldownTime = unitType.getFieldAsFloat(ATTACK2_COOLDOWN, 0); + final int damageBase = unitType.getFieldAsInteger(ATTACK2_DMG_BASE, 0); + final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_HALF, 0); + final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_QUARTER, 0); + final float damageLossFactor = unitType.getFieldAsFloat(ATTACK2_DAMAGE_LOSS_FACTOR, 0); + final int damageDice = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); + final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0); + final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_DIST, 0); + final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_RADIUS, 0); + final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0); + final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0); + final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); + final String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); + final boolean projectileHomingEnabled = unitType + .getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED, 0); + final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); + final int range = unitType.getFieldAsInteger(ATTACK2_RANGE, 0); + final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK2_RANGE_MOTION_BUFFER, 0); + final boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0); + final EnumSet targetsAllowed = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_TARGETS_ALLOWED, 0)); + final String weaponSound = unitType.getFieldAsString(ATTACK2_WEAPON_SOUND, 0); + final CWeaponType weaponType = CWeaponType + .parseWeaponType(unitType.getFieldAsString(ATTACK2_WEAPON_TYPE, 0)); + attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, + areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, + cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, + damageDice, damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, + maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, + weaponType)); + } catch (final Exception exc) { + System.err.println("Attack 2 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); + } + } + final int deathType = unitType.getFieldAsInteger(DEATH_TYPE, 0); + final boolean raise = (deathType & 0x1) != 0; + final boolean decay = (deathType & 0x2) != 0; + final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0); + final float impactZ = unitType.getFieldAsFloat(PROJECTILE_IMPACT_Z, 0); + final CDefenseType defenseType = CDefenseType.parseDefenseType(unitType.getFieldAsString(DEFENSE_TYPE, 0)); + final float deathTime = unitType.getFieldAsFloat(DEATH_TIME, 0); + unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications, + attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, + targetedAs, acquisitionRange, minimumAttackRange); + this.unitIdToUnitType.put(typeId, unitTypeInstance); + } + return unitTypeInstance; + } - private CUnitAttack createAttack(final float animationBackswingPoint, final float animationDamagePoint, - final int areaOfEffectFullDamage, final int areaOfEffectMediumDamage, final int areaOfEffectSmallDamage, - final EnumSet areaOfEffectTargets, final CAttackType attackType, final float cooldownTime, - final int damageBase, final float damageFactorMedium, final float damageFactorSmall, - final float damageLossFactor, final int damageDice, final int damageSidesPerDie, - final float damageSpillDistance, final float damageSpillRadius, final int damageUpgradeAmount, - final int maximumNumberOfTargets, final float projectileArc, final String projectileArt, - final boolean projectileHomingEnabled, final int projectileSpeed, final int range, - final float rangeMotionBuffer, final boolean showUI, final EnumSet targetsAllowed, - final String weaponSound, final CWeaponType weaponType) { - final CUnitAttack attack; - switch (weaponType) { - case MISSILE: - attack = new CUnitAttackMissile(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, - projectileSpeed); - break; - case MBOUNCE: - attack = new CUnitAttackMissileBounce(animationBackswingPoint, animationDamagePoint, attackType, - cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, - rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, - projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets, - areaOfEffectFullDamage, areaOfEffectTargets); - break; - case MSPLASH: - case ARTILLERY: - attack = new CUnitAttackMissileSplash(animationBackswingPoint, animationDamagePoint, attackType, - cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, - rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, - projectileHomingEnabled, projectileSpeed, areaOfEffectFullDamage, areaOfEffectMediumDamage, - areaOfEffectSmallDamage, areaOfEffectTargets, damageFactorMedium, damageFactorSmall); - break; - case MLINE: - case ALINE: - attack = new CUnitAttackMissileLine(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, - projectileSpeed, damageSpillDistance, damageSpillRadius); - break; - case INSTANT: - attack = new CUnitAttackInstant(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType, projectileArt); - break; - default: - case NORMAL: - attack = new CUnitAttackNormal(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType); - break; - } - return attack; - } + private CUnitAttack createAttack(final float animationBackswingPoint, final float animationDamagePoint, + final int areaOfEffectFullDamage, final int areaOfEffectMediumDamage, final int areaOfEffectSmallDamage, + final EnumSet areaOfEffectTargets, final CAttackType attackType, final float cooldownTime, + final int damageBase, final float damageFactorMedium, final float damageFactorSmall, + final float damageLossFactor, final int damageDice, final int damageSidesPerDie, + final float damageSpillDistance, final float damageSpillRadius, final int damageUpgradeAmount, + final int maximumNumberOfTargets, final float projectileArc, final String projectileArt, + final boolean projectileHomingEnabled, final int projectileSpeed, final int range, + final float rangeMotionBuffer, final boolean showUI, final EnumSet targetsAllowed, + final String weaponSound, final CWeaponType weaponType) { + final CUnitAttack attack; + switch (weaponType) { + case MISSILE: + attack = new CUnitAttackMissile(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed); + break; + case MBOUNCE: + attack = new CUnitAttackMissileBounce(animationBackswingPoint, animationDamagePoint, attackType, + cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, + rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, + projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets, + areaOfEffectFullDamage, areaOfEffectTargets); + break; + case MSPLASH: + case ARTILLERY: + attack = new CUnitAttackMissileSplash(animationBackswingPoint, animationDamagePoint, attackType, + cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, + rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, + projectileHomingEnabled, projectileSpeed, areaOfEffectFullDamage, areaOfEffectMediumDamage, + areaOfEffectSmallDamage, areaOfEffectTargets, damageFactorMedium, damageFactorSmall); + break; + case MLINE: + case ALINE: + attack = new CUnitAttackMissileLine(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed, damageSpillDistance, damageSpillRadius); + break; + case INSTANT: + attack = new CUnitAttackInstant(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArt); + break; + default: + case NORMAL: + attack = new CUnitAttackNormal(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType); + break; + } + return attack; + } - public float getPropulsionWindow(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROPULSION_WINDOW, 0); - } + public float getPropulsionWindow(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROPULSION_WINDOW, 0); + } - public float getTurnRate(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(TURN_RATE, 0); - } + public float getTurnRate(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(TURN_RATE, 0); + } - public boolean isBuilding(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsBoolean(IS_BLDG, 0); - } + public boolean isBuilding(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsBoolean(IS_BLDG, 0); + } - public String getName(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getName(); - } + public String getName(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getName(); + } - public int getA1MinDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) - + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0); - } + public int getA1MinDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) + + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0); + } - public int getA1MaxDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) - + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0) - * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0)); - } + public int getA1MaxDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) + + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0) + * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0)); + } - public int getA2MinDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) - + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0); - } + public int getA2MinDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) + + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0); + } - public int getA2MaxDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) - + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0) - * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0)); - } + public int getA2MaxDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) + + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0) + * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0)); + } - public int getDefense(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(DEFENSE, 0); - } + public int getDefense(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(DEFENSE, 0); + } - public int getA1ProjectileSpeed(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); - } + public int getA1ProjectileSpeed(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); + } - public float getA1ProjectileArc(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); - } + public float getA1ProjectileArc(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); + } - public int getA2ProjectileSpeed(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); - } + public int getA2ProjectileSpeed(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); + } - public float getA2ProjectileArc(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); - } + public float getA2ProjectileArc(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); + } - public String getA1MissileArt(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsString(ATTACK1_MISSILE_ART, 0); - } + public String getA1MissileArt(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsString(ATTACK1_MISSILE_ART, 0); + } - public String getA2MissileArt(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsString(ATTACK2_MISSILE_ART, 0); - } + public String getA2MissileArt(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsString(ATTACK2_MISSILE_ART, 0); + } - public float getA1Cooldown(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_COOLDOWN, 0); - } + public float getA1Cooldown(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_COOLDOWN, 0); + } - public float getA2Cooldown(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_COOLDOWN, 0); - } + public float getA2Cooldown(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_COOLDOWN, 0); + } - public float getProjectileLaunchX(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_X, 0); - } + public float getProjectileLaunchX(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_X, 0); + } - public float getProjectileLaunchY(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Y, 0); - } + public float getProjectileLaunchY(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Y, 0); + } - public float getProjectileLaunchZ(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Z, 0); - } + public float getProjectileLaunchZ(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Z, 0); + } }