diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index 0c16f66..0c58198 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -686,7 +686,7 @@ public class MdxComplexInstance extends ModelInstance { this.allowParticleSpawn = false; } else { - if ((this.blendTime > 0) && (lastSequence != this.sequence)) { + if ((this.blendTime > 0) && (lastSequence != this.sequence) && (lastSequence != -1)) { this.blendTimeRemaining = this.blendTime; for (int i = 0, l = this.sortedNodes.length; i < l; i++) { final SkeletalNode node = this.sortedNodes[i]; 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 7da6d63..a7817be 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -190,6 +190,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { public int renderLighting = 1; public List selModels = new ArrayList<>(); + private final Set selectedSplatModelKeys = new HashSet<>(); public List selected = new ArrayList<>(); private final Set mouseHighlightSplatModelKeys = new HashSet<>(); private final List mouseHighlightWidgets = new ArrayList<>(); @@ -386,6 +387,9 @@ public class War3MapViewer extends AbstractMdxModelViewer { try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\AmbienceSounds.slk")) { this.uiSoundsTable.readSLK(miscDataTxtStream); } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\AbilitySounds.slk")) { + this.uiSoundsTable.readSLK(miscDataTxtStream); + } } private Color parseColor(final Element selectionCircleData, final String field) { @@ -638,6 +642,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { War3MapViewer.this.widgets.remove(renderUnit); War3MapViewer.this.units.remove(renderUnit); War3MapViewer.this.worldScene.removeInstance(renderUnit.instance); + renderUnit.onRemove(War3MapViewer.this); } @Override @@ -806,6 +811,25 @@ public class War3MapViewer extends AbstractMdxModelViewer { renderPeer.getZ()); } } + + @Override + public void spawnAbilitySoundEffect(final CUnit caster, final War3ID alias) { + final RenderUnit renderPeer = War3MapViewer.this.unitToRenderPeer.get(caster); + final AbilityUI abilityUi = War3MapViewer.this.abilityDataUI.getUI(alias); + if (abilityUi.getEffectSound() != null) { + War3MapViewer.this.uiSounds.getSound(abilityUi.getEffectSound()).play( + War3MapViewer.this.worldScene.audioContext, renderPeer.getX(), renderPeer.getY(), + renderPeer.getZ()); + } + } + + @Override + public void unitPreferredSelectionReplacement(final CUnit oldUnit, final CUnit newUnit) { + final RenderUnit oldRenderPeer = War3MapViewer.this.unitToRenderPeer.get(oldUnit); + final RenderUnit newRenderPeer = War3MapViewer.this.unitToRenderPeer.get(newUnit); + oldRenderPeer.setPreferredSelectionReplacement(newRenderPeer); + + } }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, this.commandErrorListener); this.walkableObjectsTree = new Quadtree<>(this.terrain.getEntireMap()); @@ -1287,8 +1311,8 @@ public class War3MapViewer extends AbstractMdxModelViewer { } if (type == WorldEditorDataType.UNITS) { final float angle = (float) Math.toDegrees(unitAngle); - final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), playerIndex, unitX, unitY, - angle, buildingPathingPixelMap, pathingInstance); + final CUnit simulationUnit = this.simulation.internalCreateUnit(row.getAlias(), playerIndex, unitX, + unitY, angle, buildingPathingPixelMap, pathingInstance); final RenderUnitTypeData typeData = getUnitTypeData(unitId, row); if (!typeData.isAllowCustomTeamColor() || (customTeamColor == -1)) { if (typeData.getTeamColor() != -1) { @@ -1525,8 +1549,8 @@ public class War3MapViewer extends AbstractMdxModelViewer { public void deselect() { if (!this.selModels.isEmpty()) { - for (final SplatModel model : this.selModels) { - this.terrain.removeSplatBatchModel("selection"); + for (final String key : this.selectedSplatModelKeys) { + this.terrain.removeSplatBatchModel(key); } this.selModels.clear(); for (final RenderWidget unit : this.selected) { @@ -1618,7 +1642,8 @@ public class War3MapViewer extends AbstractMdxModelViewer { break; } this.selModels.add(model); - this.terrain.addSplatBatchModel("selection", model); + this.terrain.addSplatBatchModel("selection:" + path, model); + this.selectedSplatModelKeys.add("selection:" + path); } } 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 b8a8a1c..df8e291 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 @@ -71,6 +71,7 @@ public class RenderUnit implements RenderWidget { public final MdxModel specialArtModel; public SplatMover uberSplat; private float selectionHeight; + private RenderUnit preferredSelectionReplacement; public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, final float x, final float y, final float z, final int playerIndex, final UnitSoundset soundset, @@ -267,26 +268,7 @@ public class RenderUnit implements RenderWidget { final boolean boneCorpse = this.simulationUnit.isBoneCorpse(); if (dead && !this.dead) { this.unitAnimationListenerImpl.playAnimation(true, PrimaryTag.DEATH, SequenceUtils.EMPTY, 1.0f, true); - if (this.shadow != null) { - this.shadow.destroy(Gdx.gl30, map.terrain.centerOffset); - this.shadow = null; - } - if (this.buildingShadowInstance != null) { - this.buildingShadowInstance.remove(); - this.buildingShadowInstance = null; - } - if (this.uberSplat != null) { - this.uberSplat.destroy(Gdx.gl30, map.terrain.centerOffset); - this.uberSplat = null; - } - if (this.selectionCircle != null) { - this.selectionCircle.destroy(Gdx.gl30, map.terrain.centerOffset); - this.selectionCircle = null; - } - if (this.selectionPreviewHighlight != null) { - this.selectionPreviewHighlight.destroy(Gdx.gl30, map.terrain.centerOffset); - this.selectionPreviewHighlight = null; - } + removeSplats(map); } if (boneCorpse && !this.boneCorpse) { this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.BONE, @@ -429,6 +411,29 @@ public class RenderUnit implements RenderWidget { } } + private void removeSplats(final War3MapViewer map) { + if (this.shadow != null) { + this.shadow.destroy(Gdx.gl30, map.terrain.centerOffset); + this.shadow = null; + } + if (this.buildingShadowInstance != null) { + this.buildingShadowInstance.remove(); + this.buildingShadowInstance = null; + } + if (this.uberSplat != null) { + this.uberSplat.destroy(Gdx.gl30, map.terrain.centerOffset); + this.uberSplat = null; + } + if (this.selectionCircle != null) { + this.selectionCircle.destroy(Gdx.gl30, map.terrain.centerOffset); + this.selectionCircle = null; + } + if (this.selectionPreviewHighlight != null) { + this.selectionPreviewHighlight.destroy(Gdx.gl30, map.terrain.centerOffset); + this.selectionPreviewHighlight = null; + } + } + private float getGroundHeightSample(final float groundHeight, final MdxComplexInstance currentWalkableUnder, final float sampleX, final float sampleY) { final float sampleGroundHeight; @@ -536,4 +541,16 @@ public class RenderUnit implements RenderWidget { public SplatMover getSelectionPreviewHighlight() { return this.selectionPreviewHighlight; } + + public void onRemove(final War3MapViewer map) { + removeSplats(map); + } + + public void setPreferredSelectionReplacement(final RenderUnit preferredSelectionReplacement) { + this.preferredSelectionReplacement = preferredSelectionReplacement; + } + + public RenderUnit getPreferredSelectionReplacement() { + return this.preferredSelectionReplacement; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java index 42585ef..005eebb 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java @@ -31,6 +31,8 @@ public class AbilityDataUI { private static final War3ID ABILITY_UN_UBER_TIP = War3ID.fromString("auu1"); private static final War3ID ABILITY_RESEARCH_TIP = War3ID.fromString("aret"); private static final War3ID ABILITY_RESEARCH_UBER_TIP = War3ID.fromString("arut"); + private static final War3ID ABILITY_EFFECT_SOUND = War3ID.fromString("aefs"); + private static final War3ID ABILITY_EFFECT_SOUND_LOOPED = War3ID.fromString("aefl"); private static final War3ID CASTER_ART = War3ID.fromString("acat"); private static final War3ID TARGET_ART = War3ID.fromString("atat"); @@ -117,6 +119,9 @@ public class AbilityDataUI { .asList(abilityTypeData.getFieldAsString(AREA_EFFECT_ART, 0).split(",")); final List missileArt = Arrays.asList(abilityTypeData.getFieldAsString(MISSILE_ART, 0).split(",")); + final String effectSound = abilityTypeData.getFieldAsString(ABILITY_EFFECT_SOUND, 0); + final String effectSoundLooped = abilityTypeData.getFieldAsString(ABILITY_EFFECT_SOUND_LOOPED, 0); + this.rawcodeToUI.put(alias, new AbilityUI( new IconUI(iconResearch, iconResearchDisabled, iconResearchX, iconResearchY, @@ -124,7 +129,8 @@ public class AbilityDataUI { new IconUI(iconNormal, iconNormalDisabled, iconNormalX, iconNormalY, iconTip, iconUberTip), new IconUI(iconTurnOff, iconTurnOffDisabled, iconTurnOffX, iconTurnOffY, iconTurnOffTip, iconTurnOffUberTip), - casterArt, targetArt, specialArt, effectArt, areaEffectArt, missileArt)); + casterArt, targetArt, specialArt, effectArt, areaEffectArt, missileArt, effectSound, + effectSoundLooped)); } for (final War3ID alias : unitData.keySet()) { final MutableGameObject abilityTypeData = unitData.get(alias); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityUI.java index 2be75c1..28caf03 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityUI.java @@ -12,10 +12,13 @@ public class AbilityUI { private final List effectArt; private final List areaEffectArt; private final List missileArt; + private final String effectSound; + private final String effectSoundLooped; public AbilityUI(final IconUI learnIconUI, final IconUI onIconUI, final IconUI offIconUI, final List casterArt, final List targetArt, final List specialArt, - final List effectArt, final List areaEffectArt, final List missileArt) { + final List effectArt, final List areaEffectArt, final List missileArt, + final String effectSound, final String effectSoundLooped) { this.learnIconUI = learnIconUI; this.onIconUI = onIconUI; this.offIconUI = offIconUI; @@ -25,6 +28,8 @@ public class AbilityUI { this.effectArt = effectArt; this.areaEffectArt = areaEffectArt; this.missileArt = missileArt; + this.effectSound = effectSound; + this.effectSoundLooped = effectSoundLooped; } public IconUI getLearnIconUI() { @@ -63,6 +68,14 @@ public class AbilityUI { return tryGet(this.missileArt, index); } + public String getEffectSound() { + return this.effectSound; + } + + public String getEffectSoundLooped() { + return this.effectSoundLooped; + } + private static String tryGet(final List items, final int index) { if (index < items.size()) { return items.get(index); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index e472ce9..1dafd92 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -50,6 +50,7 @@ public class CSimulation implements CPlayerAPI { private final CItemData itemData; private final List units; private final List newUnits; + private final List removedUnits; private final List destructables; private final List items; private final List players; @@ -88,6 +89,7 @@ public class CSimulation implements CPlayerAPI { this.itemData = new CItemData(parsedItemData); this.units = new ArrayList<>(); this.newUnits = new ArrayList<>(); + this.removedUnits = new ArrayList<>(); this.destructables = new ArrayList<>(); this.items = new ArrayList<>(); this.projectiles = new ArrayList<>(); @@ -166,7 +168,7 @@ public class CSimulation implements CPlayerAPI { this.activeTimers.remove(time); } - public CUnit createUnit(final War3ID typeId, final int playerIndex, final float x, final float y, + public CUnit internalCreateUnit(final War3ID typeId, final int playerIndex, final float x, final float y, final float facing, final BufferedImage buildingPathingPixelMap, final RemovablePathingMapInstance pathingInstance) { final CUnit unit = this.unitData.create(this, playerIndex, typeId, x, y, facing, buildingPathingPixelMap, @@ -254,6 +256,7 @@ public class CSimulation implements CPlayerAPI { this.handleIdToUnit.remove(unit.getHandleId()); this.simulationRenderController.removeUnit(unit); this.playerHeroes[unit.getPlayerIndex()].remove(unit); + unit.onRemove(this); } } finishAddingNewUnits(); @@ -278,9 +281,24 @@ public class CSimulation implements CPlayerAPI { } + public void removeUnit(final CUnit unit) { + this.removedUnits.add(unit); + } + private void finishAddingNewUnits() { this.units.addAll(this.newUnits); this.newUnits.clear(); + for (final CUnit unit : this.removedUnits) { + this.units.remove(unit); + for (final CAbility ability : unit.getAbilities()) { + this.handleIdToAbility.remove(ability.getHandleId()); + } + this.handleIdToUnit.remove(unit.getHandleId()); + this.simulationRenderController.removeUnit(unit); + this.playerHeroes[unit.getPlayerIndex()].remove(unit); + unit.onRemove(this); + } + this.removedUnits.clear(); } public float getGameTimeOfDay() { @@ -412,4 +430,12 @@ public class CSimulation implements CPlayerAPI { this.simulationRenderController.spawnEffectOnUnit(unit, effectPath); } + public void unitSoundEffectEvent(final CUnit caster, final War3ID alias) { + this.simulationRenderController.spawnAbilitySoundEffect(caster, alias); + } + + public void unitPreferredSelectionReplacement(final CUnit unit, final CUnit newUnit) { + this.simulationRenderController.unitPreferredSelectionReplacement(unit, newUnit); + } + } 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 ccc67b3..cfeaa25 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 @@ -1038,7 +1038,8 @@ public class CUnit extends CWidget { @Override public boolean canBeTargetedBy(final CSimulation simulation, final CUnit source, final EnumSet targetsAllowed) { - if (targetsAllowed.containsAll(this.unitType.getTargetedAs())) { + if (targetsAllowed.containsAll(this.unitType.getTargetedAs()) || (!targetsAllowed.contains(CTargetType.GROUND) + && (!targetsAllowed.contains(CTargetType.STRUCTURE) && !targetsAllowed.contains(CTargetType.AIR)))) { final int sourcePlayerIndex = source.getPlayerIndex(); final CPlayer sourcePlayer = simulation.getPlayer(sourcePlayerIndex); if (!targetsAllowed.contains(CTargetType.ENEMIES) @@ -1566,4 +1567,9 @@ public class CUnit extends CWidget { public boolean isBuilding() { return this.unitType.isBuilding(); } + + public void onRemove(final CSimulation simulation) { + setLife(simulation, 0); + simulation.getWorldCollision().removeUnit(this); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/test/CAbilityCoupleInstant.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/test/CAbilityCoupleInstant.java new file mode 100644 index 0000000..197fe56 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/test/CAbilityCoupleInstant.java @@ -0,0 +1,179 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.test; + +import java.util.EnumSet; + +import com.badlogic.gdx.math.Rectangle; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitEnumFunction; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.AbstractGenericSingleIconNoSmartActiveAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.test.CBehaviorCoupleInstant; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderTargetWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; + +public class CAbilityCoupleInstant extends AbstractGenericSingleIconNoSmartActiveAbility { + + private final War3ID resultingUnitType; + private final War3ID partnerUnitType; + private final boolean moveToPartner; + private final float castRange; + private final float area; + private final EnumSet targetsAllowed; + private CBehaviorCoupleInstant behaviorCoupleInstant; + + public CAbilityCoupleInstant(final int handleId, final War3ID alias, final War3ID resultingUnitType, + final War3ID partnerUnitType, final boolean moveToPartner, final float castRange, final float area, + final EnumSet targetsAllowed) { + super(handleId, alias); + this.resultingUnitType = resultingUnitType; + this.partnerUnitType = partnerUnitType; + this.moveToPartner = moveToPartner; + this.castRange = castRange; + this.area = area; + this.targetsAllowed = targetsAllowed; + } + + @Override + public int getBaseOrderId() { + return OrderIds.coupleinstant; + } + + @Override + public boolean isToggleOn() { + return false; + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + this.behaviorCoupleInstant = new CBehaviorCoupleInstant(unit, this); + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + + } + + @Override + public void onTick(final CSimulation game, final CUnit unit) { + + } + + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + // only from engine, not ever allowed by the checks + if (target instanceof CUnit) { + return this.behaviorCoupleInstant.reset((CUnit) target); + } + return caster.pollNextOrderBehavior(game); + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { + return caster.pollNextOrderBehavior(game); + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + final PossiblePairFinderEnum possiblePairFinder = new PossiblePairFinderEnum(caster); + game.getWorldCollision().enumUnitsInRect( + new Rectangle(caster.getX() - this.area, caster.getY() - this.area, this.area * 2, this.area * 2), + possiblePairFinder); + final CUnit coupleTarget = possiblePairFinder.pairMatchFound; + if (coupleTarget == null) { + game.getCommandErrorListener(caster.getPlayerIndex()).showUnableToFindCoupleTargetError(); + return caster.pollNextOrderBehavior(game); + } + coupleTarget.order(game, new COrderTargetWidget(possiblePairFinder.pairMatchAbility.getHandleId(), + possiblePairFinder.pairMatchAbility.getBaseOrderId(), caster.getHandleId(), false), false); + return this.behaviorCoupleInstant.reset(coupleTarget); + } + + @Override + protected void innerCheckCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final CWidget target, final AbilityTargetCheckReceiver receiver) { + receiver.targetOk(target); + } + + @Override + protected void innerCheckCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + protected void innerCheckCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityTargetCheckReceiver receiver) { + receiver.targetOk(null); + } + + @Override + protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { + receiver.useOk(); + } + + public float getCastRange() { + return this.castRange; + } + + public float getArea() { + return this.area; + } + + public EnumSet getTargetsAllowed() { + return this.targetsAllowed; + } + + public War3ID getResultingUnitType() { + return this.resultingUnitType; + } + + private final class PossiblePairFinderEnum implements CUnitEnumFunction { + private final CUnit unit; + private CUnit pairMatchFound = null; + private CAbilityCoupleInstant pairMatchAbility; + + private PossiblePairFinderEnum(final CUnit unit) { + this.unit = unit; + } + + @Override + public boolean call(final CUnit otherUnit) { + if (otherUnit.getPlayerIndex() == this.unit.getPlayerIndex()) { + for (final CAbility ability : otherUnit.getAbilities()) { + if (ability instanceof CAbilityCoupleInstant) { + final CAbilityCoupleInstant otherCoupleInstant = (CAbilityCoupleInstant) ability; + if (otherCoupleInstant.partnerUnitType.equals(this.unit.getTypeId())) { + if (CAbilityCoupleInstant.this.partnerUnitType.equals(otherUnit.getTypeId())) { + // we're a pair, make sure other unit is not already actively pairing + if (!(otherUnit.getCurrentBehavior() instanceof CBehaviorCoupleInstant)) { + if (otherUnit.distance(this.unit) <= CAbilityCoupleInstant.this.area) { + this.pairMatchFound = otherUnit; + this.pairMatchAbility = otherCoupleInstant; + break; + } + } + } + } + } + } + } + return this.pairMatchFound != null; + } + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/AbstractCAbilityTypeDefinition.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/AbstractCAbilityTypeDefinition.java index 52386a4..c5d893d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/AbstractCAbilityTypeDefinition.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/AbstractCAbilityTypeDefinition.java @@ -15,6 +15,7 @@ public abstract class AbstractCAbilityTypeDefinition createAbilityType(final War3ID alias, final MutableGameObject abilityEditorData) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionCoupleInstant.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionCoupleInstant.java new file mode 100644 index 0000000..9d6d73c --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionCoupleInstant.java @@ -0,0 +1,48 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl; + +import java.util.EnumSet; +import java.util.List; + +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.CAbilityTypeDefinition; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl.CAbilityTypeCoupleInstant; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl.CAbilityTypeCoupleInstantLevelData; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; + +public class CAbilityTypeDefinitionCoupleInstant + extends AbstractCAbilityTypeDefinition implements CAbilityTypeDefinition { + protected static final War3ID RESULTING_UNIT_TYPE = War3ID.fromString("coau"); + protected static final War3ID PARTNER_UNIT_TYPE = War3ID.fromString("coa1"); + protected static final War3ID MOVE_TO_PARTNER = War3ID.fromString("coa2"); + protected static final War3ID GOLD_COST = War3ID.fromString("coa3"); + protected static final War3ID LUMBER_COST = War3ID.fromString("coa4"); + + @Override + protected CAbilityTypeCoupleInstantLevelData createLevelData(final MutableGameObject abilityEditorData, + final int level) { + final String targetsAllowedAtLevelString = abilityEditorData.getFieldAsString(TARGETS_ALLOWED, level); + final War3ID resultingUnitTypeId = War3ID + .fromString(abilityEditorData.getFieldAsString(RESULTING_UNIT_TYPE, level)); + final War3ID partnerUnitTypeId = War3ID + .fromString(abilityEditorData.getFieldAsString(PARTNER_UNIT_TYPE, level)); + final boolean moveToPartner = abilityEditorData.getFieldAsBoolean(MOVE_TO_PARTNER, level); + final float castRange = abilityEditorData.getFieldAsFloat(CAST_RANGE, level); + final float area = abilityEditorData.getFieldAsFloat(AREA, level); + + final int goldCost = abilityEditorData.getFieldAsInteger(GOLD_COST, level); + final int lumberCost = abilityEditorData.getFieldAsInteger(LUMBER_COST, level); + + final EnumSet targetsAllowedAtLevel = CTargetType.parseTargetTypeSet(targetsAllowedAtLevelString); + return new CAbilityTypeCoupleInstantLevelData(targetsAllowedAtLevel, resultingUnitTypeId, partnerUnitTypeId, + moveToPartner, castRange, area); + } + + @Override + protected CAbilityType innerCreateAbilityType(final War3ID alias, final MutableGameObject abilityEditorData, + final List levelData) { + return new CAbilityTypeCoupleInstant(alias, abilityEditorData.getCode(), levelData); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeCoupleInstant.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeCoupleInstant.java new file mode 100644 index 0000000..8e70ed6 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeCoupleInstant.java @@ -0,0 +1,25 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl; + +import java.util.List; + +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.test.CAbilityCoupleInstant; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; + +public class CAbilityTypeCoupleInstant extends CAbilityType { + + public CAbilityTypeCoupleInstant(final War3ID alias, final War3ID code, + final List levelData) { + super(alias, code, levelData); + } + + @Override + public CAbility createAbility(final int handleId) { + final CAbilityTypeCoupleInstantLevelData levelData = getLevelData(0); + return new CAbilityCoupleInstant(handleId, getAlias(), levelData.getResultingUnitTypeId(), + levelData.getPartnerUnitTypeId(), levelData.isMoveToPartner(), levelData.getCastRange(), + levelData.getArea(), levelData.getTargetsAllowed()); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeCoupleInstantLevelData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeCoupleInstantLevelData.java new file mode 100644 index 0000000..bf97556 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeCoupleInstantLevelData.java @@ -0,0 +1,47 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl; + +import java.util.EnumSet; + +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityTypeLevelData; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; + +public class CAbilityTypeCoupleInstantLevelData extends CAbilityTypeLevelData { + private final War3ID resultingUnitTypeId; + private final War3ID partnerUnitTypeId; + private final boolean moveToPartner; + private final float castRange; + private final float area; + + public CAbilityTypeCoupleInstantLevelData(final EnumSet targetsAllowed, + final War3ID resultingUnitTypeId, final War3ID partnerUnitTypeId, final boolean moveToPartner, + final float castRange, final float area) { + super(targetsAllowed); + this.resultingUnitTypeId = resultingUnitTypeId; + this.partnerUnitTypeId = partnerUnitTypeId; + this.moveToPartner = moveToPartner; + this.castRange = castRange; + this.area = area; + } + + public War3ID getResultingUnitTypeId() { + return this.resultingUnitTypeId; + } + + public War3ID getPartnerUnitTypeId() { + return this.partnerUnitTypeId; + } + + public boolean isMoveToPartner() { + return this.moveToPartner; + } + + public float getCastRange() { + return this.castRange; + } + + public float getArea() { + return this.area; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/test/CBehaviorCoupleInstant.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/test/CBehaviorCoupleInstant.java new file mode 100644 index 0000000..f923351 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/test/CBehaviorCoupleInstant.java @@ -0,0 +1,86 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.test; + +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.abilities.targeting.AbilityTargetStillAliveAndTargetableVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.test.CAbilityCoupleInstant; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CAbstractRangedBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; + +public class CBehaviorCoupleInstant extends CAbstractRangedBehavior { + + private final CAbilityCoupleInstant abilityCoupleInstant; + private final AbilityTargetStillAliveAndTargetableVisitor stillAliveVisitor; + + public CBehaviorCoupleInstant(final CUnit unit, final CAbilityCoupleInstant abilityCoupleInstant) { + super(unit); + this.abilityCoupleInstant = abilityCoupleInstant; + this.stillAliveVisitor = new AbilityTargetStillAliveAndTargetableVisitor(); + } + + public CBehaviorCoupleInstant reset(final CUnit coupleTarget) { + innerReset(coupleTarget); + return this; + } + + @Override + public boolean isWithinRange(final CSimulation simulation) { + return this.unit.canReach(this.target, this.abilityCoupleInstant.getCastRange()); + } + + @Override + public void endMove(final CSimulation game, final boolean interrupted) { + } + + @Override + public void begin(final CSimulation game) { + } + + @Override + public void end(final CSimulation game, final boolean interrupted) { + } + + @Override + public int getHighlightOrderId() { + return OrderIds.coupleinstant; + } + + @Override + protected CBehavior update(final CSimulation simulation, final boolean withinRange) { + final CBehavior targetBehavior = ((CUnit) this.target).getCurrentBehavior(); + if (targetBehavior instanceof CBehaviorCoupleInstant) { + if (((CBehaviorCoupleInstant) targetBehavior).isWithinRange(simulation)) { + // we are both within range + final CUnit newUnit = simulation.createUnit(this.abilityCoupleInstant.getResultingUnitType(), + this.unit.getPlayerIndex(), this.unit.getX(), this.unit.getY(), this.unit.getFacing()); + simulation.unitPreferredSelectionReplacement(this.unit, newUnit); + simulation.unitPreferredSelectionReplacement(((CUnit) this.target), newUnit); + simulation.removeUnit(this.unit); + simulation.removeUnit((CUnit) this.target); + simulation.unitSoundEffectEvent(newUnit, this.abilityCoupleInstant.getAlias()); + return this.unit.pollNextOrderBehavior(simulation); + } + } + this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f, true); + return this; + } + + @Override + protected CBehavior updateOnInvalidTarget(final CSimulation simulation) { + return this.unit.pollNextOrderBehavior(simulation); + } + + @Override + protected boolean checkTargetStillValid(final CSimulation simulation) { + return this.target.visit( + this.stillAliveVisitor.reset(simulation, this.unit, this.abilityCoupleInstant.getTargetsAllowed())); + } + + @Override + protected void resetBeforeMoving(final CSimulation simulation) { + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java index 1cded16..2cb879d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java @@ -12,6 +12,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.CAbilityTypeDefinition; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionChannelTest; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionColdArrows; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionCoupleInstant; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionGoldMine; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionHarvest; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionInventory; @@ -38,6 +39,7 @@ public class CAbilityData { this.codeToAbilityTypeDefinition.put(War3ID.fromString("ANcl"), new CAbilityTypeDefinitionChannelTest()); this.codeToAbilityTypeDefinition.put(War3ID.fromString("AInv"), new CAbilityTypeDefinitionInventory()); this.codeToAbilityTypeDefinition.put(War3ID.fromString("Avul"), new CAbilityTypeDefinitionInvulnerable()); + this.codeToAbilityTypeDefinition.put(War3ID.fromString("Acoi"), new CAbilityTypeDefinitionCoupleInstant()); } public CAbilityType getAbilityType(final War3ID alias) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityTargetCheckReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityTargetCheckReceiver.java index 2959ee6..6a4a8fc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityTargetCheckReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityTargetCheckReceiver.java @@ -33,4 +33,5 @@ public interface AbilityTargetCheckReceiver { UNIT_OR_POINT, NO_TARGET } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java index 05b9350..aef4c93 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java @@ -55,4 +55,8 @@ public interface SimulationRenderController { void spawnUIUnitGetItemSound(CUnit cUnit, CItem item); void spawnUIUnitDropItemSound(CUnit cUnit, CItem item); + + void spawnAbilitySoundEffect(CUnit caster, War3ID alias); + + void unitPreferredSelectionReplacement(CUnit unit, CUnit newUnit); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 7afae22..4cb0d2d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -1041,6 +1041,12 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma .play(this.uiScene.audioContext, 0, 0, 0); } + @Override + public void showUnableToFindCoupleTargetError() { + showCommandError(this.rootFrame.getErrorString("Cantfindcoupletarget")); + this.war3MapViewer.getUiSounds().getSound("InterfaceError").play(this.uiScene.audioContext, 0, 0, 0); + } + @Override public void showInventoryFullError() { showCommandError(this.rootFrame.getErrorString("InventoryFull")); @@ -2311,7 +2317,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma @Override public void lifeChanged() { if (this.selectedUnit.getSimulationUnit().isDead()) { - selectUnit(null); + final List newSelection = Arrays.asList(this.selectedUnit.getPreferredSelectionReplacement()); + selectWidgets(newSelection); + this.war3MapViewer.doSelectUnit(newSelection); } else { final float lifeRatioRemaining = this.selectedUnit.getSimulationUnit().getLife() diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java index 70fa4f3..d9b97fd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java @@ -8,4 +8,6 @@ public interface CommandErrorListener { void showNoFoodError(); void showInventoryFullError(); + + void showUnableToFindCoupleTargetError(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/SettableCommandErrorListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/SettableCommandErrorListener.java index 7bed78b..adeabcf 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/SettableCommandErrorListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/SettableCommandErrorListener.java @@ -26,4 +26,9 @@ public class SettableCommandErrorListener implements CommandErrorListener { public void setDelegate(final CommandErrorListener delegate) { this.delegate = delegate; } + + @Override + public void showUnableToFindCoupleTargetError() { + this.delegate.showUnableToFindCoupleTargetError(); + } }