Hero revival

This commit is contained in:
Retera 2021-07-03 02:19:19 -04:00
parent 28aeffd6d4
commit 3f1a19ae5d
29 changed files with 791 additions and 83 deletions

View File

@ -2,4 +2,5 @@ package com.etheller.warsmash.networking;
public class MultiplayerHack {
public static String MULTIPLAYER_HACK_SERVER_ADDR = null;
public static int LP_VAL = 0;
}

View File

@ -66,7 +66,7 @@ public class WarsmashClientWriter {
public void unitCancelTrainingItem(final int unitHandleId, final int cancelIndex) {
this.sendBuffer.clear();
this.sendBuffer.putInt(4 + 4 + 4 + 4 + 1);
this.sendBuffer.putInt(4 + 4 + 4);
this.sendBuffer.putInt(ClientToServerProtocol.UNIT_CANCEL_TRAINING);
this.sendBuffer.putInt(unitHandleId);
this.sendBuffer.putInt(cancelIndex);

View File

@ -19,4 +19,5 @@ public abstract class SubscriberSetNotifier<LISTENER_TYPE> {
public final void unsubscribe(final LISTENER_TYPE listener) {
this.set.remove(listener);
}
}

View File

@ -293,6 +293,12 @@ public class MdxComplexInstance extends ModelInstance {
final MdxModel model = (MdxModel) this.model;
final List<GenericObject> sortedGenericObjects = model.sortedGenericObjects;
final Scene scene = this.scene;
if(scene == null) {
// too bad for this instance, this is not safe to update on null scene, got NPE from this during testing,
// so we are going to skip this cycle entirely!
// (Retera)
return;
}
// Update the nodes
for (int i = 0, l = sortedNodes.length; i < l; i++) {

View File

@ -127,6 +127,7 @@ public class War3MapViewer extends AbstractMdxModelViewer {
public static int DEBUG_DEPTH = 9999;
private static final War3ID ABILITY_HERO_RAWCODE = War3ID.fromString("AHer");
private static final War3ID ABILITY_REVIVE_RAWCODE = War3ID.fromString("Arev");
private static final Color PLACEHOLDER_LUMBER_COLOR = new Color(0.0f, 200f / 255f, 80f / 255f, 1.0f);
private static final Color PLACEHOLDER_GOLD_COLOR = new Color(1.0f, 220f / 255f, 0f, 1.0f);
private static final War3ID UNIT_FILE = War3ID.fromString("umdl");
@ -718,34 +719,35 @@ public class War3MapViewer extends AbstractMdxModelViewer {
final AbilityUI heroUI = War3MapViewer.this.abilityDataUI.getUI(ABILITY_HERO_RAWCODE);
final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.get(source);
final String heroLevelUpArt = heroUI.getCasterArt(0);
final MdxModel heroLevelUpModel = loadModel(heroLevelUpArt);
if (heroLevelUpModel != null) {
final MdxComplexInstance modelInstance = (MdxComplexInstance) heroLevelUpModel
.addInstance();
modelInstance.setTeamColor(source.getPlayerIndex());
spawnFxOnOrigin(renderUnit, heroLevelUpArt);
}
final MdxModel model = (MdxModel) renderUnit.instance.model;
int index = -1;
for (int i = 0; i < model.attachments.size(); i++) {
final Attachment attachment = model.attachments.get(i);
if (attachment.getName().startsWith("origin ref")) {
index = i;
break;
}
}
if (index != -1) {
final MdxNode attachment = renderUnit.instance.getAttachment(index);
modelInstance.setParent(attachment);
}
else {
modelInstance.setLocation(renderUnit.location);
}
@Override
public void heroRevived(CUnit source) {
final AbilityUI reviveUI = War3MapViewer.this.abilityDataUI.getUI(ABILITY_REVIVE_RAWCODE);
final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.get(source);
CPlayer player = simulation.getPlayer(source.getPlayerIndex());
final String heroReviveArt = reviveUI.getTargetArt(player.getRace().ordinal());
spawnFxOnOrigin(renderUnit, heroReviveArt);
MutableGameObject row = allObjectData.getUnits().get(source.getTypeId());
modelInstance.setScene(War3MapViewer.this.worldScene);
SequenceUtils.randomBirthSequence(modelInstance);
War3MapViewer.this.projectiles
.add(new RenderAttackInstant(modelInstance, War3MapViewer.this,
(float) Math.toRadians(renderUnit.getSimulationUnit().getFacing())));
// Recreate unit shadow.... is needed here
final String unitShadow = row.getFieldAsString(UNIT_SHADOW, 0);
float unitX = source.getX();
float unitY = source.getY();
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 (mapMpq.has(texture)) {
final float x = unitX - shadowX;
final float y = unitY - shadowY;
renderUnit.shadow = terrain.addUnitShadowSplat(texture, x, y,
x + shadowWidth, y + shadowHeight, 3, 0.5f);
}
}
}
@ -853,6 +855,37 @@ public class War3MapViewer extends AbstractMdxModelViewer {
loadLightsAndShading(tileset);
}
public void spawnFxOnOrigin(RenderUnit renderUnit, String heroLevelUpArt) {
final MdxModel heroLevelUpModel = loadModel(heroLevelUpArt);
if (heroLevelUpModel != null) {
final MdxComplexInstance modelInstance = (MdxComplexInstance) heroLevelUpModel
.addInstance();
modelInstance.setTeamColor(renderUnit.playerIndex);
final MdxModel model = (MdxModel) renderUnit.instance.model;
int index = -1;
for (int i = 0; i < model.attachments.size(); i++) {
final Attachment attachment = model.attachments.get(i);
if (attachment.getName().startsWith("origin ref")) {
index = i;
break;
}
}
if (index != -1) {
final MdxNode attachment = renderUnit.instance.getAttachment(index);
modelInstance.setParent(attachment);
} else {
modelInstance.setLocation(renderUnit.location);
}
modelInstance.setScene(War3MapViewer.this.worldScene);
SequenceUtils.randomBirthSequence(modelInstance);
War3MapViewer.this.projectiles
.add(new RenderAttackInstant(modelInstance, War3MapViewer.this,
(float) Math.toRadians(renderUnit.getSimulationUnit().getFacing())));
}
}
protected BufferedImage getDestructablePathingPixelMap(final MutableGameObject row) {
return loadPathingTexture(row.getFieldAsString(DESTRUCTABLE_PATHING, 0));
}

View File

@ -147,9 +147,9 @@ public class RenderUnit implements RenderWidget {
public void populateCommandCard(final CSimulation game, final GameUI gameUI,
final CommandButtonListener commandButtonListener, final AbilityDataUI abilityDataUI,
final int subMenuOrderId) {
final int subMenuOrderId, boolean multiSelect) {
final CommandCardPopulatingAbilityVisitor commandCardPopulatingVisitor = CommandCardPopulatingAbilityVisitor.INSTANCE
.reset(game, gameUI, this.simulationUnit, commandButtonListener, abilityDataUI, subMenuOrderId);
.reset(game, gameUI, this.simulationUnit, commandButtonListener, abilityDataUI, subMenuOrderId, multiSelect);
for (final CAbility ability : this.simulationUnit.getAbilities()) {
ability.visit(commandCardPopulatingVisitor);
}
@ -271,8 +271,13 @@ public class RenderUnit implements RenderWidget {
removeSplats(map);
}
if (boneCorpse && !this.boneCorpse) {
this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.BONE,
this.simulationUnit.getEndingDecayTime(map.simulation), true);
if(simulationUnit.getUnitType().isHero()) {
this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DISSIPATE, SequenceUtils.EMPTY,
this.simulationUnit.getEndingDecayTime(map.simulation), true);
} else {
this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.BONE,
this.simulationUnit.getEndingDecayTime(map.simulation), true);
}
}
else if (corpse && !this.corpse) {
this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.FLESH,

View File

@ -56,6 +56,7 @@ public interface RenderWidget {
private float currentSpeedRatio;
private boolean currentlyAllowingRarityVariations;
private final Queue<QueuedAnimation> animationQueue = new LinkedList<>();
private int lastWalkFrame = -1;
public UnitAnimationListenerImpl(final MdxComplexInstance instance) {
this.instance = instance;
@ -98,8 +99,14 @@ public interface RenderWidget {
this.recycleSet.addAll(this.secondaryAnimationTags);
this.recycleSet.addAll(secondaryAnimationTags);
this.instance.setAnimationSpeed(speedRatio);
if(animationName != PrimaryTag.WALK && currentAnimation == PrimaryTag.WALK) {
lastWalkFrame = instance.frame;
}
if (SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet,
allowRarityVariations) != null) {
if(lastWalkFrame != -1 && animationName == PrimaryTag.WALK && currentAnimation != PrimaryTag.WALK) {
instance.setFrame(lastWalkFrame);
}
this.currentAnimation = animationName;
this.currentAnimationSecondaryTags = secondaryAnimationTags;
this.currentlyAllowingRarityVariations = allowRarityVariations;
@ -116,9 +123,15 @@ public interface RenderWidget {
this.recycleSet.clear();
this.recycleSet.addAll(this.secondaryAnimationTags);
this.recycleSet.addAll(secondaryAnimationTags);
if(animationName != PrimaryTag.WALK && currentAnimation == PrimaryTag.WALK) {
lastWalkFrame = instance.frame;
}
final Sequence sequence = SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet,
allowRarityVariations);
if (sequence != null) {
if(lastWalkFrame != -1 && animationName == PrimaryTag.WALK && currentAnimation != PrimaryTag.WALK) {
instance.setFrame(lastWalkFrame);
}
this.currentAnimation = animationName;
this.currentAnimationSecondaryTags = secondaryAnimationTags;
this.currentlyAllowingRarityVariations = allowRarityVariations;

View File

@ -45,6 +45,8 @@ public class AbilityDataUI {
private static final War3ID UNIT_ICON_NORMAL_Y = War3ID.fromString("ubpy");
private static final War3ID UNIT_ICON_NORMAL = War3ID.fromString("uico");
private static final War3ID UNIT_TIP = War3ID.fromString("utip");
private static final War3ID UNIT_REVIVE_TIP = War3ID.fromString("utpr");
private static final War3ID UNIT_AWAKEN_TIP = War3ID.fromString("uawt");
private static final War3ID UNIT_UBER_TIP = War3ID.fromString("utub");
private static final War3ID ITEM_ICON_NORMAL_X = War3ID.fromString("ubpx");
@ -62,7 +64,7 @@ public class AbilityDataUI {
private static final War3ID UPGRADE_UBER_TIP = War3ID.fromString("gub1");
private final Map<War3ID, AbilityUI> rawcodeToUI = new HashMap<>();
private final Map<War3ID, IconUI> rawcodeToUnitUI = new HashMap<>();
private final Map<War3ID, UnitIconUI> rawcodeToUnitUI = new HashMap<>();
private final Map<War3ID, ItemUI> rawcodeToItemUI = new HashMap<>();
private final Map<War3ID, List<IconUI>> rawcodeToUpgradeUI = new HashMap<>();
private final IconUI moveUI;
@ -138,11 +140,13 @@ public class AbilityDataUI {
final int iconNormalX = abilityTypeData.getFieldAsInteger(UNIT_ICON_NORMAL_X, 0);
final int iconNormalY = abilityTypeData.getFieldAsInteger(UNIT_ICON_NORMAL_Y, 0);
final String iconTip = abilityTypeData.getFieldAsString(UNIT_TIP, 0);
final String reviveTip = abilityTypeData.getFieldAsString(UNIT_REVIVE_TIP, 0);
final String awakenTip = abilityTypeData.getFieldAsString(UNIT_AWAKEN_TIP, 0);
final String iconUberTip = abilityTypeData.getFieldAsString(UNIT_UBER_TIP, 0);
final Texture iconNormal = gameUI.loadTexture(iconNormalPath);
final Texture iconNormalDisabled = gameUI.loadTexture(disable(iconNormalPath, disabledPrefix));
this.rawcodeToUnitUI.put(alias,
new IconUI(iconNormal, iconNormalDisabled, iconNormalX, iconNormalY, iconTip, iconUberTip));
new UnitIconUI(iconNormal, iconNormalDisabled, iconNormalX, iconNormalY, iconTip, iconUberTip, reviveTip, awakenTip));
}
for (final War3ID alias : itemData.keySet()) {
final MutableGameObject abilityTypeData = itemData.get(alias);
@ -212,7 +216,7 @@ public class AbilityDataUI {
return this.rawcodeToUI.get(rawcode);
}
public IconUI getUnitUI(final War3ID rawcode) {
public UnitIconUI getUnitUI(final War3ID rawcode) {
return this.rawcodeToUnitUI.get(rawcode);
}

View File

@ -0,0 +1,22 @@
package com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability;
import com.badlogic.gdx.graphics.Texture;
public class UnitIconUI extends IconUI {
private String reviveTip;
private String awakenTip;
public UnitIconUI(Texture icon, Texture iconDisabled, int buttonPositionX, int buttonPositionY, String toolTip, String uberTip, String reviveTip, String awakenTip) {
super(icon, iconDisabled, buttonPositionX, buttonPositionY, toolTip, uberTip);
this.reviveTip = reviveTip;
this.awakenTip = awakenTip;
}
public String getReviveTip() {
return reviveTip;
}
public String getAwakenTip() {
return awakenTip;
}
}

View File

@ -5,6 +5,7 @@ import com.etheller.warsmash.util.War3ID;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityUI;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.IconUI;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.UnitIconUI;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType;
@ -27,6 +28,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.G
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CAbilityHero;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityReviveHero;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer;
@ -42,20 +44,24 @@ public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor<Void
private CommandButtonListener commandButtonListener;
private AbilityDataUI abilityDataUI;
private int menuBaseOrderId;
private boolean multiSelect;
private boolean hasStop;
private final CommandCardActivationReceiverPreviewCallback previewCallback = new CommandCardActivationReceiverPreviewCallback();
private GameUI gameUI;
private boolean hasCancel ;
public CommandCardPopulatingAbilityVisitor reset(final CSimulation game, final GameUI gameUI, final CUnit unit,
final CommandButtonListener commandButtonListener, final AbilityDataUI abilityDataUI,
final int menuBaseOrderId) {
final int menuBaseOrderId, boolean multiSelect) {
this.game = game;
this.gameUI = gameUI;
this.unit = unit;
this.commandButtonListener = commandButtonListener;
this.abilityDataUI = abilityDataUI;
this.menuBaseOrderId = menuBaseOrderId;
this.multiSelect = multiSelect;
this.hasStop = false;
hasCancel = false;
return this;
}
@ -207,6 +213,9 @@ public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor<Void
}
}
else {
if(multiSelect) {
return;
}
addCommandButton(ability, buildUI, ability.getHandleId(), ability.getBaseOrderId(), 0, false, true);
}
}
@ -217,8 +226,14 @@ public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor<Void
}
private void addCommandButton(final CAbility ability, final IconUI iconUI, final int handleId, final int orderId,
final int autoCastOrderId, final boolean autoCastActive, final boolean menuButton, int goldCost,
int lumberCost, int foodCost) {
final int autoCastOrderId, final boolean autoCastActive, final boolean menuButton, int goldCost,
int lumberCost, int foodCost) {
addCommandButton(ability, iconUI, iconUI.getToolTip(), iconUI.getButtonPositionX(), iconUI.getButtonPositionY(), handleId, orderId, autoCastOrderId, autoCastActive, menuButton, goldCost, lumberCost, foodCost);
}
private void addCommandButton(final CAbility ability, final IconUI iconUI, String toolTip, int buttonPosX, int buttonPosY, final int handleId, final int orderId,
final int autoCastOrderId, final boolean autoCastActive, final boolean menuButton, int goldCost,
int lumberCost, int foodCost) {
ability.checkCanUse(this.game, this.unit, orderId, this.previewCallback.reset());
final boolean active = ((this.unit.getCurrentBehavior() != null)
&& (orderId == this.unit.getCurrentBehavior().getHighlightOrderId()));
@ -235,7 +250,7 @@ public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor<Void
}
this.commandButtonListener.commandButton(iconUI.getButtonPositionX(), iconUI.getButtonPositionY(),
disabled ? iconUI.getIconDisabled() : iconUI.getIcon(), handleId, disabled ? 0 : orderId,
autoCastOrderId, active, autoCastActive, menuButton, iconUI.getToolTip(), uberTip, goldCost, lumberCost,
autoCastOrderId, active, autoCastActive, menuButton, toolTip, uberTip, goldCost, lumberCost,
foodCost);
}
@ -248,6 +263,36 @@ public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor<Void
return null;
}
@Override
public Void accept(CAbilityReviveHero ability) {
if ((this.menuBaseOrderId == 0) && ability.isIconShowing()) {
int heroIndex = 0;
for(CUnit playerHero: game.getPlayerHeroes(unit.getPlayerIndex())) {
CAbilityHero heroData = playerHero.getHeroData();
if(playerHero.isDead() && heroData != null && heroData.isAwaitingRevive()) {
UnitIconUI unitUI = this.abilityDataUI.getUnitUI(playerHero.getTypeId());
if (unitUI != null) {
final CUnitType simulationUnitType = playerHero.getUnitType();
int goldCost = game.getGameplayConstants().getHeroReviveGoldCost(simulationUnitType.getGoldCost(), heroData.getHeroLevel());
int lumberCost = game.getGameplayConstants().getHeroReviveLumberCost(simulationUnitType.getLumberCost(), heroData.getHeroLevel());
addCommandButton(ability, unitUI, unitUI.getReviveTip() + " - " + heroData.getProperName(), heroIndex++, 0, ability.getHandleId(), playerHero.getHandleId(), 0, false, false,
goldCost, lumberCost,
simulationUnitType.getFoodUsed());
}
}
}
if (this.unit.getBuildQueueTypes()[0] != null) {
if(!hasCancel) {
hasCancel = true;
addCommandButton(ability, this.abilityDataUI.getCancelTrainUI(), ability.getHandleId(), OrderIds.cancel,
0, false, false);
}
}
}
return null;
}
@Override
public Void accept(final CAbilityQueue ability) {
if ((this.menuBaseOrderId == 0) && ability.isIconShowing()) {
@ -270,8 +315,11 @@ public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor<Void
}
}
if (this.unit.getBuildQueueTypes()[0] != null) {
addCommandButton(ability, this.abilityDataUI.getCancelTrainUI(), ability.getHandleId(), OrderIds.cancel,
0, false, false);
if(!hasCancel) {
hasCancel = true;
addCommandButton(ability, this.abilityDataUI.getCancelTrainUI(), ability.getHandleId(), OrderIds.cancel,
0, false, false);
}
}
}
return null;

View File

@ -29,8 +29,36 @@ public class CGameplayConstants {
private final float defenseArmor;
private final int heroMaxReviveCostGold;
private final int heroMaxReviveCostLumber;
private final int heroMaxReviveTime;
private final int heroMaxAwakenCostGold;
private final int heroMaxAwakenCostLumber;
private final float heroReviveManaStart;
private final float heroReviveManaFactor;
private final float heroReviveLifeFactor;
private final float heroAwakenManaStart;
private final float heroAwakenManaFactor;
private final float heroAwakenLifeFactor;
private final int heroExpRange;
private final float reviveBaseFactor;
private final float reviveLevelFactor;
private final float reviveBaseLumberFactor;
private final float reviveLumberLevelFactor;
private final float reviveMaxFactor;
private final float reviveTimeFactor;
private final float reviveMaxTimeFactor;
private final float awakenBaseFactor;
private final float awakenLevelFactor;
private final float awakenBaseLumberFactor;
private final float awakenLumberLevelFactor;
private final float awakenMaxFactor;
private final int maxHeroLevel;
private final int maxUnitLevel;
private final int[] needHeroXp;
@ -123,8 +151,37 @@ public class CGameplayConstants {
this.maxLevelHeroesDrainExp = miscData.getFieldValue("MaxLevelHeroesDrainExp") != 0;
this.buildingKillsGiveExp = miscData.getFieldValue("BuildingKillsGiveExp") != 0;
this.heroMaxReviveCostGold = miscData.getFieldValue("HeroMaxReviveCostGold");
this.heroMaxReviveCostLumber = miscData.getFieldValue("HeroMaxReviveCostLumber");
this.heroMaxReviveTime = miscData.getFieldValue("HeroMaxReviveTime");
this.heroMaxAwakenCostGold = miscData.getFieldValue("HeroMaxAwakenCostGold");
this.heroMaxAwakenCostLumber = miscData.getFieldValue("HeroMaxAwakenCostLumber");
this.heroReviveManaStart = miscData.getFieldFloatValue("HeroReviveManaStart");
this.heroReviveManaFactor = miscData.getFieldFloatValue("HeroReviveManaFactor");
this.heroReviveLifeFactor = miscData.getFieldFloatValue("HeroReviveLifeFactor");
this.heroAwakenManaStart = miscData.getFieldFloatValue("HeroAwakenManaStart");
this.heroAwakenManaFactor = miscData.getFieldFloatValue("HeroAwakenManaFactor");
this.heroAwakenLifeFactor = miscData.getFieldFloatValue("HeroAwakenLifeFactor");
this.heroExpRange = miscData.getFieldValue("HeroExpRange");
this.reviveBaseFactor = miscData.getFieldFloatValue("ReviveBaseFactor");
this.reviveLevelFactor = miscData.getFieldFloatValue("ReviveLevelFactor");
this.reviveBaseLumberFactor = miscData.getFieldFloatValue("ReviveBaseLumberFactor");
this.reviveLumberLevelFactor = miscData.getFieldFloatValue("ReviveLumberLevelFactor");
this.reviveMaxFactor = miscData.getFieldFloatValue("ReviveMaxFactor");
this.reviveTimeFactor = miscData.getFieldFloatValue("ReviveTimeFactor");
this.reviveMaxTimeFactor = miscData.getFieldFloatValue("ReviveMaxTimeFactor");
this.awakenBaseFactor = miscData.getFieldFloatValue("AwakenBaseFactor");
this.awakenLevelFactor = miscData.getFieldFloatValue("AwakenLevelFactor");
this.awakenBaseLumberFactor = miscData.getFieldFloatValue("AwakenBaseLumberFactor");
this.awakenLumberLevelFactor = miscData.getFieldFloatValue("AwakenLumberLevelFactor");
this.awakenMaxFactor = miscData.getFieldFloatValue("AwakenMaxFactor");
this.maxHeroLevel = miscData.getFieldValue("MaxHeroLevel");
this.maxUnitLevel = miscData.getFieldValue("MaxUnitLevel");
@ -364,6 +421,33 @@ public class CGameplayConstants {
return this.spellCastRangeBuffer;
}
public int getHeroReviveGoldCost(int originalCost, int level) {
int goldRevivalCost = (int)(originalCost * (reviveBaseFactor + (reviveLevelFactor * (level-1))));
return Math.min(goldRevivalCost, (int)(originalCost * reviveMaxFactor));
}
public int getHeroReviveLumberCost(int originalCost, int level) {
int lumberRevivalCost = (int)(originalCost * (reviveBaseLumberFactor + (reviveLumberLevelFactor * (level-1))));
return Math.min(lumberRevivalCost, (int)(originalCost * reviveMaxFactor));
}
public int getHeroReviveTime(int originalTime, int level) {
int revivalTime = (int)(originalTime * level * reviveTimeFactor);
return Math.min(revivalTime, (int)(originalTime * reviveMaxTimeFactor));
}
public float getHeroReviveLifeFactor() {
return heroReviveLifeFactor;
}
public float getHeroReviveManaFactor() {
return heroReviveManaFactor;
}
public float getHeroReviveManaStart() {
return heroReviveManaStart;
}
private static int getTableValue(final int[] table, int level) {
if (level <= 0) {
return 0;

View File

@ -11,7 +11,9 @@ public interface CPlayerStateListener {
void upkeepChanged();
public static final class CPlayerStateNotifier extends SubscriberSetNotifier<CPlayerStateListener>
void heroDeath();
public static final class CPlayerStateNotifier extends SubscriberSetNotifier<CPlayerStateListener>
implements CPlayerStateListener {
@Override
public void goldChanged() {
@ -40,5 +42,12 @@ public interface CPlayerStateListener {
listener.upkeepChanged();
}
}
@Override
public void heroDeath() {
for (final CPlayerStateListener listener : set) {
listener.heroDeath();
}
}
}
}

View File

@ -114,7 +114,22 @@ public class CSimulation implements CPlayerAPI {
for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) {
final CBasePlayer configPlayer = config.getPlayer(i);
final War3MapConfigStartLoc startLoc = config.getStartLoc(configPlayer.getStartLocationIndex());
final CPlayer newPlayer = new CPlayer(CRace.UNDEAD, new float[] { startLoc.getX(), startLoc.getY() },
CRace defaultRace;
switch(i) {
case 0:
defaultRace = CRace.NIGHTELF;
break;
case 1:
defaultRace = CRace.UNDEAD;
break;
case 2:
defaultRace = CRace.HUMAN;
break;
default:
defaultRace = CRace.OTHER;
break;
}
final CPlayer newPlayer = new CPlayer(defaultRace, new float[] { startLoc.getX(), startLoc.getY() },
configPlayer);
if(i < 3) {
for(int j = 0; j < 3; j++) {
@ -189,6 +204,9 @@ public class CSimulation implements CPlayerAPI {
this.newUnits.add(unit);
this.handleIdToUnit.put(unit.getHandleId(), unit);
this.worldCollision.addUnit(unit);
if(unit.getHeroData() != null) {
heroCreateEvent(unit);
}
return unit;
}
@ -389,6 +407,10 @@ public class CSimulation implements CPlayerAPI {
public void unitTrainedEvent(final CUnit trainingUnit, final CUnit trainedUnit) {
this.simulationRenderController.spawnUnitReadySound(trainedUnit);
}
public void heroReviveEvent(final CUnit trainingUnit, final CUnit trainedUnit) {
this.simulationRenderController.heroRevived(trainedUnit);
this.simulationRenderController.spawnUnitReadySound(trainedUnit);
}
public void unitRepositioned(final CUnit cUnit) {
this.simulationRenderController.unitRepositioned(cUnit);
@ -471,6 +493,10 @@ public class CSimulation implements CPlayerAPI {
};
}
public void heroDeathEvent(CUnit cUnit) {
getPlayer(cUnit.getPlayerIndex()).onHeroDeath();
}
private static final class TimeOfDayVariableEvent extends VariableEvent {
private final GlobalScope globalScope;

View File

@ -92,8 +92,7 @@ public class CUnit extends CWidget {
// questionable -- it already was -- but I meant for those to inform us
// which fields shouldn't be persisted if we do game state save later
private transient CUnitStateNotifier stateNotifier = new CUnitStateNotifier();
private transient List<CUnitStateListener> stateListenersNeedingAdd = new ArrayList<>();
private transient List<CUnitStateListener> stateListenersNeedingRemove = new ArrayList<>();
private transient List<StateListenerUpdate> stateListenersUpdates = new ArrayList<>();
private final float acquisitionRange;
private transient static AutoAttackTargetFinderEnum autoAttackTargetFinderEnum = new AutoAttackTargetFinderEnum();
@ -246,14 +245,16 @@ public class CUnit extends CWidget {
* this unit from the game.
*/
public boolean update(final CSimulation game) {
for (final CUnitStateListener listener : this.stateListenersNeedingAdd) {
this.stateNotifier.subscribe(listener);
for(StateListenerUpdate update: this.stateListenersUpdates) {
switch(update.getUpdateType()) {
case ADD:
stateNotifier.subscribe(update.listener);
break;
case REMOVE:
stateNotifier.unsubscribe(update.listener);
break;
}
}
this.stateListenersNeedingAdd.clear();
for (final CUnitStateListener listener : this.stateListenersNeedingRemove) {
this.stateNotifier.unsubscribe(listener);
}
this.stateListenersNeedingRemove.clear();
if (isDead()) {
if (this.collisionRectangle != null) {
// Moved this here because doing it on "kill" was able to happen in some cases
@ -273,6 +274,14 @@ public class CUnit extends CWidget {
if (!this.unitType.isDecay()) {
// if we dont raise AND dont decay, then now that death anim is over
// we just delete the unit
if(unitType.isHero()) {
if(!getHeroData().isAwaitingRevive()) {
setHidden(true);
getHeroData().setAwaitingRevive(true);
game.heroDeathEvent(this);
}
return false;
}
return true;
}
this.deathTurnTick = gameTurnTick;
@ -287,6 +296,14 @@ public class CUnit extends CWidget {
}
else if (game.getGameTurnTick() > (this.deathTurnTick
+ (int) (getEndingDecayTime(game) / WarsmashConstants.SIMULATION_STEP_TIME))) {
if(unitType.isHero()) {
if(!getHeroData().isAwaitingRevive()) {
setHidden(true);
getHeroData().setAwaitingRevive(true);
game.heroDeathEvent(this);
}
return false;
}
return true;
}
}
@ -340,6 +357,15 @@ public class CUnit extends CWidget {
this.queuedUnitFoodPaid = true;
}
}
else if (this.buildQueueTypes[0] == QueueItemType.HERO_REVIVE) {
final CPlayer player = game.getPlayer(this.playerIndex);
final CUnitType trainedUnitType = game.getUnit(queuedRawcode.getValue()).getUnitType();
final int newFoodUsed = player.getFoodUsed() + trainedUnitType.getFoodUsed();
if (newFoodUsed <= player.getFoodCap()) {
player.setFoodUsed(newFoodUsed);
this.queuedUnitFoodPaid = true;
}
}
else {
this.queuedUnitFoodPaid = true;
System.err.println(
@ -372,6 +398,42 @@ public class CUnit extends CWidget {
this.stateNotifier.queueChanged();
}
}
else if (this.buildQueueTypes[0] == QueueItemType.HERO_REVIVE) {
CUnit revivingHero = game.getUnit(queuedRawcode.getValue());
final CUnitType trainedUnitType = revivingHero.getUnitType();
CGameplayConstants gameplayConstants = game.getGameplayConstants();
if (this.constructionProgress >= gameplayConstants.getHeroReviveTime(trainedUnitType.getBuildTime(), revivingHero.getHeroData().getHeroLevel())) {
this.constructionProgress = 0;
revivingHero.corpse = false;
revivingHero.boneCorpse = false;
revivingHero.deathTurnTick = 0;
revivingHero.setX(getX());
revivingHero.setY(getY());
game.getWorldCollision().addUnit(revivingHero);
revivingHero.setPoint(getX(), getY(), game.getWorldCollision(), game.getRegionManager());
revivingHero.setHidden(false);
revivingHero.setLife(game, revivingHero.getMaximumLife() * gameplayConstants.getHeroReviveLifeFactor());
revivingHero.setMana( revivingHero.getMaximumMana() * gameplayConstants.getHeroReviveManaFactor() + gameplayConstants.getHeroReviveManaStart() * trainedUnitType.getManaInitial());
// dont add food cost to player 2x
revivingHero.setFoodUsed(trainedUnitType.getFoodUsed());
final CPlayer player = game.getPlayer(this.playerIndex);
player.setUnitFoodMade(revivingHero, trainedUnitType.getFoodMade());
player.addTechtreeUnlocked(queuedRawcode);
// nudge the trained unit out around us
revivingHero.nudgeAround(game, this);
game.heroReviveEvent(this, revivingHero);
if (this.rallyPoint != null) {
final int rallyOrderId = OrderIds.smart;
this.rallyPoint.visit(
UseAbilityOnTargetByIdVisitor.INSTANCE.reset(game, revivingHero, rallyOrderId));
}
for (int i = 0; i < (this.buildQueue.length - 1); i++) {
setBuildQueueItem(game, i, this.buildQueue[i + 1], this.buildQueueTypes[i + 1]);
}
setBuildQueueItem(game, this.buildQueue.length - 1, null, null);
this.stateNotifier.queueChanged();
}
}
else if (this.buildQueueTypes[0] == QueueItemType.RESEARCH) {
final CUnitType trainedUnitType = game.getUnitData().getUnitType(queuedRawcode);
if (this.constructionProgress >= trainedUnitType.getBuildTime()) {
@ -1022,11 +1084,11 @@ public class CUnit extends CWidget {
}
public void addStateListener(final CUnitStateListener listener) {
this.stateListenersNeedingAdd.add(listener);
this.stateListenersUpdates.add(new StateListenerUpdate(listener, StateListenerUpdateType.ADD));
}
public void removeStateListener(final CUnitStateListener listener) {
this.stateListenersNeedingRemove.add(listener);
this.stateListenersUpdates.add(new StateListenerUpdate(listener, StateListenerUpdateType.REMOVE));
}
public boolean isCorpse() {
@ -1089,11 +1151,12 @@ public class CUnit extends CWidget {
@Override
public boolean call(final CUnit unit) {
if (!this.game.getPlayer(this.source.getPlayerIndex()).hasAlliance(unit.getPlayerIndex(),
CAllianceType.PASSIVE)) {
CAllianceType.PASSIVE) && !unit.isDead() && !unit.isInvulnerable()) {
for (final CUnitAttack attack : this.source.getAttacks()) {
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.distance(unit) >= this.source.getUnitType().getMinimumAttackRange())
) {
if (this.source.currentBehavior != null) {
this.source.currentBehavior.end(this.game, false);
}
@ -1260,9 +1323,15 @@ public class CUnit extends CWidget {
switch (this.buildQueueTypes[0]) {
case RESEARCH:
return 999; // TODO
case UNIT:
case UNIT: {
final CUnitType trainedUnitType = simulation.getUnitData().getUnitType(this.buildQueue[0]);
return trainedUnitType.getBuildTime();
}
case HERO_REVIVE: {
CUnit hero = simulation.getUnit(this.buildQueue[0].getValue());
final CUnitType trainedUnitType = hero.getUnitType();
return simulation.getGameplayConstants().getHeroReviveTime(trainedUnitType.getBuildTime(), hero.getHeroData().getHeroLevel());
}
default:
return 0;
}
@ -1278,22 +1347,41 @@ public class CUnit extends CWidget {
switch (cancelledType) {
case RESEARCH:
break;
case UNIT:
case UNIT: {
final CPlayer player = game.getPlayer(this.playerIndex);
final CUnitType unitType = game.getUnitData().getUnitType(this.buildQueue[cancelIndex]);
player.setFoodUsed(player.getFoodUsed() - unitType.getFoodUsed());
break;
}
case HERO_REVIVE: {
final CPlayer player = game.getPlayer(this.playerIndex);
final CUnitType unitType = game.getUnit(this.buildQueue[cancelIndex].getValue()).getUnitType();
player.setFoodUsed(player.getFoodUsed() - unitType.getFoodUsed());
break;
}
}
}
switch (cancelledType) {
case RESEARCH:
break;
case UNIT:
case UNIT: {
final CPlayer player = game.getPlayer(this.playerIndex);
final CUnitType unitType = game.getUnitData().getUnitType(this.buildQueue[cancelIndex]);
player.refundFor(unitType);
break;
}
case HERO_REVIVE: {
final CPlayer player = game.getPlayer(this.playerIndex);
CUnit hero = game.getUnit(this.buildQueue[cancelIndex].getValue());
final CUnitType unitType = hero.getUnitType();
CAbilityHero heroData = hero.getHeroData();
heroData.setAwaitingRevive(true);
CGameplayConstants gameplayConstants = game.getGameplayConstants();
player.refund(gameplayConstants.getHeroReviveGoldCost(unitType.getGoldCost(), heroData.getHeroLevel()),
gameplayConstants.getHeroReviveLumberCost(unitType.getLumberCost(), heroData.getHeroLevel()));
break;
}
}
for (int i = cancelIndex; i < (this.buildQueueTypes.length - 1); i++) {
setBuildQueueItem(game, i, this.buildQueue[i + 1], this.buildQueueTypes[i + 1]);
}
@ -1309,17 +1397,32 @@ public class CUnit extends CWidget {
this.buildQueueTypes[index] = queueItemType;
if (index == 0) {
this.queuedUnitFoodPaid = true;
if ((rawcode != null) && (queueItemType == QueueItemType.UNIT)) {
final CPlayer player = game.getPlayer(this.playerIndex);
final CUnitType unitType = game.getUnitData().getUnitType(this.buildQueue[index]);
if (unitType.getFoodUsed() != 0) {
final int newFoodUsed = player.getFoodUsed() + unitType.getFoodUsed();
if (newFoodUsed <= player.getFoodCap()) {
player.setFoodUsed(newFoodUsed);
if (rawcode != null) {
if(queueItemType == QueueItemType.UNIT) {
final CPlayer player = game.getPlayer(this.playerIndex);
final CUnitType unitType = game.getUnitData().getUnitType(this.buildQueue[index]);
if (unitType.getFoodUsed() != 0) {
final int newFoodUsed = player.getFoodUsed() + unitType.getFoodUsed();
if (newFoodUsed <= player.getFoodCap()) {
player.setFoodUsed(newFoodUsed);
}
else {
this.queuedUnitFoodPaid = false;
game.getCommandErrorListener(this.playerIndex).showNoFoodError();
}
}
else {
this.queuedUnitFoodPaid = false;
game.getCommandErrorListener(this.playerIndex).showNoFoodError();
} else if(queueItemType == QueueItemType.HERO_REVIVE) {
final CPlayer player = game.getPlayer(this.playerIndex);
final CUnitType unitType = game.getUnit(this.buildQueue[index].getValue()).getUnitType();
if (unitType.getFoodUsed() != 0) {
final int newFoodUsed = player.getFoodUsed() + unitType.getFoodUsed();
if (newFoodUsed <= player.getFoodCap()) {
player.setFoodUsed(newFoodUsed);
}
else {
this.queuedUnitFoodPaid = false;
game.getCommandErrorListener(this.playerIndex).showNoFoodError();
}
}
}
}
@ -1334,13 +1437,24 @@ public class CUnit extends CWidget {
}
}
public void queueRevivingHero(final CSimulation game, final CUnit hero) {
if (queue(game, new War3ID(hero.getHandleId()), QueueItemType.HERO_REVIVE)) {
hero.getHeroData().setAwaitingRevive(false);
final CPlayer player = game.getPlayer(this.playerIndex);
int heroReviveGoldCost = game.getGameplayConstants().getHeroReviveGoldCost(hero.getUnitType().getGoldCost(), hero.getHeroData().getHeroLevel());
int heroReviveLumberCost = game.getGameplayConstants().getHeroReviveLumberCost(hero.getUnitType().getGoldCost(), hero.getHeroData().getHeroLevel());
player.charge(heroReviveGoldCost, heroReviveLumberCost);
}
}
public void queueResearch(final CSimulation game, final War3ID rawcode) {
queue(game, rawcode, QueueItemType.RESEARCH);
}
public static enum QueueItemType {
UNIT,
RESEARCH;
RESEARCH,
HERO_REVIVE;
}
public void setRallyPoint(final AbilityTarget target) {
@ -1576,4 +1690,26 @@ public class CUnit extends CWidget {
setLife(simulation, 0);
simulation.getWorldCollision().removeUnit(this);
}
private static enum StateListenerUpdateType {
ADD, REMOVE;
}
private static final class StateListenerUpdate {
private CUnitStateListener listener;
private StateListenerUpdateType updateType;
public StateListenerUpdate(CUnitStateListener listener, StateListenerUpdateType updateType) {
this.listener = listener;
this.updateType = updateType;
}
public CUnitStateListener getListener() {
return listener;
}
public StateListenerUpdateType getUpdateType() {
return updateType;
}
}
}

View File

@ -75,6 +75,7 @@ public class CUnitType {
private final int properNamesCount;
private final boolean canFlee;
private final int priority;
private boolean revivesHeroes;
public CUnitType(final String name, final String legacyName, final War3ID typeId, final int maxLife,
final int manaInitial, final int manaMaximum, final int speed, final int defense, final String abilityList,
@ -92,7 +93,7 @@ public class CUnitType {
final float strengthPerLevel, final int agility, final float agilityPerLevel, final int intelligence,
final float intelligencePerLevel, final CPrimaryAttribute primaryAttribute,
final List<War3ID> heroAbilityList, final List<String> heroProperNames, final int properNamesCount,
final boolean canFlee, final int priority) {
final boolean canFlee, final int priority, boolean revivesHeroes) {
this.name = name;
this.legacyName = legacyName;
this.typeId = typeId;
@ -146,6 +147,7 @@ public class CUnitType {
this.properNamesCount = properNamesCount;
this.canFlee = canFlee;
this.priority = priority;
this.revivesHeroes = revivesHeroes;
}
public String getName() {
@ -359,4 +361,8 @@ public class CUnitType {
public int getPriority() {
return this.priority;
}
public boolean isRevivesHeroes() {
return revivesHeroes;
}
}

View File

@ -33,6 +33,7 @@ public class CAbilityMove extends AbstractCAbility {
switch (orderId) {
case OrderIds.smart:
case OrderIds.patrol:
case OrderIds.move:
if ((target instanceof CUnit) && (target != unit)) {
receiver.targetOk(target);
}
@ -98,7 +99,9 @@ public class CAbilityMove extends AbstractCAbility {
@Override
public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) {
return caster.getFollowBehavior().reset(OrderIds.move, (CUnit) target);
CBehavior followBehavior = caster.getFollowBehavior().reset(orderId == OrderIds.smart ? OrderIds.move : orderId, (CUnit) target);
caster.setDefaultBehavior(followBehavior);
return followBehavior;
}
@Override

View File

@ -13,6 +13,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.G
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CAbilityHero;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityReviveHero;
/**
* A visitor for the lowest level inherent types of an ability. It's a bit of a
@ -47,6 +48,8 @@ public interface CAbilityVisitor<T> {
T accept(CAbilityQueue ability);
T accept(CAbilityReviveHero ability);
T accept(GenericSingleIconActiveAbility ability);
T accept(CAbilityRally ability);

View File

@ -28,6 +28,7 @@ public class CAbilityHero extends AbstractCAbility {
private HeroStatValue agility;
private HeroStatValue intelligence;
private String properName;
private boolean awaitingRevive;
public CAbilityHero(final int handleId, final List<War3ID> skillsAvailable) {
super(handleId);
@ -168,6 +169,14 @@ public class CAbilityHero extends AbstractCAbility {
return this.properName;
}
public void setAwaitingRevive(boolean awaitingRevive) {
this.awaitingRevive = awaitingRevive;
}
public boolean isAwaitingRevive() {
return awaitingRevive;
}
public void addXp(final CSimulation simulation, final CUnit unit, final int xp) {
this.xp += xp;
final CGameplayConstants gameplayConstants = simulation.getGameplayConstants();
@ -232,7 +241,7 @@ public class CAbilityHero extends AbstractCAbility {
unit.setAgilityDefenseBonus(agilityDefenseBonus);
}
public static final class HeroStatValue {
public static final class HeroStatValue {
private final float perLevelFactor;
private int base;
private int bonus;

View File

@ -0,0 +1,142 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue;
import com.etheller.warsmash.util.War3ID;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.*;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CAbilityHero;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
public final class CAbilityReviveHero extends AbstractCAbility {
public CAbilityReviveHero(final int handleId) {
super(handleId);
}
@Override
protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId,
final AbilityActivationReceiver receiver) {
CUnit deadHero = game.getUnit(orderId);
if (deadHero != null
&& deadHero.getPlayerIndex()==unit.getPlayerIndex()) {
CAbilityHero heroData = deadHero.getHeroData();
if( heroData != null && heroData.isAwaitingRevive()) {
final CPlayer player = game.getPlayer(unit.getPlayerIndex());
int heroReviveGoldCost = game.getGameplayConstants().getHeroReviveGoldCost(deadHero.getUnitType().getGoldCost(), heroData.getHeroLevel());
int heroReviveLumberCost = game.getGameplayConstants().getHeroReviveLumberCost(deadHero.getUnitType().getGoldCost(), heroData.getHeroLevel());
if (player.getGold() >= heroReviveGoldCost) {
if (player.getLumber() >= heroReviveLumberCost) {
if ((deadHero.getUnitType().getFoodUsed() == 0)
|| ((player.getFoodUsed() + deadHero.getUnitType().getFoodUsed()) <= player.getFoodCap())) {
receiver.useOk();
}
else {
receiver.notEnoughResources(ResourceType.FOOD);
}
}
else {
receiver.notEnoughResources(ResourceType.LUMBER);
}
}
else {
receiver.notEnoughResources(ResourceType.GOLD);
}
}
}
else {
/// ???
receiver.useOk();
}
}
@Override
public final void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target,
final AbilityTargetCheckReceiver<CWidget> receiver) {
receiver.orderIdNotAccepted();
}
@Override
public final void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId,
final AbilityPointTarget target, final AbilityTargetCheckReceiver<AbilityPointTarget> receiver) {
receiver.orderIdNotAccepted();
}
@Override
public final void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId,
final AbilityTargetCheckReceiver<Void> receiver) {
CUnit deadHero = game.getUnit(orderId);
if (deadHero != null
&& deadHero.getPlayerIndex()==unit.getPlayerIndex()) {
receiver.targetOk(null);
}
else if (orderId == OrderIds.cancel) {
receiver.targetOk(null);
}
else {
receiver.orderIdNotAccepted();
}
}
@Override
public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, AbilityTarget target) {
return true;
}
@Override
public void onAdd(final CSimulation game, final CUnit unit) {
}
@Override
public void onRemove(final CSimulation game, final CUnit unit) {
}
@Override
public void onTick(final CSimulation game, final CUnit unit) {
}
@Override
public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) {
return null;
}
@Override
public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId,
final AbilityPointTarget point) {
return null;
}
@Override
public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) {
if (orderId == OrderIds.cancel) {
caster.cancelBuildQueueItem(game, 0);
}
else {
caster.queueRevivingHero(game, game.getUnit(orderId));
}
return null;
}
@Override
public <T> T visit(final CAbilityVisitor<T> visitor) {
return visitor.accept(this);
}
@Override
public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) {
}
}

View File

@ -9,6 +9,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting
public class CBehaviorFollow extends CAbstractRangedBehavior {
private int higlightOrderId;
private boolean justAutoAttacked = false;
public CBehaviorFollow(final CUnit unit) {
super(unit);
@ -26,27 +27,37 @@ public class CBehaviorFollow extends CAbstractRangedBehavior {
@Override
public boolean isWithinRange(final CSimulation simulation) {
if(justAutoAttacked = this.unit.autoAcquireAttackTargets(simulation, false)) {
return true;
}
return this.unit.canReach(this.target, this.unit.getAcquisitionRange());
}
@Override
protected CBehavior update(final CSimulation simulation, final boolean withinFacingWindow) {
this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f, false);
return this;
}
@Override
protected CBehavior updateOnInvalidTarget(final CSimulation simulation) {
unit.setDefaultBehavior(unit.getStopBehavior());
return this.unit.pollNextOrderBehavior(simulation);
}
@Override
protected boolean checkTargetStillValid(final CSimulation simulation) {
if (this.justAutoAttacked) {
this.justAutoAttacked = false;
this.unit.getMoveBehavior().reset(target, this, false);
}
return this.target.visit(AbilityTargetStillAliveVisitor.INSTANCE);
}
@Override
protected void resetBeforeMoving(final CSimulation simulation) {
}
@Override

View File

@ -17,6 +17,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.G
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CAbilityHero;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityReviveHero;
public class AbilityDisableWhileUnderConstructionVisitor implements CAbilityVisitor<Void> {
public static final AbilityDisableWhileUnderConstructionVisitor INSTANCE = new AbilityDisableWhileUnderConstructionVisitor();
@ -105,6 +106,13 @@ public class AbilityDisableWhileUnderConstructionVisitor implements CAbilityVisi
return null;
}
@Override
public Void accept(CAbilityReviveHero ability) {
ability.setDisabled(true);
ability.setIconShowing(false);
return null;
}
@Override
public Void accept(final GenericSingleIconActiveAbility ability) {
ability.setDisabled(true);

View File

@ -33,6 +33,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CAbi
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CPrimaryAttribute;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityReviveHero;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType;
@ -143,6 +144,7 @@ public class CUnitData {
private static final War3ID STRUCTURES_BUILT = War3ID.fromString("ubui");
private static final War3ID UNITS_TRAINED = War3ID.fromString("utra");
private static final War3ID RESEARCHES_AVAILABLE = War3ID.fromString("ures");
private static final War3ID REVIVES_HEROES = War3ID.fromString("urev");
private static final War3ID UNIT_RACE = War3ID.fromString("urac");
private static final War3ID REQUIRES = War3ID.fromString("ureq");
@ -243,7 +245,10 @@ public class CUnitData {
if (!unitsTrained.isEmpty() || !researchesAvailable.isEmpty()) {
unit.add(simulation, new CAbilityQueue(handleIdAllocator.createId(), unitsTrained, researchesAvailable));
}
if (!unitsTrained.isEmpty()) {
if(unitTypeInstance.isRevivesHeroes()) {
unit.add(simulation, new CAbilityReviveHero(handleIdAllocator.createId()));
}
if (!unitsTrained.isEmpty() || unitTypeInstance.isRevivesHeroes()) {
unit.add(simulation, new CAbilityRally(handleIdAllocator.createId()));
}
if (unitTypeInstance.isHero()) {
@ -450,6 +455,8 @@ public class CUnitData {
final int foodUsed = unitType.getFieldAsInteger(FOOD_USED, 0);
final int foodMade = unitType.getFieldAsInteger(FOOD_MADE, 0);
boolean revivesHeroes = unitType.getFieldAsBoolean(REVIVES_HEROES, 0);
final String unitsTrainedString = unitType.getFieldAsString(UNITS_TRAINED, 0);
final String[] unitsTrainedStringItems = unitsTrainedString.trim().split(",");
final List<War3ID> unitsTrained = new ArrayList<>();
@ -538,7 +545,7 @@ public class CUnitData {
goldCost, lumberCost, foodUsed, foodMade, buildTime, preventedPathingTypes, requiredPathingTypes,
propWindow, turnRate, requirements, unitLevel, hero, strength, strPlus, agility, agiPlus,
intelligence, intPlus, primaryAttribute, heroAbilityList, heroProperNames, properNamesCount,
canFlee, priority);
canFlee, priority, revivesHeroes);
this.unitIdToUnitType.put(typeId, unitTypeInstance);
this.jassLegacyNameToUnitId.put(legacyName, typeId);
}

View File

@ -139,6 +139,13 @@ public class CPlayer extends CBasePlayer {
this.stateNotifier.goldChanged();
}
public void refund(int gold, int lumber) {
this.gold += gold;
this.lumber += lumber;
this.stateNotifier.lumberChanged();
this.stateNotifier.goldChanged();
}
public void setUnitFoodUsed(final CUnit unit, final int foodUsed) {
this.foodUsed += unit.setFoodUsed(foodUsed);
this.stateNotifier.foodChanged();
@ -148,4 +155,8 @@ public class CPlayer extends CBasePlayer {
this.foodCap += unit.setFoodMade(foodMade);
this.stateNotifier.foodChanged();
}
public void onHeroDeath() {
stateNotifier.heroDeath();
}
}

View File

@ -7,6 +7,11 @@ public class CWidgetAbilityTargetCheckReceiver implements AbilityTargetCheckRece
private CWidget target;
public CWidgetAbilityTargetCheckReceiver reset() {
this.target = null;
return this;
}
@Override
public void targetOk(final CWidget target) {
this.target = target;

View File

@ -7,6 +7,11 @@ public class PointAbilityTargetCheckReceiver implements AbilityTargetCheckReceiv
private AbilityPointTarget target;
public PointAbilityTargetCheckReceiver reset() {
this.target = null;
return this;
}
@Override
public void targetOk(final AbilityPointTarget target) {
this.target = target;

View File

@ -59,4 +59,6 @@ public interface SimulationRenderController {
void spawnAbilitySoundEffect(CUnit caster, War3ID alias);
void unitPreferredSelectionReplacement(CUnit unit, CUnit newUnit);
void heroRevived(CUnit trainedUnit);
}

View File

@ -89,6 +89,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderWidget;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.IconUI;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.ItemUI;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.UnitIconUI;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.CommandButtonListener;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CGameplayConstants;
@ -125,6 +126,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CPri
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.inventory.CAbilityInventory;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityReviveHero;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetVisitor;
@ -1009,8 +1011,29 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
abilityToUse.checkCanTargetNoTarget(this.war3MapViewer.simulation,
this.selectedUnit.getSimulationUnit(), orderId, noTargetReceiver);
if (noTargetReceiver.isTargetable()) {
boolean shiftDown = isShiftDown();
this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(),
abilityHandleId, orderId, isShiftDown());
abilityHandleId, orderId, shiftDown);
if(selectedUnits.size() > 1){
for(RenderUnit otherSelectedUnit: selectedUnits) {
if(otherSelectedUnit != activeCommandUnit) {
abilityToUse = null;
for(CAbility ability: otherSelectedUnit.getSimulationUnit().getAbilities()) {
BooleanAbilityTargetCheckReceiver<Void> receiver = BooleanAbilityTargetCheckReceiver.<Void>getInstance().reset();
ability.checkCanTargetNoTarget(war3MapViewer.simulation, otherSelectedUnit.getSimulationUnit(), activeCommandOrderId, receiver);
if(receiver.isTargetable()) {
abilityToUse = ability;
}
}
if(abilityToUse != null) {
this.unitOrderListener.issueImmediateOrder(
otherSelectedUnit.getSimulationUnit().getHandleId(),
abilityToUse.getHandleId(), this.activeCommandOrderId,
shiftDown);
}
}
}
}
}
else {
this.activeCommand = abilityToUse;
@ -1023,6 +1046,14 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
else {
this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(),
abilityHandleId, orderId, isShiftDown());
if(selectedUnits.size() > 1) {
for (RenderUnit otherSelectedUnit : selectedUnits) {
if (otherSelectedUnit != activeCommandUnit) {
this.unitOrderListener.issueImmediateOrder(otherSelectedUnit.getSimulationUnit().getHandleId(),
abilityHandleId, orderId, isShiftDown());
}
}
}
}
if (rightClick) {
this.war3MapViewer.getUiSounds().getSound("AutoCastButtonClick").play(this.uiScene.audioContext, 0, 0, 0);
@ -1106,7 +1137,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
}
this.hpBarFrameIndex = 0;
if (this.currentlyDraggingPointer == -1) {
if ((this.mouseOverUnit != null) && !this.mouseOverUnit.getSimulationWidget().isInvulnerable()) {
if ((this.mouseOverUnit != null) && !this.mouseOverUnit.getSimulationWidget().isInvulnerable() && this.mouseOverUnit.isSelectable() && !this.mouseOverUnit.getSimulationWidget().isDead()) {
final SimpleStatusBarFrame simpleStatusBarFrame = getHpBar();
positionHealthBar(simpleStatusBarFrame, this.mouseOverUnit, 1.0f);
}
@ -1265,7 +1296,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
simpleStatusBarFrame.getBarFrame().setColor(Math.min(1.0f, 2.0f - (lifeRatioRemaining * 2)),
Math.min(1.0f, lifeRatioRemaining * 2), 0, alpha);
final Vector2 unprojected = this.uiViewport.unproject(screenCoordsVector);
simpleStatusBarFrame.setWidth(unit.getSelectionScale() * 1.5f);
simpleStatusBarFrame.setWidth(unit.getSelectionScale() * 1.5f * Gdx.graphics.getWidth() / 2560);
simpleStatusBarFrame.setHeight(16);
simpleStatusBarFrame.addSetPoint(
new SetPoint(FramePoint.CENTER, this.rootFrame, FramePoint.BOTTOMLEFT, unprojected.x, unprojected.y));
@ -1450,6 +1481,12 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
return null;
}
@Override
public Void accept(CAbilityReviveHero ability) {
handleTargetCursor(ability);
return null;
}
@Override
public Void accept(final GenericSingleIconActiveAbility ability) {
handleTargetCursor(ability);
@ -1999,8 +2036,18 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
this.queueIconFrames[i].setToolTip(upgradeUI.getToolTip());
this.queueIconFrames[i].setUberTip(upgradeUI.getUberTip());
break;
case HERO_REVIVE: {
War3ID handleIdEncoded = simulationUnit.getBuildQueue()[i];
CUnit hero = war3MapViewer.simulation.getUnit(handleIdEncoded.getValue());
final UnitIconUI unitUI = this.war3MapViewer.getAbilityDataUI()
.getUnitUI(hero.getTypeId());
this.queueIconFrames[i].setTexture(unitUI.getIcon());
this.queueIconFrames[i].setToolTip(unitUI.getReviveTip() + " - " + hero.getHeroData().getProperName());
this.queueIconFrames[i].setUberTip(unitUI.getUberTip());
break;
}
case UNIT:
default:
default: {
final IconUI unitUI = this.war3MapViewer.getAbilityDataUI()
.getUnitUI(simulationUnit.getBuildQueue()[i]);
this.queueIconFrames[i].setTexture(unitUI.getIcon());
@ -2008,6 +2055,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
this.queueIconFrames[i].setUberTip(unitUI.getUberTip());
break;
}
}
}
}
this.simpleInfoPanelBuildingDetail.setVisible(!multiSelect);
@ -2021,6 +2069,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
if (simulationUnit.getBuildQueueTypes()[0] == QueueItemType.UNIT) {
this.rootFrame.setText(this.simpleBuildingBuildingActionLabel,
this.rootFrame.getTemplates().getDecoratedString("TRAINING"));
} else if (simulationUnit.getBuildQueueTypes()[0] == QueueItemType.HERO_REVIVE) {
this.rootFrame.setText(this.simpleBuildingBuildingActionLabel,
this.rootFrame.getTemplates().getDecoratedString("REVIVING"));
}
else {
this.rootFrame.setText(this.simpleBuildingBuildingActionLabel,
@ -2430,6 +2481,15 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
this.resourceBarUpkeepText.setColor(Color.CYAN);
}
@Override
public void heroDeath() {
if(this.selectedUnit!=null) {
if(this.selectedUnit.getSimulationUnit().getUnitType().isRevivesHeroes()) {
reloadSelectedUnitUI(this.selectedUnit);
}
}
}
@Override
public void ordersChanged() {
reloadSelectedUnitUI(this.selectedUnit);
@ -2474,7 +2534,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
0);
}
this.selectedUnit.populateCommandCard(this.war3MapViewer.simulation, this.rootFrame, this,
abilityDataUI, menuOrderId);
abilityDataUI, menuOrderId, selectedUnits.size() > 1);
}
}
}
@ -2539,6 +2599,28 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
this.activeCommandUnit.getSimulationUnit().getHandleId(),
this.activeCommand.getHandleId(), this.activeCommandOrderId,
rayPickUnit.getSimulationWidget().getHandleId(), shiftDown);
if(selectedUnits.size() > 1){
for(RenderUnit otherSelectedUnit: selectedUnits) {
if(otherSelectedUnit != activeCommandUnit) {
CAbility abilityToUse = null;
CWidget targetToUse = null;
for(CAbility ability: otherSelectedUnit.getSimulationUnit().getAbilities()) {
CWidgetAbilityTargetCheckReceiver receiver = CWidgetAbilityTargetCheckReceiver.INSTANCE.reset();
ability.checkCanTarget(war3MapViewer.simulation, otherSelectedUnit.getSimulationUnit(), activeCommandOrderId, rayPickUnit.getSimulationWidget(), receiver);
if(receiver.getTarget() != null) {
abilityToUse = ability;
targetToUse = receiver.getTarget();
}
}
if(abilityToUse != null) {
this.unitOrderListener.issueTargetOrder(
otherSelectedUnit.getSimulationUnit().getHandleId(),
abilityToUse.getHandleId(), this.activeCommandOrderId,
targetToUse.getHandleId(), shiftDown);
}
}
}
}
final UnitSound yesSound = (this.activeCommand instanceof CAbilityAttack)
? getSelectedUnit().soundset.yesAttack
: getSelectedUnit().soundset.yes;
@ -2597,6 +2679,28 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
this.activeCommandUnit.getSimulationUnit().getHandleId(),
this.activeCommand.getHandleId(), this.activeCommandOrderId,
clickLocationTemp2.x, clickLocationTemp2.y, shiftDown);
if(selectedUnits.size() > 1){
for(RenderUnit otherSelectedUnit: selectedUnits) {
if(otherSelectedUnit != activeCommandUnit) {
CAbility abilityToUse = null;
AbilityPointTarget targetToUse = null;
for(CAbility ability: otherSelectedUnit.getSimulationUnit().getAbilities()) {
PointAbilityTargetCheckReceiver receiver = PointAbilityTargetCheckReceiver.INSTANCE.reset();
ability.checkCanTarget(war3MapViewer.simulation, otherSelectedUnit.getSimulationUnit(), activeCommandOrderId, clickLocationTemp2, receiver);
if(receiver.getTarget() != null) {
abilityToUse = ability;
targetToUse = receiver.getTarget();
}
}
if(abilityToUse != null) {
this.unitOrderListener.issuePointOrder(
otherSelectedUnit.getSimulationUnit().getHandleId(),
abilityToUse.getHandleId(), this.activeCommandOrderId,
targetToUse.getX(), targetToUse.getY(), shiftDown);
}
}
}
}
if (getSelectedUnit().soundset.yes.playUnitResponse(
this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) {
portraitTalk();

View File

@ -764,7 +764,7 @@ public class MenuUI {
return;
}
else if (this.loadingMap != null) {
final int localPlayerIndex = 0;
final int localPlayerIndex = MultiplayerHack.LP_VAL;
try {
this.loadingMap.viewer.loadMap(this.loadingMap.map, this.loadingMap.mapInfo, localPlayerIndex);
}

View File

@ -43,9 +43,9 @@ public class DesktopLauncher {
config.gles30ContextMajorVersion = 3;
config.gles30ContextMinorVersion = 3;
// config.samples = 16;
config.vSyncEnabled = false;
config.foregroundFPS = 0;
config.backgroundFPS = 0;
// config.vSyncEnabled = false;
// config.foregroundFPS = 0;
// config.backgroundFPS = 0;
final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode();
config.width = desktopDisplayMode.width;
config.height = desktopDisplayMode.height;
@ -63,6 +63,10 @@ public class DesktopLauncher {
argIndex++;
MultiplayerHack.MULTIPLAYER_HACK_SERVER_ADDR = arg[argIndex];
}
else if ((arg.length > (argIndex + 1)) && "-lp".equals(arg[argIndex])) {
argIndex++;
MultiplayerHack.LP_VAL = Integer.parseInt(arg[argIndex]);
}
}
loadExtensions();
final DataTable warsmashIni = loadWarsmashIni();