Checkpoint with support for archer and hippo couple

This commit is contained in:
Retera 2021-06-06 12:57:21 -04:00
parent 24590f7783
commit b3256b125d
19 changed files with 532 additions and 31 deletions

View File

@ -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];

View File

@ -190,6 +190,7 @@ public class War3MapViewer extends AbstractMdxModelViewer {
public int renderLighting = 1;
public List<SplatModel> selModels = new ArrayList<>();
private final Set<String> selectedSplatModelKeys = new HashSet<>();
public List<RenderWidget> selected = new ArrayList<>();
private final Set<String> mouseHighlightSplatModelKeys = new HashSet<>();
private final List<RenderWidget> 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);
}
}

View File

@ -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;
}
}

View File

@ -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<String> 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);

View File

@ -12,10 +12,13 @@ public class AbilityUI {
private final List<String> effectArt;
private final List<String> areaEffectArt;
private final List<String> missileArt;
private final String effectSound;
private final String effectSoundLooped;
public AbilityUI(final IconUI learnIconUI, final IconUI onIconUI, final IconUI offIconUI,
final List<String> casterArt, final List<String> targetArt, final List<String> specialArt,
final List<String> effectArt, final List<String> areaEffectArt, final List<String> missileArt) {
final List<String> effectArt, final List<String> areaEffectArt, final List<String> 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<String> items, final int index) {
if (index < items.size()) {
return items.get(index);

View File

@ -50,6 +50,7 @@ public class CSimulation implements CPlayerAPI {
private final CItemData itemData;
private final List<CUnit> units;
private final List<CUnit> newUnits;
private final List<CUnit> removedUnits;
private final List<CDestructable> destructables;
private final List<CItem> items;
private final List<CPlayer> 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);
}
}

View File

@ -1038,7 +1038,8 @@ public class CUnit extends CWidget {
@Override
public boolean canBeTargetedBy(final CSimulation simulation, final CUnit source,
final EnumSet<CTargetType> 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);
}
}

View File

@ -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<CTargetType> 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<CTargetType> 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<CWidget> receiver) {
receiver.targetOk(target);
}
@Override
protected void innerCheckCanTarget(final CSimulation game, final CUnit unit, final int orderId,
final AbilityPointTarget target, final AbilityTargetCheckReceiver<AbilityPointTarget> receiver) {
receiver.orderIdNotAccepted();
}
@Override
protected void innerCheckCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId,
final AbilityTargetCheckReceiver<Void> 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<CTargetType> 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;
}
}
}

View File

@ -15,6 +15,7 @@ public abstract class AbstractCAbilityTypeDefinition<TYPE_LEVEL_DATA extends CAb
private static final War3ID LEVELS = War3ID.fromString("alev");
protected static final War3ID CAST_RANGE = War3ID.fromString("aran");
protected static final War3ID DURATION = War3ID.fromString("adur");
protected static final War3ID AREA = War3ID.fromString("aare");
@Override
public CAbilityType<?> createAbilityType(final War3ID alias, final MutableGameObject abilityEditorData) {

View File

@ -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<CAbilityTypeCoupleInstantLevelData> 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<CTargetType> 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<CAbilityTypeCoupleInstantLevelData> levelData) {
return new CAbilityTypeCoupleInstant(alias, abilityEditorData.getCode(), levelData);
}
}

View File

@ -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<CAbilityTypeCoupleInstantLevelData> {
public CAbilityTypeCoupleInstant(final War3ID alias, final War3ID code,
final List<CAbilityTypeCoupleInstantLevelData> 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());
}
}

View File

@ -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<CTargetType> 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;
}
}

View File

@ -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) {
}
}

View File

@ -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) {

View File

@ -33,4 +33,5 @@ public interface AbilityTargetCheckReceiver<TARGET_TYPE> {
UNIT_OR_POINT,
NO_TARGET
}
}

View File

@ -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);
}

View File

@ -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<RenderWidget> newSelection = Arrays.asList(this.selectedUnit.getPreferredSelectionReplacement());
selectWidgets(newSelection);
this.war3MapViewer.doSelectUnit(newSelection);
}
else {
final float lifeRatioRemaining = this.selectedUnit.getSimulationUnit().getLife()

View File

@ -8,4 +8,6 @@ public interface CommandErrorListener {
void showNoFoodError();
void showInventoryFullError();
void showUnableToFindCoupleTargetError();
}

View File

@ -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();
}
}