From fdc78a105e6f7af40af35b18ff33045738eda26a Mon Sep 17 00:00:00 2001 From: Retera Date: Tue, 7 Sep 2021 00:32:04 -0400 Subject: [PATCH] Bunch of updates to Jass VM, added a bunch more natives and features to support them --- core/assets/warsmash.ini | 14 +- .../warsmash/WarsmashGdxMapScreen.java | 41 +- .../warsmash/WarsmashGdxMenuScreen.java | 3 +- .../networking/ClientToServerListener.java | 3 + .../networking/ClientToServerProtocol.java | 1 + .../networking/ServerToClientListener.java | 3 + .../networking/ServerToClientProtocol.java | 1 + .../warsmash/networking/WarsmashClient.java | 140 +- .../WarsmashClientSendingOrderListener.java | 8 + .../networking/WarsmashClientWriter.java | 13 + .../warsmash/networking/WarsmashServer.java | 16 + .../networking/WarsmashServerParser.java | 11 + .../networking/WarsmashServerWriter.java | 15 + .../etheller/warsmash/parsers/fdf/GameUI.java | 6 +- .../fdf/frames/SimpleStatusBarFrame.java | 12 + .../parsers/fdf/frames/SpriteFrame.java | 7 + .../parsers/fdf/frames/StringFrame.java | 6 + .../etheller/warsmash/parsers/jass/Jass2.java | 1218 ++++++++++++++++- .../scope/CommonTriggerExecutionScope.java | 100 +- .../objectdata/Warcraft3MapObjectData.java | 25 + .../warsmash/parsers/w3x/w3e/Corner.java | 4 + .../warsmash/util/WarsmashConstants.java | 5 +- .../warsmash/viewer5/AudioContext.java | 28 +- .../handlers/mdx/MdxComplexInstance.java | 18 +- .../viewer5/handlers/mdx/RibbonEmitter.java | 5 +- .../viewer5/handlers/w3x/UnitSound.java | 33 +- .../viewer5/handlers/w3x/War3MapViewer.java | 322 +++-- .../w3x/camera/GameCameraManager.java | 111 +- .../handlers/w3x/environment/PathingGrid.java | 18 + .../handlers/w3x/environment/Terrain.java | 21 +- .../w3x/rendersim/RenderDestructable.java | 48 +- .../handlers/w3x/rendersim/RenderDoodad.java | 24 +- .../handlers/w3x/rendersim/RenderItem.java | 13 +- .../w3x/rendersim/RenderSpellEffect.java | 53 + .../handlers/w3x/rendersim/RenderUnit.java | 13 + .../CommandCardPopulatingAbilityVisitor.java | 65 +- .../w3x/simulation/CDestructable.java | 39 +- .../handlers/w3x/simulation/CItem.java | 16 +- .../w3x/simulation/CPlayerStateListener.java | 13 +- .../handlers/w3x/simulation/CSimulation.java | 61 +- .../handlers/w3x/simulation/CUnit.java | 152 ++ .../handlers/w3x/simulation/CUnitType.java | 10 +- .../handlers/w3x/simulation/CWidget.java | 56 + .../simulation/abilities/CAbilityAttack.java | 4 + .../simulation/abilities/CAbilityVisitor.java | 3 + .../build/AbstractCAbilityBuild.java | 15 +- .../abilities/hero/CAbilityHero.java | 2 +- .../inventory/CAbilityInventory.java | 63 +- .../abilities/queue/CAbilityQueue.java | 15 +- .../abilities/upgrade/CAbilityUpgrade.java | 168 ++- ...yDisableWhileUnderConstructionVisitor.java | 10 +- .../inventory/CBehaviorGiveItemToHero.java | 75 + .../w3x/simulation/combat/CDefenseType.java | 3 + .../w3x/simulation/config/CBasePlayer.java | 4 + .../w3x/simulation/data/CUnitData.java | 25 +- .../orders/COrderDropItemAtTargetWidget.java | 105 ++ .../simulation/orders/COrderTargetWidget.java | 2 +- .../w3x/simulation/players/CPlayer.java | 204 ++- .../w3x/simulation/players/CPlayerEvent.java | 15 +- .../w3x/simulation/players/CPlayerState.java | 1 + .../players/CPlayerUnitOrderExecutor.java | 11 + .../players/CPlayerUnitOrderListener.java | 3 + .../w3x/simulation/players/CRace.java | 2 +- .../region/CRegionTriggerEnter.java | 4 +- .../region/CRegionTriggerLeave.java | 2 +- .../w3x/simulation/sound/CMIDISound.java | 19 + .../handlers/w3x/simulation/sound/CSound.java | 5 + .../w3x/simulation/sound/CSoundFilename.java | 64 + .../w3x/simulation/sound/CSoundFromLabel.java | 32 + .../w3x/simulation/timers/CTimerJass.java | 5 +- .../trigger/enumtypes/CDamageType.java | 3 +- .../w3x/simulation/unit/CWidgetEvent.java | 87 ++ .../util/AbilityActivationReceiver.java | 2 + .../BooleanAbilityActivationReceiver.java | 5 + .../MeleeUIAbilityActivationReceiver.java | 5 + .../util/SimulationRenderController.java | 7 + .../StringMsgAbilityActivationReceiver.java | 5 + .../viewer5/handlers/w3x/ui/MeleeUI.java | 906 +++++++++--- .../handlers/w3x/ui/MenuCursorState.java | 1 + .../handlers/w3x/ui/MultiSelectionIcon.java | 215 +++ .../command/MultiSelectionIconListener.java | 9 + .../handlers/w3x/ui/dialog/CTimerDialog.java | 46 + jassparser/antlr-src/Jass.g4 | 87 +- .../etheller/interpreter/ast/Assignable.java | 16 +- .../expression/ArithmeticJassExpression.java | 38 + .../ast/expression/ArithmeticSign.java | 26 + .../ast/expression/ArithmeticSigns.java | 528 +++++++ .../FunctionCallJassExpression.java | 7 +- .../ast/expression/NegateJassExpression.java | 22 + .../ast/function/JassParameter.java | 3 + .../ast/function/NativeJassFunction.java | 7 + .../ast/function/UserJassFunction.java | 9 +- .../interpreter/ast/scope/GlobalScope.java | 7 +- .../ast/scope/trigger/Trigger.java | 3 + .../ast/statement/JassExitWhenStatement.java | 32 + .../JassLocalDefinitionStatement.java | 33 + .../ast/statement/JassLocalStatement.java | 28 + .../ast/statement/JassLoopStatement.java | 38 + .../statement/JassReturnNothingStatement.java | 24 + .../interpreter/ast/value/ArrayJassType.java | 10 + .../interpreter/ast/value/ArrayJassValue.java | 23 +- .../ast/value/BooleanJassValue.java | 5 + .../interpreter/ast/value/HandleJassType.java | 10 + .../ast/value/IntegerJassValue.java | 7 +- .../interpreter/ast/value/JassType.java | 6 +- .../ast/value/PrimitiveJassType.java | 10 + .../interpreter/ast/value/RealJassValue.java | 7 +- .../interpreter/ast/value/StringJassType.java | 18 + .../visitor/ArithmeticJassValueVisitor.java | 72 + ...hmeticLeftHandBooleanJassValueVisitor.java | 64 + ...thmeticLeftHandHandleJassValueVisitor.java | 60 + ...hmeticLeftHandIntegerJassValueVisitor.java | 63 + ...rithmeticLeftHandNullJassValueVisitor.java | 64 + ...rithmeticLeftHandRealJassValueVisitor.java | 63 + ...thmeticLeftHandStringJassValueVisitor.java | 61 + .../value/visitor/NegateJassValueVisitor.java | 55 + .../ast/visitors/JassArgumentsVisitor.java | 7 + .../ast/visitors/JassExpressionVisitor.java | 120 +- .../ast/visitors/JassGlobalsVisitor.java | 17 +- .../ast/visitors/JassProgramVisitor.java | 3 +- .../ast/visitors/JassStatementVisitor.java | 57 +- .../etheller/warsmash/util/RawcodeUtils.java | 0 resources/UI/FrameDef/SmashUI/ToolTip.fdf | 22 + 123 files changed, 6026 insertions(+), 629 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderSpellEffect.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/inventory/CBehaviorGiveItemToHero.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderDropItemAtTargetWidget.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/sound/CMIDISound.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/sound/CSound.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/sound/CSoundFilename.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/sound/CSoundFromLabel.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/unit/CWidgetEvent.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MultiSelectionIcon.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/MultiSelectionIconListener.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/dialog/CTimerDialog.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/expression/ArithmeticJassExpression.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/expression/ArithmeticSign.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/expression/ArithmeticSigns.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/expression/NegateJassExpression.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/statement/JassExitWhenStatement.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/statement/JassLocalDefinitionStatement.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/statement/JassLocalStatement.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/statement/JassLoopStatement.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/statement/JassReturnNothingStatement.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/StringJassType.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticJassValueVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandBooleanJassValueVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandHandleJassValueVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandIntegerJassValueVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandNullJassValueVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandRealJassValueVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandStringJassValueVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/visitor/NegateJassValueVisitor.java rename {core => jassparser}/src/com/etheller/warsmash/util/RawcodeUtils.java (100%) diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index cb2f17d..419ec11 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -1,5 +1,5 @@ [DataSources] -Count=9 +Count=8 Type00=MPQ Path00="D:\Games\Warcraft III Patch 1.22\war3.mpq" Type01=MPQ @@ -8,13 +8,11 @@ Type02=MPQ Path02="D:\Games\Warcraft III Patch 1.22\War3xlocal.mpq" Type03=MPQ Path03="D:\Games\Warcraft III Patch 1.22\War3Patch.mpq" -Type04=MPQ -Path04="D:\Games\Warcraft III Patch 1.22\Warsmash\War3Mod.mpq" +Type04=Folder +Path04="..\..\resources" Type05=Folder -Path05="..\..\resources" +Path05="D:\Backups\Warsmash\Data" Type06=Folder -Path06="D:\Backups\Warsmash\Data" +Path06="D:\Games\Warcraft III Patch 1.22\Maps" Type07=Folder -Path07="D:\Games\Warcraft III Patch 1.22\Maps" -Type08=Folder -Path08="." +Path07="." diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java b/core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java index 36aa772..94ee309 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java @@ -30,12 +30,12 @@ import com.etheller.warsmash.datasources.DataSourceDescriptor; import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; import com.etheller.warsmash.datasources.MpqDataSourceDescriptor; import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.parsers.jass.Jass2; +import com.etheller.warsmash.parsers.jass.Jass2.CommonEnvironment; import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; -import com.etheller.warsmash.util.DataSourceFileHandle; import com.etheller.warsmash.util.ImageUtils; -import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.Model; import com.etheller.warsmash.viewer5.ModelInstance; import com.etheller.warsmash.viewer5.ModelViewer; @@ -53,7 +53,6 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.MeleeUI; public class WarsmashGdxMapScreen implements InputProcessor, Screen { public static final boolean ENABLE_AUDIO = true; - private static final boolean ENABLE_MUSIC = true; private final War3MapViewer viewer; private final Rectangle tempRect = new Rectangle(); @@ -76,6 +75,7 @@ public class WarsmashGdxMapScreen implements InputProcessor, Screen { private final WarsmashGdxMultiScreenGame screenManager; private final WarsmashGdxMenuScreen menuScreen; private final CPlayerUnitOrderListener uiOrderListener; + private CommonEnvironment commonEnv; public WarsmashGdxMapScreen(final War3MapViewer mapViewer, final WarsmashGdxMultiScreenGame screenManager, final WarsmashGdxMenuScreen menuScreen, final CPlayerUnitOrderListener uiOrderListener) { @@ -150,7 +150,8 @@ public class WarsmashGdxMapScreen implements InputProcessor, Screen { if (width < ((height * 4) / 3)) { aspect3By4Width = width; aspect3By4Height = (width * 3) / 4; - } else { + } + else { aspect3By4Width = (height * 4) / 3; aspect3By4Height = height; } @@ -171,13 +172,6 @@ public class WarsmashGdxMapScreen implements InputProcessor, Screen { this.shapeRenderer = new ShapeRenderer(); -// Jass2.loadJUI(this.codebase, this.uiViewport, fontGenerator, this.uiScene, this.viewer, -// new RootFrameListener() { -// @Override -// public void onCreate(final GameUI rootFrame) { -// WarsmashGdxMapGame.this.gameUI = rootFrame; -// } -// }, "Scripts\\common.jui", "Scripts\\melee.jui"); final Element cameraRatesElement = this.viewer.miscData.get("CameraRates"); final CameraRates cameraRates = new CameraRates(cameraRatesElement.getFieldFloatValue("AOA"), cameraRatesElement.getFieldFloatValue("FOV"), cameraRatesElement.getFieldFloatValue("Rotation"), @@ -188,22 +182,6 @@ public class WarsmashGdxMapScreen implements InputProcessor, Screen { @Override public void onCreate(final GameUI rootFrame) { WarsmashGdxMapScreen.this.viewer.setGameUI(rootFrame); - - if (ENABLE_MUSIC) { - final String musicField = rootFrame - .getSkinField("Music_V" + WarsmashConstants.GAME_VERSION); - final String[] musics = musicField.split(";"); - String musicPath = musics[(int) (Math.random() * musics.length)]; - if (false) { - musicPath = "Sound\\Music\\mp3Music\\PH1.mp3"; - } - final Music music = Gdx.audio.newMusic( - new DataSourceFileHandle(WarsmashGdxMapScreen.this.viewer.dataSource, musicPath)); - music.setVolume(1.0f); - music.setLooping(true); - music.play(); - WarsmashGdxMapScreen.this.currentMusic = music; - } } }, this.uiOrderListener, new Runnable() { @Override @@ -224,9 +202,12 @@ public class WarsmashGdxMapScreen implements InputProcessor, Screen { try { this.viewer.loadAfterUI(); - } catch (final IOException e) { + } + catch (final IOException e) { throw new RuntimeException(e); } + this.commonEnv = Jass2.loadCommon(this.viewer.mapMpq, this.uiViewport, this.uiScene, this.viewer, this.meleeUI, + "Scripts\\common.j", "Scripts\\Blizzard.j", "war3map.j"); } public static DataSource parseDataSources(final DataTable warsmashIni) { @@ -508,8 +489,6 @@ public class WarsmashGdxMapScreen implements InputProcessor, Screen { @Override public void hide() { - if (this.currentMusic != null) { - this.currentMusic.stop(); - } + this.meleeUI.gameClosed(); } } diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java b/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java index 2faf476..d6dbff8 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java @@ -52,7 +52,6 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.MenuUI; public class WarsmashGdxMenuScreen implements InputProcessor, Screen, SingleModelScreen { private static final boolean ENABLE_AUDIO = true; - private static final boolean ENABLE_MUSIC = true; private DataSource codebase; private MdxViewer viewer; private MdxModel model; @@ -185,7 +184,7 @@ public class WarsmashGdxMenuScreen implements InputProcessor, Screen, SingleMode public void onCreate(final GameUI rootFrame) { // WarsmashGdxMapGame.this.viewer.setGameUI(rootFrame); - if (ENABLE_MUSIC) { + if (WarsmashConstants.ENABLE_MUSIC) { final String musicField = rootFrame .getSkinField("GlueMusic_V" + WarsmashConstants.GAME_VERSION); final String[] musics = musicField.split(";"); diff --git a/core/src/com/etheller/warsmash/networking/ClientToServerListener.java b/core/src/com/etheller/warsmash/networking/ClientToServerListener.java index 972606e..8e872df 100644 --- a/core/src/com/etheller/warsmash/networking/ClientToServerListener.java +++ b/core/src/com/etheller/warsmash/networking/ClientToServerListener.java @@ -14,6 +14,9 @@ public interface ClientToServerListener { void issueDropItemAtPointOrder(SocketAddress sourceAddress, int unitHandleId, int abilityHandleId, int orderId, int targetHandleId, float x, float y, final boolean queue); + void issueDropItemAtTargetOrder(SocketAddress sourceAddress, int unitHandleId, int abilityHandleId, int orderId, + int targetHandleId, int targetHeroHandleId, final boolean queue); + void issueImmediateOrder(SocketAddress sourceAddress, int unitHandleId, int abilityHandleId, int orderId, boolean queue); diff --git a/core/src/com/etheller/warsmash/networking/ClientToServerProtocol.java b/core/src/com/etheller/warsmash/networking/ClientToServerProtocol.java index 50faac5..a3587fd 100644 --- a/core/src/com/etheller/warsmash/networking/ClientToServerProtocol.java +++ b/core/src/com/etheller/warsmash/networking/ClientToServerProtocol.java @@ -5,6 +5,7 @@ public class ClientToServerProtocol { public static final int ISSUE_TARGET_ORDER = 1; public static final int ISSUE_POINT_ORDER = 2; public static final int ISSUE_DROP_ITEM_ORDER = 3; + public static final int ISSUE_DROP_ITEM_ON_TARGET_ORDER = 9; public static final int ISSUE_IMMEDIATE_ORDER = 4; public static final int UNIT_CANCEL_TRAINING = 5; public static final int FINISHED_TURN = 6; diff --git a/core/src/com/etheller/warsmash/networking/ServerToClientListener.java b/core/src/com/etheller/warsmash/networking/ServerToClientListener.java index 9903ca2..bec702a 100644 --- a/core/src/com/etheller/warsmash/networking/ServerToClientListener.java +++ b/core/src/com/etheller/warsmash/networking/ServerToClientListener.java @@ -12,6 +12,9 @@ public interface ServerToClientListener { void issueDropItemAtPointOrder(int playerIndex, int unitHandleId, int abilityHandleId, int orderId, int targetHandleId, float x, float y, final boolean queue); + void issueDropItemAtTargetOrder(int playerIndex, int unitHandleId, int abilityHandleId, int orderId, + int targetHandleId, int targetHeroHandleId, final boolean queue); + void issueImmediateOrder(int playerIndex, int unitHandleId, int abilityHandleId, int orderId, boolean queue); void unitCancelTrainingItem(int playerIndex, int unitHandleId, int cancelIndex); diff --git a/core/src/com/etheller/warsmash/networking/ServerToClientProtocol.java b/core/src/com/etheller/warsmash/networking/ServerToClientProtocol.java index 0c88662..98c579d 100644 --- a/core/src/com/etheller/warsmash/networking/ServerToClientProtocol.java +++ b/core/src/com/etheller/warsmash/networking/ServerToClientProtocol.java @@ -5,6 +5,7 @@ public class ServerToClientProtocol { public static final int ISSUE_TARGET_ORDER = 1; public static final int ISSUE_POINT_ORDER = 2; public static final int ISSUE_DROP_ITEM_ORDER = 3; + public static final int ISSUE_DROP_ITEM_ON_TARGET_ORDER = 10; public static final int ISSUE_IMMEDIATE_ORDER = 4; public static final int UNIT_CANCEL_TRAINING = 5; public static final int FINISHED_TURN = 6; diff --git a/core/src/com/etheller/warsmash/networking/WarsmashClient.java b/core/src/com/etheller/warsmash/networking/WarsmashClient.java index 21f56a2..ac76499 100644 --- a/core/src/com/etheller/warsmash/networking/WarsmashClient.java +++ b/core/src/com/etheller/warsmash/networking/WarsmashClient.java @@ -3,7 +3,10 @@ package com.etheller.warsmash.networking; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; -import java.util.*; +import java.util.ArrayDeque; +import java.util.HashMap; +import java.util.Map; +import java.util.Queue; import com.badlogic.gdx.Gdx; import com.etheller.warsmash.networking.udp.OrderedUdpClient; @@ -53,18 +56,21 @@ public class WarsmashClient implements ServerToClientListener, GameTurnManager { Gdx.app.postRunnable(new Runnable() { @Override public void run() { - int currentServerTurnInProgress = latestCompletedTurn + 1; - if(currentServerTurnInProgress > latestLocallyRequestedTurn) { - queuedMessages.add(new QueuedMessage(latestCompletedTurn) { + final int currentServerTurnInProgress = WarsmashClient.this.latestCompletedTurn + 1; + if (currentServerTurnInProgress > WarsmashClient.this.latestLocallyRequestedTurn) { + WarsmashClient.this.queuedMessages.add(new QueuedMessage(WarsmashClient.this.latestCompletedTurn) { @Override public void run() { executor.issueTargetOrder(unitHandleId, abilityHandleId, orderId, targetHandleId, queue); } }); - } else if(currentServerTurnInProgress == latestLocallyRequestedTurn) { + } + else if (currentServerTurnInProgress == WarsmashClient.this.latestLocallyRequestedTurn) { executor.issueTargetOrder(unitHandleId, abilityHandleId, orderId, targetHandleId, queue); - } else { - System.err.println("Turn tick system mismatch: " + currentServerTurnInProgress + " < " + latestLocallyRequestedTurn); + } + else { + System.err.println("Turn tick system mismatch: " + currentServerTurnInProgress + " < " + + WarsmashClient.this.latestLocallyRequestedTurn); } } }); @@ -77,18 +83,22 @@ public class WarsmashClient implements ServerToClientListener, GameTurnManager { Gdx.app.postRunnable(new Runnable() { @Override public void run() { - int currentServerTurnInProgress = latestCompletedTurn + 1; - if(currentServerTurnInProgress > latestLocallyRequestedTurn) { - queuedMessages.add(new QueuedMessage(latestCompletedTurn) { + final int currentServerTurnInProgress = WarsmashClient.this.latestCompletedTurn + 1; + if (currentServerTurnInProgress > WarsmashClient.this.latestLocallyRequestedTurn) { + WarsmashClient.this.queuedMessages.add(new QueuedMessage(WarsmashClient.this.latestCompletedTurn) { @Override public void run() { executor.issuePointOrder(unitHandleId, abilityHandleId, orderId, x, y, queue); } }); - } else if(currentServerTurnInProgress == latestLocallyRequestedTurn) { - executor.issuePointOrder(unitHandleId, abilityHandleId, orderId, x, y, queue);; - } else { - System.err.println("Turn tick system mismatch: " + currentServerTurnInProgress + " < " + latestLocallyRequestedTurn); + } + else if (currentServerTurnInProgress == WarsmashClient.this.latestLocallyRequestedTurn) { + executor.issuePointOrder(unitHandleId, abilityHandleId, orderId, x, y, queue); + ; + } + else { + System.err.println("Turn tick system mismatch: " + currentServerTurnInProgress + " < " + + WarsmashClient.this.latestLocallyRequestedTurn); } } }); @@ -102,18 +112,52 @@ public class WarsmashClient implements ServerToClientListener, GameTurnManager { Gdx.app.postRunnable(new Runnable() { @Override public void run() { - int currentServerTurnInProgress = latestCompletedTurn + 1; - if(currentServerTurnInProgress > latestLocallyRequestedTurn) { - queuedMessages.add(new QueuedMessage(latestCompletedTurn) { + final int currentServerTurnInProgress = WarsmashClient.this.latestCompletedTurn + 1; + if (currentServerTurnInProgress > WarsmashClient.this.latestLocallyRequestedTurn) { + WarsmashClient.this.queuedMessages.add(new QueuedMessage(WarsmashClient.this.latestCompletedTurn) { @Override public void run() { - executor.issueDropItemAtPointOrder(unitHandleId, abilityHandleId, orderId, targetHandleId, x, y, queue); + executor.issueDropItemAtPointOrder(unitHandleId, abilityHandleId, orderId, targetHandleId, + x, y, queue); } }); - } else if(currentServerTurnInProgress == latestLocallyRequestedTurn) { - executor.issueDropItemAtPointOrder(unitHandleId, abilityHandleId, orderId, targetHandleId, x, y, queue); - } else { - System.err.println("Turn tick system mismatch: " + currentServerTurnInProgress + " < " + latestLocallyRequestedTurn); + } + else if (currentServerTurnInProgress == WarsmashClient.this.latestLocallyRequestedTurn) { + executor.issueDropItemAtPointOrder(unitHandleId, abilityHandleId, orderId, targetHandleId, x, y, + queue); + } + else { + System.err.println("Turn tick system mismatch: " + currentServerTurnInProgress + " < " + + WarsmashClient.this.latestLocallyRequestedTurn); + } + } + }); + } + + @Override + public void issueDropItemAtTargetOrder(final int playerIndex, final int unitHandleId, final int abilityHandleId, + final int orderId, final int targetHandleId, final int targetHeroHandleId, final boolean queue) { + final CPlayerUnitOrderExecutor executor = getExecutor(playerIndex); + Gdx.app.postRunnable(new Runnable() { + @Override + public void run() { + final int currentServerTurnInProgress = WarsmashClient.this.latestCompletedTurn + 1; + if (currentServerTurnInProgress > WarsmashClient.this.latestLocallyRequestedTurn) { + WarsmashClient.this.queuedMessages.add(new QueuedMessage(WarsmashClient.this.latestCompletedTurn) { + @Override + public void run() { + executor.issueDropItemAtTargetOrder(unitHandleId, abilityHandleId, orderId, targetHandleId, + targetHeroHandleId, queue); + } + }); + } + else if (currentServerTurnInProgress == WarsmashClient.this.latestLocallyRequestedTurn) { + executor.issueDropItemAtTargetOrder(unitHandleId, abilityHandleId, orderId, targetHandleId, + targetHeroHandleId, queue); + } + else { + System.err.println("Turn tick system mismatch: " + currentServerTurnInProgress + " < " + + WarsmashClient.this.latestLocallyRequestedTurn); } } }); @@ -126,18 +170,21 @@ public class WarsmashClient implements ServerToClientListener, GameTurnManager { Gdx.app.postRunnable(new Runnable() { @Override public void run() { - int currentServerTurnInProgress = latestCompletedTurn + 1; - if(currentServerTurnInProgress > latestLocallyRequestedTurn) { - queuedMessages.add(new QueuedMessage(latestCompletedTurn) { + final int currentServerTurnInProgress = WarsmashClient.this.latestCompletedTurn + 1; + if (currentServerTurnInProgress > WarsmashClient.this.latestLocallyRequestedTurn) { + WarsmashClient.this.queuedMessages.add(new QueuedMessage(WarsmashClient.this.latestCompletedTurn) { @Override public void run() { executor.issueImmediateOrder(unitHandleId, abilityHandleId, orderId, queue); } }); - } else if(currentServerTurnInProgress == latestLocallyRequestedTurn) { + } + else if (currentServerTurnInProgress == WarsmashClient.this.latestLocallyRequestedTurn) { executor.issueImmediateOrder(unitHandleId, abilityHandleId, orderId, queue); - } else { - System.err.println("Turn tick system mismatch: " + currentServerTurnInProgress + " < " + latestLocallyRequestedTurn); + } + else { + System.err.println("Turn tick system mismatch: " + currentServerTurnInProgress + " < " + + WarsmashClient.this.latestLocallyRequestedTurn); } } }); @@ -149,18 +196,21 @@ public class WarsmashClient implements ServerToClientListener, GameTurnManager { Gdx.app.postRunnable(new Runnable() { @Override public void run() { - int currentServerTurnInProgress = latestCompletedTurn + 1; - if(currentServerTurnInProgress > latestLocallyRequestedTurn) { - queuedMessages.add(new QueuedMessage(latestCompletedTurn) { + final int currentServerTurnInProgress = WarsmashClient.this.latestCompletedTurn + 1; + if (currentServerTurnInProgress > WarsmashClient.this.latestLocallyRequestedTurn) { + WarsmashClient.this.queuedMessages.add(new QueuedMessage(WarsmashClient.this.latestCompletedTurn) { @Override public void run() { executor.unitCancelTrainingItem(unitHandleId, cancelIndex); } }); - } else if(currentServerTurnInProgress == latestLocallyRequestedTurn) { + } + else if (currentServerTurnInProgress == WarsmashClient.this.latestLocallyRequestedTurn) { executor.unitCancelTrainingItem(unitHandleId, cancelIndex); - } else { - System.err.println("Turn tick system mismatch: " + currentServerTurnInProgress + " < " + latestLocallyRequestedTurn); + } + else { + System.err.println("Turn tick system mismatch: " + currentServerTurnInProgress + " < " + + WarsmashClient.this.latestLocallyRequestedTurn); } } }); @@ -168,7 +218,7 @@ public class WarsmashClient implements ServerToClientListener, GameTurnManager { @Override public void finishedTurn(final int gameTurnTick) { - if(WarsmashConstants.VERBOSE_LOGGING) { + if (WarsmashConstants.VERBOSE_LOGGING) { System.out.println("finishedTurn " + gameTurnTick); } Gdx.app.postRunnable(new Runnable() { @@ -183,9 +233,10 @@ public class WarsmashClient implements ServerToClientListener, GameTurnManager { public void turnCompleted(final int gameTurnTick) { this.writer.finishedTurn(gameTurnTick); this.writer.send(); - latestLocallyRequestedTurn = gameTurnTick; - while(!queuedMessages.isEmpty() && queuedMessages.peek().messageTurnTick == latestLocallyRequestedTurn) { - queuedMessages.poll().run(); + this.latestLocallyRequestedTurn = gameTurnTick; + while (!this.queuedMessages.isEmpty() + && (this.queuedMessages.peek().messageTurnTick == this.latestLocallyRequestedTurn)) { + this.queuedMessages.poll().run(); } } @@ -207,8 +258,10 @@ public class WarsmashClient implements ServerToClientListener, GameTurnManager { @Override public void heartbeat() { - // Not doing anything here at the moment. The act of the server sending us that packet - // will let the middle layer UDP system know to re-request any lost packets based + // Not doing anything here at the moment. The act of the server sending us that + // packet + // will let the middle layer UDP system know to re-request any lost packets + // based // on the heartbeat seq no. But at app layer, here, we can ignore it. System.out.println("got heartbeat() from server"); } @@ -223,16 +276,17 @@ public class WarsmashClient implements ServerToClientListener, GameTurnManager { } private static abstract class QueuedMessage implements Runnable { - private int messageTurnTick; + private final int messageTurnTick; - public QueuedMessage(int messageTurnTick) { + public QueuedMessage(final int messageTurnTick) { this.messageTurnTick = messageTurnTick; } public final int getMessageTurnTick() { - return messageTurnTick; + return this.messageTurnTick; } + @Override public abstract void run(); } } diff --git a/core/src/com/etheller/warsmash/networking/WarsmashClientSendingOrderListener.java b/core/src/com/etheller/warsmash/networking/WarsmashClientSendingOrderListener.java index 9fd00f4..d49938b 100644 --- a/core/src/com/etheller/warsmash/networking/WarsmashClientSendingOrderListener.java +++ b/core/src/com/etheller/warsmash/networking/WarsmashClientSendingOrderListener.java @@ -30,6 +30,14 @@ public class WarsmashClientSendingOrderListener implements CPlayerUnitOrderListe this.writer.send(); } + @Override + public void issueDropItemAtTargetOrder(final int unitHandleId, final int abilityHandleId, final int orderId, + final int targetItemHandleId, final int targetHeroHandleId, final boolean queue) { + this.writer.issueDropItemAtTargetOrder(unitHandleId, abilityHandleId, orderId, targetItemHandleId, + targetHeroHandleId, queue); + this.writer.send(); + } + @Override public void issueImmediateOrder(final int unitHandleId, final int abilityHandleId, final int orderId, final boolean queue) { diff --git a/core/src/com/etheller/warsmash/networking/WarsmashClientWriter.java b/core/src/com/etheller/warsmash/networking/WarsmashClientWriter.java index e050440..4729e6d 100644 --- a/core/src/com/etheller/warsmash/networking/WarsmashClientWriter.java +++ b/core/src/com/etheller/warsmash/networking/WarsmashClientWriter.java @@ -53,6 +53,19 @@ public class WarsmashClientWriter { this.sendBuffer.put(queue ? (byte) 1 : (byte) 0); } + public void issueDropItemAtTargetOrder(final int unitHandleId, final int abilityHandleId, final int orderId, + final int targetHandleId, final int targetHeroHandleId, final boolean queue) { + this.sendBuffer.clear(); + this.sendBuffer.putInt(4 + 4 + 4 + 4 + 4 + 4 + 1); + this.sendBuffer.putInt(ClientToServerProtocol.ISSUE_DROP_ITEM_ON_TARGET_ORDER); + this.sendBuffer.putInt(unitHandleId); + this.sendBuffer.putInt(abilityHandleId); + this.sendBuffer.putInt(orderId); + this.sendBuffer.putInt(targetHandleId); + this.sendBuffer.putInt(targetHeroHandleId); + this.sendBuffer.put(queue ? (byte) 1 : (byte) 0); + } + public void issueImmediateOrder(final int unitHandleId, final int abilityHandleId, final int orderId, final boolean queue) { this.sendBuffer.clear(); diff --git a/core/src/com/etheller/warsmash/networking/WarsmashServer.java b/core/src/com/etheller/warsmash/networking/WarsmashServer.java index 6dafe6c..444c894 100644 --- a/core/src/com/etheller/warsmash/networking/WarsmashServer.java +++ b/core/src/com/etheller/warsmash/networking/WarsmashServer.java @@ -108,6 +108,22 @@ public class WarsmashServer implements ClientToServerListener { }); } + @Override + public void issueDropItemAtTargetOrder(final SocketAddress sourceAddress, final int unitHandleId, + final int abilityHandleId, final int orderId, final int targetHandleId, final int targetHeroHandleId, + final boolean queue) { + System.out.println("issueDropItemAtTargetOrder from " + sourceAddress); + final int playerIndex = getPlayerIndex(sourceAddress); + this.turnActions.add(new Runnable() { + @Override + public void run() { + WarsmashServer.this.writer.issueDropItemAtTargetOrder(playerIndex, unitHandleId, abilityHandleId, + orderId, targetHandleId, targetHeroHandleId, queue); + WarsmashServer.this.writer.send(); + } + }); + } + @Override public void issueImmediateOrder(final SocketAddress sourceAddress, final int unitHandleId, final int abilityHandleId, final int orderId, final boolean queue) { diff --git a/core/src/com/etheller/warsmash/networking/WarsmashServerParser.java b/core/src/com/etheller/warsmash/networking/WarsmashServerParser.java index 4e7a1f7..320ce1d 100644 --- a/core/src/com/etheller/warsmash/networking/WarsmashServerParser.java +++ b/core/src/com/etheller/warsmash/networking/WarsmashServerParser.java @@ -60,6 +60,17 @@ public class WarsmashServerParser implements OrderedUdpServerListener { targetHandleId, x, y, queue); break; } + case ClientToServerProtocol.ISSUE_DROP_ITEM_ON_TARGET_ORDER: { + final int unitHandleId = buffer.getInt(); + final int abilityHandleId = buffer.getInt(); + final int orderId = buffer.getInt(); + final int targetHandleId = buffer.getInt(); + final int targetHeroHandleId = buffer.getInt(); + final boolean queue = buffer.get() == 1; + this.listener.issueDropItemAtTargetOrder(sourceAddress, unitHandleId, abilityHandleId, orderId, + targetHandleId, targetHeroHandleId, queue); + break; + } case ClientToServerProtocol.ISSUE_IMMEDIATE_ORDER: { final int unitHandleId = buffer.getInt(); final int abilityHandleId = buffer.getInt(); diff --git a/core/src/com/etheller/warsmash/networking/WarsmashServerWriter.java b/core/src/com/etheller/warsmash/networking/WarsmashServerWriter.java index ae6dbae..2045c33 100644 --- a/core/src/com/etheller/warsmash/networking/WarsmashServerWriter.java +++ b/core/src/com/etheller/warsmash/networking/WarsmashServerWriter.java @@ -63,6 +63,21 @@ public class WarsmashServerWriter implements ServerToClientListener { this.sendBuffer.put(queue ? (byte) 1 : (byte) 0); } + @Override + public void issueDropItemAtTargetOrder(final int playerIndex, final int unitHandleId, final int abilityHandleId, + final int orderId, final int targetHandleId, final int targetHeroHandleId, final boolean queue) { + this.sendBuffer.clear(); + this.sendBuffer.putInt(4 + 4 + 4 + 4 + 4 + 4 + 4 + 1); + this.sendBuffer.putInt(ServerToClientProtocol.ISSUE_DROP_ITEM_ON_TARGET_ORDER); + this.sendBuffer.putInt(playerIndex); + this.sendBuffer.putInt(unitHandleId); + this.sendBuffer.putInt(abilityHandleId); + this.sendBuffer.putInt(orderId); + this.sendBuffer.putInt(targetHandleId); + this.sendBuffer.putInt(targetHeroHandleId); + this.sendBuffer.put(queue ? (byte) 1 : (byte) 0); + } + @Override public void issueImmediateOrder(final int playerIndex, final int unitHandleId, final int abilityHandleId, final int orderId, final boolean queue) { diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index d55e532..7e10b04 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -1054,7 +1054,11 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } public String getErrorString(final String key) { - String errorString = this.errorStrings.getField(key, this.racialCommandIndex); + final String errorString = this.errorStrings.getField(key, this.racialCommandIndex); + return getTrigStr(errorString); + } + + public String getTrigStr(String errorString) { if (errorString.startsWith("TRIGSTR_")) { errorString = this.mapStrings.get(Integer.parseInt(errorString.substring(8))); } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/SimpleStatusBarFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/SimpleStatusBarFrame.java index 4fad3cc..43c274a 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/SimpleStatusBarFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/SimpleStatusBarFrame.java @@ -1,5 +1,7 @@ package com.etheller.warsmash.parsers.fdf.frames; +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; @@ -8,6 +10,7 @@ public class SimpleStatusBarFrame extends AbstractUIFrame { private final TextureFrame barFrame; private final TextureFrame borderFrame; private final float barInset; + private float lastValue = Float.NaN; public SimpleStatusBarFrame(final String name, final UIFrame parent, final boolean decorateFileNames, final boolean borderBelow, final float barInset) { @@ -31,6 +34,14 @@ public class SimpleStatusBarFrame extends AbstractUIFrame { } } + @Override + protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { + if (!Float.isNaN(this.lastValue)) { + this.barFrame.setWidth(((this.renderBounds.width - (this.barInset * 2)) * this.lastValue)); + } + super.innerPositionBounds(gameUI, viewport); + } + public boolean isDecorateFileNames() { return this.decorateFileNames; } @@ -38,6 +49,7 @@ public class SimpleStatusBarFrame extends AbstractUIFrame { public void setValue(final float value) { this.barFrame.setTexCoord(0, value, 0, 1); this.barFrame.setWidth(((this.renderBounds.width - (this.barInset * 2)) * value)); + this.lastValue = value; } public TextureFrame getBarFrame() { diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java index 79e32f2..9b6a369 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.parsers.fdf.frames; +import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; @@ -135,4 +136,10 @@ public class SpriteFrame extends AbstractUIFrame { } + public void setVertexColor(final Color color) { + if (this.instance != null) { + this.instance.setVertexColor(color); + } + } + } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java index 11cfeb1..900c9a6 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java @@ -28,6 +28,7 @@ public class StringFrame extends AbstractRenderableFrame { private float alpha = 1.0f; private final SimpleFrame internalFramesContainer; private float predictedViewportHeight; + private float predictedViewportWidth; static ShapeRenderer shapeRenderer = new ShapeRenderer(); private final Color fontHighlightColor; @@ -425,6 +426,7 @@ public class StringFrame extends AbstractRenderableFrame { this.internalFramesContainer.setWidth(usedWidthMax); this.internalFramesContainer.setHeight(usedHeight); this.predictedViewportHeight = (usedHeight - this.frameFont.getCapHeight()) + this.frameFont.getLineHeight(); + this.predictedViewportWidth = usedWidthMax; this.internalFramesContainer.clearFramePointAssignments(); switch (this.justifyH) { @@ -490,6 +492,10 @@ public class StringFrame extends AbstractRenderableFrame { return this.predictedViewportHeight; } + public float getPredictedViewportWidth() { + return this.predictedViewportWidth; + } + public BitmapFont getFrameFont() { return this.frameFont; } diff --git a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java index d71ee1a..d18fb26 100644 --- a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java +++ b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java @@ -13,6 +13,7 @@ import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.Recognizer; +import com.badlogic.gdx.audio.Sound; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; @@ -61,14 +62,20 @@ import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.handlers.w3x.UnitSound; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderDestructable; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.ItemUI; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructableType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; 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.CUnitType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.inventory.CAbilityInventory; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.ai.AIDifficulty; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.config.CPlayerAPI; @@ -79,6 +86,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceTy import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CMapControl; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CMapFlag; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CMapPlacement; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerColor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerGameResult; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerJass; @@ -89,6 +97,10 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CRacePrefer import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CStartLocPrio; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.region.CRegion; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.region.CRegionTriggerEnter; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.sound.CMIDISound; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.sound.CSound; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.sound.CSoundFilename; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.sound.CSoundFromLabel; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.state.CGameState; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.state.CUnitState; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.timers.CTimerJass; @@ -113,17 +125,17 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.C import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CVersion; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CWeaponSoundTypeJass; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.unit.CUnitTypeJass; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.MeleeUI; public class Jass2 { public static final boolean REPORT_SYNTAX_ERRORS = true; public static CommonEnvironment loadCommon(final DataSource dataSource, final Viewport uiViewport, - final Scene uiScene, final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener, - final String... files) { + final Scene uiScene, final War3MapViewer war3MapViewer, final MeleeUI meleeUI, final String... files) { final JassProgramVisitor jassProgramVisitor = new JassProgramVisitor(); final CommonEnvironment environment = new CommonEnvironment(jassProgramVisitor, dataSource, uiViewport, uiScene, - war3MapViewer, rootFrameListener); + war3MapViewer, meleeUI); for (final String jassFile : files) { try { JassLexer lexer; @@ -147,6 +159,8 @@ public class Jass2 { final String sourceName = String.format("%s:%d:%d: ", jassFile, line, charPositionInLine); System.err.println(sourceName + "line " + line + ":" + charPositionInLine + " " + msg); + throw new IllegalStateException( + sourceName + "line " + line + ":" + charPositionInLine + " " + msg); } }); jassProgramVisitor.visit(parser.program()); @@ -407,13 +421,16 @@ public class Jass2 { } } - private static final class CommonEnvironment { + public static final class CommonEnvironment { private GameUI gameUI; private Element skin; + private final JassProgramVisitor jassProgramVisitor; - public CommonEnvironment(final JassProgramVisitor jassProgramVisitor, final DataSource dataSource, + private CommonEnvironment(final JassProgramVisitor jassProgramVisitor, final DataSource dataSource, final Viewport uiViewport, final Scene uiScene, final War3MapViewer war3MapViewer, - final RootFrameListener rootFrameListener) { + final MeleeUI meleeUI) { + this.jassProgramVisitor = jassProgramVisitor; + this.gameUI = war3MapViewer.getGameUI(); final Rectangle tempRect = new Rectangle(); final CSimulation simulation = war3MapViewer.simulation; final GlobalScope globals = jassProgramVisitor.getGlobals(); @@ -565,7 +582,7 @@ public class Jass2 { return new HandleJassValue(playerscoreType, CPlayerScore.VALUES[i]); } }); - jassProgramVisitor.getJassNativeManager().createNative("ConvertGameResult", new JassFunction() { + jassProgramVisitor.getJassNativeManager().createNative("ConvertPlayerGameResult", new JassFunction() { @Override public JassValue call(final List arguments, final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { @@ -698,7 +715,7 @@ public class Jass2 { public JassValue call(final List arguments, final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); - return new HandleJassValue(gametypeType, CMapFlag.getById(i)); + return new HandleJassValue(mapflagType, CMapFlag.getById(i)); } }); jassProgramVisitor.getJassNativeManager().createNative("ConvertMapVisibility", new JassFunction() { @@ -736,7 +753,7 @@ public class Jass2 { public JassValue call(final List arguments, final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); - return new HandleJassValue(playercolorType, CMapControl.VALUES[i]); + return new HandleJassValue(playercolorType, CPlayerColor.VALUES[i]); } }); jassProgramVisitor.getJassNativeManager().createNative("ConvertPlayerSlotState", new JassFunction() { @@ -792,7 +809,7 @@ public class Jass2 { public JassValue call(final List arguments, final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); - return new HandleJassValue(fogstateType, CFogState.VALUES[i]); + return new HandleJassValue(fogstateType, CFogState.getById(i)); } }); jassProgramVisitor.getJassNativeManager().createNative("ConvertEffectType", new JassFunction() { @@ -832,7 +849,7 @@ public class Jass2 { public JassValue call(final List arguments, final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); - return new HandleJassValue(attacktypeType, CDamageType.VALUES[i]); + return new HandleJassValue(damagetypeType, CDamageType.VALUES[i]); } }); jassProgramVisitor.getJassNativeManager().createNative("ConvertWeaponType", new JassFunction() { @@ -1392,7 +1409,7 @@ public class Jass2 { simulation.getWorldCollision().enumUnitsInRect(rect, new CUnitEnumFunction() { @Override public boolean call(final CUnit unit) { - if (filter.evaluate(globalScope, + if ((filter == null) || filter.evaluate(globalScope, CommonTriggerExecutionScope.filterScope(triggerScope, unit))) { // TODO the trigger scope for evaluation here might need to be a clean one? group.add(unit); @@ -1440,15 +1457,15 @@ public class Jass2 { final float x = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); final float y = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); final float radius = arguments.get(3).visit(RealJassValueVisitor.getInstance()).floatValue(); - final TriggerBooleanExpression filter = arguments.get(4) - .visit(ObjectJassValueVisitor.getInstance()); + final TriggerBooleanExpression filter = nullable(arguments, 4, + ObjectJassValueVisitor.getInstance()); simulation.getWorldCollision().enumUnitsInRect(tempRect.set(x - radius, y - radius, radius, radius), new CUnitEnumFunction() { @Override public boolean call(final CUnit unit) { if (unit.distance(x, y) <= radius) { - if (filter.evaluate(globalScope, + if ((filter == null) || filter.evaluate(globalScope, CommonTriggerExecutionScope.filterScope(triggerScope, unit))) { // TODO the trigger scope for evaluation here might need to be a clean one? group.add(unit); @@ -1470,15 +1487,15 @@ public class Jass2 { final float x = (float) whichLocation.x; final float y = (float) whichLocation.y; final float radius = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); - final TriggerBooleanExpression filter = arguments.get(3) - .visit(ObjectJassValueVisitor.getInstance()); + final TriggerBooleanExpression filter = nullable(arguments, 3, + ObjectJassValueVisitor.getInstance()); simulation.getWorldCollision().enumUnitsInRect(tempRect.set(x - radius, y - radius, radius, radius), new CUnitEnumFunction() { @Override public boolean call(final CUnit unit) { if (unit.distance(x, y) <= radius) { - if (filter.evaluate(globalScope, + if ((filter == null) || filter.evaluate(globalScope, CommonTriggerExecutionScope.filterScope(triggerScope, unit))) { // TODO the trigger scope for evaluation here might need to be a clean one? group.add(unit); @@ -1710,7 +1727,7 @@ public class Jass2 { callback.call(Collections.emptyList(), globalScope, CommonTriggerExecutionScope.enumScope(triggerScope, unit)); } - return new HandleJassValue(unitType, group.get(0)); + return null; } }); jassProgramVisitor.getJassNativeManager().createNative("FirstOfGroup", new JassFunction() { @@ -1782,11 +1799,11 @@ public class Jass2 { final TriggerExecutionScope triggerScope) { final List force = arguments.get(0) .visit(ObjectJassValueVisitor.>getInstance()); - final TriggerBooleanExpression filter = arguments.get(1) - .visit(ObjectJassValueVisitor.getInstance()); + final TriggerBooleanExpression filter = nullable(arguments, 1, + ObjectJassValueVisitor.getInstance()); for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) { final CPlayerJass jassPlayer = simulation.getPlayer(i); - if (filter.evaluate(globalScope, + if ((filter == null) || filter.evaluate(globalScope, CommonTriggerExecutionScope.filterScope(triggerScope, jassPlayer))) { force.add(jassPlayer); } @@ -2296,7 +2313,7 @@ public class Jass2 { final TriggerExecutionScope triggerScope) { final Trigger trigger = arguments.get(0) .visit(ObjectJassValueVisitor.getInstance()); - final CTimerJass timer = arguments.get(0) + final CTimerJass timer = arguments.get(1) .visit(ObjectJassValueVisitor.getInstance()); timer.addEvent(trigger); return new HandleJassValue(eventType, new RemovableTriggerEvent() { @@ -2351,7 +2368,10 @@ public class Jass2 { @Override public JassValue call(final List arguments, final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { - throw new UnsupportedOperationException("Not yet implemented: TriggerRegisterGameEvent"); + final Trigger trigger = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final JassGameEventsWar3 gameEvent = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + return new HandleJassValue(eventType, + simulation.registerGameEvent(globalScope, trigger, gameEvent)); } }); jassProgramVisitor.getJassNativeManager().createNative("GetWinningPlayer", new JassFunction() { @@ -2367,8 +2387,8 @@ public class Jass2 { final TriggerExecutionScope triggerScope) { final Trigger trigger = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); final CRegion region = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); - final TriggerBooleanExpression boolexpr = arguments.get(2) - .visit(ObjectJassValueVisitor.getInstance()); + final TriggerBooleanExpression boolexpr = nullable(arguments, 2, + ObjectJassValueVisitor.getInstance()); return new HandleJassValue(eventType, region.add(new CRegionTriggerEnter(globalScope, trigger, boolexpr))); } @@ -2498,6 +2518,1077 @@ public class Jass2 { whichPlayer.addEvent(globalScope, whichTrigger, whichPlayerEvent)); } }); + // TODO past this point things are inconsistent about ordering + jassProgramVisitor.getJassNativeManager().createNative("GetCameraMargin", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int whichMargin = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + final Rectangle playableMapArea = war3MapViewer.terrain.getPlayableMapArea(); + switch (whichMargin) { + case 0:// CAMERA_MARGIN_LEFT + return new RealJassValue(war3MapViewer.terrain.getDefaultCameraBounds()[0] - playableMapArea.x); + case 1:// CAMERA_MARGIN_RIGHT + return new RealJassValue((playableMapArea.x + playableMapArea.width) + - war3MapViewer.terrain.getDefaultCameraBounds()[2]); + case 2:// CAMERA_MARGIN_TOP + return new RealJassValue((playableMapArea.y + playableMapArea.height) + - war3MapViewer.terrain.getDefaultCameraBounds()[3]); + case 3:// CAMERA_MARGIN_BOTTOM + return new RealJassValue(war3MapViewer.terrain.getDefaultCameraBounds()[1] - playableMapArea.y); + default: + throw new IllegalArgumentException( + "Must input one of these constants: [CAMERA_MARGIN_LEFT, CAMERA_MARGIN_RIGHT, CAMERA_MARGIN_TOP, CAMERA_MARGIN_BOTTOM]"); + } + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetCameraBoundMinX", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new RealJassValue(meleeUI.getCameraManager().getCameraBounds().getX()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetCameraBoundMinY", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new RealJassValue(meleeUI.getCameraManager().getCameraBounds().getY()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetCameraBoundMaxX", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Rectangle cameraBounds = meleeUI.getCameraManager().getCameraBounds(); + return new RealJassValue(cameraBounds.getX() + cameraBounds.getWidth()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetCameraBoundMaxY", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Rectangle cameraBounds = meleeUI.getCameraManager().getCameraBounds(); + return new RealJassValue(cameraBounds.getY() + cameraBounds.getHeight()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetCameraBounds", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final double left = arguments.get(0).visit(RealJassValueVisitor.getInstance()); + final double bottom = arguments.get(1).visit(RealJassValueVisitor.getInstance()); + final double right = arguments.get(2).visit(RealJassValueVisitor.getInstance()); + final double top = arguments.get(3).visit(RealJassValueVisitor.getInstance()); + meleeUI.getCameraManager().setCameraBounds(new Rectangle((float) left, (float) bottom, + (float) (right - left), (float) (top - bottom))); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetDayNightModels", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String terrainDNCFile = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + final String unitDNCFile = arguments.get(1).visit(StringJassValueVisitor.getInstance()); + war3MapViewer.setDayNightModels(terrainDNCFile, unitDNCFile); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("NewSoundEnvironment", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String environmentName = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + System.err.println("#########"); + System.err.println("# Engine requested sound environment: " + environmentName); + System.err.println("# I don't know how to do that on LibGDX, so for now, I do nothing!"); + System.err.println("#########"); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("CreateMIDISound", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String soundLabel = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + final int fadeInRate = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + final int fadeOutRate = arguments.get(2).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(soundType, new CMIDISound(soundLabel, fadeInRate, fadeOutRate)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetFloatGameState", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CGameState gameState = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + switch (gameState) { + case TIME_OF_DAY: + return new RealJassValue(simulation.getGameTimeOfDay()); + } + throw new IllegalArgumentException("Not a float game state: " + gameState); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetFloatGameState", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CGameState gameState = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final float value = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + switch (gameState) { + case TIME_OF_DAY: + simulation.setGameTimeOfDay(value); + return null; + } + throw new IllegalArgumentException("Not a float game state: " + gameState); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("StartSound", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CSound soundHandle = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + soundHandle.start(); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetMapMusic", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String musicName = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + final boolean random = arguments.get(1).visit(BooleanJassValueVisitor.getInstance()); + final int index = arguments.get(2).visit(IntegerJassValueVisitor.getInstance()); + + String musicField; + if (!CommonEnvironment.this.gameUI.hasSkinField(musicName)) { + // TODO this versioning system should probably be more general case than this, + // maybe at the level + // of skin field lookup??? + final String versionedMusic = "Music_V" + WarsmashConstants.GAME_VERSION; + if (!CommonEnvironment.this.gameUI.hasSkinField(versionedMusic)) { + musicField = musicName; + } + else { + musicField = CommonEnvironment.this.gameUI.getSkinField(versionedMusic); + } + } + else { + musicField = CommonEnvironment.this.gameUI.getSkinField(musicName); + } + final String[] musics = musicField.split(";"); + meleeUI.playMusic(musics, random, index); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("CreateItem", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int rawcode = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + final double x = arguments.get(1).visit(RealJassValueVisitor.getInstance()); + final double y = arguments.get(2).visit(RealJassValueVisitor.getInstance()); + return new HandleJassValue(itemType, + simulation.createItem(new War3ID(rawcode), (float) x, (float) y)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("CreateDestructable", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int rawcode = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + final float x = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float y = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float facing = arguments.get(3).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float scale = arguments.get(4).visit(RealJassValueVisitor.getInstance()).floatValue(); + final int variation = arguments.get(5).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(destructableType, + simulation.createDestructable(new War3ID(rawcode), x, y, facing, scale, variation)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("CreateDestructableZ", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int rawcode = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + final float x = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float y = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float z = arguments.get(3).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float facing = arguments.get(4).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float scale = arguments.get(5).visit(RealJassValueVisitor.getInstance()).floatValue(); + final int variation = arguments.get(6).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(destructableType, + simulation.createDestructableZ(new War3ID(rawcode), x, y, z, facing, scale, variation)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("KillDestructable", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CDestructable dest = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + dest.setLife(simulation, 0f); + return null; + } + }); + + jassProgramVisitor.getJassNativeManager().createNative("Player", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int playerIndex = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(playerType, simulation.getPlayer(playerIndex)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("CreateUnit", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayer player = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final int rawcode = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + final double x = arguments.get(2).visit(RealJassValueVisitor.getInstance()); + final double y = arguments.get(3).visit(RealJassValueVisitor.getInstance()); + final double facing = arguments.get(4).visit(RealJassValueVisitor.getInstance()); + final CUnit newUnit = simulation.createUnit(new War3ID(rawcode), player.getId(), (float) x, + (float) y, (float) facing); + final CUnitType newUnitType = newUnit.getUnitType(); + final int foodUsed = newUnitType.getFoodUsed(); + newUnit.setFoodUsed(foodUsed); + player.setFoodUsed(player.getFoodUsed() + foodUsed); + if (newUnitType.getFoodMade() != 0) { + player.setFoodCap(player.getFoodCap() + newUnitType.getFoodMade()); + } + // nudge unit + newUnit.setPoint((float) x, (float) y, simulation.getWorldCollision(), + simulation.getRegionManager()); + return new HandleJassValue(unitType, newUnit); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("CreateUnitAtLoc", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayer player = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final int rawcode = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + final Point2D.Double whichLocation = arguments.get(2).visit(ObjectJassValueVisitor.getInstance()); + final float facing = arguments.get(3).visit(RealJassValueVisitor.getInstance()).floatValue(); + final CUnit newUnit = simulation.createUnit(new War3ID(rawcode), player.getId(), + (float) whichLocation.x, (float) whichLocation.y, facing); + final CUnitType newUnitType = newUnit.getUnitType(); + final int foodUsed = newUnitType.getFoodUsed(); + newUnit.setFoodUsed(foodUsed); + player.setFoodUsed(player.getFoodUsed() + foodUsed); + if (newUnitType.getFoodMade() != 0) { + player.setFoodCap(player.getFoodCap() + newUnitType.getFoodMade()); + } + // nudge unit + newUnit.setPoint((float) whichLocation.x, (float) whichLocation.y, simulation.getWorldCollision(), + simulation.getRegionManager()); + return new HandleJassValue(unitType, newUnit); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("CreateBlightedGoldmine", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + // TODO this needs to setup a non-blighted mine underneath!!! + final CPlayerJass player = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final double x = arguments.get(1).visit(RealJassValueVisitor.getInstance()); + final double y = arguments.get(2).visit(RealJassValueVisitor.getInstance()); + final double facing = arguments.get(3).visit(RealJassValueVisitor.getInstance()); + return new HandleJassValue(unitType, simulation.createUnit(War3ID.fromString("ugol"), + player.getId(), (float) x, (float) y, (float) facing)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetUnitColor", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CUnit whichUnit = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final CPlayerColor whichPlayerColor = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + final RenderUnit renderPeer = war3MapViewer.getRenderPeer(whichUnit); + renderPeer.setPlayerColor(whichPlayerColor.ordinal()); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetResourceAmount", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CUnit whichUnit = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final int resourceAmount = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + whichUnit.setGold(resourceAmount); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetUnitState", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CUnit whichUnit = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final CUnitState whichUnitState = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + final float value = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); + whichUnit.setUnitState(simulation, whichUnitState, value); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetUnitState", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CUnit whichUnit = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final CUnitState whichUnitState = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + return new RealJassValue(whichUnit.getUnitState(simulation, whichUnitState)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("IsUnitType", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CUnit whichUnit = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final CUnitTypeJass whichUnitType = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + return BooleanJassValue.of(whichUnit.isUnitType(whichUnitType)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetPlayerState", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayer player = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final CPlayerState whichPlayerState = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + final int value = arguments.get(2).visit(IntegerJassValueVisitor.getInstance()); + player.setPlayerState(simulation, whichPlayerState, value); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetPlayerTechResearched", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayer player = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final int techIdRawcode = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + final boolean specificOnly = arguments.get(2).visit(BooleanJassValueVisitor.getInstance()); + return BooleanJassValue.of(player.getTechtreeUnlocked(new War3ID(techIdRawcode)) > 0); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetPlayerTechMaxAllowed", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayer player = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final int techIdRawcode = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + final int maximum = arguments.get(2).visit(IntegerJassValueVisitor.getInstance()); + player.setTechtreeMaxAllowed(new War3ID(techIdRawcode), maximum); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetPlayerTechMaxAllowed", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayer player = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final int techIdRawcode = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + return new IntegerJassValue(player.getTechtreeMaxAllowed(new War3ID(techIdRawcode))); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("IsFogEnabled", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + // TODO fog of war!! + return BooleanJassValue.FALSE; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("IsFogMaskEnabled", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + // TODO fog of war!! + return BooleanJassValue.FALSE; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("CreateSoundFromLabel", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String soundLabel = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + final boolean looping = arguments.get(1).visit(BooleanJassValueVisitor.getInstance()); + final boolean is3D = arguments.get(2).visit(BooleanJassValueVisitor.getInstance()); + final boolean stopWhenOutOfRange = arguments.get(3).visit(BooleanJassValueVisitor.getInstance()); + final int fadeInRate = arguments.get(4).visit(IntegerJassValueVisitor.getInstance()); + final int fadeOutRate = arguments.get(5).visit(IntegerJassValueVisitor.getInstance()); + final UnitSound sound = war3MapViewer.getUiSounds().getSound(soundLabel); + return new HandleJassValue(soundType, + new CSoundFromLabel(sound, + is3D ? war3MapViewer.worldScene.audioContext : meleeUI.getUiScene().audioContext, + looping, is3D, stopWhenOutOfRange, fadeInRate, fadeOutRate)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("CreateSound", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String fileName = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + final boolean looping = arguments.get(1).visit(BooleanJassValueVisitor.getInstance()); + final boolean is3D = arguments.get(2).visit(BooleanJassValueVisitor.getInstance()); + final boolean stopWhenOutOfRange = arguments.get(3).visit(BooleanJassValueVisitor.getInstance()); + final int fadeInRate = arguments.get(4).visit(IntegerJassValueVisitor.getInstance()); + final int fadeOutRate = arguments.get(5).visit(IntegerJassValueVisitor.getInstance()); + final String eaxSetting = arguments.get(6).visit(StringJassValueVisitor.getInstance()); + final Sound newSound = UnitSound.createSound(war3MapViewer.mapMpq, fileName); + + return new HandleJassValue(soundType, + new CSoundFilename(newSound, + is3D ? war3MapViewer.worldScene.audioContext : meleeUI.getUiScene().audioContext, + looping, stopWhenOutOfRange, fadeInRate, fadeOutRate, eaxSetting)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetSoundParamsFromLabel", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CSoundFilename sound = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final String soundLabel = arguments.get(1).visit(StringJassValueVisitor.getInstance()); + + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("VersionGet", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new HandleJassValue(versionType, CVersion.VALUES[WarsmashConstants.GAME_VERSION]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetAllItemTypeSlots", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int slots = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + simulation.setAllItemTypeSlots(slots); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetAllUnitTypeSlots", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int slots = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + simulation.setAllItemTypeSlots(slots); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("TriggerRegisterPlayerUnitEvent", + new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Trigger whichTrigger = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final CPlayer whichPlayer = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + final JassGameEventsWar3 whichPlayerEvent = arguments.get(2) + .visit(ObjectJassValueVisitor.getInstance()); + final TriggerBooleanExpression filter = nullable(arguments, 3, + ObjectJassValueVisitor.getInstance()); + return new HandleJassValue(eventType, + whichPlayer.addUnitEvent(globalScope, whichTrigger, whichPlayerEvent, filter)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("TriggerEvaluate", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Trigger whichTrigger = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return BooleanJassValue.of(whichTrigger.evaluate(globalScope, triggerScope)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("TriggerExecute", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Trigger whichTrigger = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + whichTrigger.execute(globalScope, triggerScope); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("Preloader", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String filename = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + doPreloadScript(dataSource, war3MapViewer, filename); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("CreateTimerDialog", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CTimerJass timer = nullable(arguments, 0, ObjectJassValueVisitor.getInstance()); + return new HandleJassValue(timerdialogType, meleeUI.createTimerDialog(timer)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("IsPlayerObserver", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayer player = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return BooleanJassValue.of(player.isObserver()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetSoundParamsFromLabel", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CSoundFilename sound = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final String soundLabel = arguments.get(1).visit(StringJassValueVisitor.getInstance()); + // TODO NYI + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetSoundDuration", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CSoundFilename sound = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final int duration = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + // TODO NYI + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetSoundPitch", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CSoundFilename sound = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final float pitch = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + // TODO NYI + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetSoundVolume", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CSoundFilename sound = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final int volume = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + // TODO NYI + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("AddWeatherEffect", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Rectangle where = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final int effectId = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + // TODO NYI + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("EnableWeatherEffect", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Rectangle where = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final boolean enable = arguments.get(1).visit(BooleanJassValueVisitor.getInstance()); + // TODO NYI + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("TriggerRegisterDeathEvent", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Trigger whichTrigger = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final CWidget whichWidget = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + return new HandleJassValue(eventType, whichWidget.addDeathEvent(globalScope, whichTrigger)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("TriggerRegisterUnitEvent", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Trigger whichTrigger = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final CUnit whichWidget = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + final JassGameEventsWar3 whichPlayerEvent = arguments.get(2) + .visit(ObjectJassValueVisitor.getInstance()); + return new HandleJassValue(eventType, + whichWidget.addEvent(globalScope, whichTrigger, whichPlayerEvent)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("IsUnitHidden", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CUnit unit = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return BooleanJassValue.of(unit.isHidden()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetPlayerHandicapXP", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayer player = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final float handicap = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + player.setHandicapXP(handicap); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetChangingUnit", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + // TODO this is supposed to have some magic nonsense going on where apparently + // EVENT_WIDGET_DEATH + // assigns to the return value of this and fires itself upon changing owner!!?? + return unitType.getNullValue(); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetChangingUnitPrevOwner", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + // TODO this is supposed to have some magic nonsense going on where apparently + // EVENT_WIDGET_DEATH + // assigns to the return value of this and fires itself upon changing owner!!?? + return playerType.getNullValue(); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetRandomReal", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final float lowBound = arguments.get(0).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float highBound = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + return new RealJassValue( + (simulation.getSeededRandom().nextFloat() * (highBound - lowBound)) + lowBound); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetRandomInt", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int lowBound = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + final int highBound = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + return new IntegerJassValue( + (simulation.getSeededRandom().nextInt((highBound - lowBound) + 1)) + lowBound); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetWidgetX", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CWidget whichWidget = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return new RealJassValue(whichWidget.getX()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetWidgetY", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CWidget whichWidget = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return new RealJassValue(whichWidget.getY()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetUnitX", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CUnit whichWidget = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return new RealJassValue(whichWidget.getX()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetUnitY", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CUnit whichWidget = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return new RealJassValue(whichWidget.getY()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetItemX", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CItem whichWidget = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return new RealJassValue(whichWidget.getX()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetItemY", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CItem whichWidget = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return new RealJassValue(whichWidget.getY()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetItemLoc", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CItem whichWidget = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return new HandleJassValue(locationType, + new Point2D.Double(whichWidget.getX(), whichWidget.getY())); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetUnitLoc", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CUnit whichWidget = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return new HandleJassValue(locationType, + new Point2D.Double(whichWidget.getX(), whichWidget.getY())); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("AddSpecialEffectTarget", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String modelName = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + final CWidget targetWidget = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + final String attachPointName = arguments.get(2).visit(StringJassValueVisitor.getInstance()); + return new HandleJassValue(effectType, + war3MapViewer.addSpecialEffectTarget(modelName, targetWidget, attachPointName)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetItemInvulnerable", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CItem whichWidget = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final boolean flag = arguments.get(1).visit(BooleanJassValueVisitor.getInstance()); + whichWidget.setInvulernable(flag); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetDestructableInvulnerable", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CDestructable whichWidget = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final boolean flag = arguments.get(1).visit(BooleanJassValueVisitor.getInstance()); + whichWidget.setInvulnerable(flag); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SuspendTimeOfDay", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final boolean flag = arguments.get(0).visit(BooleanJassValueVisitor.getInstance()); + simulation.setTimeOfDaySuspended(flag); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("UnitAddItem", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CUnit whichUnit = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final CItem whichItem = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + final CAbilityInventory inventoryData = whichUnit.getInventoryData(); + if (inventoryData != null) { + inventoryData.giveItem(simulation, whichUnit, whichItem, false); + } + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("UnitItemInSlot", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CUnit whichUnit = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final int whichSlot = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + final CAbilityInventory inventoryData = whichUnit.getInventoryData(); + if (inventoryData != null) { + return new HandleJassValue(itemType, inventoryData.getItemInSlot(whichSlot)); + } + return new HandleJassValue(itemType, null); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetCameraTargetController", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CUnit whichUnit = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final float xoffset = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float yoffset = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); + final boolean inheritOrientation = arguments.get(3).visit(BooleanJassValueVisitor.getInstance()); + meleeUI.getCameraManager().setTargetController(war3MapViewer.getRenderPeer(whichUnit), xoffset, + yoffset, inheritOrientation); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetUnitInvulnerable", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CUnit whichUnit = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final boolean flag = arguments.get(1).visit(BooleanJassValueVisitor.getInstance()); + whichUnit.setInvulnerable(flag); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ShowUnit", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CUnit whichUnit = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final boolean show = arguments.get(1).visit(BooleanJassValueVisitor.getInstance()); + whichUnit.setHidden(!show); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("KillUnit", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CUnit whichUnit = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + if (!whichUnit.isDead()) { + whichUnit.setLife(simulation, 0f); + } + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("RemoveUnit", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CUnit whichUnit = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + simulation.removeUnit(whichUnit); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetTriggerWidget", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new HandleJassValue(widgetType, + ((CommonTriggerExecutionScope) triggerScope).getTriggerWidget()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetPlayerRace", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayer whichPlayer = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return new HandleJassValue(raceType, whichPlayer.getRace()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetPlayerId", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayer whichPlayer = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return new IntegerJassValue(whichPlayer.getId()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetDestructableLife", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CDestructable whichDestructable = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + return new RealJassValue(whichDestructable.getLife()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetDestructableMaxLife", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CDestructable whichDestructable = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + return new RealJassValue(whichDestructable.getMaxLife()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetDestructableAnimation", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CDestructable whichDestructable = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + final String animation = CommonEnvironment.this.gameUI + .getTrigStr(arguments.get(1).visit(StringJassValueVisitor.getInstance())); + final RenderDestructable renderPeer = war3MapViewer.getRenderPeer(whichDestructable); + renderPeer.setAnimation(animation); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("DestructableRestoreLife", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CDestructable whichDestructable = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + final float life = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + final boolean birth = arguments.get(2).visit(BooleanJassValueVisitor.getInstance()); + // TODO this "restore" function, is it a summation or assignment below??? + // Guessing assign is OK + whichDestructable.setLife(simulation, life); + if (!birth) { + final RenderDestructable renderPeer = war3MapViewer.getRenderPeer(whichDestructable); + renderPeer.notifyLifeRestored(); + } // else birth plays automatically + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("DisplayTimedTextToPlayer", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayer whichPlayer = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final float x = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float y = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float duration = arguments.get(3).visit(RealJassValueVisitor.getInstance()).floatValue(); + final String message = CommonEnvironment.this.gameUI + .getTrigStr(arguments.get(4).visit(StringJassValueVisitor.getInstance())); + if (whichPlayer == simulation.getPlayer(war3MapViewer.getLocalPlayerIndex())) { + meleeUI.displayTimedText(x, y, duration, message); + } + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("DisplayTextToPlayer", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayer whichPlayer = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final float x = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float y = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); + final String message = CommonEnvironment.this.gameUI + .getTrigStr(arguments.get(3).visit(StringJassValueVisitor.getInstance())); + if (whichPlayer == simulation.getPlayer(war3MapViewer.getLocalPlayerIndex())) { + meleeUI.displayTimedText(x, y, (message.length() / 6) + 5, message); + } + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("IsPlayerInForce", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayer whichPlayer = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final List force = arguments.get(1) + .visit(ObjectJassValueVisitor.>getInstance()); + return BooleanJassValue.of(force.contains(whichPlayer)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetUnitTypeId", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CUnit whichUnit = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + if (whichUnit == null) { + return new IntegerJassValue(0); + } + return new IntegerJassValue(whichUnit.getTypeId().getValue()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetItemTypeId", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CItem whichUnit = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + if (whichUnit == null) { + return new IntegerJassValue(0); + } + return new IntegerJassValue(whichUnit.getTypeId().getValue()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetOwningPlayer", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CUnit whichUnit = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return new HandleJassValue(playerType, simulation.getPlayer(whichUnit.getPlayerIndex())); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetFilterPlayer", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new HandleJassValue(playerType, + ((CommonTriggerExecutionScope) triggerScope).getFilterPlayer()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetDyingUnit", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new HandleJassValue(unitType, ((CommonTriggerExecutionScope) triggerScope).getDyingUnit()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetKillingUnit", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new HandleJassValue(unitType, ((CommonTriggerExecutionScope) triggerScope).getKillingUnit()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetTriggerUnit", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new HandleJassValue(unitType, + ((CommonTriggerExecutionScope) triggerScope).getTriggeringUnit()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetEnumPlayer", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new HandleJassValue(playerType, + ((CommonTriggerExecutionScope) triggerScope).getEnumPlayer()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetBlightLoc", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayer whichPlayer = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final Point2D.Double whichLocation = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + final float radius = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); + final boolean addBlight = arguments.get(3).visit(BooleanJassValueVisitor.getInstance()); + final Rectangle blightRectangle = new Rectangle((float) (whichLocation.x - radius), + (float) (whichLocation.y - radius), radius * 2, radius * 2); + final float blightRectangleMaxX = blightRectangle.x + blightRectangle.width; + final float blightRectangleMaxY = blightRectangle.y + blightRectangle.height; + final float rSquared = radius * radius; + for (float x = blightRectangle.x; x < blightRectangleMaxX; x += 128.0f) { + for (float y = blightRectangle.y; y < blightRectangleMaxY; y += 128.0f) { + final float dx = x - (float) whichLocation.x; + final float dy = y - (float) whichLocation.y; + final float distSquared = (dx * dx) + (dy * dy); + if (distSquared <= rSquared) { + for (float pathX = -64; pathX < 64; pathX += 32f) { + for (float pathY = -64; pathY < 64; pathY += 32f) { + simulation.getPathingGrid().setBlighted(x + pathX + 16, y + pathY + 16, + addBlight); + } + } + war3MapViewer.terrain.getCorner(x, y).setBlight(true); + } + } + } + war3MapViewer.terrain.updateGroundTextures(blightRectangle); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetLocalPlayer", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new HandleJassValue(playerType, simulation.getPlayer(war3MapViewer.getLocalPlayerIndex())); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("IsUnitInGroup", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CUnit whichUnit = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final List group = arguments.get(1).visit(ObjectJassValueVisitor.>getInstance()); + return BooleanJassValue.of(group.contains(whichUnit)); + } + }); } private void registerConfigNatives(final JassProgramVisitor jassProgramVisitor, final War3MapConfig mapConfig, @@ -2699,7 +3790,7 @@ public class Jass2 { public JassValue call(final List arguments, final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { final CGameType gameType = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - return new BooleanJassValue(mapConfig.isGameTypeSupported(gameType)); + return BooleanJassValue.of(mapConfig.isGameTypeSupported(gameType)); } }); jassProgramVisitor.getJassNativeManager().createNative("GetGameTypeSelected", new JassFunction() { @@ -2714,7 +3805,7 @@ public class Jass2 { public JassValue call(final List arguments, final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { final CMapFlag mapFlag = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - return new BooleanJassValue(mapConfig.isMapFlagSet(mapFlag)); + return BooleanJassValue.of(mapConfig.isMapFlagSet(mapFlag)); } }); jassProgramVisitor.getJassNativeManager().createNative("GetGamePlacement", new JassFunction() { @@ -2944,7 +4035,7 @@ public class Jass2 { final TriggerExecutionScope triggerScope) { final CPlayerJass player = arguments.get(0) .visit(ObjectJassValueVisitor.getInstance()); - return new BooleanJassValue(player.isSelectable()); + return BooleanJassValue.of(player.isSelectable()); } }); jassProgramVisitor.getJassNativeManager().createNative("GetPlayerController", new JassFunction() { @@ -2962,7 +4053,7 @@ public class Jass2 { final TriggerExecutionScope triggerScope) { final CPlayerJass player = arguments.get(0) .visit(ObjectJassValueVisitor.getInstance()); - return new HandleJassValue(playerslotstateType, player.getController()); + return new HandleJassValue(playerslotstateType, player.getSlotState()); } }); jassProgramVisitor.getJassNativeManager().createNative("GetPlayerTaxRate", new JassFunction() { @@ -2986,7 +4077,7 @@ public class Jass2 { .visit(ObjectJassValueVisitor.getInstance()); final CRacePreference racePref = arguments.get(1) .visit(ObjectJassValueVisitor.getInstance()); - return new BooleanJassValue(player.isRacePrefSet(racePref)); + return BooleanJassValue.of(player.isRacePrefSet(racePref)); } }); jassProgramVisitor.getJassNativeManager().createNative("GetPlayerName", new JassFunction() { @@ -2999,6 +4090,11 @@ public class Jass2 { } }); } + + public void main() { + this.jassProgramVisitor.getGlobals().getFunctionByName("main").call(Collections.emptyList(), + new GlobalScope(), null); + } } private static void setupTriggerAPI(final JassProgramVisitor jassProgramVisitor, final HandleJassType triggerType, @@ -3242,4 +4338,64 @@ public class Jass2 { } }); } + + private static T nullable(final List arguments, final int index, + final ObjectJassValueVisitor visitor) { + final JassValue arg = arguments.get(index); + if (arg == null) { + return null; + } + return arg.visit(visitor); + } + + private static void doPreloadScript(final DataSource dataSource, final War3MapViewer war3MapViewer, + final String filename) { + final JassProgramVisitor jassProgramVisitor = new JassProgramVisitor(); + jassProgramVisitor.getJassNativeManager().createNative("Preload", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String filename = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + war3MapViewer.load(filename, war3MapViewer.mapPathSolver, war3MapViewer.solverParams); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("PreloadEnd", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final float timeout = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + return null; + } + }); + try { + JassLexer lexer; + try { + lexer = new JassLexer(CharStreams.fromStream(dataSource.getResourceAsStream(filename))); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + final JassParser parser = new JassParser(new CommonTokenStream(lexer)); +// parser.removeErrorListener(ConsoleErrorListener.INSTANCE); + parser.addErrorListener(new BaseErrorListener() { + @Override + public void syntaxError(final Recognizer recognizer, final Object offendingSymbol, final int line, + final int charPositionInLine, final String msg, final RecognitionException e) { + if (!REPORT_SYNTAX_ERRORS) { + return; + } + + final String sourceName = String.format("%s:%d:%d: ", filename, line, charPositionInLine); + + System.err.println(sourceName + "line " + line + ":" + charPositionInLine + " " + msg); + throw new IllegalStateException(sourceName + "line " + line + ":" + charPositionInLine + " " + msg); + } + }); + jassProgramVisitor.visit(parser.program()); + } + catch (final Exception e) { + e.printStackTrace(); + } + } } diff --git a/core/src/com/etheller/warsmash/parsers/jass/scope/CommonTriggerExecutionScope.java b/core/src/com/etheller/warsmash/parsers/jass/scope/CommonTriggerExecutionScope.java index 492cfc6..d2c10a7 100644 --- a/core/src/com/etheller/warsmash/parsers/jass/scope/CommonTriggerExecutionScope.java +++ b/core/src/com/etheller/warsmash/parsers/jass/scope/CommonTriggerExecutionScope.java @@ -64,13 +64,14 @@ public class CommonTriggerExecutionScope extends TriggerExecutionScope { private CDestructable orderTargetDestructable; private CItem orderTargetItem; private CUnit orderTargetUnit; + private CWidget triggerWidget; public CommonTriggerExecutionScope(final Trigger triggeringTrigger) { super(triggeringTrigger); } - public CommonTriggerExecutionScope(final TriggerExecutionScope parentScope) { - super(parentScope.getTriggeringTrigger()); + public CommonTriggerExecutionScope(final Trigger triggeringTrigger, final TriggerExecutionScope parentScope) { + super(triggeringTrigger); if (parentScope instanceof CommonTriggerExecutionScope) { copyFrom((CommonTriggerExecutionScope) parentScope); } @@ -134,6 +135,7 @@ public class CommonTriggerExecutionScope extends TriggerExecutionScope { this.orderTargetDestructable = parentScope.orderTargetDestructable; this.orderTargetItem = parentScope.orderTargetItem; this.orderTargetUnit = parentScope.orderTargetUnit; + this.triggerWidget = parentScope.triggerWidget; } public CUnit getEnumUnit() { @@ -144,6 +146,10 @@ public class CommonTriggerExecutionScope extends TriggerExecutionScope { return this.triggeringUnit; } + public CWidget getTriggerWidget() { + return this.triggerWidget; + } + public CUnit getFilterUnit() { return this.filterUnit; } @@ -350,63 +356,121 @@ public class CommonTriggerExecutionScope extends TriggerExecutionScope { public static CommonTriggerExecutionScope filterScope(final TriggerExecutionScope parentScope, final CUnit filterUnit) { - final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(parentScope); + final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(parentScope.getTriggeringTrigger(), + parentScope); scope.filterUnit = filterUnit; return scope; } public static CommonTriggerExecutionScope enumScope(final TriggerExecutionScope parentScope, final CUnit enumUnit) { - final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(parentScope); + final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(parentScope.getTriggeringTrigger(), + parentScope); scope.enumUnit = enumUnit; return scope; } + public static CommonTriggerExecutionScope filterScope(final TriggerExecutionScope parentScope, + final CItem filterItem) { + final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(parentScope.getTriggeringTrigger(), + parentScope); + scope.filterItem = filterItem; + return scope; + } + + public static CommonTriggerExecutionScope enumScope(final TriggerExecutionScope parentScope, final CItem enumItem) { + final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(parentScope.getTriggeringTrigger(), + parentScope); + scope.enumItem = enumItem; + return scope; + } + + public static CommonTriggerExecutionScope filterScope(final TriggerExecutionScope parentScope, + final CDestructable filterDestructable) { + final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(parentScope.getTriggeringTrigger(), + parentScope); + scope.filterDestructable = filterDestructable; + return scope; + } + + public static CommonTriggerExecutionScope enumScope(final TriggerExecutionScope parentScope, + final CDestructable enumDestructable) { + final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(parentScope.getTriggeringTrigger(), + parentScope); + scope.enumDestructable = enumDestructable; + return scope; + } + public static CommonTriggerExecutionScope filterScope(final TriggerExecutionScope parentScope, final CPlayerJass filterPlayer) { - final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(parentScope); + final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(parentScope.getTriggeringTrigger(), + parentScope); scope.filterPlayer = filterPlayer; return scope; } public static CommonTriggerExecutionScope enumScope(final TriggerExecutionScope parentScope, final CPlayerJass enumPlayer) { - final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(parentScope); + final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(parentScope.getTriggeringTrigger(), + parentScope); scope.enumPlayer = enumPlayer; return scope; } - public static CommonTriggerExecutionScope expiringTimer(final CTimerJass cTimerJass) { - final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(TriggerExecutionScope.EMPTY); + public static CommonTriggerExecutionScope expiringTimer(final Trigger trigger, final CTimerJass cTimerJass) { + final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(trigger, TriggerExecutionScope.EMPTY); scope.expiringTimer = cTimerJass; return scope; } - public static CommonTriggerExecutionScope unitEnterRegionScope(final TriggerExecutionScope parentScope, - final CUnit enteringUnit, final CRegion triggeringRegion) { - final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(parentScope); + public static CommonTriggerExecutionScope unitEnterRegionScope(final Trigger trigger, + final TriggerExecutionScope parentScope, final CUnit enteringUnit, final CRegion triggeringRegion) { + final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(trigger, parentScope); scope.enteringUnit = enteringUnit; scope.triggeringRegion = triggeringRegion; return scope; } - public static CommonTriggerExecutionScope unitLeaveRegionScope(final TriggerExecutionScope parentScope, - final CUnit leavingUnit, final CRegion triggeringRegion) { - final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(parentScope); + public static CommonTriggerExecutionScope unitLeaveRegionScope(final Trigger trigger, + final TriggerExecutionScope parentScope, final CUnit leavingUnit, final CRegion triggeringRegion) { + final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(trigger, parentScope); scope.leavingUnit = leavingUnit; scope.triggeringRegion = triggeringRegion; return scope; } - public static CommonTriggerExecutionScope playerHeroLevelScope(final CUnit hero) { - final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(TriggerExecutionScope.EMPTY); + public static CommonTriggerExecutionScope playerHeroLevelScope(final Trigger trigger, final CUnit hero) { + final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(trigger, TriggerExecutionScope.EMPTY); scope.levelingUnit = hero; return scope; } - public static CommonTriggerExecutionScope playerHeroRevivableScope(final CUnit hero) { - final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(TriggerExecutionScope.EMPTY); + public static CommonTriggerExecutionScope playerHeroRevivableScope(final Trigger trigger, final CUnit hero) { + final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(trigger, TriggerExecutionScope.EMPTY); scope.revivableUnit = hero; return scope; } + public static CommonTriggerExecutionScope playerUnitDeathScope(final Trigger trigger, final CUnit dyingUnit, + final CUnit killingUnit) { + final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(trigger, TriggerExecutionScope.EMPTY); + scope.dyingUnit = dyingUnit; + scope.triggerWidget = dyingUnit; + scope.triggeringUnit = dyingUnit; + scope.killingUnit = killingUnit; + return scope; + } + + public static CommonTriggerExecutionScope widgetTriggerScope(final Trigger trigger, final CWidget triggerWidget) { + final CommonTriggerExecutionScope scope = new CommonTriggerExecutionScope(trigger, TriggerExecutionScope.EMPTY); + scope.triggerWidget = triggerWidget; + return scope; + } + + public static interface UnitEventScopeBuilder { + CommonTriggerExecutionScope create(Trigger trigger, CUnit unit); + } + + public static interface WidgetEventScopeBuilder { + CommonTriggerExecutionScope create(Trigger trigger, CWidget unit); + } } diff --git a/core/src/com/etheller/warsmash/parsers/w3x/objectdata/Warcraft3MapObjectData.java b/core/src/com/etheller/warsmash/parsers/w3x/objectdata/Warcraft3MapObjectData.java index 8236c53..c5d30b8 100644 --- a/core/src/com/etheller/warsmash/parsers/w3x/objectdata/Warcraft3MapObjectData.java +++ b/core/src/com/etheller/warsmash/parsers/w3x/objectdata/Warcraft3MapObjectData.java @@ -3,18 +3,22 @@ package com.etheller.warsmash.parsers.w3x.objectdata; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.StandardObjectData; import com.etheller.warsmash.units.StandardObjectData.WarcraftData; +import com.etheller.warsmash.units.custom.ObjectDataChangeEntry; import com.etheller.warsmash.units.custom.WTS; import com.etheller.warsmash.units.custom.WTSFile; import com.etheller.warsmash.units.custom.War3ObjectDataChangeset; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; +import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.util.WorldEditStrings; import com.google.common.io.LittleEndianDataInputStream; @@ -135,6 +139,27 @@ public final class Warcraft3MapObjectData { if (dataSource.has("war3map.w3u")) { unitChangeset.load(new LittleEndianDataInputStream(dataSource.getResourceAsStream("war3map.w3u")), wts, inlineWTS); + // push unit changes to items.... as a Reign of Chaos support... + Iterator> entryIterator = unitChangeset.getOriginal().iterator(); + while (entryIterator.hasNext()) { + final Entry entry = entryIterator.next(); + final String rawcodeString = entry.toString(); + final String oldIdString = entry.getValue().getOldId().toString(); + if ((standardUnits.get(oldIdString) == null) && (standardItems.get(oldIdString) != null)) { + itemChangeset.getOriginal().put(entry.getKey(), entry.getValue()); + entryIterator.remove(); + } + } + entryIterator = unitChangeset.getCustom().iterator(); + while (entryIterator.hasNext()) { + final Entry entry = entryIterator.next(); + final String rawcodeString = entry.toString(); + final String oldIdString = entry.getValue().getOldId().toString(); + if ((standardUnits.get(oldIdString) == null) && (standardItems.get(oldIdString) != null)) { + itemChangeset.getCustom().put(entry.getKey(), entry.getValue()); + entryIterator.remove(); + } + } } if (dataSource.has("war3map.w3t")) { itemChangeset.load(new LittleEndianDataInputStream(dataSource.getResourceAsStream("war3map.w3t")), wts, diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java b/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java index ff64e7c..a92a58a 100644 --- a/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java @@ -107,6 +107,10 @@ public class Corner { return this.blight; } + public void setBlight(final boolean flag) { + this.blight = flag ? 0b00100000 : 0; + } + public int getWater() { return this.water; } diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java index dc10458..61f078b 100644 --- a/core/src/com/etheller/warsmash/util/WarsmashConstants.java +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -6,7 +6,7 @@ public class WarsmashConstants { * With version, we use 0 for RoC, 1 for TFT emulation, and probably 2+ or * whatever for custom mods and other stuff */ - public static int GAME_VERSION = 0; + public static int GAME_VERSION = 1; public static final int REPLACEABLE_TEXTURE_LIMIT = 64; public static final float SIMULATION_STEP_TIME = 1 / 20f; public static final int PORT_NUMBER = 6115; @@ -36,4 +36,7 @@ public class WarsmashConstants { // workaround to fix it if you need the local files // to take priority over built-ins for tilesets. public static final boolean FIX_FLAT_FILES_TILESET_LOADING = false; + public static final boolean ENABLE_MUSIC = false; + public static final boolean LOAD_UNITS_FROM_WORLDEDIT_DATA = false; + public static final boolean LOCAL_TEMP_TEST_ALL_PLAYERS_PLAYING = true; } diff --git a/core/src/com/etheller/warsmash/viewer5/AudioContext.java b/core/src/com/etheller/warsmash/viewer5/AudioContext.java index 68383f2..bcc0e32 100644 --- a/core/src/com/etheller/warsmash/viewer5/AudioContext.java +++ b/core/src/com/etheller/warsmash/viewer5/AudioContext.java @@ -78,11 +78,29 @@ public class AudioContext { } public AudioPanner createPanner() { - return new AudioPanner(this.listener) { - @Override - public void connect(final AudioDestination destination) { - } - }; + return createPanner(true); + } + + public AudioPanner createPanner(final boolean stopWhenOutOfRange) { + if (!stopWhenOutOfRange) { + return new AudioPanner(this.listener) { + @Override + public void connect(final AudioDestination destination) { + } + + @Override + public boolean isWithinListenerDistance() { + return true; + } + }; + } + else { + return new AudioPanner(this.listener) { + @Override + public void connect(final AudioDestination destination) { + } + }; + } } public AudioBufferSource createBufferSource() { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index 460b981..3068431 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.List; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Quaternion; @@ -73,6 +74,7 @@ public class MdxComplexInstance extends ModelInstance { private float blendTime; private float blendTimeRemaining; public boolean additiveOverrideMeshMode = false; + private boolean hasAnyUnselectableMesh = false; public MdxComplexInstance(final MdxModel model) { super(model); @@ -85,6 +87,9 @@ public class MdxComplexInstance extends ModelInstance { this.geosetColors = new float[model.geosets.size()][]; for (int i = 0, l = model.geosets.size(); i < l; i++) { this.geosetColors[i] = new float[4]; + if (model.geosets.get(i).unselectable) { + this.hasAnyUnselectableMesh = true; + } } this.layerAlphas = new float[model.layers.size()]; @@ -677,6 +682,17 @@ public class MdxComplexInstance extends ModelInstance { return this; } + /** + * Set the vertex color of this instance. + */ + public MdxComplexInstance setVertexColor(final Color color) { + this.vertexColor[0] = color.r; + this.vertexColor[1] = color.g; + this.vertexColor[2] = color.b; + this.vertexColor[3] = color.a; + return this; + } + public MdxComplexInstance setVertexAlpha(final float alpha) { this.vertexColor[3] = alpha; @@ -808,7 +824,7 @@ public class MdxComplexInstance extends ModelInstance { intersected = true; } } - return intersected || intersectRayBounds(ray, intersection); + return intersected || (!this.hasAnyUnselectableMesh && intersectRayBounds(ray, intersection)); } /** diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/RibbonEmitter.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/RibbonEmitter.java index e815f21..a2c7220 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/RibbonEmitter.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/RibbonEmitter.java @@ -1,5 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.mdx; +import com.etheller.warsmash.util.WarsmashConstants; + public class RibbonEmitter extends MdxEmitter { public Ribbon first; public Ribbon last; @@ -16,7 +18,8 @@ public class RibbonEmitter extends MdxEmitter 1) { - fileVar += Math.min(doodad.getVariation(), numVar - 1); - } - - fileVar += ".mdx"; - - final float maxPitch = row.readSLKTagFloat("maxPitch"); - final float maxRoll = row.readSLKTagFloat("maxRoll"); - if (type == WorldEditorDataType.DESTRUCTIBLES) { - final String shadowString = row.readSLKTag("shadow"); - if ((shadowString != null) && (shadowString.length() > 0) && !"_".equals(shadowString)) { - destructableShadow = this.terrain.addShadow(shadowString, doodad.getLocation()[0], - doodad.getLocation()[1]); - } - - final BufferedImage destructablePathingPixelMap = getDestructablePathingPixelMap(row); - if (destructablePathingPixelMap != null) { - destructablePathing = this.terrain.pathingGrid.createRemovablePathingOverlayTexture( - doodad.getLocation()[0], doodad.getLocation()[1], - (int) Math.toDegrees(doodad.getAngle()), destructablePathingPixelMap); - if (doodad.getLife() > 0) { - destructablePathing.add(); - } - } - final BufferedImage destructablePathingDeathPixelMap = getDestructablePathingDeathPixelMap(row); - if (destructablePathingDeathPixelMap != null) { - destructablePathingDeath = this.terrain.pathingGrid.createRemovablePathingOverlayTexture( - doodad.getLocation()[0], doodad.getLocation()[1], - (int) Math.toDegrees(doodad.getAngle()), destructablePathingDeathPixelMap); - if (doodad.getLife() <= 0) { - destructablePathingDeath.add(); - } - } - } - // First see if the model is local. - // Doodads referring to local models may have invalid variations, so if the - // variation doesn't exist, try without a variation. - - String path; - if (this.mapMpq.has(fileVar)) { - path = fileVar; - } - else { - path = file; - } - MdxModel model; - if (this.mapMpq.has(path)) { - model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); - } - else { - model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams); - } - - if (type == WorldEditorDataType.DESTRUCTIBLES) { - final float x = doodad.getLocation()[0]; - final float y = doodad.getLocation()[1]; - final CDestructable simulationDestructable = this.simulation.createDestructable(row.getAlias(), x, - y, destructablePathing, destructablePathingDeath); - simulationDestructable.setLife(this.simulation, - simulationDestructable.getLife() * (doodad.getLife() / 100f)); - final RenderDestructable renderDestructable = new RenderDestructable(this, model, row, doodad, type, - maxPitch, maxRoll, doodad.getLife(), destructableShadow, simulationDestructable); - if (row.readSLKTagBoolean("walkable")) { - final float angle = doodad.getAngle(); - final BoundingBox boundingBox = model.bounds.getBoundingBox(); - final Rectangle renderDestructableBounds = getRotatedBoundingBox(x, y, angle, boundingBox); - System.out.println("ROTATED BOUNDS TO: " + renderDestructableBounds); - this.walkableObjectsTree.add((MdxComplexInstance) renderDestructable.instance, - renderDestructableBounds); - renderDestructable.walkableBounds = renderDestructableBounds; - } - this.widgets.add(renderDestructable); - this.destructableToRenderPeer.put(simulationDestructable, renderDestructable); - } - else { - this.doodads.add(new RenderDoodad(this, model, row, doodad, type, maxPitch, maxRoll)); - } + if ((doodad.getFlags() & 0x2) == 0) { + continue; } + final War3ID doodadId = doodad.getId(); + final int doodadVariation = doodad.getVariation(); + final float[] location = doodad.getLocation(); + final float facingRadians = doodad.getAngle(); + final short lifePercent = doodad.getLife(); + final float[] scale = doodad.getScale(); + createDestructableOrDoodad(doodadId, modifications, doodadVariation, location, facingRadians, lifePercent, + scale); } // Cliff/Terrain doodads. @@ -1124,6 +1052,125 @@ public class War3MapViewer extends AbstractMdxModelViewer { this.anyReady = true; } + private void createDoodad(final MutableGameObject row, final int doodadVariation, final float[] location, + final float facingRadians, final float[] scale) { + final MdxModel model = getDoodadModel(doodadVariation, row); + final float maxPitch = row.readSLKTagFloat("maxPitch"); + final float maxRoll = row.readSLKTagFloat("maxRoll"); + final float defScale = row.readSLKTagFloat("defScale"); + final RenderDoodad renderDoodad = new RenderDoodad(this, model, row, location, scale, facingRadians, maxPitch, + maxRoll, defScale); + renderDoodad.instance.uniformScale(defScale); + this.doodads.add(renderDoodad); + } + + private RenderDestructable createNewDestructable(final War3ID doodadId, final MutableGameObject row, + final int doodadVariation, final float[] location, final float facingRadians, final short lifePercent, + final float[] scale) { + BuildingShadow destructableShadow = null; + RemovablePathingMapInstance destructablePathing = null; + RemovablePathingMapInstance destructablePathingDeath = null; + final MdxModel model = getDoodadModel(doodadVariation, row); + + final float maxPitch = row.readSLKTagFloat("maxPitch"); + final float maxRoll = row.readSLKTagFloat("maxRoll"); + final String shadowString = row.readSLKTag("shadow"); + if ((shadowString != null) && (shadowString.length() > 0) && !"_".equals(shadowString)) { + destructableShadow = this.terrain.addShadow(shadowString, location[0], location[1]); + } + + final BufferedImage destructablePathingPixelMap = getDestructablePathingPixelMap(row); + if (destructablePathingPixelMap != null) { + destructablePathing = this.terrain.pathingGrid.createRemovablePathingOverlayTexture(location[0], + location[1], (int) Math.toDegrees(facingRadians), destructablePathingPixelMap); + if (lifePercent > 0) { + destructablePathing.add(); + } + } + final BufferedImage destructablePathingDeathPixelMap = getDestructablePathingDeathPixelMap(row); + if (destructablePathingDeathPixelMap != null) { + destructablePathingDeath = this.terrain.pathingGrid.createRemovablePathingOverlayTexture(location[0], + location[1], (int) Math.toDegrees(facingRadians), destructablePathingDeathPixelMap); + if (lifePercent <= 0) { + destructablePathingDeath.add(); + } + } + final float x = location[0]; + final float y = location[1]; + final CDestructable simulationDestructable = this.simulation.internalCreateDestructable(row.getAlias(), x, y, + destructablePathing, destructablePathingDeath); + // Used to be this, but why: (float) Math.sqrt((scale[0]) * (scale[1]) * + // (scale[2])); + final float selectionScale = 1.0f; + simulationDestructable.setLife(this.simulation, simulationDestructable.getLife() * (lifePercent / 100f)); + final RenderDestructable renderDestructable = new RenderDestructable(this, model, row, location, scale, + facingRadians, selectionScale, maxPitch, maxRoll, lifePercent, destructableShadow, + simulationDestructable); + if (row.readSLKTagBoolean("walkable")) { + final float angle = facingRadians; + final BoundingBox boundingBox = model.bounds.getBoundingBox(); + final Rectangle renderDestructableBounds = getRotatedBoundingBox(x, y, angle, boundingBox); + this.walkableObjectsTree.add((MdxComplexInstance) renderDestructable.instance, renderDestructableBounds); + renderDestructable.walkableBounds = renderDestructableBounds; + } + this.widgets.add(renderDestructable); + this.destructableToRenderPeer.put(simulationDestructable, renderDestructable); + return renderDestructable; + } + + private void createDestructableOrDoodad(final War3ID doodadId, final Warcraft3MapObjectData modifications, + final int doodadVariation, final float[] location, final float facingRadians, final short lifePercent, + final float[] scale) { + MutableGameObject row = modifications.getDoodads().get(doodadId); + if (row == null) { + row = modifications.getDestructibles().get(doodadId); + if (row != null) { + createNewDestructable(doodadId, row, doodadVariation, location, facingRadians, lifePercent, scale); + } + } + else { + createDoodad(row, doodadVariation, location, facingRadians, scale); + } + } + + private MdxModel getDoodadModel(final int doodadVariation, final MutableGameObject row) { + String file = row.readSLKTag("file"); + final int numVar = row.readSLKTagInt("numVar"); + + if (file.endsWith(".mdx") || file.endsWith(".mdl")) { + file = file.substring(0, file.length() - 4); + } + + String fileVar = file; + + file += ".mdx"; + + if (numVar > 1) { + fileVar += Math.min(doodadVariation, numVar - 1); + } + + fileVar += ".mdx"; + // First see if the model is local. + // Doodads referring to local models may have invalid variations, so if the + // variation doesn't exist, try without a variation. + + String path; + if (this.mapMpq.has(fileVar)) { + path = fileVar; + } + else { + path = file; + } + MdxModel model; + if (this.mapMpq.has(path)) { + model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); + } + else { + model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams); + } + return model; + } + private Rectangle getRotatedBoundingBox(final float x, final float y, final float angle, final BoundingBox boundingBox) { final float x1 = boundingBox.min.x; @@ -1170,7 +1217,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { this.soundsetNameToSoundset = new HashMap<>(); - if (this.dataSource.has("war3mapUnits.doo")) { + if (this.dataSource.has("war3mapUnits.doo") && WarsmashConstants.LOAD_UNITS_FROM_WORLDEDIT_DATA) { final War3MapUnitsDoo dooFile = mpq.readUnits(); // Collect the units and items data. @@ -1184,9 +1231,10 @@ public class War3MapViewer extends AbstractMdxModelViewer { final float unitAngle = unit.getAngle(); final int editorConfigHitPointPercent = unit.getHitpoints(); - final CUnit unitCreated = createNewUnit(modifications, unitId, unitX, unitY, unitZ, playerIndex, + final CWidget widgetCreated = createNewUnit(modifications, unitId, unitX, unitY, unitZ, playerIndex, customTeamColor, unitAngle); - if (unitCreated != null) { + if (widgetCreated instanceof CUnit) { + final CUnit unitCreated = (CUnit) widgetCreated; if (editorConfigHitPointPercent > 0) { unitCreated.setLife(this.simulation, unitCreated.getMaximumLife() * (editorConfigHitPointPercent / 100f)); @@ -1205,7 +1253,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { this.anyReady = true; } - private CUnit createNewUnit(final Warcraft3MapObjectData modifications, final War3ID unitId, float unitX, + private CWidget createNewUnit(final Warcraft3MapObjectData modifications, final War3ID unitId, float unitX, float unitY, final float unitZ, final int playerIndex, int customTeamColor, final float unitAngle) { UnitSoundset soundset = null; MutableGameObject row = null; @@ -1215,7 +1263,6 @@ public class War3MapViewer extends AbstractMdxModelViewer { Splat buildingUberSplat = null; SplatMover buildingUberSplatDynamicIngame = null; BufferedImage buildingPathingPixelMap = null; - final float unitVertexScale = 1.0f; RemovablePathingMapInstance pathingInstance = null; BuildingShadow buildingShadowInstance = null; @@ -1421,7 +1468,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { } else { - final CItem simulationItem = this.simulation.createItem(row.getAlias(), unitX, unitY); + final CItem simulationItem = this.simulation.internalCreateItem(row.getAlias(), unitX, unitY); final RenderItem renderItem = new RenderItem(this, model, row, unitX, unitY, unitZ, unitAngle, soundset, portraitModel, simulationItem); this.widgets.add(renderItem); @@ -1438,6 +1485,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { if (unitShadowSplatDynamicIngame != null) { renderItem.shadow = unitShadowSplatDynamicIngame; } + return simulationItem; } } else { @@ -1671,7 +1719,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { if (!localPlayer.hasAlliance(selectedUnitPlayerIndex, CAllianceType.PASSIVE)) { allyKey = "e:"; } - else if (localPlayer.hasAlliance(selectedUnitPlayerIndex, CAllianceType.HELP_REQUEST)) { + else if (localPlayer.hasAlliance(selectedUnitPlayerIndex, CAllianceType.SHARED_CONTROL)) { allyKey = "f:"; } } @@ -1780,7 +1828,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { if (!localPlayer.hasAlliance(selectedUnitPlayerIndex, CAllianceType.PASSIVE)) { allyKey = "e:"; } - else if (localPlayer.hasAlliance(selectedUnitPlayerIndex, CAllianceType.HELP_REQUEST)) { + else if (localPlayer.hasAlliance(selectedUnitPlayerIndex, CAllianceType.SHARED_CONTROL)) { allyKey = "f:"; } } @@ -2246,4 +2294,32 @@ public class War3MapViewer extends AbstractMdxModelViewer { public void setGameTurnManager(final GameTurnManager gameTurnManager) { this.gameTurnManager = gameTurnManager; } + + public RenderEffect addSpecialEffectTarget(final String modelName, final CWidget targetWidget, + final String attachPointName) { + if (targetWidget instanceof CUnit) { + final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.get(targetWidget); + final MdxModel spawnedEffectModel = (MdxModel) load(mdx(modelName), PathSolver.DEFAULT, null); + if (spawnedEffectModel != null) { + final MdxComplexInstance modelInstance = (MdxComplexInstance) spawnedEffectModel.addInstance(); + modelInstance.setTeamColor(renderUnit.playerIndex); + modelInstance.setLocation(renderUnit.location); + modelInstance.setScene(War3MapViewer.this.worldScene); + final RenderSpellEffect renderAttackInstant = new RenderSpellEffect(modelInstance, War3MapViewer.this, + (float) Math.toRadians(renderUnit.getSimulationUnit().getFacing()), + RenderSpellEffect.DEFAULT_ANIMATION_QUEUE); + War3MapViewer.this.projectiles.add(renderAttackInstant); + return renderAttackInstant; + } + } + else if (targetWidget instanceof CItem) { + // TODO this is stupid api, who would do this? + throw new UnsupportedOperationException("API for addSpecialEffectTarget() on item is NYI"); + } + else if (targetWidget instanceof CDestructable) { + // TODO this is stupid api, who would do this? + throw new UnsupportedOperationException("API for addSpecialEffectTarget() on destructable is NYI"); + } + return null; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/GameCameraManager.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/GameCameraManager.java index 017deeb..06654d2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/GameCameraManager.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/GameCameraManager.java @@ -3,6 +3,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.camera; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.math.Rectangle; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; public final class GameCameraManager extends CameraManager { private static final CameraRates INFINITE_CAMERA_RATES = new CameraRates(Float.POSITIVE_INFINITY, @@ -11,8 +12,13 @@ public final class GameCameraManager extends CameraManager { private final CameraPreset[] presets; private final CameraRates cameraRates; public final CameraPanControls cameraPanControls; + private Rectangle cameraBounds; private int currentPreset = 0; private float fov; + private RenderUnit targetControllerUnit; + private float targetControllerXOffset; + private float targetControllerYOffset; + private boolean targetControllerInheritOrientation; public GameCameraManager(final CameraPreset[] presets, final CameraRates cameraRates) { this.presets = presets; @@ -20,6 +26,10 @@ public final class GameCameraManager extends CameraManager { this.cameraPanControls = new CameraPanControls(); } + public void setCameraBounds(final Rectangle cameraBounds) { + this.cameraBounds = cameraBounds; + } + @Override public void updateCamera() { final CameraPreset cameraPreset = this.presets[this.currentPreset]; @@ -30,8 +40,16 @@ public final class GameCameraManager extends CameraManager { private void updateCamera(final CameraPreset cameraPreset, final CameraRates cameraRate) { this.quatHeap2.idt(); this.quatHeap.idt(); - this.horizontalAngle = applyAtRate(this.horizontalAngle, (float) Math.toRadians( - cameraPreset.getRotation(this.cameraPanControls.insertDown, this.cameraPanControls.deleteDown) - 90), + final float newHorizontalAngle; + if (this.targetControllerInheritOrientation && (this.targetControllerUnit != null)) { + newHorizontalAngle = this.targetControllerUnit.getFacing(); + } + else { + newHorizontalAngle = (float) Math.toRadians( + cameraPreset.getRotation(this.cameraPanControls.insertDown, this.cameraPanControls.deleteDown) + - 90); + } + this.horizontalAngle = applyAtRate(this.horizontalAngle, newHorizontalAngle, (float) Math.toRadians(cameraRate.rotation * 3)); this.quatHeap.setFromAxisRad(0, 0, 1, this.horizontalAngle); this.distance = applyAtRate(this.distance, Math.max(1200, cameraPreset.getDistance()), cameraRate.distance); @@ -69,41 +87,61 @@ public final class GameCameraManager extends CameraManager { } public void applyVelocity(final float deltaTime, boolean up, boolean down, boolean left, boolean right) { - final float velocityX; - final float velocityY; - up |= this.cameraPanControls.up; - down |= this.cameraPanControls.down; - left |= this.cameraPanControls.left; - right |= this.cameraPanControls.right; - if (up) { - if (down) { + if (this.targetControllerUnit != null) { + this.target.x = this.targetControllerUnit.getX() + this.targetControllerXOffset; + this.target.y = this.targetControllerUnit.getY() + this.targetControllerYOffset; + } + else { + final float velocityX; + final float velocityY; + up |= this.cameraPanControls.up; + down |= this.cameraPanControls.down; + left |= this.cameraPanControls.left; + right |= this.cameraPanControls.right; + if (up) { + if (down) { + velocityY = 0; + } + else { + velocityY = this.cameraRates.forward; + } + } + else if (down) { + velocityY = -this.cameraRates.forward; + } + else { velocityY = 0; } - else { - velocityY = this.cameraRates.forward; + if (right) { + if (left) { + velocityX = 0; + } + else { + velocityX = this.cameraRates.strafe; + } } - } - else if (down) { - velocityY = -this.cameraRates.forward; - } - else { - velocityY = 0; - } - if (right) { - if (left) { + else if (left) { + velocityX = -this.cameraRates.strafe; + } + else { velocityX = 0; } - else { - velocityX = this.cameraRates.strafe; + this.target.add(velocityX * deltaTime, velocityY * deltaTime, 0); + } + if (this.cameraBounds != null) { + if (this.target.x < this.cameraBounds.x) { + this.target.x = this.cameraBounds.x; + } + if (this.target.y < this.cameraBounds.y) { + this.target.y = this.cameraBounds.y; + } + if (this.target.x > (this.cameraBounds.x + this.cameraBounds.width)) { + this.target.x = this.cameraBounds.x + this.cameraBounds.width; + } + if (this.target.y > (this.cameraBounds.y + this.cameraBounds.height)) { + this.target.y = this.cameraBounds.y + this.cameraBounds.height; } } - else if (left) { - velocityX = -this.cameraRates.strafe; - } - else { - velocityX = 0; - } - this.target.add(velocityX * deltaTime, velocityY * deltaTime, 0); } @@ -176,4 +214,17 @@ public final class GameCameraManager extends CameraManager { } return false; } + + public Rectangle getCameraBounds() { + return this.cameraBounds; + } + + public void setTargetController(final RenderUnit targetControllerUnit, final float xoffset, final float yoffset, + final boolean inheritOrientation) { + this.targetControllerUnit = targetControllerUnit; + this.targetControllerXOffset = xoffset; + this.targetControllerYOffset = yoffset; + this.targetControllerInheritOrientation = inheritOrientation; + + } } \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java index 474617f..3056c18 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java @@ -252,6 +252,24 @@ public class PathingGrid { return (short) (this.pathingGrid[index] | this.dynamicPathingOverlay[index]); } + public void setCellPathing(final int cellX, final int cellY, final short pathingValue) { + final int index = (cellY * this.pathingGridSizes[0]) + cellX; + this.pathingGrid[index] = pathingValue; + } + + public void setCellBlighted(final int cellX, final int cellY, final boolean blighted) { + if (blighted) { + setCellPathing(cellX, cellY, (short) (getCellPathing(cellX, cellY) | (short) PathingFlags.BLIGHTED)); + } + else { + setCellPathing(cellX, cellY, (short) (getCellPathing(cellX, cellY) & (short) ~PathingFlags.BLIGHTED)); + } + } + + public void setBlighted(final float x, final float y, final boolean blighted) { + setCellBlighted(getCellX(x), getCellY(y), blighted); + } + public boolean isPathable(final float x, final float y, final PathingType pathingType) { return !PathingFlags.isPathingFlag(getPathing(x, y), pathingType.preventionFlag); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 359cc18..51c67e3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -406,6 +406,7 @@ public class Terrain { this.centerOffset = w3eFile.getCenterOffset(); this.uberSplatModels = new LinkedHashMap<>(); this.uberSplatModelsList = new ArrayList<>(); + this.defaultCameraBounds = w3iFile.getCameraBounds(); this.mapBounds = w3iFile.getCameraBoundsComplements(); this.shaderMapBounds = new float[] { (this.mapBounds[0] * 128.0f) + this.centerOffset[0], (this.mapBounds[2] * 128.0f) + this.centerOffset[1], @@ -724,7 +725,7 @@ public class Terrain { System.out.println("cliff: " + this.corners[x][y].cliff); } - private void updateGroundTextures(final Rectangle area) { + public void updateGroundTextures(final Rectangle area) { final Rectangle adjusted = new Rectangle(area.x - 1, area.y - 1, area.width + 2, area.height + 2); final Rectangle updateArea = new Rectangle(); Intersector.intersectRectangles(new Rectangle(0, 0, this.columns - 1, this.rows - 1), adjusted, updateArea); @@ -1343,6 +1344,7 @@ public class Terrain { public PathingGrid pathingGrid; private final Rectangle shaderMapBoundsRectangle; private final Rectangle entireMapRectangle; + private final float[] defaultCameraBounds; private static final class UnloadedTexture { private final int width; @@ -1390,6 +1392,19 @@ public class Terrain { return 0; } + public RenderCorner getCorner(final float x, final float y) { + final float userCellSpaceX = (x - this.centerOffset[0]) / 128.0f; + final float userCellSpaceY = (y - this.centerOffset[1]) / 128.0f; + final int cellX = (int) userCellSpaceX; + final int cellY = (int) userCellSpaceY; + + if ((cellX >= 0) && (cellX < (this.mapSize[0] - 1)) && (cellY >= 0) && (cellY < (this.mapSize[1] - 1))) { + return this.corners[cellX][cellY]; + } + + return null; + } + public float getWaterHeight(final float x, final float y) { final float userCellSpaceX = (x - this.centerOffset[0]) / 128.0f; final float userCellSpaceY = (y - this.centerOffset[1]) / 128.0f; @@ -1564,4 +1579,8 @@ public class Terrain { return (char) ('A' + layerHeightOffset); } } + + public float[] getDefaultCameraBounds() { + return this.defaultCameraBounds; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDestructable.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDestructable.java index d646817..ea02abf 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDestructable.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDestructable.java @@ -1,13 +1,15 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; +import java.util.EnumSet; + import com.badlogic.gdx.Gdx; import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; -import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; @@ -34,10 +36,10 @@ public class RenderDestructable extends RenderDoodad implements RenderWidget { private String replaceableTextureFile; public RenderDestructable(final War3MapViewer map, final MdxModel model, final MutableGameObject row, - final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type, + final float[] location3D, final float[] scale3D, final float facingRadians, final float selectionScale, final float maxPitch, final float maxRoll, final float life, final BuildingShadow destructableShadow, final CDestructable simulationDestructable) { - super(map, model, row, doodad, type, maxPitch, maxRoll); + super(map, model, row, location3D, scale3D, facingRadians, maxPitch, maxRoll, selectionScale); this.life = simulationDestructable.getLife(); this.destructableShadow = destructableShadow; this.simulationDestructable = simulationDestructable; @@ -187,4 +189,44 @@ public class RenderDestructable extends RenderDoodad implements RenderWidget { public SplatMover getSelectionCircle() { return this.selectionCircle; } + + public CDestructable getSimulationDestructable() { + return this.simulationDestructable; + } + + public UnitAnimationListenerImpl getUnitAnimationListenerImpl() { + return this.unitAnimationListenerImpl; + } + + public void setAnimation(final String sequence) { + final EnumSet primaryTags = EnumSet.noneOf(PrimaryTag.class); + PrimaryTag bestPrimaryTag = null; + final EnumSet secondaryTags = EnumSet.noneOf(SecondaryTag.class); + TokenLoop: for (final String token : sequence.split("\\s+")) { + final String upperCaseToken = token.toUpperCase(); + for (final PrimaryTag primaryTag : PrimaryTag.values()) { + if (upperCaseToken.equals(primaryTag.name())) { + primaryTags.add(primaryTag); + bestPrimaryTag = primaryTag; + continue TokenLoop; + } + } + for (final SecondaryTag secondaryTag : SecondaryTag.values()) { + if (upperCaseToken.equals(secondaryTag.name())) { + secondaryTags.add(secondaryTag); + continue TokenLoop; + } + } + break; + } + this.dead = this.simulationDestructable.isDead(); + this.life = this.simulationDestructable.getLife(); + this.unitAnimationListenerImpl.playAnimation(true, bestPrimaryTag, secondaryTags, 1.0f, true); + } + + public void notifyLifeRestored() { + this.dead = this.simulationDestructable.isDead(); + this.life = this.simulationDestructable.getLife(); + this.unitAnimationListenerImpl.playAnimation(true, getAnimation(), SequenceUtils.EMPTY, 1.0f, true); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDoodad.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDoodad.java index 2ce6b6e..1d105cd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDoodad.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDoodad.java @@ -2,7 +2,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; import com.badlogic.gdx.math.Quaternion; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; -import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.viewer5.ModelInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; @@ -23,8 +22,8 @@ public class RenderDoodad { protected float selectionScale; public RenderDoodad(final War3MapViewer map, final MdxModel model, final MutableGameObject row, - final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type, - final float maxPitch, final float maxRoll) { + final float[] location3D, final float[] scale3D, final float facingRadians, final float maxPitch, + final float maxRoll, final float selectionScale) { this.maxPitch = maxPitch; this.maxRoll = maxRoll; final boolean isSimple = row.readSLKTagBoolean("lightweight"); @@ -38,7 +37,7 @@ public class RenderDoodad { ((MdxComplexInstance) instance).setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); } - instance.move(doodad.getLocation()); + instance.move(location3D); // TODO: the following pitch/roll system is a heuristic, and we probably want to // revisit it later. // Specifically, I was pretty convinced that whichever is applied first @@ -46,10 +45,9 @@ public class RenderDoodad { // to find the angle used for the other of the two // (instead of measuring down from an imaginary flat ground plane, as we do // currently). - final float facingRadians = doodad.getAngle(); float pitch, roll; - this.x = doodad.getLocation()[0]; - this.y = doodad.getLocation()[1]; + this.x = location3D[0]; + this.y = location3D[1]; { if (!map.terrain.inPlayableArea(this.x, this.y)) { ((MdxComplexInstance) instance).setVertexColor(VERTEX_COLOR_BLACK); @@ -76,16 +74,8 @@ public class RenderDoodad { instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Y, -pitch)); instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_X, roll)); // instance.rotate(new Quaternion().setEulerAnglesRad(facingRadians, 0, 0)); - final float[] scale = doodad.getScale(); - instance.scale(scale); - if (type == WorldEditorDataType.DOODADS) { - final float defScale = row.readSLKTagFloat("defScale"); - instance.uniformScale(defScale); - this.selectionScale = defScale; - } - else { - this.selectionScale = (float) Math.sqrt((scale[0]) * (scale[1]) * (scale[2])); - } + instance.scale(scale3D); + this.selectionScale = selectionScale; instance.setScene(map.worldScene); this.instance = instance; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItem.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItem.java index 626f3db..a1bf80d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItem.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItem.java @@ -92,6 +92,7 @@ public class RenderItem implements RenderWidget { this.instance.show(); if (this.shadow != null) { this.shadow.show(map.terrain.centerOffset); + this.shadow.setLocation(this.location[0], this.location[1], map.terrain.centerOffset); } } } @@ -111,12 +112,10 @@ public class RenderItem implements RenderWidget { final float prevY = this.location[1]; final float simulationX = this.simulationItem.getX(); final float simulationY = this.simulationItem.getY(); - final float simDx = simulationX - this.location[0]; - final float simDy = simulationY - this.location[1]; + final float dx = simulationX - prevX; + final float dy = simulationY - prevY; this.location[0] = simulationX; this.location[1] = simulationY; - final float dx = this.location[0] - prevX; - final float dy = this.location[1] - prevY; final float groundHeight; // land units will have their feet pass under the surface of the water, so items // here are in the same place @@ -150,7 +149,7 @@ public class RenderItem implements RenderWidget { @Override public float getSelectionScale() { - return 1.0f; + return 64.0f; } @Override @@ -202,4 +201,8 @@ public class RenderItem implements RenderWidget { public SplatMover getSelectionCircle() { return this.selectionCircle; } + + public CItem getSimulationItem() { + return this.simulationItem; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderSpellEffect.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderSpellEffect.java new file mode 100644 index 0000000..521ec9e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderSpellEffect.java @@ -0,0 +1,53 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; + +import java.util.List; + +import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.Sequence; +import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.IndexedSequence; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; +import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; + +public class RenderSpellEffect implements RenderEffect { + public static final PrimaryTag[] DEFAULT_ANIMATION_QUEUE = { PrimaryTag.BIRTH, PrimaryTag.STAND, PrimaryTag.DEATH }; + private final MdxComplexInstance modelInstance; + private final PrimaryTag[] animationQueue; + private int animationQueueIndex; + private final List sequences; + + public RenderSpellEffect(final MdxComplexInstance modelInstance, final War3MapViewer war3MapViewer, final float yaw, + final PrimaryTag[] animationQueue) { + this.modelInstance = modelInstance; + this.animationQueue = animationQueue; + final MdxModel model = (MdxModel) this.modelInstance.model; + this.sequences = model.getSequences(); + this.modelInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); + this.modelInstance.localRotation.setFromAxisRad(0, 0, 1, yaw); + this.modelInstance.sequenceEnded = true; + playNextAnimation(); + } + + @Override + public boolean updateAnimations(final War3MapViewer war3MapViewer, final float deltaTime) { + playNextAnimation(); + final boolean everythingDone = this.animationQueueIndex >= this.animationQueue.length; + if (everythingDone) { + war3MapViewer.worldScene.removeInstance(this.modelInstance); + } + return everythingDone; + } + + private void playNextAnimation() { + while (this.modelInstance.sequenceEnded && (this.animationQueueIndex < this.animationQueue.length)) { + final IndexedSequence sequence = SequenceUtils.selectSequence(this.animationQueue[this.animationQueueIndex], + SequenceUtils.EMPTY, this.sequences, true); + if ((sequence != null) && (sequence.index != -1)) { + this.modelInstance.setSequence(sequence.index); + } + this.animationQueueIndex++; + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 77d022b..59b5391 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -563,4 +563,17 @@ public class RenderUnit implements RenderWidget { return this.selectionCircle; } + public boolean groupsWith(final RenderUnit selectedUnit) { + return this.simulationUnit.getUnitType() == selectedUnit.getSimulationUnit().getUnitType(); + } + + public void setPlayerColor(final int ordinal) { + this.playerIndex = ordinal; + getInstance().setTeamColor(ordinal); + } + + public float getFacing() { + return this.facing; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java index 8aeab7c..c54f0c5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java @@ -29,6 +29,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CAbi 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.upgrade.CAbilityUpgrade; 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; @@ -238,23 +239,25 @@ public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor + void heroTokensChanged(); + + public static final class CPlayerStateNotifier extends SubscriberSetNotifier implements CPlayerStateListener { @Override public void goldChanged() { @@ -49,5 +51,12 @@ public interface CPlayerStateListener { listener.heroDeath(); } } + + @Override + public void heroTokensChanged() { + for (final CPlayerStateListener listener : set) { + listener.heroTokensChanged(); + } + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 383ed3e..500daa3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -45,6 +45,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerJass import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CRace; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.region.CRegionManager; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.timers.CTimer; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.JassGameEventsWar3; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CPlayerSlotState; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; @@ -79,6 +81,7 @@ public class CSimulation implements CPlayerAPI { private transient CommandErrorListener commandErrorListener; private final CRegionManager regionManager; private final List timeOfDayVariableEvents = new ArrayList<>(); + private boolean timeOfDaySuspended; public CSimulation(final War3MapConfig config, final DataTable miscData, final MutableObjectData parsedUnitData, final MutableObjectData parsedItemData, final MutableObjectData parsedDestructableData, @@ -115,6 +118,9 @@ public class CSimulation implements CPlayerAPI { final CRace defaultRace = CRace.UNDEAD; final CPlayer newPlayer = new CPlayer(defaultRace, new float[] { startLoc.getX(), startLoc.getY() }, configPlayer); + if (WarsmashConstants.LOCAL_TEMP_TEST_ALL_PLAYERS_PLAYING) { + newPlayer.setSlotState(CPlayerSlotState.PLAYING); + } this.players.add(newPlayer); } this.players.get(this.players.size() - 4).setName(miscData.getLocalizedString("WESTRING_PLAYER_NA")); @@ -164,6 +170,7 @@ public class CSimulation implements CPlayerAPI { if (nextTimer.getEngineFireTick() > timer.getEngineFireTick()) { listIterator.previous(); listIterator.add(timer); + break; } } this.activeTimers.add(timer); @@ -187,7 +194,7 @@ public class CSimulation implements CPlayerAPI { return unit; } - public CDestructable createDestructable(final War3ID typeId, final float x, final float y, + public CDestructable internalCreateDestructable(final War3ID typeId, final float x, final float y, final RemovablePathingMapInstance pathingInstance, final RemovablePathingMapInstance pathingInstanceDeath) { final CDestructable dest = this.destructableData.create(this, typeId, x, y, this.handleIdAllocator, pathingInstance, pathingInstanceDeath); @@ -198,18 +205,32 @@ public class CSimulation implements CPlayerAPI { return dest; } - public CItem createItem(final War3ID alias, final float unitX, final float unitY) { + public CItem internalCreateItem(final War3ID alias, final float unitX, final float unitY) { final CItem item = this.itemData.create(this, alias, unitX, unitY, this.handleIdAllocator.createId()); this.handleIdToItem.put(item.getHandleId(), item); this.items.add(item); return item; } + public CItem createItem(final War3ID alias, final float unitX, final float unitY) { + return this.simulationRenderController.createItem(this, alias, unitX, unitY); + } + public CUnit createUnit(final War3ID typeId, final int playerIndex, final float x, final float y, final float facing) { return this.simulationRenderController.createUnit(this, typeId, playerIndex, x, y, facing); } + public CDestructable createDestructable(final War3ID typeId, final float x, final float y, final float facing, + final float scale, final int variation) { + return this.simulationRenderController.createDestructable(typeId, x, y, facing, scale, variation); + } + + public CDestructable createDestructableZ(final War3ID typeId, final float x, final float y, final float z, + final float facing, final float scale, final int variation) { + return this.simulationRenderController.createDestructableZ(typeId, x, y, z, facing, scale, variation); + } + public CUnit getUnit(final int handleId) { return this.handleIdToUnit.get(handleId); } @@ -288,8 +309,10 @@ public class CSimulation implements CPlayerAPI { } this.gameTurnTick++; final float timeOfDayBefore = this.getGameTimeOfDay(); - this.currentGameDayTimeElapsed = (this.currentGameDayTimeElapsed + WarsmashConstants.SIMULATION_STEP_TIME) - % this.gameplayConstants.getGameDayLength(); + if (!this.timeOfDaySuspended) { + this.currentGameDayTimeElapsed = (this.currentGameDayTimeElapsed + WarsmashConstants.SIMULATION_STEP_TIME) + % this.gameplayConstants.getGameDayLength(); + } final float timeOfDayAfter = this.getGameTimeOfDay(); while (!this.activeTimers.isEmpty() && (this.activeTimers.peek().getEngineFireTick() <= this.gameTurnTick)) { this.activeTimers.pop().fire(this); @@ -327,6 +350,11 @@ public class CSimulation implements CPlayerAPI { * this.gameplayConstants.getGameDayHours(); } + public void setGameTimeOfDay(final float value) { + final float elapsed = value / this.gameplayConstants.getGameDayHours(); + this.currentGameDayTimeElapsed = elapsed * this.gameplayConstants.getGameDayLength(); + } + public int getGameTurnTick() { return this.gameTurnTick; } @@ -482,6 +510,16 @@ public class CSimulation implements CPlayerAPI { }; } + public RemovableTriggerEvent registerGameEvent(final GlobalScope globalScope, final Trigger trigger, + final JassGameEventsWar3 gameEvent) { + System.err.println("Game event not yet implemented: " + gameEvent); + return new RemovableTriggerEvent() { + @Override + public void remove() { + } + }; + } + public void heroDeathEvent(final CUnit cUnit) { this.simulationRenderController.heroDeathEvent(cUnit); } @@ -524,4 +562,19 @@ public class CSimulation implements CPlayerAPI { } return RemovableTriggerEvent.DO_NOTHING; } + + public void setAllItemTypeSlots(final int slots) { + System.err.println( + "Ignoring call to set all item type slots to: " + slots + " (marketplace is not yet implemented)"); + } + + public void setAllUnitTypeSlots(final int slots) { + System.err.println( + "Ignoring call to set all unit type slots to: " + slots + " (marketplace is not yet implemented)"); + } + + public void setTimeOfDaySuspended(final boolean flag) { + this.timeOfDaySuspended = flag; + + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index a3f003d..2e135ea 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -38,6 +38,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorStop; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderNoTarget; @@ -49,6 +50,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.region.CRegion; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.region.CRegionEnumFunction; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.region.CRegionManager; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.state.CUnitState; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.unit.CUnitTypeJass; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityTargetCheckReceiver; @@ -263,6 +266,7 @@ public class CUnit extends CWidget { break; } } + this.stateListenersUpdates.clear(); if (isDead()) { if (this.collisionRectangle != null) { // Moved this here because doing it on "kill" was able to happen in some cases @@ -988,6 +992,8 @@ public class CUnit extends CWidget { } } } + fireDeathEvents(simulation); + simulation.getPlayer(this.playerIndex).fireUnitDeathEvents(this, source); } public boolean canReach(final AbilityTarget target, final float range) { @@ -1610,6 +1616,15 @@ public class CUnit extends CWidget { this.defaultBehavior = defaultBehavior; } + public CAbilityGoldMine getGoldMineData() { + for (final CAbility ability : this.abilities) { + if (ability instanceof CAbilityGoldMine) { + return ((CAbilityGoldMine) ability); + } + } + return null; + } + public int getGold() { for (final CAbility ability : this.abilities) { if (ability instanceof CAbilityGoldMine) { @@ -1744,4 +1759,141 @@ public class CUnit extends CWidget { return this.updateType; } } + + public void cancelUpgrade(final CSimulation game) { + throw new RuntimeException("NYI"); + } + + public void beginUpgrade(final CSimulation game, final War3ID rawcode) { + + } + + public void setUnitState(final CSimulation game, final CUnitState whichUnitState, final float value) { + switch (whichUnitState) { + case LIFE: + setLife(game, value); + break; + case MANA: + setMana(value); + break; + case MAX_LIFE: + setMaximumLife((int) value); + break; + case MAX_MANA: + setMaximumMana((int) value); + break; + } + } + + public float getUnitState(final CSimulation game, final CUnitState whichUnitState) { + switch (whichUnitState) { + case LIFE: + return getLife(); + case MANA: + return getMana(); + case MAX_LIFE: + return getMaximumLife(); + case MAX_MANA: + return getMaximumMana(); + } + return 0; + } + + public boolean isUnitType(final CUnitTypeJass whichUnitType) { + switch (whichUnitType) { + case HERO: + return getHeroData() != null; + case DEAD: + return isDead(); + case STRUCTURE: + return isBuilding(); + + case FLYING: + return getUnitType().getTargetedAs().contains(CTargetType.AIR); + case GROUND: + return getUnitType().getTargetedAs().contains(CTargetType.GROUND); + + case ATTACKS_FLYING: + for (final CUnitAttack attack : getAttacks()) { + if (attack.getTargetsAllowed().contains(CTargetType.AIR)) { + return true; + } + } + return false; + case ATTACKS_GROUND: + for (final CUnitAttack attack : getAttacks()) { + if (attack.getTargetsAllowed().contains(CTargetType.GROUND)) { + return true; + } + } + return false; + + case MELEE_ATTACKER: + boolean hasAttacks = false; + for (final CUnitAttack attack : getAttacks()) { + if (attack.getWeaponType() != CWeaponType.NORMAL) { + return false; + } + hasAttacks = true; + } + return hasAttacks; + + case RANGED_ATTACKER: + for (final CUnitAttack attack : getAttacks()) { + if (attack.getWeaponType() != CWeaponType.NORMAL) { + return true; + } + } + return false; + + case GIANT: + return getUnitType().getClassifications().contains(CUnitClassification.GIANT); + case SUMMONED: + return getUnitType().getClassifications().contains(CUnitClassification.SUMMONED); + case STUNNED: + return getCurrentBehavior().getHighlightOrderId() == OrderIds.stunned; + case PLAGUED: + throw new UnsupportedOperationException( + "cannot ask engine if unit is plagued: plague is not yet implemented"); + case SNARED: + throw new UnsupportedOperationException( + "cannot ask engine if unit is snared: snare is not yet implemented"); + + case UNDEAD: + return getUnitType().getClassifications().contains(CUnitClassification.UNDEAD); + case MECHANICAL: + return getUnitType().getClassifications().contains(CUnitClassification.MECHANICAL); + case PEON: + return getUnitType().getClassifications().contains(CUnitClassification.PEON); + case SAPPER: + return getUnitType().getClassifications().contains(CUnitClassification.SAPPER); + case TOWNHALL: + return getUnitType().getClassifications().contains(CUnitClassification.TOWNHALL); + case ANCIENT: + return this.unitType.getClassifications().contains(CUnitClassification.ANCIENT); + + case TAUREN: + return getUnitType().getClassifications().contains(CUnitClassification.TAUREN); + case POISONED: + throw new UnsupportedOperationException( + "cannot ask engine if unit is poisoned: poison is not yet implemented"); + case POLYMORPHED: + throw new UnsupportedOperationException( + "cannot ask engine if unit is POLYMORPHED: POLYMORPHED is not yet implemented"); + case SLEEPING: + throw new UnsupportedOperationException( + "cannot ask engine if unit is SLEEPING: SLEEPING is not yet implemented"); + case RESISTANT: + throw new UnsupportedOperationException( + "cannot ask engine if unit is RESISTANT: RESISTANT is not yet implemented"); + case ETHEREAL: + throw new UnsupportedOperationException( + "cannot ask engine if unit is ETHEREAL: ETHEREAL is not yet implemented"); + case MAGIC_IMMUNE: + throw new UnsupportedOperationException( + "cannot ask engine if unit is MAGIC_IMMUNE: MAGIC_IMMUNE is not yet implemented"); + + } + return false; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java index cea6b5f..7defb59 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java @@ -50,6 +50,7 @@ public class CUnitType { private final List structuresBuilt; private final List unitsTrained; private final List researchesAvailable; + private final List upgradesTo; private final CUnitRace unitRace; private final int goldCost; private final int lumberCost; @@ -85,8 +86,8 @@ public class CUnitType { final CDefenseType defenseType, final float impactZ, final BufferedImage buildingPathingPixelMap, final float deathTime, final EnumSet targetedAs, final float defaultAcquisitionRange, final float minimumAttackRange, final List structuresBuilt, final List unitsTrained, - final List researchesAvailable, final CUnitRace unitRace, final int goldCost, final int lumberCost, - final int foodUsed, final int foodMade, final int buildTime, + final List researchesAvailable, final List upgradesTo, final CUnitRace unitRace, + final int goldCost, final int lumberCost, final int foodUsed, final int foodMade, final int buildTime, final EnumSet preventedPathingTypes, final EnumSet requiredPathingTypes, final float propWindow, final float turnRate, final List requirements, final int level, final boolean hero, final int strength, @@ -122,6 +123,7 @@ public class CUnitType { this.structuresBuilt = structuresBuilt; this.unitsTrained = unitsTrained; this.researchesAvailable = researchesAvailable; + this.upgradesTo = upgradesTo; this.unitRace = unitRace; this.goldCost = goldCost; this.lumberCost = lumberCost; @@ -262,6 +264,10 @@ public class CUnitType { return this.researchesAvailable; } + public List getUpgradesTo() { + return this.upgradesTo; + } + public CUnitRace getRace() { return this.unitRace; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java index 11bed5f..ff1903b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java @@ -1,11 +1,20 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; +import java.util.ArrayList; +import java.util.EnumMap; import java.util.EnumSet; +import java.util.List; import com.badlogic.gdx.math.Rectangle; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.trigger.RemovableTriggerEvent; +import com.etheller.interpreter.ast.scope.trigger.Trigger; +import com.etheller.warsmash.parsers.jass.scope.CommonTriggerExecutionScope; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.JassGameEventsWar3; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.unit.CWidgetEvent; public abstract class CWidget implements AbilityTarget { protected static final Rectangle tempRect = new Rectangle(); @@ -13,6 +22,8 @@ public abstract class CWidget implements AbilityTarget { private float x; private float y; protected float life; + private final EnumMap> eventTypeToEvents = new EnumMap<>( + JassGameEventsWar3.class); public CWidget(final int handleId, final float x, final float y, final float life) { this.handleId = handleId; @@ -78,4 +89,49 @@ public abstract class CWidget implements AbilityTarget { } public abstract boolean isInvulnerable(); + + public void fireDeathEvents(final CSimulation simulation) { + fireEvents(CommonTriggerExecutionScope::widgetTriggerScope, JassGameEventsWar3.EVENT_WIDGET_DEATH); + } + + private List getOrCreateEventList(final JassGameEventsWar3 eventType) { + List playerEvents = this.eventTypeToEvents.get(eventType); + if (playerEvents == null) { + playerEvents = new ArrayList<>(); + this.eventTypeToEvents.put(eventType, playerEvents); + } + return playerEvents; + } + + private List getEventList(final JassGameEventsWar3 eventType) { + return this.eventTypeToEvents.get(eventType); + } + + public RemovableTriggerEvent addEvent(final GlobalScope globalScope, final Trigger whichTrigger, + final JassGameEventsWar3 eventType) { + final CWidgetEvent playerEvent = new CWidgetEvent(globalScope, this, whichTrigger, eventType, null); + getOrCreateEventList(eventType).add(playerEvent); + return playerEvent; + } + + public void removeEvent(final CWidgetEvent playerEvent) { + final List eventList = getEventList(playerEvent.getEventType()); + if (eventList != null) { + eventList.remove(playerEvent); + } + } + + private void fireEvents(final CommonTriggerExecutionScope.WidgetEventScopeBuilder eventScopeBuilder, + final JassGameEventsWar3 eventType) { + final List eventList = getEventList(eventType); + if (eventList != null) { + for (final CWidgetEvent event : eventList) { + event.fire(this, eventScopeBuilder.create(event.getTrigger(), this)); + } + } + } + + public RemovableTriggerEvent addDeathEvent(final GlobalScope globalScope, final Trigger whichTrigger) { + return addEvent(globalScope, whichTrigger, JassGameEventsWar3.EVENT_WIDGET_DEATH); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java index d0e59a2..d37610c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; 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.CWidget; @@ -43,6 +44,9 @@ public class CAbilityAttack extends AbstractCAbility { return; } } + else if (target instanceof CDestructable) { + // fall thru to below + } else { receiver.orderIdNotAccepted(); return; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java index 5874170..a17d1e1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java @@ -14,6 +14,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CAbi 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.upgrade.CAbilityUpgrade; /** * A visitor for the lowest level inherent types of an ability. It's a bit of a @@ -48,6 +49,8 @@ public interface CAbilityVisitor { T accept(CAbilityQueue ability); + T accept(CAbilityUpgrade ability); + T accept(CAbilityReviveHero ability); T accept(GenericSingleIconActiveAbility ability); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java index 811ca64..b576fdd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java @@ -42,7 +42,8 @@ public abstract class AbstractCAbilityBuild extends AbstractCAbility implements if (unitType != null) { final CPlayer player = game.getPlayer(unit.getPlayerIndex()); final List requirements = unitType.getRequirements(); - boolean requirementsMet = true; + final boolean techtreeAllowedByMax = player.isTechtreeAllowedByMax(orderIdAsRawtype); + boolean requirementsMet = techtreeAllowedByMax; for (final CUnitTypeRequirement requirement : requirements) { if (player.getTechtreeUnlocked(requirement.getRequirement()) < requirement.getRequiredLevel()) { requirementsMet = false; @@ -69,8 +70,13 @@ public abstract class AbstractCAbilityBuild extends AbstractCAbility implements } } else { - for (final CUnitTypeRequirement requirement : requirements) { - receiver.missingRequirement(requirement.getRequirement(), requirement.getRequiredLevel()); + if (techtreeAllowedByMax) { + for (final CUnitTypeRequirement requirement : requirements) { + receiver.missingRequirement(requirement.getRequirement(), requirement.getRequiredLevel()); + } + } + else { + receiver.techtreeMaximumReached(); } } } @@ -108,7 +114,8 @@ public abstract class AbstractCAbilityBuild extends AbstractCAbility implements } @Override - public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, AbilityTarget target) { + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, + final AbilityTarget target) { return true; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CAbilityHero.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CAbilityHero.java index a8afef8..8f2a166 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CAbilityHero.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CAbilityHero.java @@ -187,7 +187,7 @@ public class CAbilityHero extends AbstractCAbility { } public void addXp(final CSimulation simulation, final CUnit unit, final int xp) { - this.xp += xp; + this.xp += xp * simulation.getPlayer(unit.getPlayerIndex()).getHandicapXP(); final CGameplayConstants gameplayConstants = simulation.getGameplayConstants(); while ((this.heroLevel < gameplayConstants.getMaxHeroLevel()) && (this.xp >= gameplayConstants.getNeedHeroXPSum(this.heroLevel))) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/inventory/CAbilityInventory.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/inventory/CAbilityInventory.java index 803dc6e..1b2128c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/inventory/CAbilityInventory.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/inventory/CAbilityInventory.java @@ -18,7 +18,9 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.inventory.CBehaviorDropItem; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.inventory.CBehaviorGetItem; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.inventory.CBehaviorGiveItemToHero; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; @@ -31,6 +33,7 @@ public class CAbilityInventory extends AbstractGenericNoIconAbility { private final List[] itemsHeldAbilities; private CBehaviorGetItem behaviorGetItem; private CBehaviorDropItem behaviorDropItem; + private CBehaviorGiveItemToHero behaviorGiveItem; public CAbilityInventory(final int handleId, final War3ID alias, final boolean canDropItems, final boolean canGetItems, final boolean canUseItems, final boolean dropItemsOnDeath, @@ -51,6 +54,7 @@ public class CAbilityInventory extends AbstractGenericNoIconAbility { public void onAdd(final CSimulation game, final CUnit unit) { this.behaviorGetItem = new CBehaviorGetItem(unit, this); this.behaviorDropItem = new CBehaviorDropItem(unit, this); + this.behaviorGiveItem = new CBehaviorGiveItemToHero(unit, this); } @Override @@ -81,16 +85,37 @@ public class CAbilityInventory extends AbstractGenericNoIconAbility { } } else if ((orderId >= OrderIds.itemuse00) && (orderId <= OrderIds.itemuse05)) { - final CAbility cAbility = this.itemsHeldAbilities[orderId - OrderIds.itemuse00].get(0); + final int slot = orderId - OrderIds.itemuse00; + final CAbility cAbility = this.itemsHeldAbilities[slot].get(0); int forwardedOrderId = orderId; if (cAbility instanceof GenericSingleIconActiveAbility) { forwardedOrderId = ((GenericSingleIconActiveAbility) cAbility).getBaseOrderId(); } - cAbility.checkBeforeQueue(game, caster, forwardedOrderId, target); + final boolean checkResult = cAbility.checkBeforeQueue(game, caster, forwardedOrderId, target); + if (!checkResult) { + // we will never call begin, so we need to consume a charge of perishables here + // assuming this is a no-queue instant use perishable... later if we have some + // other weird case where "check before queue" false is supposed to mean you + // can't use the skill, then this would consume charges without using it, and + // that would be stupid but I don't think we will do that since checkCanUse + // should be failing at that point. So then we should have never called + // checkBeforeQueue. + final CItem cItem = this.itemsHeld[slot]; + consumePerishableCharge(game, caster, slot, cItem); + } + return checkResult; } return super.checkBeforeQueue(game, caster, orderId, target); } + private void consumePerishableCharge(final CSimulation game, final CUnit caster, final int slot, + final CItem cItem) { + if (cItem.getItemType().isPerishable()) { + dropItem(game, caster, slot, caster.getX(), caster.getY(), false); + game.removeItem(cItem); + } + } + @Override public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { @@ -121,6 +146,11 @@ public class CAbilityInventory extends AbstractGenericNoIconAbility { return this.behaviorDropItem.reset(itemToDrop, target); } + public CBehavior beginDropItem(final CSimulation game, final CUnit caster, final int orderId, + final CItem itemToDrop, final CUnit targetHero) { + return this.behaviorGiveItem.reset(itemToDrop, targetHero); + } + @Override public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final AbilityPointTarget point) { @@ -133,10 +163,7 @@ public class CAbilityInventory extends AbstractGenericNoIconAbility { final int slot = orderId - OrderIds.itemuse00; final CBehavior behavior = this.itemsHeldAbilities[slot].get(0).beginNoTarget(game, caster, orderId); final CItem cItem = this.itemsHeld[slot]; - if (cItem.getItemType().isPerishable()) { - dropItem(game, caster, slot, caster.getX(), caster.getY(), false); - game.removeItem(cItem); - } + consumePerishableCharge(game, caster, slot, cItem); return behavior; } @@ -172,7 +199,24 @@ public class CAbilityInventory extends AbstractGenericNoIconAbility { receiver.orderIdNotAccepted(); } } - receiver.orderIdNotAccepted(); + else if (orderId == OrderIds.dropitem) { + if (target instanceof CUnit) { + final CUnit hero = (CUnit) target; + if ((hero.getInventoryData() != null) && game.getPlayer(hero.getPlayerIndex()) + .hasAlliance(unit.getPlayerIndex(), CAllianceType.PASSIVE)) { + receiver.targetOk(target); + } + else { + receiver.orderIdNotAccepted(); + } + } + else { + receiver.orderIdNotAccepted(); + } + } + else { + receiver.orderIdNotAccepted(); + } } } @@ -189,9 +233,12 @@ public class CAbilityInventory extends AbstractGenericNoIconAbility { @Override public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { - if (orderId == OrderIds.dropitem) { + if (orderId != OrderIds.dropitem) { receiver.orderIdNotAccepted(); } + else { + receiver.targetOk(target); + } } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java index 7250841..f750f85 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java @@ -48,7 +48,8 @@ public final class CAbilityQueue extends AbstractCAbility { if (unitType != null) { final CPlayer player = game.getPlayer(unit.getPlayerIndex()); final List requirements = unitType.getRequirements(); - boolean requirementsMet = true; + final boolean techtreeAllowedByMax = player.isTechtreeAllowedByMax(orderIdAsRawtype); + boolean requirementsMet = techtreeAllowedByMax; for (final CUnitTypeRequirement requirement : requirements) { if (player.getTechtreeUnlocked(requirement.getRequirement()) < requirement.getRequiredLevel()) { requirementsMet = false; @@ -74,8 +75,13 @@ public final class CAbilityQueue extends AbstractCAbility { } } else { - for (final CUnitTypeRequirement requirement : requirements) { - receiver.missingRequirement(requirement.getRequirement(), requirement.getRequiredLevel()); + if (techtreeAllowedByMax) { + for (final CUnitTypeRequirement requirement : requirements) { + receiver.missingRequirement(requirement.getRequirement(), requirement.getRequiredLevel()); + } + } + else { + receiver.techtreeMaximumReached(); } } } @@ -116,7 +122,8 @@ public final class CAbilityQueue extends AbstractCAbility { } @Override - public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, AbilityTarget target) { + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, + final AbilityTarget target) { return true; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/upgrade/CAbilityUpgrade.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/upgrade/CAbilityUpgrade.java index fb52922..77e0912 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/upgrade/CAbilityUpgrade.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/upgrade/CAbilityUpgrade.java @@ -1,5 +1,171 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.upgrade; -public class CAbilityUpgrade { +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +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.CUnitType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitTypeRequirement; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +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.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; + +public final class CAbilityUpgrade extends AbstractCAbility { + private final Set upgradesTo; + + public CAbilityUpgrade(final int handleId, final List upgradesTo) { + super(handleId); + this.upgradesTo = new LinkedHashSet<>(upgradesTo); + } + + public Set getUpgradesTo() { + return this.upgradesTo; + } + + @Override + protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { + final War3ID orderIdAsRawtype = new War3ID(orderId); + if (this.upgradesTo.contains(orderIdAsRawtype) && (unit.getBuildQueue()[0] == null)) { + final CUnitType unitType = game.getUnitData().getUnitType(orderIdAsRawtype); + if (unitType != null) { + final CPlayer player = game.getPlayer(unit.getPlayerIndex()); + final List requirements = unitType.getRequirements(); + final boolean techtreeAllowedByMax = player.isTechtreeAllowedByMax(orderIdAsRawtype); + boolean requirementsMet = techtreeAllowedByMax; + for (final CUnitTypeRequirement requirement : requirements) { + if (player.getTechtreeUnlocked(requirement.getRequirement()) < requirement.getRequiredLevel()) { + requirementsMet = false; + } + } + if (requirementsMet) { + if (player.getGold() >= unitType.getGoldCost()) { + if (player.getLumber() >= unitType.getLumberCost()) { + if ((unitType.getFoodUsed() == 0) + || ((player.getFoodUsed() + unitType.getFoodUsed()) <= player.getFoodCap())) { + receiver.useOk(); + } + else { + receiver.notEnoughResources(ResourceType.FOOD); + } + } + else { + receiver.notEnoughResources(ResourceType.LUMBER); + } + } + else { + receiver.notEnoughResources(ResourceType.GOLD); + } + } + else { + if (techtreeAllowedByMax) { + for (final CUnitTypeRequirement requirement : requirements) { + receiver.missingRequirement(requirement.getRequirement(), requirement.getRequiredLevel()); + } + } + else { + receiver.techtreeMaximumReached(); + } + } + } + else { + receiver.useOk(); + } + } + else { + /// ??? + receiver.useOk(); + } + } + + @Override + public final void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public final void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public final void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityTargetCheckReceiver receiver) { + if (this.upgradesTo.contains(new War3ID(orderId))) { + 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, + final 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.cancelUpgrade(game); + } + else { + final War3ID rawcode = new War3ID(orderId); + if (this.upgradesTo.contains(rawcode)) { + caster.beginUpgrade(game, rawcode); + } + } + return null; + } + + @Override + public T visit(final CAbilityVisitor visitor) { + return visitor.accept(this); + } + + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java index 7580353..95e0aed 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java @@ -18,6 +18,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CAbi 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.upgrade.CAbilityUpgrade; public class AbilityDisableWhileUnderConstructionVisitor implements CAbilityVisitor { public static final AbilityDisableWhileUnderConstructionVisitor INSTANCE = new AbilityDisableWhileUnderConstructionVisitor(); @@ -107,7 +108,14 @@ public class AbilityDisableWhileUnderConstructionVisitor implements CAbilityVisi } @Override - public Void accept(CAbilityReviveHero ability) { + public Void accept(final CAbilityUpgrade ability) { + ability.setDisabled(true); + ability.setIconShowing(false); + return null; + } + + @Override + public Void accept(final CAbilityReviveHero ability) { ability.setDisabled(true); ability.setIconShowing(false); return null; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/inventory/CBehaviorGiveItemToHero.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/inventory/CBehaviorGiveItemToHero.java new file mode 100644 index 0000000..6959522 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/inventory/CBehaviorGiveItemToHero.java @@ -0,0 +1,75 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.inventory; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; +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.inventory.CAbilityInventory; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetStillAliveVisitor; +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 CBehaviorGiveItemToHero extends CAbstractRangedBehavior { + private final CAbilityInventory inventory; + private CItem targetItem; + private CUnit targetHero; + + public CBehaviorGiveItemToHero(final CUnit unit, final CAbilityInventory inventory) { + super(unit); + this.inventory = inventory; + } + + public CBehaviorGiveItemToHero reset(final CItem targetItem, final CUnit targetHero) { + innerReset(targetHero); + this.targetItem = targetItem; + this.targetHero = targetHero; + return this; + } + + @Override + public boolean isWithinRange(final CSimulation simulation) { + return this.unit.canReach(this.target, simulation.getGameplayConstants().getGiveItemRange()); + } + + @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.dropitem; + } + + @Override + protected CBehavior update(final CSimulation simulation, final boolean withinFacingWindow) { + this.inventory.dropItem(simulation, this.unit, this.targetItem, this.target.getX(), this.target.getY(), true); + if (this.targetHero.getInventoryData() != null) { + this.targetHero.getInventoryData().giveItem(simulation, this.targetHero, this.targetItem, false); + } + return this.unit.pollNextOrderBehavior(simulation); + } + + @Override + protected CBehavior updateOnInvalidTarget(final CSimulation simulation) { + return this.unit.pollNextOrderBehavior(simulation); + } + + @Override + protected boolean checkTargetStillValid(final CSimulation simulation) { + return this.target.visit(AbilityTargetStillAliveVisitor.INSTANCE); + } + + @Override + protected void resetBeforeMoving(final CSimulation simulation) { + + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CDefenseType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CDefenseType.java index 1360513..28a5e93 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CDefenseType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CDefenseType.java @@ -28,6 +28,9 @@ public enum CDefenseType implements CodeKeyType { if (upperCaseTypeString.equals("HEAVY")) { return LARGE; } + if (upperCaseTypeString.trim().isEmpty()) { + System.err.println("bad"); + } return valueOf(upperCaseTypeString); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/config/CBasePlayer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/config/CBasePlayer.java index 3709093..c98729d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/config/CBasePlayer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/config/CBasePlayer.java @@ -176,6 +176,10 @@ public abstract class CBasePlayer implements CPlayerJass { return this.slotState; } + public void setSlotState(final CPlayerSlotState slotState) { + this.slotState = slotState; + } + @Override public int getTaxRate(final int otherPlayerIndex, final CPlayerState whichResource) { final Integer taxRate = this.taxRates[otherPlayerIndex].get(whichResource); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index abfb4e4..2cf78a3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -34,6 +34,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CPri 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.upgrade.CAbilityUpgrade; 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; @@ -144,6 +145,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 UPGRADES_TO = War3ID.fromString("uupt"); private static final War3ID REVIVES_HEROES = War3ID.fromString("urev"); private static final War3ID UNIT_RACE = War3ID.fromString("urac"); @@ -242,9 +244,13 @@ public class CUnitData { } final List unitsTrained = unitTypeInstance.getUnitsTrained(); final List researchesAvailable = unitTypeInstance.getResearchesAvailable(); + final List upgradesTo = unitTypeInstance.getUpgradesTo(); if (!unitsTrained.isEmpty() || !researchesAvailable.isEmpty()) { unit.add(simulation, new CAbilityQueue(handleIdAllocator.createId(), unitsTrained, researchesAvailable)); } + if (!upgradesTo.isEmpty()) { + unit.add(simulation, new CAbilityUpgrade(handleIdAllocator.createId(), upgradesTo)); + } if (unitTypeInstance.isRevivesHeroes()) { unit.add(simulation, new CAbilityReviveHero(handleIdAllocator.createId())); } @@ -466,6 +472,15 @@ public class CUnitData { } } + final String upgradesToString = unitType.getFieldAsString(UPGRADES_TO, 0); + final String[] upgradesToStringItems = upgradesToString.trim().split(","); + final List upgradesTo = new ArrayList<>(); + for (final String upgradesToStringItem : upgradesToStringItems) { + if (upgradesToStringItem.length() == 4) { + upgradesTo.add(War3ID.fromString(upgradesToStringItem)); + } + } + final String researchesAvailableString = unitType.getFieldAsString(RESEARCHES_AVAILABLE, 0); final String[] researchesAvailableStringItems = researchesAvailableString.trim().split(","); final List researchesAvailable = new ArrayList<>(); @@ -541,11 +556,11 @@ public class CUnitData { unitTypeInstance = new CUnitType(unitName, legacyName, typeId, life, manaInitial, manaMaximum, speed, defense, abilityList, isBldg, movementType, moveHeight, collisionSize, classifications, attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, targetedAs, - acquisitionRange, minimumAttackRange, structuresBuilt, unitsTrained, researchesAvailable, unitRace, - goldCost, lumberCost, foodUsed, foodMade, buildTime, preventedPathingTypes, requiredPathingTypes, - propWindow, turnRate, requirements, unitLevel, hero, strength, strPlus, agility, agiPlus, - intelligence, intPlus, primaryAttribute, heroAbilityList, heroProperNames, properNamesCount, - canFlee, priority, revivesHeroes); + acquisitionRange, minimumAttackRange, structuresBuilt, unitsTrained, researchesAvailable, + upgradesTo, unitRace, goldCost, lumberCost, foodUsed, foodMade, buildTime, preventedPathingTypes, + requiredPathingTypes, propWindow, turnRate, requirements, unitLevel, hero, strength, strPlus, + agility, agiPlus, intelligence, intPlus, primaryAttribute, heroAbilityList, heroProperNames, + properNamesCount, canFlee, priority, revivesHeroes); this.unitIdToUnitType.put(typeId, unitTypeInstance); this.jassLegacyNameToUnitId.put(legacyName, typeId); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderDropItemAtTargetWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderDropItemAtTargetWidget.java new file mode 100644 index 0000000..72f4c64 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderDropItemAtTargetWidget.java @@ -0,0 +1,105 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; +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.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.inventory.CAbilityInventory; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; + +public class COrderDropItemAtTargetWidget implements COrder { + private final int abilityHandleId; + private final int orderId; + private final int itemHandleId; + private final int targetHeroHandleId; + private final boolean queued; + + public COrderDropItemAtTargetWidget(final int abilityHandleId, final int orderId, final int itemHandleId, + final int targetHeroHandleId, final boolean queued) { + this.abilityHandleId = abilityHandleId; + this.orderId = orderId; + this.itemHandleId = itemHandleId; + this.targetHeroHandleId = targetHeroHandleId; + this.queued = queued; + } + + @Override + public int getAbilityHandleId() { + return this.abilityHandleId; + } + + @Override + public int getOrderId() { + return this.orderId; + } + + @Override + public CWidget getTarget(final CSimulation game) { + final CWidget target = game.getWidget(this.targetHeroHandleId); + return target; + } + + @Override + public boolean isQueued() { + return this.queued; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster) { + final CAbilityInventory ability = (CAbilityInventory) game.getAbility(this.abilityHandleId); + ability.checkCanUse(game, caster, this.orderId, this.abilityActivationReceiver.reset()); + if (this.abilityActivationReceiver.isUseOk()) { + final CItem itemToDrop = (CItem) game.getWidget(this.itemHandleId); + final CUnit targetHero = (CUnit) game.getWidget(this.targetHeroHandleId); + return ability.beginDropItem(game, caster, this.orderId, itemToDrop, targetHero); + } + else { + game.getCommandErrorListener(caster.getPlayerIndex()) + .showCommandError(this.abilityActivationReceiver.getMessage()); + return caster.pollNextOrderBehavior(game); + } + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + this.abilityHandleId; + result = (prime * result) + this.itemHandleId; + result = (prime * result) + this.orderId; + result = (prime * result) + (this.queued ? 1231 : 1237); + result = (prime * result) + this.targetHeroHandleId; + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final COrderDropItemAtTargetWidget other = (COrderDropItemAtTargetWidget) obj; + if (this.abilityHandleId != other.abilityHandleId) { + return false; + } + if (this.itemHandleId != other.itemHandleId) { + return false; + } + if (this.orderId != other.orderId) { + return false; + } + if (this.queued != other.queued) { + return false; + } + if (this.targetHeroHandleId != other.targetHeroHandleId) { + return false; + } + return true; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java index 0262d05..38fced2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java @@ -50,7 +50,7 @@ public class COrderTargetWidget implements COrder { if (abilityActivationReceiver.isUseOk()) { final CWidget target = game.getWidget(this.targetHandleId); final StringMsgTargetCheckReceiver targetReceiver = (StringMsgTargetCheckReceiver) targetCheckReceiver; - ability.checkCanTarget(game, caster, this.orderId, target, targetReceiver); + ability.checkCanTarget(game, caster, this.orderId, target, targetReceiver.reset()); if (targetReceiver.getTarget() != null) { return ability.begin(game, caster, this.orderId, targetReceiver.getTarget()); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java index 0bf8ea4..4e7a481 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java @@ -9,10 +9,12 @@ import java.util.Map; import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.trigger.RemovableTriggerEvent; import com.etheller.interpreter.ast.scope.trigger.Trigger; +import com.etheller.interpreter.ast.scope.trigger.TriggerBooleanExpression; import com.etheller.warsmash.parsers.jass.scope.CommonTriggerExecutionScope; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CPlayerStateListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CPlayerStateListener.CPlayerStateNotifier; +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; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.config.CBasePlayer; @@ -21,19 +23,37 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.JassGameEve public class CPlayer extends CBasePlayer { private final CRace race; private final float[] startLocation; - private int gold = 500; - private int lumber = 150; + private int gold; + private int lumber; + private int heroTokens; private int foodCap; private int foodUsed; + private int foodCapCeiling = 101; // TODO should not have a default, I put 101 to make it stand out private final Map rawcodeToTechtreeUnlocked = new HashMap<>(); + private final Map rawcodeToTechtreeMaxAllowed = new HashMap<>(); private final List heroes = new ArrayList<>(); private final EnumMap> eventTypeToEvents = new EnumMap<>( JassGameEventsWar3.class); + // Player state data + private boolean givesBounty = false; + private boolean alliedVictory = false; + private int gameResult; + private int placed; + private boolean observerOnDeath; + private boolean observer; + private boolean unfollowable; + private int goldUpkeepRate; + private int lumberUpkeepRate; + private int goldGathered; + private int lumberGathered; + private boolean noCreepSleep; + // if you use triggers for this then the transient tag here becomes really // 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 CPlayerStateNotifier stateNotifier = new CPlayerStateNotifier(); + private float handicapXP; public CPlayer(final CRace race, final float[] startLocation, final CBasePlayer configPlayer) { super(configPlayer); @@ -57,6 +77,10 @@ public class CPlayer extends CBasePlayer { return this.lumber; } + public int getHeroTokens() { + return this.heroTokens; + } + public int getFoodCap() { return this.foodCap; } @@ -65,6 +89,10 @@ public class CPlayer extends CBasePlayer { return this.foodUsed; } + public int getFoodCapCeiling() { + return this.foodCapCeiling; + } + public float[] getStartLocation() { return this.startLocation; } @@ -79,11 +107,21 @@ public class CPlayer extends CBasePlayer { this.stateNotifier.lumberChanged(); } + public void setHeroTokens(final int heroTokens) { + this.heroTokens = heroTokens; + this.stateNotifier.heroTokensChanged(); + } + public void setFoodCap(final int foodCap) { this.foodCap = foodCap; this.stateNotifier.foodChanged(); } + public void setFoodCapCeiling(final int foodCapCeiling) { + this.foodCapCeiling = foodCapCeiling; + this.stateNotifier.foodChanged(); + } + public void setFoodUsed(final int foodUsed) { this.foodUsed = foodUsed; this.stateNotifier.foodChanged(); @@ -117,6 +155,18 @@ public class CPlayer extends CBasePlayer { } } + public void setTechtreeMaxAllowed(final War3ID war3id, final int maximum) { + this.rawcodeToTechtreeMaxAllowed.put(war3id, maximum); + } + + public int getTechtreeMaxAllowed(final War3ID war3id) { + final Integer maxAllowed = this.rawcodeToTechtreeMaxAllowed.get(war3id); + if (maxAllowed != null) { + return maxAllowed; + } + return -1; + } + public void addStateListener(final CPlayerStateListener listener) { this.stateNotifier.subscribe(listener); } @@ -169,16 +219,17 @@ public class CPlayer extends CBasePlayer { public void onHeroDeath(final CUnit hero) { this.stateNotifier.heroDeath(); - firePlayerUnitEvents(hero, CommonTriggerExecutionScope.playerHeroRevivableScope(hero), + firePlayerUnitEvents(hero, CommonTriggerExecutionScope::playerHeroRevivableScope, JassGameEventsWar3.EVENT_PLAYER_HERO_REVIVABLE); } - private void firePlayerUnitEvents(final CUnit hero, final CommonTriggerExecutionScope eventScope, + private void firePlayerUnitEvents(final CUnit hero, + final CommonTriggerExecutionScope.UnitEventScopeBuilder eventScopeBuilder, final JassGameEventsWar3 eventType) { final List eventList = getEventList(eventType); if (eventList != null) { for (final CPlayerEvent event : eventList) { - event.fire(hero, eventScope); + event.fire(hero, eventScopeBuilder.create(event.getTrigger(), hero)); } } } @@ -188,10 +239,20 @@ public class CPlayer extends CBasePlayer { } public void fireHeroLevelEvents(final CUnit hero) { - firePlayerUnitEvents(hero, CommonTriggerExecutionScope.playerHeroRevivableScope(hero), + firePlayerUnitEvents(hero, CommonTriggerExecutionScope::playerHeroRevivableScope, JassGameEventsWar3.EVENT_PLAYER_HERO_LEVEL); } + public void fireUnitDeathEvents(final CUnit dyingUnit, final CUnit killingUnit) { + final List eventList = getEventList(JassGameEventsWar3.EVENT_PLAYER_UNIT_DEATH); + if (eventList != null) { + for (final CPlayerEvent event : eventList) { + event.fire(dyingUnit, + CommonTriggerExecutionScope.playerUnitDeathScope(event.getTrigger(), dyingUnit, killingUnit)); + } + } + } + private List getOrCreateEventList(final JassGameEventsWar3 eventType) { List playerEvents = this.eventTypeToEvents.get(eventType); if (playerEvents == null) { @@ -208,7 +269,14 @@ public class CPlayer extends CBasePlayer { @Override public RemovableTriggerEvent addEvent(final GlobalScope globalScope, final Trigger whichTrigger, final JassGameEventsWar3 eventType) { - final CPlayerEvent playerEvent = new CPlayerEvent(globalScope, this, whichTrigger, eventType); + final CPlayerEvent playerEvent = new CPlayerEvent(globalScope, this, whichTrigger, eventType, null); + getOrCreateEventList(eventType).add(playerEvent); + return playerEvent; + } + + public RemovableTriggerEvent addUnitEvent(final GlobalScope globalScope, final Trigger whichTrigger, + final JassGameEventsWar3 eventType, final TriggerBooleanExpression filter) { + final CPlayerEvent playerEvent = new CPlayerEvent(globalScope, this, whichTrigger, eventType, filter); getOrCreateEventList(eventType).add(playerEvent); return playerEvent; } @@ -220,4 +288,126 @@ public class CPlayer extends CBasePlayer { eventList.remove(playerEvent); } } + + public void setPlayerState(final CSimulation simulation, final CPlayerState whichPlayerState, final int value) { + switch (whichPlayerState) { + case GAME_RESULT: + this.gameResult = value; + break; + case RESOURCE_GOLD: + setGold(value); + break; + case RESOURCE_LUMBER: + setLumber(value); + break; + case RESOURCE_HERO_TOKENS: + setHeroTokens(value); + break; + case RESOURCE_FOOD_CAP: + setFoodCap(value); + break; + case RESOURCE_FOOD_USED: + setFoodUsed(value); + break; + case FOOD_CAP_CEILING: + setFoodCapCeiling(value); + break; + case ALLIED_VICTORY: + this.alliedVictory = (value != 0); + break; + case GIVES_BOUNTY: + this.givesBounty = (value != 0); + break; + case PLACED: + this.placed = value; + case OBSERVER_ON_DEATH: + this.observerOnDeath = (value != 0); + case OBSERVER: + this.observer = (value != 0); + case UNFOLLOWABLE: + this.unfollowable = (value != 0); + case GOLD_UPKEEP_RATE: + this.goldUpkeepRate = value; + break; + case LUMBER_UPKEEP_RATE: + this.lumberUpkeepRate = value; + break; + case GOLD_GATHERED: + this.goldGathered = value; + break; + case LUMBER_GATHERED: + this.goldGathered = value; + break; + case NO_CREEP_SLEEP: + this.noCreepSleep = (value != 0); + break; + default: + break; + } + } + + public int getPlayerState(final CSimulation simulation, final CPlayerState whichPlayerState) { + switch (whichPlayerState) { + case GAME_RESULT: + return this.gameResult; + case RESOURCE_GOLD: + return getGold(); + case RESOURCE_LUMBER: + return getLumber(); + case RESOURCE_HERO_TOKENS: + return getHeroTokens(); + case RESOURCE_FOOD_CAP: + return getFoodCap(); + case RESOURCE_FOOD_USED: + return getFoodUsed(); + case FOOD_CAP_CEILING: + return getFoodCapCeiling(); + case ALLIED_VICTORY: + return this.alliedVictory ? 1 : 0; + case GIVES_BOUNTY: + return this.givesBounty ? 1 : 0; + case PLACED: + return this.placed; + case OBSERVER_ON_DEATH: + return this.observerOnDeath ? 1 : 0; + case OBSERVER: + return this.observer ? 1 : 0; + case UNFOLLOWABLE: + return this.unfollowable ? 1 : 0; + case GOLD_UPKEEP_RATE: + return this.goldUpkeepRate; + case LUMBER_UPKEEP_RATE: + return this.lumberUpkeepRate; + case GOLD_GATHERED: + return this.goldGathered; + case LUMBER_GATHERED: + return this.lumberGathered; + case NO_CREEP_SLEEP: + return this.noCreepSleep ? 1 : 0; + default: + return 0; + } + } + + public boolean isObserver() { + return this.observer; + } + + public boolean isTechtreeAllowedByMax(final War3ID techtree) { + final int techtreeMaxAllowed = getTechtreeMaxAllowed(techtree); + if (techtreeMaxAllowed > 0) { + if (getTechtreeUnlocked(techtree) >= techtreeMaxAllowed) { + return false; + } + } + return true; + } + + public void setHandicapXP(final float handicapXP) { + this.handicapXP = handicapXP; + } + + public float getHandicapXP() { + return this.handicapXP; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerEvent.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerEvent.java index ce67ff3..358b623 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerEvent.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerEvent.java @@ -4,6 +4,8 @@ import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.scope.trigger.RemovableTriggerEvent; import com.etheller.interpreter.ast.scope.trigger.Trigger; +import com.etheller.interpreter.ast.scope.trigger.TriggerBooleanExpression; +import com.etheller.warsmash.parsers.jass.scope.CommonTriggerExecutionScope; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.JassGameEventsWar3; @@ -12,13 +14,15 @@ public class CPlayerEvent implements RemovableTriggerEvent { private final CPlayerJass player; private final Trigger trigger; private final JassGameEventsWar3 eventType; + private final TriggerBooleanExpression filter; public CPlayerEvent(final GlobalScope globalScope, final CPlayerJass player, final Trigger trigger, - final JassGameEventsWar3 eventType) { + final JassGameEventsWar3 eventType, final TriggerBooleanExpression filter) { this.globalScope = globalScope; this.player = player; this.trigger = trigger; this.eventType = eventType; + this.filter = filter; } public Trigger getTrigger() { @@ -35,6 +39,13 @@ public class CPlayerEvent implements RemovableTriggerEvent { } public void fire(final CUnit hero, final TriggerExecutionScope scope) { - this.trigger.evaluate(this.globalScope, scope); + if (this.filter != null) { + if (!this.filter.evaluate(this.globalScope, CommonTriggerExecutionScope.filterScope(scope, hero))) { + return; + } + } + if (this.trigger.evaluate(this.globalScope, scope)) { + this.trigger.execute(this.globalScope, scope); + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerState.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerState.java index 9965b4f..5da049f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerState.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerState.java @@ -1,6 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; public enum CPlayerState { + GAME_RESULT, // current resource levels // RESOURCE_GOLD, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java index f15b849..c2ec252 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java @@ -5,6 +5,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorHoldPosition; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderDropItemAtPoint; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderDropItemAtTargetWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderNoTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderTargetPoint; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderTargetWidget; @@ -38,6 +39,16 @@ public class CPlayerUnitOrderExecutor implements CPlayerUnitOrderListener { } } + @Override + public void issueDropItemAtTargetOrder(final int unitHandleId, final int abilityHandleId, final int orderId, + final int targetItemHandleId, final int targetHeroHandleId, final boolean queue) { + final CUnit unit = this.game.getUnit(unitHandleId); + if (this.playerIndex == unit.getPlayerIndex()) { + unit.order(this.game, new COrderDropItemAtTargetWidget(abilityHandleId, orderId, targetItemHandleId, + targetHeroHandleId, queue), queue); + } + } + @Override public void issuePointOrder(final int unitHandleId, final int abilityHandleId, final int orderId, final float x, final float y, final boolean queue) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java index 7cf6e32..77d94a1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java @@ -8,6 +8,9 @@ public interface CPlayerUnitOrderListener { void issueDropItemAtPointOrder(int unitHandleId, int abilityHandleId, int orderId, int targetHandleId, float x, float y, final boolean queue); + void issueDropItemAtTargetOrder(int unitHandleId, int abilityHandleId, int orderId, int targetItemHandleId, + int targetHeroHandleId, final boolean queue); + void issueImmediateOrder(int unitHandleId, int abilityHandleId, int orderId, boolean queue); void unitCancelTrainingItem(int unitHandleId, int cancelIndex); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CRace.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CRace.java index 9043d91..a3f7f04 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CRace.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CRace.java @@ -14,7 +14,7 @@ public enum CRace { this.id = id; } - public static CRace[] VALUES = values(); + public static CRace[] VALUES = { null, HUMAN, ORC, UNDEAD, NIGHTELF, DEMON, null, OTHER }; public int getId() { return this.id; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/region/CRegionTriggerEnter.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/region/CRegionTriggerEnter.java index 7e187a9..55b63eb 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/region/CRegionTriggerEnter.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/region/CRegionTriggerEnter.java @@ -20,10 +20,10 @@ public class CRegionTriggerEnter { } public void fire(final CUnit unit, final CRegion region) { - if (this.filter.evaluate(this.globalScope, + if ((this.filter == null) || this.filter.evaluate(this.globalScope, CommonTriggerExecutionScope.filterScope(TriggerExecutionScope.EMPTY, unit))) { final CommonTriggerExecutionScope eventScope = CommonTriggerExecutionScope - .unitEnterRegionScope(TriggerExecutionScope.EMPTY, unit, region); + .unitEnterRegionScope(this.trigger, TriggerExecutionScope.EMPTY, unit, region); if (this.trigger.evaluate(this.globalScope, eventScope)) { this.trigger.execute(this.globalScope, eventScope); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/region/CRegionTriggerLeave.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/region/CRegionTriggerLeave.java index 4b18f22..abf06dc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/region/CRegionTriggerLeave.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/region/CRegionTriggerLeave.java @@ -23,7 +23,7 @@ public class CRegionTriggerLeave { if (this.filter.evaluate(this.globalScope, CommonTriggerExecutionScope.filterScope(TriggerExecutionScope.EMPTY, unit))) { final CommonTriggerExecutionScope eventScope = CommonTriggerExecutionScope - .unitLeaveRegionScope(TriggerExecutionScope.EMPTY, unit, region); + .unitLeaveRegionScope(this.trigger, TriggerExecutionScope.EMPTY, unit, region); if (this.trigger.evaluate(this.globalScope, eventScope)) { this.trigger.execute(this.globalScope, eventScope); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/sound/CMIDISound.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/sound/CMIDISound.java new file mode 100644 index 0000000..6ac8f3e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/sound/CMIDISound.java @@ -0,0 +1,19 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.sound; + +public class CMIDISound implements CSound { + private final String soundLabel; + private final int fadeInRate; + private final int fadeOutRate; + + public CMIDISound(final String soundLabel, final int fadeInRate, final int fadeOutRate) { + this.soundLabel = soundLabel; + this.fadeInRate = fadeInRate; + this.fadeOutRate = fadeOutRate; + } + + @Override + public void start() { + System.err.println( + "Not starting MIDI sound because we don't have a LibGDX API to play those: " + this.soundLabel); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/sound/CSound.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/sound/CSound.java new file mode 100644 index 0000000..c4ee11d --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/sound/CSound.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.sound; + +public interface CSound { + void start(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/sound/CSoundFilename.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/sound/CSoundFilename.java new file mode 100644 index 0000000..68e7d6a --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/sound/CSoundFilename.java @@ -0,0 +1,64 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.sound; + +import com.badlogic.gdx.audio.Sound; +import com.etheller.warsmash.viewer5.AudioBufferSource; +import com.etheller.warsmash.viewer5.AudioContext; +import com.etheller.warsmash.viewer5.AudioPanner; + +public class CSoundFilename implements CSound { + + private final Sound sound; + private final boolean looping; + private final boolean stopWhenOutOfRange; + private final int fadeInRate; + private final int fadeOutRate; + private final AudioContext audioContext; + private float x; + private float y; + private float z; + private final float volume = 1.0f; + private final float pitch = 1.0f; + private final float minDistance = 99999; + private final float distanceCutoff = 99999; + private final String eaxSetting; + + public CSoundFilename(final Sound sound, final AudioContext audioContext, final boolean looping, + final boolean stopWhenOutOfRange, final int fadeInRate, final int fadeOutRate, final String eaxSetting) { + this.sound = sound; + this.audioContext = audioContext; + this.looping = looping; + this.stopWhenOutOfRange = stopWhenOutOfRange; + this.fadeInRate = fadeInRate; + this.fadeOutRate = fadeOutRate; + this.eaxSetting = eaxSetting; + } + + public void setPosition(final float x, final float y, final float z) { + this.x = x; + this.y = y; + this.z = z; + + } + + @Override + public void start() { + if (this.audioContext == null) { + return; + } + final AudioPanner panner = this.audioContext.createPanner(this.stopWhenOutOfRange); + final AudioBufferSource source = this.audioContext.createBufferSource(); + + // Panner settings + panner.setPosition(this.x, this.y, this.z); + panner.setDistances(this.distanceCutoff, this.minDistance); + panner.connect(this.audioContext.destination); + + // Source. + source.buffer = this.sound; + source.connect(panner); + + // Make a sound. + source.start(0, this.volume, this.pitch, this.looping); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/sound/CSoundFromLabel.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/sound/CSoundFromLabel.java new file mode 100644 index 0000000..45eef0e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/sound/CSoundFromLabel.java @@ -0,0 +1,32 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.sound; + +import com.etheller.warsmash.viewer5.AudioContext; +import com.etheller.warsmash.viewer5.handlers.w3x.UnitSound; + +public class CSoundFromLabel implements CSound { + + private final UnitSound sound; + private final boolean looping; + private final boolean is3d; + private final boolean stopWhenOutOfRange; + private final int fadeInRate; + private final int fadeOutRate; + private final AudioContext audioContext; + + public CSoundFromLabel(final UnitSound sound, final AudioContext audioContext, final boolean looping, + final boolean is3d, final boolean stopWhenOutOfRange, final int fadeInRate, final int fadeOutRate) { + this.sound = sound; + this.audioContext = audioContext; + this.looping = looping; + this.is3d = is3d; + this.stopWhenOutOfRange = stopWhenOutOfRange; + this.fadeInRate = fadeInRate; + this.fadeOutRate = fadeOutRate; + } + + @Override + public void start() { + this.sound.play(this.audioContext, 0, 0, 0); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/timers/CTimerJass.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/timers/CTimerJass.java index 7a44665..8659923 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/timers/CTimerJass.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/timers/CTimerJass.java @@ -24,9 +24,10 @@ public class CTimerJass extends CTimer { @Override public void onFire() { - final CommonTriggerExecutionScope executionScope = CommonTriggerExecutionScope.expiringTimer(this); - this.handlerFunc.call(Collections.emptyList(), this.jassGlobalScope, executionScope); + final CommonTriggerExecutionScope handlerScope = CommonTriggerExecutionScope.expiringTimer(null, this); + this.handlerFunc.call(Collections.emptyList(), this.jassGlobalScope, handlerScope); for (final Trigger trigger : this.eventTriggers) { + final CommonTriggerExecutionScope executionScope = CommonTriggerExecutionScope.expiringTimer(trigger, this); if (trigger.evaluate(this.jassGlobalScope, executionScope)) { trigger.execute(this.jassGlobalScope, executionScope); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CDamageType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CDamageType.java index 2269db5..09c96af 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CDamageType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CDamageType.java @@ -26,7 +26,8 @@ public enum CDamageType { DEMOLITION, SLOW_POISON, SPIRIT_LINK, - SHADOW_STRIKE; + SHADOW_STRIKE, + UNIVERSAL; public static CDamageType[] VALUES = values(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/unit/CWidgetEvent.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/unit/CWidgetEvent.java new file mode 100644 index 0000000..7bb3ace --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/unit/CWidgetEvent.java @@ -0,0 +1,87 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.unit; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; +import com.etheller.interpreter.ast.scope.trigger.RemovableTriggerEvent; +import com.etheller.interpreter.ast.scope.trigger.Trigger; +import com.etheller.interpreter.ast.scope.trigger.TriggerBooleanExpression; +import com.etheller.warsmash.parsers.jass.scope.CommonTriggerExecutionScope; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.JassGameEventsWar3; + +public class CWidgetEvent implements RemovableTriggerEvent { + private final GlobalScope globalScope; + private final CWidget widget; + private final Trigger trigger; + private final JassGameEventsWar3 eventType; + private final TriggerBooleanExpression filter; + + public CWidgetEvent(final GlobalScope globalScope, final CWidget widget, final Trigger trigger, + final JassGameEventsWar3 eventType, final TriggerBooleanExpression filter) { + this.globalScope = globalScope; + this.widget = widget; + this.trigger = trigger; + this.eventType = eventType; + this.filter = filter; + } + + public Trigger getTrigger() { + return this.trigger; + } + + public JassGameEventsWar3 getEventType() { + return this.eventType; + } + + @Override + public void remove() { + this.widget.removeEvent(this); + } + + public void fire(final CWidget triggerWidget, final TriggerExecutionScope scope) { + if (this.filter != null) { + if (!this.filter.evaluate(this.globalScope, triggerWidget.visit(ScopeBuilder.INSTANCE.reset(scope)))) { + return; + } + } + if (this.trigger.evaluate(this.globalScope, scope)) { + this.trigger.execute(this.globalScope, scope); + } + } + + private static final class ScopeBuilder implements AbilityTargetVisitor { + public static final ScopeBuilder INSTANCE = new ScopeBuilder(); + private TriggerExecutionScope parentScope; + + public ScopeBuilder reset(final TriggerExecutionScope parentScope) { + this.parentScope = parentScope; + return this; + } + + @Override + public CommonTriggerExecutionScope accept(final AbilityPointTarget target) { + throw new IllegalStateException("what?"); + } + + @Override + public CommonTriggerExecutionScope accept(final CUnit target) { + return CommonTriggerExecutionScope.filterScope(this.parentScope, target); + } + + @Override + public CommonTriggerExecutionScope accept(final CDestructable target) { + return CommonTriggerExecutionScope.filterScope(this.parentScope, target); + } + + @Override + public CommonTriggerExecutionScope accept(final CItem target) { + return CommonTriggerExecutionScope.filterScope(this.parentScope, target); + } + + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java index 09d79a4..a49a09e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java @@ -16,4 +16,6 @@ public interface AbilityActivationReceiver { void cargoCapacityUnavailable(); void disabled(); + + void techtreeMaximumReached(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java index b6ae3b7..fdee4fe 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java @@ -26,6 +26,11 @@ public class BooleanAbilityActivationReceiver implements AbilityActivationReceiv this.ok = false; } + @Override + public void techtreeMaximumReached() { + this.ok = false; + } + @Override public void casterMovementDisabled() { this.ok = false; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/MeleeUIAbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/MeleeUIAbilityActivationReceiver.java index fe5420d..24e1947 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/MeleeUIAbilityActivationReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/MeleeUIAbilityActivationReceiver.java @@ -64,6 +64,11 @@ public class MeleeUIAbilityActivationReceiver implements AbilityActivationReceiv this.genericError.onClick(this.commandErrorListener, this.worldSceneAudioContext, this.commandedUnit); } + @Override + public void techtreeMaximumReached() { + this.genericError.onClick(this.commandErrorListener, this.worldSceneAudioContext, this.commandedUnit); + } + @Override public void casterMovementDisabled() { this.genericError.onClick(this.commandErrorListener, this.worldSceneAudioContext, this.commandedUnit); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java index 49fdf0c..23a0817 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java @@ -22,6 +22,13 @@ public interface SimulationRenderController { CUnit createUnit(CSimulation simulation, final War3ID typeId, final int playerIndex, final float x, final float y, final float facing); + CItem createItem(CSimulation simulation, final War3ID typeId, final float x, final float y); + + CDestructable createDestructable(War3ID typeId, float x, float y, float facing, float scale, int variation); + + CDestructable createDestructableZ(War3ID typeId, float x, float y, float z, float facing, float scale, + int variation); + void createInstantAttackEffect(CSimulation cSimulation, CUnit source, CUnitAttackInstant attack, CWidget target); void spawnDamageSound(CWidget damagedDestructable, String weaponSound, String armorType); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java index 5f8697c..2e8280a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java @@ -40,6 +40,11 @@ public class StringMsgAbilityActivationReceiver implements AbilityActivationRece this.message = "NOTEXTERN: Requires " + type; } + @Override + public void techtreeMaximumReached() { + this.message = "NOTEXTERN: Techtree maximum reached."; + } + @Override public void cargoCapacityUnavailable() { this.message = "NOTEXTERN: Cargo capacity unavailable."; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 4a7ead3..986bb11 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -16,6 +16,7 @@ import java.util.concurrent.TimeUnit; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; +import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.graphics.Pixmap.Blending; @@ -54,6 +55,7 @@ import com.etheller.warsmash.parsers.fdf.frames.UIFrame; import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; import com.etheller.warsmash.units.Element; import com.etheller.warsmash.units.manager.MutableObjectData; +import com.etheller.warsmash.util.DataSourceFileHandle; import com.etheller.warsmash.util.FastNumberFormat; import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.RenderMathUtils; @@ -84,6 +86,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.camera.GameCameraManager; import com.etheller.warsmash.viewer5.handlers.w3x.camera.PortraitCameraManager; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.PathingFlags; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderItem; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderWidget; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; @@ -125,23 +128,28 @@ 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.hero.CPrimaryAttribute; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.inventory.CAbilityInventory; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.mine.CAbilityGoldMine; 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; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.upgrade.CAbilityUpgrade; 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.CodeKeyType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissileSplash; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CUnitRace; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CBuildingPathingType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerUnitOrderListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CRace; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.timers.CTimer; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationErrorHandler; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityTargetCheckReceiver; @@ -153,7 +161,9 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.ClickableActionFram import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.ClickableFrame; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandCardCommandListener; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.MultiSelectionIconListener; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.QueueIconListener; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.dialog.CTimerDialog; import com.hiveworkshop.rms.parsers.mdlx.MdlxLayer.FilterMode; public class MeleeUI implements CUnitStateListener, CommandButtonListener, CommandCardCommandListener, @@ -204,6 +214,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private TextureFrame[] tooltipResourceIconFrames; private StringFrame[] tooltipResourceTextFrames; + // hovertip + private UIFrame hovertipFrame; + private StringFrame hovertipText; + private UIFrame simpleInfoPanelUnitDetail; private StringFrame simpleNameValue; private StringFrame simpleClassValue; @@ -218,7 +232,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private SimpleStatusBarFrame simpleBuildingBuildTimeIndicator; private final QueueIcon[] queueIconFrames = new QueueIcon[WarsmashConstants.BUILD_QUEUE_SIZE]; private QueueIcon selectWorkerInsideFrame; - private final QueueIcon[] selectedUnitFrames = new QueueIcon[WarsmashConstants.MAX_SELECTION_SIZE]; + private final UIFrame[] selectedUnitHighlightBackdrop = new UIFrame[WarsmashConstants.MAX_SELECTION_SIZE]; + private final MultiSelectionIcon[] selectedUnitFrames = new MultiSelectionIcon[WarsmashConstants.MAX_SELECTION_SIZE]; private UIFrame attack1Icon; private TextureFrame attack1IconBackdrop; @@ -254,10 +269,16 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private MeleeUIMinimap meleeUIMinimap; private final CPlayerUnitOrderListener unitOrderListener; private StringFrame errorMessageFrame; + // TODO array of game msgs? +// private final List gameMessageFrames = new ArrayList<>(); + private StringFrame gameMessagesFrame; private long lastErrorMessageExpireTime; private long lastErrorMessageFadeTime; + private long lastGameMessageExpireTime; + private long lastGameMessageFadeTime; private MenuCursorState cursorState; + private Color cursorColor; private CAbilityView activeCommand; private int activeCommandOrderId; private RenderUnit activeCommandUnit; @@ -312,16 +333,25 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private final Runnable exitGameRunnable; private SimpleFrame smashEscMenu; private RenderWidget mouseOverUnit; + private RenderWidget currentHoverTipUnit; private final Vector3 lastMouseDragStart = new Vector3(); private final Vector3 lastMouseClickLocation = new Vector3(); private final List hpBarFrames = new ArrayList<>(); private int hpBarFrameIndex = 0; private boolean allowDrag; - private int currentlyDraggingPointer; + private int currentlyDraggingPointer = -1; private final ShapeRenderer shapeRenderer = new ShapeRenderer(); private final List multiSelectUnitStateListeners = new ArrayList<>(); private long lastUnitClickTime = 0; + private RenderWidget lastClickUnit; + private MultiSelectionIconListener multiSelectClickListener; + private float frontQueueIconWidth; + private int draggingMouseButton; + private Music[] currentMusics; + private int currentMusicIndex; + private boolean currentMusicRandomizeIndex; + private final List timerDialogs = new ArrayList<>(); public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, final Scene uiScene, final Scene portraitScene, final CameraPreset[] cameraPresets, final CameraRates cameraRates, @@ -366,11 +396,13 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma try { minimapTexture = ImageUtils.getTextureNoColorCorrection(TgaFile.readTGA("war3mapMap.tga", war3MapViewer.dataSource.getResourceAsStream("war3mapMap.tga"))); - } catch (final IOException e) { + } + catch (final IOException e) { System.err.println("Could not load minimap TGA file"); e.printStackTrace(); } - } else if (war3MapViewer.dataSource.has("war3mapMap.blp")) { + } + else if (war3MapViewer.dataSource.has("war3mapMap.blp")) { minimapTexture = ImageUtils.getAnyExtensionTexture(war3MapViewer.dataSource, "war3mapMap.blp"); } final Texture[] teamColors = new Texture[WarsmashConstants.MAX_PLAYERS]; @@ -396,7 +428,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (race == null) { racialSkinKey = "Human"; racialCommandIndex = 0; - } else { + } + else { switch (race) { case HUMAN: racialSkinKey = "Human"; @@ -427,12 +460,14 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.rootFrameListener.onCreate(this.rootFrame); try { this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); - } catch (final IOException exc) { + } + catch (final IOException exc) { throw new IllegalStateException("Unable to load FrameDef.toc", exc); } try { this.rootFrame.loadTOCFile("UI\\FrameDef\\SmashFrameDef.toc"); - } catch (final IOException exc) { + } + catch (final IOException exc) { throw new IllegalStateException("Unable to load SmashFrameDef.toc", exc); } this.damageBackdrops = new InfoPanelIconBackdrops(CAttackType.values(), this.rootFrame, "Damage", "Neutral"); @@ -601,7 +636,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.unitManaText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitManaPointText", 0); final float infoPanelUnitDetailWidth = GameUI.convertY(this.uiViewport, 0.180f); - final float infoPanelUnitDetailHeight = GameUI.convertY(this.uiViewport, 0.112f); + final float infoPanelUnitDetailHeight = GameUI.convertY(this.uiViewport, 0.120f); this.smashSimpleInfoPanel = this.rootFrame.createSimpleFrame("SmashSimpleInfoPanel", this.rootFrame, 0); this.smashSimpleInfoPanel .addAnchor(new AnchorDefinition(FramePoint.BOTTOM, 0, GameUI.convertY(this.uiViewport, 0.0f))); @@ -665,11 +700,11 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.queueIconFrames[0] .addSetPoint(new SetPoint(FramePoint.CENTER, this.smashSimpleInfoPanel, FramePoint.BOTTOMLEFT, (infoPanelUnitDetailWidth * (15 + 19f)) / 256, (infoPanelUnitDetailWidth * (66 + 19f)) / 256)); - final float frontQueueIconWidth = (infoPanelUnitDetailWidth * 38) / 256; - this.queueIconFrames[0].setWidth(frontQueueIconWidth); - this.queueIconFrames[0].setHeight(frontQueueIconWidth); - queueIconFrameBackdrop0.setWidth(frontQueueIconWidth); - queueIconFrameBackdrop0.setHeight(frontQueueIconWidth); + this.frontQueueIconWidth = (infoPanelUnitDetailWidth * 38) / 256; + this.queueIconFrames[0].setWidth(this.frontQueueIconWidth); + this.queueIconFrames[0].setHeight(this.frontQueueIconWidth); + queueIconFrameBackdrop0.setWidth(this.frontQueueIconWidth); + queueIconFrameBackdrop0.setHeight(this.frontQueueIconWidth); this.rootFrame.add(this.queueIconFrames[0]); for (int i = 1; i < this.queueIconFrames.length; i++) { @@ -697,38 +732,105 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma .addSetPoint(new SetPoint(FramePoint.CENTER, this.selectWorkerInsideFrame, FramePoint.CENTER, 0, 0)); this.selectWorkerInsideFrame .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.queueIconFrames[1], FramePoint.TOPLEFT, 0, 0)); - this.selectWorkerInsideFrame.setWidth(frontQueueIconWidth); - this.selectWorkerInsideFrame.setHeight(frontQueueIconWidth); - selectWorkerInsideIconFrameBackdrop.setWidth(frontQueueIconWidth); - selectWorkerInsideIconFrameBackdrop.setHeight(frontQueueIconWidth); + this.selectWorkerInsideFrame.setWidth(this.frontQueueIconWidth); + this.selectWorkerInsideFrame.setHeight(this.frontQueueIconWidth); + selectWorkerInsideIconFrameBackdrop.setWidth(this.frontQueueIconWidth); + selectWorkerInsideIconFrameBackdrop.setHeight(this.frontQueueIconWidth); this.rootFrame.add(this.selectWorkerInsideFrame); + final int halfSelectionMaxSize = this.selectedUnitFrames.length / 2; for (int i = 0; i < this.selectedUnitFrames.length; i++) { - this.selectedUnitFrames[i] = new QueueIcon("SmashMultiSelectUnitIcon", this.smashSimpleInfoPanel, this, i); + final FilterModeTextureFrame selectedSubgroupHighlightBackdrop = new FilterModeTextureFrame( + "SmashMultiSelectUnitIconHighlightBackdrop", this.smashSimpleInfoPanel, true, + new Vector4Definition(0, 1, 0, 1)); + selectedSubgroupHighlightBackdrop.setFilterMode(FilterMode.ADDITIVE); + this.selectedUnitHighlightBackdrop[i] = selectedSubgroupHighlightBackdrop; + selectedSubgroupHighlightBackdrop.setTexture("SelectedSubgroupHighlight", this.rootFrame); + selectedSubgroupHighlightBackdrop.setWidth(this.frontQueueIconWidth * 1.37f); + selectedSubgroupHighlightBackdrop.setHeight(this.frontQueueIconWidth * 1.75f); + selectedSubgroupHighlightBackdrop.setColor(1.0f, 1.0f, 0.0f, 1.0f); + this.rootFrame.add(selectedSubgroupHighlightBackdrop); + selectedSubgroupHighlightBackdrop + .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.smashSimpleInfoPanel, FramePoint.TOPLEFT, + ((-this.frontQueueIconWidth * .37f) / 2) + (this.frontQueueIconWidth * .10f) + + (this.frontQueueIconWidth * 1.10f * (i % halfSelectionMaxSize)), + (((this.frontQueueIconWidth * .37f) / 2) + (this.frontQueueIconWidth * -.75f)) + - (this.frontQueueIconWidth * 1.5f * (i / halfSelectionMaxSize)))); + } + this.multiSelectClickListener = new MultiSelectionIconListener() { + @Override + public void multiSelectIconClicked(final int index) { + if (index < MeleeUI.this.selectedUnits.size()) { + final RenderUnit clickUnit = MeleeUI.this.selectedUnits.get(index); + if (MeleeUI.this.activeCommand != null) { + useActiveCommandOnUnit(isShiftDown(), clickUnit); + } + else if (clickUnit == MeleeUI.this.selectedUnit) { + final List newSelection = Arrays.asList(MeleeUI.this.selectedUnit); + selectWidgets(newSelection); + MeleeUI.this.war3MapViewer.doSelectUnit(newSelection); + } + else { + selectUnit(clickUnit); + } + } + } + + @Override + public void multiSelectIconPress(final int index) { + } + + @Override + public void multiSelectIconRelease(final int index) { + } + }; + for (int i = 0; i < this.selectedUnitFrames.length; i++) { + this.selectedUnitFrames[i] = new MultiSelectionIcon("SmashMultiSelectUnitIcon", this.smashSimpleInfoPanel, + this.multiSelectClickListener, i); final TextureFrame multiSelectUnitIconFrameBackdrop = new TextureFrame("SmashMultiSelectUnitIconBackdrop", this.selectedUnitFrames[i], false, new Vector4Definition(0, 1, 0, 1)); - final TextureFrame selectedSubgroupHighlightBackdrop = new TextureFrame("SmashMultiSelectUnitIconBackdrop", - this.selectedUnitFrames[i], false, new Vector4Definition(0, 1, 0, 1)); - this.selectedUnitFrames[i].set(multiSelectUnitIconFrameBackdrop); + + final SimpleStatusBarFrame hpBarFrame = new SimpleStatusBarFrame( + "SmashMultiSelectHpBar" + this.hpBarFrameIndex, this.rootFrame, true, true, 3.0f); + hpBarFrame.getBarFrame().setTexture("SimpleHpBarConsole", this.rootFrame); + hpBarFrame.getBorderFrame().setTexture("Textures\\Black32.blp", this.rootFrame); + hpBarFrame.setWidth(this.frontQueueIconWidth); + hpBarFrame.setHeight(this.frontQueueIconWidth * MultiSelectionIcon.HP_BAR_HEIGHT_RATIO); + hpBarFrame.addSetPoint(new SetPoint(FramePoint.TOP, multiSelectUnitIconFrameBackdrop, FramePoint.BOTTOM, 0, + -this.frontQueueIconWidth * MultiSelectionIcon.HP_BAR_SPACING_RATIO)); + + final SimpleStatusBarFrame manaBarFrame = new SimpleStatusBarFrame( + "SmashMultiSelectManaBar" + this.hpBarFrameIndex, this.rootFrame, true, true, 3.0f); + manaBarFrame.getBarFrame().setTexture("SimpleManaBarConsole", this.rootFrame); + manaBarFrame.getBorderFrame().setTexture("Textures\\Black32.blp", this.rootFrame); + manaBarFrame.setWidth(this.frontQueueIconWidth); + manaBarFrame.setHeight(this.frontQueueIconWidth * MultiSelectionIcon.HP_BAR_HEIGHT_RATIO); + manaBarFrame.addSetPoint(new SetPoint(FramePoint.TOP, hpBarFrame, FramePoint.BOTTOM, 0, + -this.frontQueueIconWidth * MultiSelectionIcon.HP_BAR_SPACING_RATIO)); + manaBarFrame.getBarFrame().setColor(0f, 0f, 1f, 1f); + + this.selectedUnitFrames[i].set(multiSelectUnitIconFrameBackdrop, hpBarFrame, manaBarFrame); multiSelectUnitIconFrameBackdrop .addSetPoint(new SetPoint(FramePoint.CENTER, this.selectedUnitFrames[i], FramePoint.CENTER, 0, 0)); - final int halfSelectionMaxSize = this.selectedUnitFrames.length / 2; this.selectedUnitFrames[i] .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.smashSimpleInfoPanel, FramePoint.TOPLEFT, - (frontQueueIconWidth * .10f) + (frontQueueIconWidth * 1.10f * (i % halfSelectionMaxSize)), - (frontQueueIconWidth * -.5f) - (frontQueueIconWidth * 1.5f * (i / halfSelectionMaxSize)))); - this.selectedUnitFrames[i].setWidth(frontQueueIconWidth); - this.selectedUnitFrames[i].setHeight(frontQueueIconWidth); - multiSelectUnitIconFrameBackdrop.setWidth(frontQueueIconWidth); - multiSelectUnitIconFrameBackdrop.setHeight(frontQueueIconWidth); + (this.frontQueueIconWidth * .10f) + + (this.frontQueueIconWidth * 1.10f * (i % halfSelectionMaxSize)), + (this.frontQueueIconWidth * -.75f) + - (this.frontQueueIconWidth * 1.5f * (i / halfSelectionMaxSize)))); + this.selectedUnitFrames[i].setWidth(this.frontQueueIconWidth); + this.selectedUnitFrames[i].setHeight(this.frontQueueIconWidth); + multiSelectUnitIconFrameBackdrop.setWidth(this.frontQueueIconWidth); + multiSelectUnitIconFrameBackdrop.setHeight(this.frontQueueIconWidth); this.rootFrame.add(this.selectedUnitFrames[i]); + this.selectedUnitFrames[i].setVisible(false); } this.smashAttack1IconWrapper = (SimpleFrame) this.rootFrame.createSimpleFrame("SmashSimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, 0); this.smashAttack1IconWrapper.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, - FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.032f))); + FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.040f))); this.smashAttack1IconWrapper.setWidth(GameUI.convertX(this.uiViewport, 0.1f)); this.smashAttack1IconWrapper.setHeight(GameUI.convertY(this.uiViewport, 0.030125f)); this.attack1Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.smashAttack1IconWrapper, @@ -741,7 +843,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.simpleInfoPanelUnitDetail, 0); this.smashAttack2IconWrapper .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0.1f), GameUI.convertY(this.uiViewport, -0.03125f))); + GameUI.convertX(this.uiViewport, 0.1f), GameUI.convertY(this.uiViewport, -0.03925f))); this.smashAttack2IconWrapper.setWidth(GameUI.convertX(this.uiViewport, 0.1f)); this.smashAttack2IconWrapper.setHeight(GameUI.convertY(this.uiViewport, 0.030125f)); this.attack2Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.smashAttack2IconWrapper, @@ -753,7 +855,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.smashArmorIconWrapper = (SimpleFrame) this.rootFrame.createSimpleFrame("SmashSimpleInfoPanelIconArmor", this.simpleInfoPanelUnitDetail, 0); this.smashArmorIconWrapper.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, - FramePoint.TOPLEFT, GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.0625f))); + FramePoint.TOPLEFT, GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.0705f))); this.smashArmorIconWrapper.setWidth(GameUI.convertX(this.uiViewport, 0.1f)); this.smashArmorIconWrapper.setHeight(GameUI.convertY(this.uiViewport, 0.030125f)); this.armorIcon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconArmor", this.smashArmorIconWrapper, 0); @@ -764,7 +866,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.smashHeroInfoPanelWrapper = (SimpleFrame) this.rootFrame.createSimpleFrame("SmashSimpleInfoPanelIconHero", this.simpleInfoPanelUnitDetail, 0); this.smashHeroInfoPanelWrapper.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, - FramePoint.TOPLEFT, GameUI.convertX(this.uiViewport, 0.1f), GameUI.convertY(this.uiViewport, -0.029f))); + FramePoint.TOPLEFT, GameUI.convertX(this.uiViewport, 0.1f), GameUI.convertY(this.uiViewport, -0.037f))); this.smashHeroInfoPanelWrapper.setWidth(GameUI.convertX(this.uiViewport, 0.1f)); this.smashHeroInfoPanelWrapper.setHeight(GameUI.convertY(this.uiViewport, 0.0625f)); this.heroInfoPanel = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconHero", this.smashHeroInfoPanelWrapper, @@ -850,6 +952,18 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.errorMessageFrame.setFontShadowOffsetY(GameUI.convertY(this.uiViewport, -0.001f)); this.errorMessageFrame.setVisible(false); + final float worldFrameUnitMessageFontHeight = fontHeights.getFieldFloatValue("WorldFrameUnitMessage"); + this.gameMessagesFrame = this.rootFrame.createStringFrame("SmashUnitMessageFrame", this.rootFrame, Color.WHITE, + TextJustify.LEFT, TextJustify.MIDDLE, worldFrameUnitMessageFontHeight); + this.gameMessagesFrame.addAnchor(new AnchorDefinition(FramePoint.LEFT, 0, 0)); + this.gameMessagesFrame.setWidth(GameUI.convertX(this.uiViewport, 0.35f)); + this.gameMessagesFrame.setHeight(GameUI.convertY(this.uiViewport, worldFrameUnitMessageFontHeight)); + + this.gameMessagesFrame.setFontShadowColor(new Color(0f, 0f, 0f, 0.9f)); + this.gameMessagesFrame.setFontShadowOffsetX(GameUI.convertX(this.uiViewport, 0.001f)); + this.gameMessagesFrame.setFontShadowOffsetY(GameUI.convertY(this.uiViewport, -0.001f)); + this.gameMessagesFrame.setVisible(true); + commandButtonIndex = 0; for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { @@ -903,6 +1017,14 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.tooltipText.addAnchor(new AnchorDefinition(FramePoint.TOPLEFT, GameUI.convertX(this.uiViewport, 0.003f), GameUI.convertY(this.uiViewport, -0.003f))); this.tooltipFrame.setVisible(false); + + this.hovertipFrame = this.rootFrame.createFrame("SmashHoverTip", this.rootFrame, 0, 0); + this.hovertipText = (StringFrame) this.rootFrame.getFrameByName("SmashHoverTipText", 0); + this.hovertipText.setWidth(GameUI.convertX(this.uiViewport, 0.274f)); + this.hovertipText.addAnchor(new AnchorDefinition(FramePoint.TOPLEFT, GameUI.convertX(this.uiViewport, 0.006f), + GameUI.convertY(this.uiViewport, -0.006f))); + this.hovertipFrame.setVisible(false); + this.tooltipUberTipText = (StringFrame) this.rootFrame.getFrameByName("SmashUberTipText", 0); this.tooltipUberTipText.setWidth(GameUI.convertX(this.uiViewport, 0.274f)); this.uberTipNoResourcesSetPoint = new SetPoint(FramePoint.TOPLEFT, this.tooltipText, FramePoint.BOTTOMLEFT, 0, @@ -938,7 +1060,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma Gdx.input.setCursorCatched(true); } - this.meleeUIMinimap = createMinimap(this.war3MapViewer); + this.meleeUIMinimap = + + createMinimap(this.war3MapViewer); this.meleeUIAbilityActivationReceiver = new MeleeUIAbilityActivationReceiver( new AbilityActivationErrorHandler(this.rootFrame.getErrorString("NoGold"), @@ -1032,14 +1156,16 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } } - } else { + } + else { this.activeCommand = abilityToUse; this.activeCommandOrderId = orderId; this.activeCommandUnit = this.selectedUnit; clearAndRepopulateCommandCard(); } } - } else { + } + else { this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), abilityHandleId, orderId, isShiftDown()); if (this.selectedUnits.size() > 1) { @@ -1063,7 +1189,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.activeCommandUnit = null; this.activeCommand = null; this.activeCommandOrderId = -1; - } else { + } + else { this.subMenuOrderIdStack.add(orderId); } clearAndRepopulateCommandCard(); @@ -1079,6 +1206,16 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.errorMessageFrame.setAlpha(1.0f); } + public void showGameMessage(final String message, final float expireTime) { + this.rootFrame.setText(this.gameMessagesFrame, message); + this.gameMessagesFrame.setVisible(true); + final long millis = TimeUtils.millis(); + + this.lastGameMessageExpireTime = millis + (long) (expireTime * 1000); + this.lastGameMessageFadeTime = millis + (long) (expireTime * 900); + this.gameMessagesFrame.setAlpha(1.0f); + } + @Override public void showCantPlaceError() { showCommandError(this.rootFrame.getErrorString("Cantplace")); @@ -1132,12 +1269,34 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } this.hpBarFrameIndex = 0; if (this.currentlyDraggingPointer == -1) { - if ((this.mouseOverUnit != null) && !this.mouseOverUnit.getSimulationWidget().isInvulnerable() - && this.mouseOverUnit.isSelectable() && !this.mouseOverUnit.getSimulationWidget().isDead()) { + if ((this.mouseOverUnit != null) && isUnitSelectable(this.mouseOverUnit)) { final SimpleStatusBarFrame simpleStatusBarFrame = getHpBar(); positionHealthBar(simpleStatusBarFrame, this.mouseOverUnit, 1.0f); + final String hoverTipTextValue = getWorldFrameHoverTipText(this.mouseOverUnit); + this.hovertipFrame.setVisible(hoverTipTextValue != null); + if (hoverTipTextValue != null) { + this.rootFrame.setText(this.hovertipText, hoverTipTextValue); + final float predictedViewportHeight = this.hovertipText.getPredictedViewportHeight() + + GameUI.convertY(this.uiViewport, 0.009f); + this.hovertipFrame.setHeight(predictedViewportHeight); + this.hovertipFrame.setWidth( + this.hovertipText.getPredictedViewportWidth() + GameUI.convertX(this.uiViewport, 0.012f)); + this.hovertipFrame.positionBounds(this.rootFrame, this.uiViewport); + this.hovertipFrame.addSetPoint(new SetPoint(FramePoint.BOTTOM, simpleStatusBarFrame, FramePoint.TOP, + 0, GameUI.convertY(this.uiViewport, 0.003f))); + } + if (this.mouseOverUnit.getSimulationWidget().isInvulnerable() + || (this.mouseOverUnit instanceof RenderItem)) { + // this is a bit silly, for now I'm using it to position the "Gold" text on gold + // mines even though they are invulnerable + simpleStatusBarFrame.setVisible(false); + } } - } else if (this.currentlyDraggingPointer == Input.Buttons.LEFT) { + else { + this.hovertipFrame.setVisible(false); + } + } + else if (this.currentlyDraggingPointer == Input.Buttons.LEFT) { final float minDragX = Math.min(this.lastMouseClickLocation.x, this.lastMouseDragStart.x); final float minDragY = Math.min(this.lastMouseClickLocation.y, this.lastMouseDragStart.y); final float maxDragX = Math.max(this.lastMouseClickLocation.x, this.lastMouseDragStart.x); @@ -1148,10 +1307,12 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma @Override public boolean call(final CUnit unit) { final RenderUnit renderUnit = MeleeUI.this.war3MapViewer.getRenderPeer(unit); - if (!unit.isInvulnerable() && !unit.isDead() && renderUnit.isSelectable() + if (!unit.isDead() && renderUnit.isSelectable() && MeleeUI.this.dragSelectPreviewUnitsUpcoming.add(renderUnit)) { final SimpleStatusBarFrame simpleStatusBarFrame = getHpBar(); - positionHealthBar(simpleStatusBarFrame, renderUnit, 1.0f); + if (!unit.isInvulnerable()) { + positionHealthBar(simpleStatusBarFrame, renderUnit, 1.0f); + } if (!MeleeUI.this.dragSelectPreviewUnits.contains(renderUnit)) { MeleeUI.this.war3MapViewer.showUnitMouseOverHighlight(renderUnit); } @@ -1186,11 +1347,13 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (this.activeCommand != null) { if (this.draggingItem != null) { setCursorState(MenuCursorState.HOLD_ITEM); - } else { + } + else { setCursorState(MenuCursorState.TARGET_CURSOR); this.activeCommand.visit(this.cursorTargetSetupVisitor.reset(baseMouseX, baseMouseY)); } - } else { + } + else { if (this.cursorModelInstance != null) { this.cursorModelInstance.detach(); this.cursorModelInstance = null; @@ -1208,24 +1371,50 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (down) { if (left) { setCursorState(MenuCursorState.SCROLL_DOWN_LEFT); - } else if (right) { + } + else if (right) { setCursorState(MenuCursorState.SCROLL_DOWN_RIGHT); - } else { + } + else { setCursorState(MenuCursorState.SCROLL_DOWN); } - } else if (up) { + } + else if (up) { if (left) { setCursorState(MenuCursorState.SCROLL_UP_LEFT); - } else if (right) { + } + else if (right) { setCursorState(MenuCursorState.SCROLL_UP_RIGHT); - } else { + } + else { setCursorState(MenuCursorState.SCROLL_UP); } - } else if (left) { + } + else if (left) { setCursorState(MenuCursorState.SCROLL_LEFT); - } else if (right) { + } + else if (right) { setCursorState(MenuCursorState.SCROLL_RIGHT); - } else { + } + else if (this.mouseOverUnit != null) { + if (this.mouseOverUnit instanceof RenderUnit) { + final RenderUnit mouseOverUnitUnit = (RenderUnit) this.mouseOverUnit; + final int playerIndex = mouseOverUnitUnit.getSimulationUnit().getPlayerIndex(); + if (!this.localPlayer.hasAlliance(playerIndex, CAllianceType.PASSIVE)) { + setCursorState(MenuCursorState.SELECT, Color.RED); + } + else if (this.localPlayer.hasAlliance(playerIndex, CAllianceType.SHARED_CONTROL)) { + setCursorState(MenuCursorState.SELECT, Color.GREEN); + } + else { + setCursorState(MenuCursorState.SELECT, Color.YELLOW); + } + } + else { + setCursorState(MenuCursorState.SELECT, Color.YELLOW); + } + } + else { setCursorState(MenuCursorState.NORMAL); } } @@ -1252,11 +1441,75 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final long currentMillis = TimeUtils.millis(); if (currentMillis > this.lastErrorMessageExpireTime) { this.errorMessageFrame.setVisible(false); - } else if (currentMillis > this.lastErrorMessageFadeTime) { + } + else if (currentMillis > this.lastErrorMessageFadeTime) { final float fadeAlpha = (this.lastErrorMessageExpireTime - currentMillis) / (float) WORLD_FRAME_MESSAGE_FADE_DURATION; this.errorMessageFrame.setAlpha(fadeAlpha); } + if (currentMillis > this.lastGameMessageExpireTime) { + this.gameMessagesFrame.setVisible(false); + } + else if (currentMillis > this.lastGameMessageFadeTime) { + final float fadeAlpha = (this.lastGameMessageExpireTime - currentMillis) + / (float) (this.lastGameMessageExpireTime - this.lastGameMessageFadeTime); + this.gameMessagesFrame.setAlpha(fadeAlpha); + } + if (this.currentMusics != null) { + if (!this.currentMusics[this.currentMusicIndex].isPlaying()) { + if (this.currentMusicRandomizeIndex) { + this.currentMusicIndex = (int) (Math.random() * this.currentMusics.length); + } + else { + this.currentMusicIndex = (this.currentMusicIndex + 1) % this.currentMusics.length; + } + this.currentMusics[this.currentMusicIndex].play(); + } + } + for (final CTimerDialog timerDialog : this.timerDialogs) { + timerDialog.update(this.rootFrame, this.war3MapViewer.simulation); + } + } + + private boolean isUnitSelectable(final RenderWidget mouseOverUnit) { + return mouseOverUnit.isSelectable() && !mouseOverUnit.getSimulationWidget().isDead(); + } + + private String getWorldFrameHoverTipText(final RenderWidget whichUnit) { + if (whichUnit instanceof RenderUnit) { + final RenderUnit renderUnit = (RenderUnit) whichUnit; + final CUnit simulationUnit = renderUnit.getSimulationUnit(); + final CAbilityHero heroData = simulationUnit.getHeroData(); + if (heroData != null) { + final String level = this.rootFrame.getTemplates().getDecoratedString("LEVEL"); + return heroData.getProperName() + "|n" + level + " " + heroData.getHeroLevel(); + } + else { + final boolean neutralHostile = simulationUnit.getPlayerIndex() == (WarsmashConstants.MAX_PLAYERS - 4); + final boolean neutralPassive = simulationUnit.getPlayerIndex() == (WarsmashConstants.MAX_PLAYERS - 1); + if ((neutralPassive && simulationUnit.isBuilding()) || neutralHostile) { + String returnValue = simulationUnit.getUnitType().getName(); + final CAbilityGoldMine goldMineData = simulationUnit.getGoldMineData(); + if (goldMineData != null) { + final String colonGold = this.rootFrame.getTemplates().getDecoratedString("COLON_GOLD"); + returnValue += "|n" + colonGold + " " + goldMineData.getGold(); + } + final int creepLevel = simulationUnit.getUnitType().getLevel(); + if (neutralHostile && (creepLevel > 0)) { + final String level = this.rootFrame.getTemplates().getDecoratedString("LEVEL"); + returnValue += "|n" + level + " " + creepLevel; + } + return returnValue; + } + } + } + else if (whichUnit instanceof RenderItem) { + final RenderItem renderItem = (RenderItem) whichUnit; + final ItemUI itemUI = this.war3MapViewer.getAbilityDataUI() + .getItemUI(renderItem.getSimulationItem().getTypeId()); + return itemUI.getName(); + } + return null; } private void positionHealthBar(final SimpleStatusBarFrame simpleStatusBarFrame, final RenderWidget unit, @@ -1295,22 +1548,30 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma true, 3.0f); this.rootFrame.add(simpleStatusBarFrame); this.hpBarFrames.add(simpleStatusBarFrame); - } else { + } + else { simpleStatusBarFrame = this.hpBarFrames.get(this.hpBarFrameIndex); } this.hpBarFrameIndex++; return simpleStatusBarFrame; } - private void setCursorState(final MenuCursorState state) { + private void setCursorState(final MenuCursorState state, final Color color) { if (state != this.cursorState) { if (state.getAnimationName() != null) { this.cursorFrame.setSequence(state.getAnimationName()); } } + if (color != this.cursorColor) { + this.cursorFrame.setVertexColor(color); + } this.cursorState = state; } + private void setCursorState(final MenuCursorState state) { + setCursorState(state, Color.WHITE); + } + public void render(final SpriteBatch batch, final GlyphLayout glyphLayout) { final BitmapFont font = this.rootFrame.getFont(); font.setColor(Color.YELLOW); @@ -1391,7 +1652,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } handlePlacementCursor(ability, radius); - } else { + } + else { handleTargetCursor(ability); } return null; @@ -1463,6 +1725,12 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma return null; } + @Override + public Void accept(final CAbilityUpgrade ability) { + handleTargetCursor(ability); + return null; + } + @Override public Void accept(final CAbilityReviveHero ability) { handleTargetCursor(ability); @@ -1575,7 +1843,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma Color.rgba8888(1, 0, 0, 1.0f)); } } - } else { + } + else { for (int i = 0; i < MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmap.getWidth(); i++) { for (int j = 0; j < MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmap.getHeight(); j++) { boolean blocked = false; @@ -1678,7 +1947,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final MdxNode attachment = renderUnit.instance.getAttachment(index); this.rallyPointInstance.setParent(attachment); this.rallyPointInstance.setLocation(0, 0, 0); - } else { + } + else { this.rallyPointInstance.setParent(null); final float rallyPointX = target.getX(); final float rallyPointY = target.getY(); @@ -1747,10 +2017,14 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } public void talk() { - this.recycleSet.clear(); - this.recycleSet.addAll(this.unit.getSecondaryAnimationTags()); - this.recycleSet.add(SecondaryTag.TALK); - SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, this.recycleSet, true); + // TODO we somehow called talk from null by clicking a unit right at the same + // time it died, so I do a null check here until I study that case further. + if (this.modelInstance != null) { + this.recycleSet.clear(); + this.recycleSet.addAll(this.unit.getSecondaryAnimationTags()); + this.recycleSet.add(SecondaryTag.TALK); + SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, this.recycleSet, true); + } } public void setSelectedUnit(final RenderUnit unit) { @@ -1762,7 +2036,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } this.modelInstance = null; this.portraitCameraManager.setModelInstance(null, null); - } else { + } + else { final MdxModel portraitModel = unit.portraitModel; if (portraitModel != null) { if (this.modelInstance != null) { @@ -1803,7 +2078,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma index++; } } - } else { + } + else { if (this.selectedUnit != null) { final CAbilityInventory inventory = this.selectedUnit.getSimulationUnit().getInventoryData(); if (inventory != null) { @@ -1866,11 +2142,15 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.rallyPointInstance.detach(); this.inventoryCover.setVisible(true); this.inventoryBarFrame.setVisible(false); - for (final QueueIcon iconFrame : this.selectedUnitFrames) { + for (final MultiSelectionIcon iconFrame : this.selectedUnitFrames) { iconFrame.setVisible(false); } + for (final UIFrame frame : this.selectedUnitHighlightBackdrop) { + frame.setVisible(false); + } repositionWaypointFlags(null); - } else { + } + else { unit.getSimulationUnit().addStateListener(this); reloadSelectedUnitUI(unit); } @@ -1893,7 +2173,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.rallyPointInstance.detach(); rallyPoint.visit(this.rallyPositioningVisitor.reset(this.rallyPointInstance)); this.rallyPointInstance.setScene(this.war3MapViewer.worldScene); - } else { + } + else { this.rallyPointInstance.hide(); this.rallyPointInstance.detach(); } @@ -1904,7 +2185,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (this.selectedUnit != null) { final CUnit simulationUnit = this.selectedUnit.getSimulationUnit(); repositionWaypointFlags(simulationUnit); - } else { + } + else { repositionWaypointFlags(null); } } @@ -1924,13 +2206,15 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma waypointModelInstance.detach(); target.visit(this.rallyPositioningVisitor.reset(waypointModelInstance)); waypointModelInstance.setScene(this.war3MapViewer.worldScene); - } else { + } + else { waypointModelInstance.hide(); waypointModelInstance.detach(); } orderIndex++; } - } else { + } + else { iterator = Collections.emptyIterator(); } for (; (orderIndex < this.waypointModelInstances.size()) || (iterator.hasNext()); orderIndex++) { @@ -1943,11 +2227,13 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma waypointModelInstance.detach(); target.visit(this.rallyPositioningVisitor.reset(waypointModelInstance)); waypointModelInstance.setScene(this.war3MapViewer.worldScene); - } else { + } + else { waypointModelInstance.hide(); waypointModelInstance.detach(); } - } else { + } + else { waypointModelInstance.hide(); waypointModelInstance.detach(); } @@ -1981,7 +2267,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (maximumMana > 0) { this.rootFrame.setText(this.unitManaText, FastNumberFormat.formatWholeNumber(simulationUnit.getMana()) + " / " + maximumMana); - } else { + } + else { this.rootFrame.setText(this.unitManaText, ""); } final boolean multiSelect = this.selectedUnits.size() > 1; @@ -1990,6 +2277,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (!multiSelect) { for (int i = 0; i < this.selectedUnitFrames.length; i++) { this.selectedUnitFrames[i].setVisible(false); + this.selectedUnitHighlightBackdrop[i].setVisible(false); } } if ((simulationUnit.getBuildQueue()[0] != null) @@ -1998,7 +2286,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final QueueItemType queueItemType = simulationUnit.getBuildQueueTypes()[i]; if (queueItemType == null) { this.queueIconFrames[i].setVisible(false); - } else { + } + else { this.queueIconFrames[i].setVisible(true); switch (queueItemType) { case RESEARCH: @@ -2041,10 +2330,12 @@ 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) { + } + else if (simulationUnit.getBuildQueueTypes()[0] == QueueItemType.HERO_REVIVE) { this.rootFrame.setText(this.simpleBuildingBuildingActionLabel, this.rootFrame.getTemplates().getDecoratedString("REVIVING")); - } else { + } + else { this.rootFrame.setText(this.simpleBuildingBuildingActionLabel, this.rootFrame.getTemplates().getDecoratedString("RESEARCHING")); } @@ -2053,19 +2344,44 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.armorIcon.setVisible(false); this.heroInfoPanel.setVisible(false); this.selectWorkerInsideFrame.setVisible(false); - } else if (multiSelect) { + } + else if (multiSelect) { for (int i = 0; i < this.queueIconFrames.length; i++) { this.queueIconFrames[i].setVisible(false); } for (int i = 0; i < this.selectedUnitFrames.length; i++) { final boolean useIcon = i < this.selectedUnits.size(); this.selectedUnitFrames[i].setVisible(useIcon); + final boolean focused = useIcon && this.selectedUnits.get(i).groupsWith(this.selectedUnit); + this.selectedUnitHighlightBackdrop[i].setVisible(focused); if (useIcon) { - final CUnitType unitType = this.selectedUnits.get(i).getSimulationUnit().getUnitType(); + final CUnit multiSelectedUnit = this.selectedUnits.get(i).getSimulationUnit(); + final CUnitType unitType = multiSelectedUnit.getUnitType(); final IconUI unitUI = this.war3MapViewer.getAbilityDataUI().getUnitUI(unitType.getTypeId()); this.selectedUnitFrames[i].setTexture(unitUI.getIcon()); this.selectedUnitFrames[i].setToolTip(unitUI.getToolTip()); this.selectedUnitFrames[i].setUberTip(unitUI.getUberTip()); + this.selectedUnitFrames[i] + .setLifeRatioRemaining(multiSelectedUnit.getLife() / multiSelectedUnit.getMaximumLife()); + final boolean useManaBar = multiSelectedUnit.getMaximumMana() > 0; + this.selectedUnitFrames[i].setManaBarVisible(useManaBar); + if (useManaBar) { + this.selectedUnitFrames[i].setManaRatioRemaining( + multiSelectedUnit.getMana() / multiSelectedUnit.getMaximumMana()); + } + if (focused) { + this.selectedUnitFrames[i].showFocused(this.rootFrame, this.uiViewport); + if (useManaBar) { + this.selectedUnitHighlightBackdrop[i].setHeight(this.frontQueueIconWidth * 1.75f); + } + else { + this.selectedUnitHighlightBackdrop[i].setHeight(this.frontQueueIconWidth * 1.55f); + } + this.selectedUnitHighlightBackdrop[i].positionBounds(this.rootFrame, this.uiViewport); + } + else { + this.selectedUnitFrames[i].showUnFocused(this.rootFrame, this.uiViewport); + } } } this.simpleInfoPanelBuildingDetail.setVisible(false); @@ -2078,7 +2394,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.armorIcon.setVisible(false); this.heroInfoPanel.setVisible(false); this.selectWorkerInsideFrame.setVisible(false); - } else { + } + else { for (final QueueIcon queueIconFrame : this.queueIconFrames) { queueIconFrame.setVisible(false); } @@ -2113,22 +2430,24 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma + attackTwoTemporaryDamageBonus + ")"; } this.rootFrame.setText(this.attack2InfoPanelIconValue, attackTwoDmgText); - } else { + } + else { this.attack2Icon.setVisible(false); } this.smashArmorIconWrapper.addSetPoint( new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.0625f))); + GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.0705f))); this.smashArmorIconWrapper.positionBounds(this.rootFrame, this.uiViewport); this.armorIcon.positionBounds(this.rootFrame, this.uiViewport); - } else { + } + else { this.attack1Icon.setVisible(false); this.attack2Icon.setVisible(false); this.smashArmorIconWrapper.addSetPoint( new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.032f))); + GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.040f))); this.smashArmorIconWrapper.positionBounds(this.rootFrame, this.uiViewport); this.armorIcon.positionBounds(this.rootFrame, this.uiViewport); } @@ -2167,7 +2486,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final CGameplayConstants gameplayConstants = this.war3MapViewer.simulation.getGameplayConstants(); this.simpleHeroLevelBar.setValue((heroData.getXp() - gameplayConstants.getNeedHeroXPSum(heroLevel - 1)) / (float) gameplayConstants.getNeedHeroXP(heroLevel)); - } else { + } + else { this.simpleClassValue.setVisible(!simulationUnit.isBuilding()); this.rootFrame.setText(this.simpleNameValue, unitTypeName); String classText = null; @@ -2178,7 +2498,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } if (classText != null) { this.rootFrame.setText(this.simpleClassValue, classText); - } else { + } + else { this.rootFrame.setText(this.simpleClassValue, ""); } this.simpleHeroLevelBar.setVisible(false); @@ -2198,10 +2519,12 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.selectWorkerInsideFrame.setVisible(true); this.selectWorkerInsideFrame.setTexture(this.war3MapViewer.getAbilityDataUI() .getUnitUI(simulationUnit.getWorkerInside().getTypeId()).getIcon()); - } else { + } + else { this.selectWorkerInsideFrame.setVisible(false); } - } else { + } + else { this.rootFrame.setText(this.simpleBuildingActionLabel, ""); this.selectWorkerInsideFrame.setVisible(false); } @@ -2215,13 +2538,15 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma String defenseDisplayString; if (simulationUnit.isInvulnerable()) { defenseDisplayString = this.rootFrame.getTemplates().getDecoratedString("INVULNERABLE"); - } else { + } + else { defenseDisplayString = Integer.toString(simulationUnit.getCurrentDefenseDisplay()); final int temporaryDefenseBonus = simulationUnit.getTemporaryDefenseBonus(); if (temporaryDefenseBonus != 0) { if (temporaryDefenseBonus > 0) { defenseDisplayString += "|cFF00FF00 (+" + temporaryDefenseBonus + ")"; - } else { + } + else { defenseDisplayString += "|cFFFF0000 (+" + temporaryDefenseBonus + ")"; } } @@ -2261,15 +2586,18 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma activelyUsed ? (OrderIds.itemuse00 + index) : 0, index + 1, activelyUsed, false, false, itemUI.getName(), this.recycleStringBuilder.toString(), '\0', itemType.getGoldCost(), itemType.getLumberCost(), 0); - } else { + } + else { if (index >= inventory.getItemCapacity()) { inventoryIcon.setCommandButtonData(this.consoleInventoryNoCapacityTexture, 0, 0, 0, false, false, false, null, null, '\0', 0, 0, 0); - } else { + } + else { if (this.draggingItem != null) { inventoryIcon.setCommandButtonData(null, 0, 0, index + 1, true, false, false, null, null, '\0', 0, 0, 0); - } else { + } + else { inventoryIcon.clear(); } } @@ -2341,7 +2669,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final Texture suffixTexture = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); if (suffixTexture != null) { this.damageBackdropTextures[index] = suffixTexture; - } else { + } + else { skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey(); this.damageBackdropTextures[index] = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); } @@ -2374,7 +2703,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } selectWidgets(newSelection); this.war3MapViewer.doSelectUnit(newSelection); - } else { + } + else { final float lifeRatioRemaining = this.selectedUnit.getSimulationUnit().getLife() / this.selectedUnit.getSimulationUnit().getMaxLife(); this.rootFrame.setText(this.unitLifeText, @@ -2401,7 +2731,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (foodCap == 0) { this.rootFrame.setText(this.resourceBarSupplyText, Integer.toString(this.localPlayer.getFoodUsed())); this.resourceBarSupplyText.setColor(Color.WHITE); - } else { + } + else { this.rootFrame.setText(this.resourceBarSupplyText, this.localPlayer.getFoodUsed() + "/" + foodCap); this.resourceBarSupplyText.setColor(this.localPlayer.getFoodUsed() > foodCap ? Color.RED : Color.WHITE); } @@ -2422,6 +2753,11 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } + @Override + public void heroTokensChanged() { + // TODO Auto-generated method stub + } + @Override public void ordersChanged() { reloadSelectedUnitUI(this.selectedUnit); @@ -2455,7 +2791,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, menuOrderId, 0, false, false, true, cancelUI.getToolTip(), cancelUI.getUberTip(), cancelUI.getHotkey(), 0, 0, 0); - } else { + } + else { if (menuOrderId != 0) { final int exitOrderId = this.subMenuOrderIdStack.size() > 1 ? this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 2) @@ -2489,6 +2826,13 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma War3MapViewer.DEBUG_DEPTH = 0; } } + if (keycode == Input.Keys.TAB) { + if (this.selectedUnits.size() > 1) { + advanceSelectedSubGroup(); + this.war3MapViewer.getUiSounds().getSound("SubGroupSelectionChange").play(this.uiScene.audioContext, 0, + 0, 0); + } + } final String keyString = Input.Keys.toString(keycode); final char c = keyString.length() == 1 ? keyString.charAt(0) : ' '; for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { @@ -2512,6 +2856,22 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma public boolean touchDown(final int screenX, final int screenY, final float worldScreenY, final int button) { this.allowDrag = false; + if (button == Input.Buttons.FORWARD) { + if (this.selectedUnits.size() > 1) { + advanceSelectedSubGroup(); + this.war3MapViewer.getUiSounds().getSound("SubGroupSelectionChange").play(this.uiScene.audioContext, 0, + 0, 0); + } + return false; + } + else if (button == Input.Buttons.BACK) { + if (this.selectedUnits.size() > 1) { + advanceSelectedSubGroupReverse(); + this.war3MapViewer.getUiSounds().getSound("SubGroupSelectionChange").play(this.uiScene.audioContext, 0, + 0, 0); + } + return false; + } screenCoordsVector.set(screenX, screenY); this.uiViewport.unproject(screenCoordsVector); if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { @@ -2533,60 +2893,15 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma setDraggingItem(null); } clearAndRepopulateCommandCard(); - } else { + } + else { final boolean shiftDown = isShiftDown(); final RenderWidget rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY, this.activeCommandUnitTargetFilter); if (rayPickUnit != null) { - this.unitOrderListener.issueTargetOrder( - this.activeCommandUnit.getSimulationUnit().getHandleId(), - this.activeCommand.getHandleId(), this.activeCommandOrderId, - rayPickUnit.getSimulationWidget().getHandleId(), shiftDown); - if (this.selectedUnits.size() > 1) { - for (final RenderUnit otherSelectedUnit : this.selectedUnits) { - if (otherSelectedUnit != this.activeCommandUnit) { - CAbility abilityToUse = null; - CWidget targetToUse = null; - for (final CAbility ability : otherSelectedUnit.getSimulationUnit() - .getAbilities()) { - final CWidgetAbilityTargetCheckReceiver receiver = CWidgetAbilityTargetCheckReceiver.INSTANCE - .reset(); - ability.checkCanTarget(this.war3MapViewer.simulation, - otherSelectedUnit.getSimulationUnit(), this.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; - if (yesSound.playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - if (this.activeCommand instanceof CAbilityRally) { - this.war3MapViewer.getUiSounds().getSound("RallyPointPlace").play(this.uiScene.audioContext, - 0, 0, 0); - } - if (!shiftDown) { - this.subMenuOrderIdStack.clear(); - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - clearAndRepopulateCommandCard(); - } - } else { + useActiveCommandOnUnit(shiftDown, rayPickUnit); + } + else { this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); @@ -2607,7 +2922,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.activeCommandOrderId = -1; setDraggingItem(null); clearAndRepopulateCommandCard(); - } else { + } + else { this.activeCommand.checkCanTarget(this.war3MapViewer.simulation, this.activeCommandUnit.getSimulationUnit(), this.activeCommandOrderId, clickLocationTemp2, PointAbilityTargetCheckReceiver.INSTANCE); @@ -2616,7 +2932,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if ((this.activeCommand instanceof CAbilityAttack) && (this.activeCommandOrderId == OrderIds.attack)) { this.war3MapViewer.showConfirmation(clickLocationTemp, 1, 0, 0); - } else { + } + else { this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); } this.unitOrderListener.issuePointOrder( @@ -2657,7 +2974,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (this.activeCommand instanceof AbstractCAbilityBuild) { this.war3MapViewer.getUiSounds().getSound("PlaceBuildingDefault") .play(this.uiScene.audioContext, 0, 0, 0); - } else if (this.activeCommand instanceof CAbilityRally) { + } + else if (this.activeCommand instanceof CAbilityRally) { this.war3MapViewer.getUiSounds().getSound("RallyPointPlace") .play(this.uiScene.audioContext, 0, 0, 0); } @@ -2674,7 +2992,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } - } else { + } + else { if (button == Input.Buttons.RIGHT) { if ((getSelectedUnit() != null) && (getSelectedUnit().getSimulationUnit() .getPlayerIndex() == this.war3MapViewer.getLocalPlayerIndex())) { @@ -2717,14 +3036,17 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma .play(this.uiScene.audioContext, 0, 0, 0); } this.selectedSoundCount = 0; - } else { + } + else { rightClickMove(screenX, worldScreenY); } - } else { + } + else { rightClickMove(screenX, worldScreenY); } } - } else { + } + else { this.war3MapViewer.getClickLocation(this.lastMouseClickLocation, screenX, (int) worldScreenY); if (Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT)) { final short pathing = this.war3MapViewer.simulation.getPathingGrid() @@ -2733,9 +3055,11 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } this.lastMouseDragStart.set(this.lastMouseClickLocation); this.allowDrag = true; + this.draggingMouseButton = button; } } - } else { + } + else { if (clickedUIFrame instanceof ClickableFrame) { this.mouseDownUIFrame = (ClickableFrame) clickedUIFrame; this.mouseDownUIFrame.mouseDown(this.rootFrame, this.uiViewport); @@ -2744,6 +3068,96 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma return false; } + private void advanceSelectedSubGroup() { + boolean foundSubSelection = false; + for (final RenderUnit unit : this.selectedUnits) { + if (foundSubSelection) { + if (!unit.groupsWith(this.selectedUnit)) { + selectUnit(unit); + return; + } + } + else if (unit == this.selectedUnit) { + foundSubSelection = true; + } + } + if (!this.selectedUnits.isEmpty()) { + selectUnit(this.selectedUnits.get(0)); + } + } + + private void advanceSelectedSubGroupReverse() { + boolean foundSubSelection = false; + for (int i = this.selectedUnits.size() - 1; i >= 0; i--) { + final RenderUnit unit = this.selectedUnits.get(i); + if (foundSubSelection) { + if (!unit.groupsWith(this.selectedUnit)) { + selectUnit(unit); + return; + } + } + else if (unit == this.selectedUnit) { + foundSubSelection = true; + } + } + if (!this.selectedUnits.isEmpty()) { + selectUnit(this.selectedUnits.get(this.selectedUnits.size() - 1)); + } + } + + private void useActiveCommandOnUnit(final boolean shiftDown, final RenderWidget rayPickUnit) { + if (this.draggingItem != null) { + this.unitOrderListener.issueDropItemAtTargetOrder(this.activeCommandUnit.getSimulationUnit().getHandleId(), + this.activeCommand.getHandleId(), this.activeCommandOrderId, this.draggingItem.getHandleId(), + rayPickUnit.getSimulationWidget().getHandleId(), shiftDown); + setDraggingItem(null); + } + else { + this.unitOrderListener.issueTargetOrder(this.activeCommandUnit.getSimulationUnit().getHandleId(), + this.activeCommand.getHandleId(), this.activeCommandOrderId, + rayPickUnit.getSimulationWidget().getHandleId(), shiftDown); + if (this.selectedUnits.size() > 1) { + for (final RenderUnit otherSelectedUnit : this.selectedUnits) { + if (otherSelectedUnit != this.activeCommandUnit) { + CAbility abilityToUse = null; + CWidget targetToUse = null; + for (final CAbility ability : otherSelectedUnit.getSimulationUnit().getAbilities()) { + final CWidgetAbilityTargetCheckReceiver receiver = CWidgetAbilityTargetCheckReceiver.INSTANCE + .reset(); + ability.checkCanTarget(this.war3MapViewer.simulation, otherSelectedUnit.getSimulationUnit(), + this.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; + if (yesSound.playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + if (this.activeCommand instanceof CAbilityRally) { + this.war3MapViewer.getUiSounds().getSound("RallyPointPlace").play(this.uiScene.audioContext, 0, 0, 0); + } + if (!shiftDown) { + this.subMenuOrderIdStack.clear(); + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + clearAndRepopulateCommandCard(); + } + } + private void rightClickMove(final int screenX, final float worldScreenY) { this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); @@ -2787,6 +3201,26 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private void selectWidgets(final List selectedUnits) { final List units = new ArrayList<>(); + Collections.sort(selectedUnits, new Comparator() { + @Override + public int compare(final RenderWidget widget1, final RenderWidget widget2) { + final CUnitType unitType1 = ((RenderUnit) widget2).getSimulationUnit().getUnitType(); + final CUnitType unitType2 = ((RenderUnit) widget1).getSimulationUnit().getUnitType(); + final int prioSort = unitType1.getPriority() - unitType2.getPriority(); + if (prioSort == 0) { + final int levelSort = unitType1.getLevel() - unitType2.getLevel(); + if (levelSort == 0) { + return unitType1.getTypeId().getValue() - unitType2.getTypeId().getValue(); + } + else { + return levelSort; + } + } + else { + return prioSort; + } + } + }); for (final RenderWidget widget : selectedUnits) { if (widget instanceof RenderUnit) { units.add((RenderUnit) widget); @@ -2811,7 +3245,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (selectionChanged) { this.selectedSoundCount = 0; } - if (unit.getSimulationUnit().getPlayerIndex() == this.war3MapViewer.getLocalPlayerIndex()) { + if ((unit.getSimulationUnit().getPlayerIndex() == this.war3MapViewer.getLocalPlayerIndex()) + || (unit.getSimulationUnit().getUnitType().getRace() == CUnitRace.CRITTERS) + || ((unit.getSimulationUnit().getUnitType().getRace() == CUnitRace.OTHER) + && (unit.getSimulationUnit().getPlayerIndex() == (WarsmashConstants.MAX_PLAYERS - 1)))) { if (unit.soundset != null) { UnitSound ackSoundToPlay = unit.soundset.what; int soundIndex; @@ -2820,11 +3257,13 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma ackSoundToPlay = this.war3MapViewer.getUiSounds() .getSound(this.rootFrame.getSkinField("ConstructingBuilding")); soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); - } else { + } + else { if ((this.selectedSoundCount >= 3) && (pissedSoundCount > 0)) { soundIndex = this.selectedSoundCount - 3; ackSoundToPlay = unit.soundset.pissed; - } else { + } + else { soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); } } @@ -2837,18 +3276,21 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma playedNewSound = true; } } - } else { + } + else { this.war3MapViewer.getUiSounds().getSound("InterfaceClick").play(this.uiScene.audioContext, 0, 0, 0); } if (selectionChanged) { for (final MultiSelectUnitStateListener listener : this.multiSelectUnitStateListeners) { listener.dispose(); } + this.multiSelectUnitStateListeners.clear(); selectUnit(unit); if (selectedUnits.size() > 1) { + int index = 0; for (final RenderUnit renderUnit : selectedUnits) { final MultiSelectUnitStateListener multiSelectUnitStateListener = new MultiSelectUnitStateListener( - renderUnit); + renderUnit, index++); renderUnit.getSimulationUnit().addStateListener(multiSelectUnitStateListener); this.multiSelectUnitStateListeners.add(multiSelectUnitStateListener); } @@ -2857,12 +3299,19 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (playedNewSound) { portraitTalk(); } - } else { + } + else { selectUnit(null); } } public boolean touchUp(final int screenX, final int screenY, final float worldScreenY, final int button) { + if (button == Input.Buttons.FORWARD) { + return false; + } + else if (button == Input.Buttons.BACK) { + return false; + } this.currentlyDraggingPointer = -1; screenCoordsVector.set(screenX, screenY); this.uiViewport.unproject(screenCoordsVector); @@ -2870,16 +3319,23 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (this.mouseDownUIFrame != null) { if (clickedUIFrame == this.mouseDownUIFrame) { this.mouseDownUIFrame.onClick(button); + String soundKey; if (this.mouseDownUIFrame instanceof ClickableActionFrame) { - this.war3MapViewer.getUiSounds().getSound("InterfaceClick").play(this.uiScene.audioContext, 0, 0, - 0); - } else { - this.war3MapViewer.getUiSounds().getSound("MenuButtonClick").play(this.uiScene.audioContext, 0, 0, - 0); + if (this.mouseDownUIFrame instanceof MultiSelectionIcon) { + soundKey = "SubGroupSelectionChange"; + } + else { + soundKey = "InterfaceClick"; + } } + else { + soundKey = "MenuButtonClick"; + } + this.war3MapViewer.getUiSounds().getSound(soundKey).play(this.uiScene.audioContext, 0, 0, 0); } this.mouseDownUIFrame.mouseUp(this.rootFrame, this.uiViewport); - } else { + } + else { if (!this.dragSelectPreviewUnits.isEmpty()) { if (this.allowDrag) { final List selectedWidgets = new ArrayList<>(); @@ -2902,13 +3358,6 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } } - Collections.sort(selectedWidgets, new Comparator() { - @Override - public int compare(final RenderWidget widget1, final RenderWidget widget2) { - return ((RenderUnit) widget2).getSimulationUnit().getUnitType().getPriority() - - ((RenderUnit) widget1).getSimulationUnit().getUnitType().getPriority(); - } - }); this.war3MapViewer.clearUnitMouseOverHighlight(); @@ -2916,24 +3365,29 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma selectWidgets(selectedWidgets); } this.dragSelectPreviewUnits.clear(); - } else { + } + else { if (this.allowDrag) { - if ((button == Input.Buttons.LEFT) && (this.mouseOverUnit != null)) { + if ((button == Input.Buttons.LEFT) && (this.mouseOverUnit != null) + && isUnitSelectable(this.mouseOverUnit)) { final long currentMillis = TimeUtils.millis(); final List unitList = new ArrayList<>(); final boolean shiftDown = isShiftDown(); - final boolean controlDown = isControlDown() || ((currentMillis - this.lastUnitClickTime) < 500); + final boolean controlDown = isControlDown() || (((currentMillis - this.lastUnitClickTime) < 500) + && (this.mouseOverUnit == this.lastClickUnit)); if (shiftDown) { unitList.addAll(this.selectedUnits); } if ((this.mouseOverUnit instanceof RenderUnit) && controlDown) { processSelectNearbyUnits(unitList, shiftDown, (RenderUnit) this.mouseOverUnit); - } else { + } + else { processClickSelect(unitList, shiftDown, this.mouseOverUnit); } this.war3MapViewer.doSelectUnit(unitList); selectWidgets(unitList); this.lastUnitClickTime = currentMillis; + this.lastClickUnit = this.mouseOverUnit; } } } @@ -2963,10 +3417,12 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (shiftDown) { if (this.selectedUnits.contains(mouseOverUnit)) { unitList.remove(mouseOverUnit); - } else { + } + else { unitList.add(mouseOverUnit); } - } else { + } + else { unitList.add(mouseOverUnit); } } @@ -2988,7 +3444,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma screenCoordsVector.y); this.cameraManager.target.x = worldPoint.x; this.cameraManager.target.y = worldPoint.y; - } else { + } + else { if (this.allowDrag) { if (null != this.mouseOverUnit) { this.war3MapViewer.clearUnitMouseOverHighlight(); @@ -2998,9 +3455,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); this.currentlyDraggingPointer = pointer; - if (pointer == Input.Buttons.MIDDLE) { + if (this.draggingMouseButton == Input.Buttons.MIDDLE) { this.cameraManager.target.add(this.lastMouseClickLocation.sub(clickLocationTemp).scl(-1)); - } else if (pointer == Input.Buttons.LEFT) { + } + else if (this.draggingMouseButton == Input.Buttons.LEFT) { // update mouseover } this.lastMouseClickLocation.set(clickLocationTemp); @@ -3026,7 +3484,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (mousedUIFrame instanceof ClickableActionFrame) { loadTooltip((ClickableActionFrame) mousedUIFrame); } - } else { + } + else { this.mouseOverUIFrame = null; this.tooltipFrame.setVisible(false); } @@ -3060,7 +3519,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final String uberTip = mousedUIFrame.getUberTip(); if ((toolTip == null) || (uberTip == null)) { this.tooltipFrame.setVisible(false); - } else { + } + else { this.rootFrame.setText(this.tooltipUberTipText, uberTip); int resourceIndex = 0; if (goldCost != 0) { @@ -3088,7 +3548,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (resourceIndex != 0) { this.tooltipUberTipText.addSetPoint(this.uberTipWithResourcesSetPoint); resourcesHeight = 0.014f; - } else { + } + else { this.tooltipUberTipText.addSetPoint(this.uberTipNoResourcesSetPoint); resourcesHeight = 0.004f; } @@ -3135,7 +3596,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma selectWidgets(unitList); break; } - } else { + } + else { this.unitOrderListener.unitCancelTrainingItem(simulationUnit.getHandleId(), index); } } @@ -3163,14 +3625,17 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma setDraggingItem(null); MeleeUI.this.activeCommand = null; MeleeUI.this.activeCommandUnit = null; - } else { + } + else { if (itemInSlot != null) { setDraggingItem(itemInSlot); MeleeUI.this.activeCommand = inventoryData; MeleeUI.this.activeCommandUnit = selectedUnit2; + MeleeUI.this.activeCommandOrderId = OrderIds.dropitem; } } - } else { + } + else { final CSimulation game = MeleeUI.this.war3MapViewer.simulation; final BooleanAbilityActivationReceiver receiver = BooleanAbilityActivationReceiver.INSTANCE; final CAbilityInventory inventoryData = simulationUnit.getInventoryData(); @@ -3197,9 +3662,11 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private final class MultiSelectUnitStateListener implements CUnitStateListener { private final RenderUnit sourceUnit; + private int index; - public MultiSelectUnitStateListener(final RenderUnit sourceUnit) { + public MultiSelectUnitStateListener(final RenderUnit sourceUnit, final int index) { this.sourceUnit = sourceUnit; + this.index = index; } public void dispose() { @@ -3211,8 +3678,17 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (this.sourceUnit.getSimulationUnit().isDead()) { MeleeUI.this.selectedUnits.remove(this.sourceUnit); MeleeUI.this.war3MapViewer.doUnselectUnit(this.sourceUnit); - MeleeUI.this.multiSelectUnitStateListeners.remove(this); + MeleeUI.this.multiSelectUnitStateListeners.remove(this.index); + for (int i = this.index; i < MeleeUI.this.multiSelectUnitStateListeners.size(); i++) { + MeleeUI.this.multiSelectUnitStateListeners.get(i).index--; + } dispose(); + reloadSelectedUnitUI(MeleeUI.this.selectedUnit); + } + else { + MeleeUI.this.selectedUnitFrames[this.index] + .setLifeRatioRemaining(this.sourceUnit.getSimulationUnit().getLife() + / this.sourceUnit.getSimulationUnit().getMaximumLife()); } } @@ -3247,4 +3723,56 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } + + public GameCameraManager getCameraManager() { + return this.cameraManager; + } + + public Music playMusic(final String[] musics, final boolean random, int index) { + if (WarsmashConstants.ENABLE_MUSIC) { + stopMusic(); + if (random) { + index = (int) (Math.random() * musics.length); + } + this.currentMusics = new Music[musics.length]; + for (int i = 0; i < musics.length; i++) { + final Music newMusic = Gdx.audio + .newMusic(new DataSourceFileHandle(this.war3MapViewer.dataSource, musics[i])); + newMusic.setVolume(1.0f); + this.currentMusics[i] = newMusic; + } + this.currentMusicIndex = index; + this.currentMusicRandomizeIndex = random; + this.currentMusics[index].play(); + } + return null; + } + + public void gameClosed() { + stopMusic(); + } + + private void stopMusic() { + if (this.currentMusics != null) { + for (final Music music : this.currentMusics) { + music.stop(); + } + this.currentMusics = null; + } + } + + public Scene getUiScene() { + return this.uiScene; + } + + public CTimerDialog createTimerDialog(final CTimer timer) { + final UIFrame timerDialog = this.rootFrame.createFrame("TimerDialog", this.rootFrame, 0, 0); + final StringFrame valueFrame = (StringFrame) this.rootFrame.getFrameByName("TimeDialogValue", 0); + final StringFrame titleFrame = (StringFrame) this.rootFrame.getFrameByName("TimerDialogTitle", 0); + return new CTimerDialog(timer, timerDialog, valueFrame, titleFrame); + } + + public void displayTimedText(final float x, final float y, final float duration, final String message) { + showGameMessage(message, duration); // TODO x y + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuCursorState.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuCursorState.java index 60baa30..e1752a3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuCursorState.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuCursorState.java @@ -10,6 +10,7 @@ public enum MenuCursorState { SCROLL_DOWN_RIGHT("Scroll Down Right"), SCROLL_UP_LEFT("Scroll Up Left"), SCROLL_UP_RIGHT("Scroll Up Right"), + SELECT("Select"), TARGET_CURSOR(null), // handled specially HOLD_ITEM("HoldItem"); private String animationName; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MultiSelectionIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MultiSelectionIcon.java new file mode 100644 index 0000000..79c5ff1 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MultiSelectionIcon.java @@ -0,0 +1,215 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui; + +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.parsers.fdf.frames.AbstractRenderableFrame; +import com.etheller.warsmash.parsers.fdf.frames.SimpleStatusBarFrame; +import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; +import com.etheller.warsmash.parsers.fdf.frames.UIFrame; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.ClickableActionFrame; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.MultiSelectionIconListener; + +public class MultiSelectionIcon extends AbstractRenderableFrame implements ClickableActionFrame { + + public static final float HP_BAR_HEIGHT_RATIO = 0.175f; + public static final float HP_BAR_SPACING_RATIO = 0.02f; + private TextureFrame iconFrame; + private final MultiSelectionIconListener clickListener; + private float defaultWidth; + private float defaultHeight; + private final int queueIconIndexId; + + private String toolTip; + private String uberTip; + private boolean focused; + private SimpleStatusBarFrame hpBarFrame; + private SimpleStatusBarFrame manaBarFrame; + + public MultiSelectionIcon(final String name, final UIFrame parent, final MultiSelectionIconListener clickListener, + final int queueIconIndexId) { + super(name, parent); + this.clickListener = clickListener; + this.queueIconIndexId = queueIconIndexId; + } + + public void set(final TextureFrame iconFrame, final SimpleStatusBarFrame hpBarFrame, + final SimpleStatusBarFrame manaBarFrame) { + this.iconFrame = iconFrame; + this.hpBarFrame = hpBarFrame; + this.manaBarFrame = manaBarFrame; + setVisible(true); + } + + public void clear() { + setVisible(false); + } + + public void setTexture(final Texture texture) { + this.iconFrame.setTexture(texture); + } + + @Override + protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { + this.iconFrame.positionBounds(gameUI, viewport); + this.hpBarFrame.positionBounds(gameUI, viewport); + this.manaBarFrame.positionBounds(gameUI, viewport); + } + + @Override + protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { + this.iconFrame.render(batch, baseFont, glyphLayout); + this.hpBarFrame.render(batch, baseFont, glyphLayout); + this.manaBarFrame.render(batch, baseFont, glyphLayout); + } + + @Override + public UIFrame touchDown(final float screenX, final float screenY, final int button) { + if (isVisible() && this.renderBounds.contains(screenX, screenY)) { + return this; + } + return super.touchDown(screenX, screenY, button); + } + + @Override + public UIFrame touchUp(final float screenX, final float screenY, final int button) { + if (isVisible() && this.renderBounds.contains(screenX, screenY)) { + return this; + } + return super.touchUp(screenX, screenY, button); + } + + @Override + public void onClick(final int button) { + this.clickListener.multiSelectIconClicked(this.queueIconIndexId); + } + + @Override + public void setWidth(final float width) { + this.defaultWidth = width; + super.setWidth(width); + } + + @Override + public void setHeight(final float height) { + this.defaultHeight = height; + super.setHeight(height); + } + + private void innerSetDimensions(final float newWidth, final float newHeight) { + this.iconFrame.setWidth(newWidth); + this.iconFrame.setHeight(newHeight); + this.hpBarFrame.setWidth(newWidth * 1.05f); + this.hpBarFrame.setHeight(newHeight * HP_BAR_HEIGHT_RATIO); + this.manaBarFrame.setWidth(newWidth * 1.05f); + this.manaBarFrame.setHeight(newHeight * HP_BAR_HEIGHT_RATIO); + } + + public void showUnFocused(final GameUI gameUI, final Viewport uiViewport) { + final float newWidth = this.defaultWidth * 0.75f; + final float newHeight = this.defaultHeight * 0.75f; + innerSetDimensions(newWidth, newHeight); + positionBounds(gameUI, uiViewport); + this.focused = false; + } + + public void showFocused(final GameUI gameUI, final Viewport uiViewport) { + innerSetDimensions(this.defaultWidth, this.defaultHeight); + positionBounds(gameUI, uiViewport); + this.focused = true; + } + + public void showMousePressed(final GameUI gameUI, final Viewport uiViewport) { + final float ratio = this.focused ? 0.95f : 0.70f; + final float newWidth = this.defaultWidth * ratio; + final float newHeight = this.defaultHeight * ratio; + innerSetDimensions(newWidth, newHeight); + positionBounds(gameUI, uiViewport); + } + + @Override + public void mouseDown(final GameUI gameUI, final Viewport uiViewport) { + this.clickListener.multiSelectIconPress(this.queueIconIndexId); + showMousePressed(gameUI, uiViewport); + } + + public void showMouseReleased(final GameUI gameUI, final Viewport uiViewport) { + final float ratio = this.focused ? 1.00f : 0.75f; + final float newWidth = this.defaultWidth * ratio; + final float newHeight = this.defaultHeight * ratio; + innerSetDimensions(newWidth, newHeight); + positionBounds(gameUI, uiViewport); + } + + @Override + public void mouseUp(final GameUI gameUI, final Viewport uiViewport) { + this.clickListener.multiSelectIconRelease(this.queueIconIndexId); + showMouseReleased(gameUI, uiViewport); + } + + @Override + public void mouseEnter(final GameUI gameUI, final Viewport uiViewport) { + } + + @Override + public void mouseExit(final GameUI gameUI, final Viewport uiViewport) { + } + + @Override + public UIFrame getFrameChildUnderMouse(final float screenX, final float screenY) { + if (isVisible() && this.renderBounds.contains(screenX, screenY)) { + return this; + } + return null; + } + + public void setToolTip(final String toolTip) { + this.toolTip = toolTip; + } + + public void setUberTip(final String uberTip) { + this.uberTip = uberTip; + } + + @Override + public String getToolTip() { + return this.toolTip; + } + + @Override + public String getUberTip() { + return this.uberTip; + } + + @Override + public int getToolTipFoodCost() { + return 0; + } + + @Override + public int getToolTipGoldCost() { + return 0; + } + + @Override + public int getToolTipLumberCost() { + return 0; + } + + public void setLifeRatioRemaining(final float lifeRatioRemaining) { + this.hpBarFrame.getBarFrame().setColor(Math.min(1.0f, 2.0f - (lifeRatioRemaining * 2)), + Math.min(1.0f, lifeRatioRemaining * 2), 0, 1.0f); + this.hpBarFrame.setValue(lifeRatioRemaining); + } + + public void setManaRatioRemaining(final float lifeRatioRemaining) { + this.manaBarFrame.setValue(lifeRatioRemaining); + } + + public void setManaBarVisible(final boolean visible) { + this.manaBarFrame.setVisible(visible); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/MultiSelectionIconListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/MultiSelectionIconListener.java new file mode 100644 index 0000000..7cebf08 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/MultiSelectionIconListener.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui.command; + +public interface MultiSelectionIconListener { + void multiSelectIconClicked(int index); + + void multiSelectIconPress(int index); + + void multiSelectIconRelease(int index); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/dialog/CTimerDialog.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/dialog/CTimerDialog.java new file mode 100644 index 0000000..94ae0b6 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/dialog/CTimerDialog.java @@ -0,0 +1,46 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui.dialog; + +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.parsers.fdf.frames.StringFrame; +import com.etheller.warsmash.parsers.fdf.frames.UIFrame; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.timers.CTimer; + +public class CTimerDialog { + private final CTimer timer; + private final UIFrame timerDialogFrame; + private final StringFrame valueFrame; + private final StringFrame titleFrame; + + public CTimerDialog(final CTimer timer, final UIFrame timerDialogFrame, final StringFrame valueFrame, + final StringFrame titleFrame) { + this.timer = timer; + this.timerDialogFrame = timerDialogFrame; + this.valueFrame = valueFrame; + this.titleFrame = titleFrame; + } + + public void setTitle(final GameUI rootFrame, final String title) { + rootFrame.setText(this.titleFrame, title); + } + + public void setValue(final GameUI rootFrame, final String value) { + rootFrame.setText(this.valueFrame, value); + } + + public void setVisible(final boolean visible) { + this.timerDialogFrame.setVisible(visible); + } + + public void update(final GameUI rootFrame, final CSimulation simulation) { + if (this.timerDialogFrame.isVisible() && (this.timer != null)) { + final float remaining = this.timer.getRemaining(simulation); + final int secondsRemaining = (int) remaining; + final int minutes = secondsRemaining / 60; + final int seconds = secondsRemaining % 60; + + rootFrame.setText(this.valueFrame, minutes + ":" + seconds); + } + } + +} diff --git a/jassparser/antlr-src/Jass.g4 b/jassparser/antlr-src/Jass.g4 index cab4935..7251574 100644 --- a/jassparser/antlr-src/Jass.g4 +++ b/jassparser/antlr-src/Jass.g4 @@ -34,17 +34,74 @@ global : | CONSTANT? type ID assignTail newlines # DefinitionGlobal ; +local : + LOCAL type ID newlines # BasicLocal + | + LOCAL type ID assignTail newlines # DefinitionLocal + ; assignTail: EQUALS expression; -expression: +multDivExpression: + multDivExpression '*' baseExpression # MultiplicationExpression + | + multDivExpression '/' baseExpression # DivisionExpression + | + baseExpression # BaseMultiplicationExpression + ; + +simpleArithmeticExpression: + simpleArithmeticExpression '+' multDivExpression # AdditionExpression + | + simpleArithmeticExpression '-' multDivExpression # SubtrationExpression + | + multDivExpression # BaseAdditionExpression + ; + +boolComparisonExpression: + boolComparisonExpression '<' simpleArithmeticExpression # BooleanLessExpression + | + boolComparisonExpression '>' simpleArithmeticExpression # BooleanGreaterExpression + | + boolComparisonExpression '<=' simpleArithmeticExpression # BooleanLessOrEqualsExpression + | + boolComparisonExpression '>=' simpleArithmeticExpression # BooleanGreaterOrEqualsExpression + | + simpleArithmeticExpression # BaseBoolComparisonExpression + ; + +boolEqualityExpression: + boolEqualityExpression '==' boolComparisonExpression # EqualsExpression + | + boolEqualityExpression '!=' boolComparisonExpression # NotEqualsExpression + | + boolComparisonExpression # BaseBoolExpression + ; + +boolAndsExpression: + boolAndsExpression AND boolEqualityExpression # BooleanAndExpression + | + boolEqualityExpression # BaseBoolAndsExpression + ; + +boolExpression: + boolExpression OR boolAndsExpression # BooleanOrExpression + | + boolAndsExpression # BaseBoolOrsExpression + ; + +baseExpression: ID # ReferenceExpression | STRING_LITERAL #StringLiteralExpression | INTEGER #IntegerLiteralExpression | + RAWCODE #RawcodeLiteralExpression + | + REAL #RealLiteralExpression + | FUNCTION ID #FunctionReferenceExpression | NULL # NullExpression @@ -59,8 +116,13 @@ expression: | '(' expression ')' # ParentheticalExpression | - NOT expression # NotExpression + NOT baseExpression # NotExpression + | + '-' baseExpression # NegateExpression ; + +expression: + boolExpression; functionExpression: ID '(' argsList ')' @@ -72,6 +134,8 @@ argsList: expression # SingleArgument | expression ',' argsList # ListArgument + | + # EmptyArgument ; //#booleanExpression: @@ -87,6 +151,14 @@ statement: | RETURN expression newlines # ReturnStatement | + RETURN newlines # ReturnNothingStatement + | + EXITWHEN expression newlines # ExitWhenStatement + | + local # LocalStatement + | + LOOP newlines statements ENDLOOP newlines # LoopStatement + | IF ifStatementPartial # IfStatement ; @@ -182,16 +254,27 @@ ELSE : 'else'; ENDIF : 'endif'; ELSEIF : 'elseif'; CONSTANT : 'constant'; +LOCAL : 'local'; +LOOP : 'loop'; +ENDLOOP : 'endloop'; +EXITWHEN : 'exitwhen'; STRING_LITERAL : ('"'.*?'"'); + INTEGER : [0]|([1-9][0-9]*) ; +RAWCODE : ('\''.*?'\''); + +REAL : (([0]|([1-9][0-9]*))'.'[0-9]*)|('.'([0]|([1-9][0-9]*))) ; + NULL : 'null' ; TRUE : 'true' ; FALSE : 'false' ; NOT : 'not'; +OR : 'or'; +AND : 'and'; ID : ([a-zA-Z_][a-zA-Z_0-9]*) ; // match identifiers diff --git a/jassparser/src/com/etheller/interpreter/ast/Assignable.java b/jassparser/src/com/etheller/interpreter/ast/Assignable.java index 0bc7262..a9931b7 100644 --- a/jassparser/src/com/etheller/interpreter/ast/Assignable.java +++ b/jassparser/src/com/etheller/interpreter/ast/Assignable.java @@ -13,11 +13,19 @@ public class Assignable { } public void setValue(final JassValue value) { - final JassType valueType = value.visit(JassTypeGettingValueVisitor.getInstance()); - if (!this.type.isAssignableFrom(valueType)) { - throw new RuntimeException("Incompatible types " + valueType.getName() + " != " + this.type.getName()); + if (value == null) { + if (!this.type.isNullable()) { + throw new RuntimeException("Type " + this.type.getName() + " cannot be assigned to null!"); + } + this.value = this.type.getNullValue(); + } + else { + final JassType valueType = value.visit(JassTypeGettingValueVisitor.getInstance()); + if (!this.type.isAssignableFrom(valueType)) { + throw new RuntimeException("Incompatible types " + valueType.getName() + " != " + this.type.getName()); + } + this.value = value; } - this.value = value; } public JassValue getValue() { diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/ArithmeticJassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/ArithmeticJassExpression.java new file mode 100644 index 0000000..7657488 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/expression/ArithmeticJassExpression.java @@ -0,0 +1,38 @@ +package com.etheller.interpreter.ast.expression; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.visitor.ArithmeticJassValueVisitor; +import com.etheller.interpreter.ast.value.visitor.ArithmeticLeftHandNullJassValueVisitor; + +public class ArithmeticJassExpression implements JassExpression { + + private final JassExpression leftExpression; + private final JassExpression rightExpression; + private final ArithmeticSign arithmeticSign; + + public ArithmeticJassExpression(final JassExpression leftExpression, final JassExpression rightExpression, + final ArithmeticSign arithmeticSign) { + this.leftExpression = leftExpression; + this.rightExpression = rightExpression; + this.arithmeticSign = arithmeticSign; + } + + @Override + public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { + final JassValue leftValue = this.leftExpression.evaluate(globalScope, localScope, triggerScope); + final JassValue rightValue = this.rightExpression.evaluate(globalScope, localScope, triggerScope); + if (leftValue == null) { + if (rightValue == null) { + return this.arithmeticSign.apply((String) null, (String) null); + } + else { + return rightValue.visit(ArithmeticLeftHandNullJassValueVisitor.INSTANCE.reset(this.arithmeticSign)); + } + } + return leftValue.visit(ArithmeticJassValueVisitor.INSTANCE.reset(rightValue, this.arithmeticSign)); + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/ArithmeticSign.java b/jassparser/src/com/etheller/interpreter/ast/expression/ArithmeticSign.java new file mode 100644 index 0000000..ce9dfce --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/expression/ArithmeticSign.java @@ -0,0 +1,26 @@ +package com.etheller.interpreter.ast.expression; + +import com.etheller.interpreter.ast.value.BooleanJassValue; +import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.HandleJassValue; +import com.etheller.interpreter.ast.value.IntegerJassValue; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.RealJassValue; + +public interface ArithmeticSign { + JassValue apply(BooleanJassValue left, BooleanJassValue right); + + JassValue apply(RealJassValue left, RealJassValue right); + + JassValue apply(RealJassValue left, IntegerJassValue right); + + JassValue apply(IntegerJassValue left, RealJassValue right); + + JassValue apply(IntegerJassValue left, IntegerJassValue right); + + JassValue apply(String left, String right); + + JassValue apply(HandleJassValue left, HandleJassValue right); + + JassValue apply(CodeJassValue left, CodeJassValue right); +} diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/ArithmeticSigns.java b/jassparser/src/com/etheller/interpreter/ast/expression/ArithmeticSigns.java new file mode 100644 index 0000000..61b2d43 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/expression/ArithmeticSigns.java @@ -0,0 +1,528 @@ +package com.etheller.interpreter.ast.expression; + +import com.etheller.interpreter.ast.value.BooleanJassValue; +import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.HandleJassValue; +import com.etheller.interpreter.ast.value.IntegerJassValue; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.RealJassValue; +import com.etheller.interpreter.ast.value.StringJassValue; + +public enum ArithmeticSigns implements ArithmeticSign { + ADD() { + @Override + public JassValue apply(final BooleanJassValue left, final BooleanJassValue right) { + throw new UnsupportedOperationException("Cannot perform numeric arithmetic on boolean"); + } + + @Override + public JassValue apply(final RealJassValue left, final RealJassValue right) { + return new RealJassValue(left.getValue() + right.getValue()); + } + + @Override + public JassValue apply(final RealJassValue left, final IntegerJassValue right) { + return new RealJassValue(left.getValue() + right.getValue()); + } + + @Override + public JassValue apply(final IntegerJassValue left, final RealJassValue right) { + return new RealJassValue(left.getValue() + right.getValue()); + } + + @Override + public JassValue apply(final IntegerJassValue left, final IntegerJassValue right) { + return new IntegerJassValue(left.getValue() + right.getValue()); + } + + @Override + public JassValue apply(final String left, final String right) { + return new StringJassValue(left + right); + } + + @Override + public JassValue apply(final HandleJassValue left, final HandleJassValue right) { + throw new UnsupportedOperationException("Cannot perform arithmetic on handle"); + } + + @Override + public JassValue apply(final CodeJassValue left, final CodeJassValue right) { + throw new UnsupportedOperationException("Cannot perform arithmetic on code"); + } + }, + SUBTRACT() { + @Override + public JassValue apply(final BooleanJassValue left, final BooleanJassValue right) { + throw new UnsupportedOperationException("Cannot perform numeric arithmetic on boolean"); + } + + @Override + public JassValue apply(final RealJassValue left, final RealJassValue right) { + return new RealJassValue(left.getValue() - right.getValue()); + } + + @Override + public JassValue apply(final RealJassValue left, final IntegerJassValue right) { + return new RealJassValue(left.getValue() - right.getValue()); + } + + @Override + public JassValue apply(final IntegerJassValue left, final RealJassValue right) { + return new RealJassValue(left.getValue() - right.getValue()); + } + + @Override + public JassValue apply(final IntegerJassValue left, final IntegerJassValue right) { + return new IntegerJassValue(left.getValue() - right.getValue()); + } + + @Override + public JassValue apply(final String left, final String right) { + throw new UnsupportedOperationException("Cannot perform arithmetic on string"); + } + + @Override + public JassValue apply(final HandleJassValue left, final HandleJassValue right) { + throw new UnsupportedOperationException("Cannot perform arithmetic on handle"); + } + + @Override + public JassValue apply(final CodeJassValue left, final CodeJassValue right) { + throw new UnsupportedOperationException("Cannot perform arithmetic on code"); + } + }, + MULTIPLY() { + @Override + public JassValue apply(final BooleanJassValue left, final BooleanJassValue right) { + throw new UnsupportedOperationException("Cannot perform numeric arithmetic on boolean"); + } + + @Override + public JassValue apply(final RealJassValue left, final RealJassValue right) { + return new RealJassValue(left.getValue() * right.getValue()); + } + + @Override + public JassValue apply(final RealJassValue left, final IntegerJassValue right) { + return new RealJassValue(left.getValue() * right.getValue()); + } + + @Override + public JassValue apply(final IntegerJassValue left, final RealJassValue right) { + return new RealJassValue(left.getValue() * right.getValue()); + } + + @Override + public JassValue apply(final IntegerJassValue left, final IntegerJassValue right) { + return new IntegerJassValue(left.getValue() * right.getValue()); + } + + @Override + public JassValue apply(final String left, final String right) { + throw new UnsupportedOperationException("Cannot perform arithmetic on string"); + } + + @Override + public JassValue apply(final HandleJassValue left, final HandleJassValue right) { + throw new UnsupportedOperationException("Cannot perform arithmetic on handle"); + } + + @Override + public JassValue apply(final CodeJassValue left, final CodeJassValue right) { + throw new UnsupportedOperationException("Cannot perform arithmetic on code"); + } + }, + DIVIDE() { + @Override + public JassValue apply(final BooleanJassValue left, final BooleanJassValue right) { + throw new UnsupportedOperationException("Cannot perform numeric arithmetic on boolean"); + } + + @Override + public JassValue apply(final RealJassValue left, final RealJassValue right) { + return new RealJassValue(left.getValue() / right.getValue()); + } + + @Override + public JassValue apply(final RealJassValue left, final IntegerJassValue right) { + return new RealJassValue(left.getValue() / right.getValue()); + } + + @Override + public JassValue apply(final IntegerJassValue left, final RealJassValue right) { + return new RealJassValue(left.getValue() / right.getValue()); + } + + @Override + public JassValue apply(final IntegerJassValue left, final IntegerJassValue right) { + return new IntegerJassValue(left.getValue() / right.getValue()); + } + + @Override + public JassValue apply(final String left, final String right) { + throw new UnsupportedOperationException("Cannot perform arithmetic on string"); + } + + @Override + public JassValue apply(final HandleJassValue left, final HandleJassValue right) { + throw new UnsupportedOperationException("Cannot perform arithmetic on handle"); + } + + @Override + public JassValue apply(final CodeJassValue left, final CodeJassValue right) { + throw new UnsupportedOperationException("Cannot perform arithmetic on code"); + } + }, + OR() { + @Override + public JassValue apply(final BooleanJassValue left, final BooleanJassValue right) { + return BooleanJassValue.of(left.getValue() || right.getValue()); + } + + @Override + public JassValue apply(final RealJassValue left, final RealJassValue right) { + throw new UnsupportedOperationException("Cannot perform boolean arithmetic on number"); + } + + @Override + public JassValue apply(final RealJassValue left, final IntegerJassValue right) { + throw new UnsupportedOperationException("Cannot perform boolean arithmetic on number"); + } + + @Override + public JassValue apply(final IntegerJassValue left, final RealJassValue right) { + throw new UnsupportedOperationException("Cannot perform boolean arithmetic on number"); + } + + @Override + public JassValue apply(final IntegerJassValue left, final IntegerJassValue right) { + throw new UnsupportedOperationException("Cannot perform boolean arithmetic on number"); + } + + @Override + public JassValue apply(final String left, final String right) { + throw new UnsupportedOperationException("Cannot perform boolean arithmetic on string"); + } + + @Override + public JassValue apply(final HandleJassValue left, final HandleJassValue right) { + throw new UnsupportedOperationException("Cannot perform boolean arithmetic on handle"); + } + + @Override + public JassValue apply(final CodeJassValue left, final CodeJassValue right) { + throw new UnsupportedOperationException("Cannot perform boolean arithmetic on code"); + } + }, + AND() { + @Override + public JassValue apply(final BooleanJassValue left, final BooleanJassValue right) { + return BooleanJassValue.of(left.getValue() && right.getValue()); + } + + @Override + public JassValue apply(final RealJassValue left, final RealJassValue right) { + throw new UnsupportedOperationException("Cannot perform boolean arithmetic on number"); + } + + @Override + public JassValue apply(final RealJassValue left, final IntegerJassValue right) { + throw new UnsupportedOperationException("Cannot perform boolean arithmetic on number"); + } + + @Override + public JassValue apply(final IntegerJassValue left, final RealJassValue right) { + throw new UnsupportedOperationException("Cannot perform boolean arithmetic on number"); + } + + @Override + public JassValue apply(final IntegerJassValue left, final IntegerJassValue right) { + throw new UnsupportedOperationException("Cannot perform boolean arithmetic on number"); + } + + @Override + public JassValue apply(final String left, final String right) { + throw new UnsupportedOperationException("Cannot perform boolean arithmetic on string"); + } + + @Override + public JassValue apply(final HandleJassValue left, final HandleJassValue right) { + throw new UnsupportedOperationException("Cannot perform boolean arithmetic on handle"); + } + + @Override + public JassValue apply(final CodeJassValue left, final CodeJassValue right) { + throw new UnsupportedOperationException("Cannot perform boolean arithmetic on code"); + } + }, + EQUALS() { + @Override + public JassValue apply(final BooleanJassValue left, final BooleanJassValue right) { + return BooleanJassValue.of(left.getValue() == right.getValue()); + } + + @Override + public JassValue apply(final RealJassValue left, final RealJassValue right) { + return BooleanJassValue.of(Math.abs(left.getValue() - right.getValue()) <= 0.00001); + } + + @Override + public JassValue apply(final RealJassValue left, final IntegerJassValue right) { + return BooleanJassValue.of(Math.abs(left.getValue() - right.getValue()) <= 0.00001); + } + + @Override + public JassValue apply(final IntegerJassValue left, final RealJassValue right) { + return BooleanJassValue.of(Math.abs(left.getValue() - right.getValue()) <= 0.00001); + } + + @Override + public JassValue apply(final IntegerJassValue left, final IntegerJassValue right) { + return BooleanJassValue.of(Math.abs(left.getValue() - right.getValue()) <= 0.00001); + } + + @Override + public BooleanJassValue apply(final String left, final String right) { + return BooleanJassValue.of(equals(left, right)); + } + + @Override + public BooleanJassValue apply(final HandleJassValue left, final HandleJassValue right) { + return BooleanJassValue.of(equals(left, right)); + } + + @Override + public BooleanJassValue apply(final CodeJassValue left, final CodeJassValue right) { + return BooleanJassValue.of(equals(left, right)); + } + }, + NOT_EQUALS() { + @Override + public JassValue apply(final BooleanJassValue left, final BooleanJassValue right) { + return BooleanJassValue.of(left.getValue() != right.getValue()); + } + + @Override + public JassValue apply(final RealJassValue left, final RealJassValue right) { + return BooleanJassValue.of(left.getValue() != right.getValue()); + } + + @Override + public JassValue apply(final RealJassValue left, final IntegerJassValue right) { + return BooleanJassValue.of(left.getValue() != right.getValue()); + } + + @Override + public JassValue apply(final IntegerJassValue left, final RealJassValue right) { + return BooleanJassValue.of(left.getValue() != right.getValue()); + } + + @Override + public JassValue apply(final IntegerJassValue left, final IntegerJassValue right) { + return BooleanJassValue.of(left.getValue() != right.getValue()); + } + + @Override + public JassValue apply(final String left, final String right) { + return BooleanJassValue.of(!equals(left, right)); + } + + @Override + public JassValue apply(final HandleJassValue left, final HandleJassValue right) { + return BooleanJassValue.of(!equals(left, right)); + } + + @Override + public BooleanJassValue apply(final CodeJassValue left, final CodeJassValue right) { + return BooleanJassValue.of(!equals(left, right)); + } + }, + LESS() { + @Override + public JassValue apply(final BooleanJassValue left, final BooleanJassValue right) { + throw new UnsupportedOperationException("Invalid type for specified operator"); + } + + @Override + public JassValue apply(final RealJassValue left, final RealJassValue right) { + return BooleanJassValue.of(left.getValue() < right.getValue()); + } + + @Override + public JassValue apply(final RealJassValue left, final IntegerJassValue right) { + return BooleanJassValue.of(left.getValue() < right.getValue()); + } + + @Override + public JassValue apply(final IntegerJassValue left, final RealJassValue right) { + return BooleanJassValue.of(left.getValue() < right.getValue()); + } + + @Override + public JassValue apply(final IntegerJassValue left, final IntegerJassValue right) { + return BooleanJassValue.of(left.getValue() < right.getValue()); + } + + @Override + public JassValue apply(final String left, final String right) { + throw new UnsupportedOperationException("Invalid type for specified operator"); + } + + @Override + public JassValue apply(final HandleJassValue left, final HandleJassValue right) { + throw new UnsupportedOperationException("Invalid type for specified operator"); + } + + @Override + public BooleanJassValue apply(final CodeJassValue left, final CodeJassValue right) { + throw new UnsupportedOperationException("Invalid type for specified operator"); + } + }, + LESS_OR_EQUALS() { + @Override + public JassValue apply(final BooleanJassValue left, final BooleanJassValue right) { + throw new UnsupportedOperationException("Invalid type for specified operator"); + } + + @Override + public JassValue apply(final RealJassValue left, final RealJassValue right) { + return BooleanJassValue.of(left.getValue() <= right.getValue()); + } + + @Override + public JassValue apply(final RealJassValue left, final IntegerJassValue right) { + return BooleanJassValue.of(left.getValue() <= right.getValue()); + } + + @Override + public JassValue apply(final IntegerJassValue left, final RealJassValue right) { + return BooleanJassValue.of(left.getValue() <= right.getValue()); + } + + @Override + public JassValue apply(final IntegerJassValue left, final IntegerJassValue right) { + return BooleanJassValue.of(left.getValue() <= right.getValue()); + } + + @Override + public JassValue apply(final String left, final String right) { + throw new UnsupportedOperationException("Invalid type for specified operator"); + } + + @Override + public JassValue apply(final HandleJassValue left, final HandleJassValue right) { + throw new UnsupportedOperationException("Invalid type for specified operator"); + } + + @Override + public BooleanJassValue apply(final CodeJassValue left, final CodeJassValue right) { + throw new UnsupportedOperationException("Invalid type for specified operator"); + } + }, + GREATER() { + @Override + public JassValue apply(final BooleanJassValue left, final BooleanJassValue right) { + throw new UnsupportedOperationException("Invalid type for specified operator"); + } + + @Override + public JassValue apply(final RealJassValue left, final RealJassValue right) { + return BooleanJassValue.of(left.getValue() > right.getValue()); + } + + @Override + public JassValue apply(final RealJassValue left, final IntegerJassValue right) { + return BooleanJassValue.of(left.getValue() > right.getValue()); + } + + @Override + public JassValue apply(final IntegerJassValue left, final RealJassValue right) { + return BooleanJassValue.of(left.getValue() > right.getValue()); + } + + @Override + public JassValue apply(final IntegerJassValue left, final IntegerJassValue right) { + return BooleanJassValue.of(left.getValue() > right.getValue()); + } + + @Override + public JassValue apply(final String left, final String right) { + throw new UnsupportedOperationException("Invalid type for specified operator"); + } + + @Override + public JassValue apply(final HandleJassValue left, final HandleJassValue right) { + throw new UnsupportedOperationException("Invalid type for specified operator"); + } + + @Override + public BooleanJassValue apply(final CodeJassValue left, final CodeJassValue right) { + throw new UnsupportedOperationException("Invalid type for specified operator"); + } + }, + GREATER_OR_EQUALS() { + @Override + public JassValue apply(final BooleanJassValue left, final BooleanJassValue right) { + throw new UnsupportedOperationException("Invalid type for specified operator"); + } + + @Override + public JassValue apply(final RealJassValue left, final RealJassValue right) { + return BooleanJassValue.of(left.getValue() >= right.getValue()); + } + + @Override + public JassValue apply(final RealJassValue left, final IntegerJassValue right) { + return BooleanJassValue.of(left.getValue() >= right.getValue()); + } + + @Override + public JassValue apply(final IntegerJassValue left, final RealJassValue right) { + return BooleanJassValue.of(left.getValue() >= right.getValue()); + } + + @Override + public JassValue apply(final IntegerJassValue left, final IntegerJassValue right) { + return BooleanJassValue.of(left.getValue() >= right.getValue()); + } + + @Override + public JassValue apply(final String left, final String right) { + throw new UnsupportedOperationException("Invalid type for specified operator"); + } + + @Override + public JassValue apply(final HandleJassValue left, final HandleJassValue right) { + throw new UnsupportedOperationException("Invalid type for specified operator"); + } + + @Override + public BooleanJassValue apply(final CodeJassValue left, final CodeJassValue right) { + throw new UnsupportedOperationException("Invalid type for specified operator"); + } + }; + + private static boolean equals(final String left, final String right) { + boolean equals; + if (left == null) { + if (right == null) { + equals = true; + } + else { + equals = false; + } + } + else { + equals = left.equals(right); + } + return equals; + } + + private static boolean equals(final HandleJassValue left, final HandleJassValue right) { + return (left.getJavaValue() == right.getJavaValue()) && (left.getType() == right.getType()); + } + + private static boolean equals(final CodeJassValue left, final CodeJassValue right) { + return (left.getValue() == right.getValue()); + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/FunctionCallJassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/FunctionCallJassExpression.java index 7d87b6a..3c07ca9 100644 --- a/jassparser/src/com/etheller/interpreter/ast/expression/FunctionCallJassExpression.java +++ b/jassparser/src/com/etheller/interpreter/ast/expression/FunctionCallJassExpression.java @@ -30,7 +30,12 @@ public class FunctionCallJassExpression implements JassExpression { final JassValue evaluatedExpression = expr.evaluate(globalScope, localScope, triggerScope); evaluatedExpressions.add(evaluatedExpression); } - return functionByName.call(evaluatedExpressions, globalScope, triggerScope); + try { + return functionByName.call(evaluatedExpressions, globalScope, triggerScope); + } + catch (final Exception exc) { + throw new RuntimeException("Function call by name failed for name: " + this.functionName, exc); + } } } diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/NegateJassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/NegateJassExpression.java new file mode 100644 index 0000000..57e4845 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/expression/NegateJassExpression.java @@ -0,0 +1,22 @@ +package com.etheller.interpreter.ast.expression; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.visitor.NegateJassValueVisitor; + +public class NegateJassExpression implements JassExpression { + private final JassExpression expression; + + public NegateJassExpression(final JassExpression expression) { + this.expression = expression; + } + + @Override + public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { + return this.expression.evaluate(globalScope, localScope, triggerScope) + .visit(NegateJassValueVisitor.getInstance()); + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/function/JassParameter.java b/jassparser/src/com/etheller/interpreter/ast/function/JassParameter.java index f9c460c..553acf2 100644 --- a/jassparser/src/com/etheller/interpreter/ast/function/JassParameter.java +++ b/jassparser/src/com/etheller/interpreter/ast/function/JassParameter.java @@ -22,6 +22,9 @@ public class JassParameter { } public boolean matchesType(final JassValue value) { + if (value == null) { + return this.type.isNullable(); + } final JassType valueType = value.visit(JassTypeGettingValueVisitor.getInstance()); return this.type.isAssignableFrom(valueType); } diff --git a/jassparser/src/com/etheller/interpreter/ast/function/NativeJassFunction.java b/jassparser/src/com/etheller/interpreter/ast/function/NativeJassFunction.java index 1cfbf0d..91c900f 100644 --- a/jassparser/src/com/etheller/interpreter/ast/function/NativeJassFunction.java +++ b/jassparser/src/com/etheller/interpreter/ast/function/NativeJassFunction.java @@ -22,6 +22,13 @@ public class NativeJassFunction extends AbstractJassFunction { @Override protected JassValue innerCall(final List arguments, final GlobalScope globalScope, final TriggerExecutionScope triggerScope, final LocalScope localScope) { + if (this.implementation == null) { + System.err.println( + "Call to native function that was declared but had no native implementation: " + this.name); + return null; +// throw new UnsupportedOperationException( +// "Call to native function that was declared but had no native implementation: " + this.name); + } return this.implementation.call(arguments, globalScope, triggerScope); } } diff --git a/jassparser/src/com/etheller/interpreter/ast/function/UserJassFunction.java b/jassparser/src/com/etheller/interpreter/ast/function/UserJassFunction.java index 92dff1f..8cac6a3 100644 --- a/jassparser/src/com/etheller/interpreter/ast/function/UserJassFunction.java +++ b/jassparser/src/com/etheller/interpreter/ast/function/UserJassFunction.java @@ -5,6 +5,7 @@ import java.util.List; import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.LocalScope; import com.etheller.interpreter.ast.scope.TriggerExecutionScope; +import com.etheller.interpreter.ast.statement.JassReturnNothingStatement; import com.etheller.interpreter.ast.statement.JassStatement; import com.etheller.interpreter.ast.value.JassType; import com.etheller.interpreter.ast.value.JassValue; @@ -32,7 +33,13 @@ public final class UserJassFunction extends AbstractJassFunction { final JassValue returnValue = statement.execute(globalScope, localScope, triggerScope); if (returnValue != null) { if (returnValue.visit(JassTypeGettingValueVisitor.getInstance()) != this.returnType) { - throw new RuntimeException("Invalid return type"); + if ((this.returnType == JassType.NOTHING) + && (returnValue == JassReturnNothingStatement.RETURN_NOTHING_NOTICE)) { + return null; + } + else { + throw new RuntimeException("Invalid return type"); + } } return returnValue; } diff --git a/jassparser/src/com/etheller/interpreter/ast/scope/GlobalScope.java b/jassparser/src/com/etheller/interpreter/ast/scope/GlobalScope.java index fa7cf07..7063203 100644 --- a/jassparser/src/com/etheller/interpreter/ast/scope/GlobalScope.java +++ b/jassparser/src/com/etheller/interpreter/ast/scope/GlobalScope.java @@ -69,7 +69,12 @@ public final class GlobalScope { public void createGlobal(final String name, final JassType type, final JassValue value) { final GlobalScopeAssignable assignable = new GlobalScopeAssignable(type, this); - assignable.setValue(value); + try { + assignable.setValue(value); + } + catch (final Exception exc) { + throw new RuntimeException("Global initialization failed for name: " + name, exc); + } this.globals.put(name, assignable); } diff --git a/jassparser/src/com/etheller/interpreter/ast/scope/trigger/Trigger.java b/jassparser/src/com/etheller/interpreter/ast/scope/trigger/Trigger.java index aca40c4..81c103a 100644 --- a/jassparser/src/com/etheller/interpreter/ast/scope/trigger/Trigger.java +++ b/jassparser/src/com/etheller/interpreter/ast/scope/trigger/Trigger.java @@ -56,6 +56,9 @@ public class Trigger { } public void execute(final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { + if (!this.enabled) { + return; + } for (final JassFunction action : this.actions) { action.call(Collections.emptyList(), globalScope, triggerScope); } diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassExitWhenStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassExitWhenStatement.java new file mode 100644 index 0000000..f6fdd0e --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassExitWhenStatement.java @@ -0,0 +1,32 @@ +package com.etheller.interpreter.ast.statement; + +import com.etheller.interpreter.ast.expression.JassExpression; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.StringJassValue; +import com.etheller.interpreter.ast.value.visitor.BooleanJassValueVisitor; + +public class JassExitWhenStatement implements JassStatement { + public static final StringJassValue LOOP_EXIT_NOTICE = new StringJassValue("EXIT"); + private final int lineNo; + private final JassExpression expression; + + public JassExitWhenStatement(final int lineNo, final JassExpression expression) { + this.lineNo = lineNo; + this.expression = expression; + } + + @Override + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { + globalScope.setLineNumber(this.lineNo); + if (this.expression.evaluate(globalScope, localScope, triggerScope) + .visit(BooleanJassValueVisitor.getInstance())) { + return LOOP_EXIT_NOTICE; + } + return null; + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassLocalDefinitionStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassLocalDefinitionStatement.java new file mode 100644 index 0000000..2b96f48 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassLocalDefinitionStatement.java @@ -0,0 +1,33 @@ +package com.etheller.interpreter.ast.statement; + +import com.etheller.interpreter.ast.expression.JassExpression; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; +import com.etheller.interpreter.ast.value.JassType; +import com.etheller.interpreter.ast.value.JassValue; + +public class JassLocalDefinitionStatement implements JassStatement { + private final String identifier; + private final JassExpression expression; + private final int lineNo; + private final JassType type; + + public JassLocalDefinitionStatement(final int lineNo, final String identifier, final JassType type, + final JassExpression expression) { + this.lineNo = lineNo; + this.identifier = identifier; + this.type = type; + this.expression = expression; + } + + @Override + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { + globalScope.setLineNumber(this.lineNo); + localScope.createLocal(this.identifier, this.type, + this.expression.evaluate(globalScope, localScope, triggerScope)); + return null; + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassLocalStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassLocalStatement.java new file mode 100644 index 0000000..448aa27 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassLocalStatement.java @@ -0,0 +1,28 @@ +package com.etheller.interpreter.ast.statement; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; +import com.etheller.interpreter.ast.value.JassType; +import com.etheller.interpreter.ast.value.JassValue; + +public class JassLocalStatement implements JassStatement { + private final String identifier; + private final int lineNo; + private final JassType type; + + public JassLocalStatement(final int lineNo, final String identifier, final JassType type) { + this.lineNo = lineNo; + this.identifier = identifier; + this.type = type; + } + + @Override + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { + globalScope.setLineNumber(this.lineNo); + localScope.createLocal(this.identifier, this.type); + return null; + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassLoopStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassLoopStatement.java new file mode 100644 index 0000000..a402d86 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassLoopStatement.java @@ -0,0 +1,38 @@ +package com.etheller.interpreter.ast.statement; + +import java.util.List; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; +import com.etheller.interpreter.ast.value.JassValue; + +public class JassLoopStatement implements JassStatement { + private final int lineNo; + private final List statements; + + public JassLoopStatement(final int lineNo, final List statements) { + this.lineNo = lineNo; + this.statements = statements; + } + + @Override + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { + globalScope.setLineNumber(this.lineNo); + while (true) { + for (final JassStatement statement : this.statements) { + final JassValue returnValue = statement.execute(globalScope, localScope, triggerScope); + if (returnValue != null) { + if (returnValue == JassExitWhenStatement.LOOP_EXIT_NOTICE) { + return null; + } + else { + return returnValue; + } + } + } + } + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassReturnNothingStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassReturnNothingStatement.java new file mode 100644 index 0000000..ee96f44 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassReturnNothingStatement.java @@ -0,0 +1,24 @@ +package com.etheller.interpreter.ast.statement; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.StringJassValue; + +public class JassReturnNothingStatement implements JassStatement { + public static final StringJassValue RETURN_NOTHING_NOTICE = new StringJassValue("nothing"); + private final int lineNo; + + public JassReturnNothingStatement(final int lineNo) { + this.lineNo = lineNo; + } + + @Override + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { + globalScope.setLineNumber(this.lineNo); + return RETURN_NOTHING_NOTICE; + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/ArrayJassType.java b/jassparser/src/com/etheller/interpreter/ast/value/ArrayJassType.java index 109a559..c951be3 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/ArrayJassType.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/ArrayJassType.java @@ -27,4 +27,14 @@ public class ArrayJassType implements JassType { public TYPE visit(final JassTypeVisitor visitor) { return visitor.accept(this); } + + @Override + public boolean isNullable() { + return false; + } + + @Override + public JassValue getNullValue() { + return null; + } } diff --git a/jassparser/src/com/etheller/interpreter/ast/value/ArrayJassValue.java b/jassparser/src/com/etheller/interpreter/ast/value/ArrayJassValue.java index 158d8d3..2a33879 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/ArrayJassValue.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/ArrayJassValue.java @@ -15,20 +15,29 @@ public class ArrayJassValue implements JassValue { return visitor.accept(this); } - public void set(final int index, final JassValue value) { - if (value.visit(JassTypeGettingValueVisitor.getInstance()) != type.getPrimitiveType()) { - throw new IllegalStateException( - "Illegal type for assignment to " + type.getPrimitiveType().getName() + " array"); + public void set(final int index, JassValue value) { + final JassType primitiveType = this.type.getPrimitiveType(); + if (value == null) { + if (primitiveType.isNullable()) { + value = primitiveType.getNullValue(); + } + else { + throw new IllegalStateException( + "Attempted to set " + this.type.getName() + " to null in array at index " + index + "!"); + } } - data[index] = value; + if (value.visit(JassTypeGettingValueVisitor.getInstance()) != primitiveType) { + throw new IllegalStateException("Illegal type for assignment to " + primitiveType.getName() + " array"); + } + this.data[index] = value; } public JassValue get(final int index) { - return data[index]; + return this.data[index]; } public ArrayJassType getType() { - return type; + return this.type; } } diff --git a/jassparser/src/com/etheller/interpreter/ast/value/BooleanJassValue.java b/jassparser/src/com/etheller/interpreter/ast/value/BooleanJassValue.java index 602a8f5..70ba770 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/BooleanJassValue.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/BooleanJassValue.java @@ -19,6 +19,11 @@ public class BooleanJassValue implements JassValue { return visitor.accept(this); } + @Override + public String toString() { + return Boolean.toString(this.value); + } + public static BooleanJassValue inverse(final BooleanJassValue value) { if (value.value) { return FALSE; diff --git a/jassparser/src/com/etheller/interpreter/ast/value/HandleJassType.java b/jassparser/src/com/etheller/interpreter/ast/value/HandleJassType.java index bd025fb..60dae19 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/HandleJassType.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/HandleJassType.java @@ -40,4 +40,14 @@ public class HandleJassType implements JassType { return visitor.accept(this); } + @Override + public boolean isNullable() { + return true; + } + + @Override + public HandleJassValue getNullValue() { + return new HandleJassValue(this, null); + } + } diff --git a/jassparser/src/com/etheller/interpreter/ast/value/IntegerJassValue.java b/jassparser/src/com/etheller/interpreter/ast/value/IntegerJassValue.java index bec6cfe..e8953c8 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/IntegerJassValue.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/IntegerJassValue.java @@ -8,11 +8,16 @@ public class IntegerJassValue implements JassValue { } public int getValue() { - return value; + return this.value; } @Override public TYPE visit(final JassValueVisitor visitor) { return visitor.accept(this); } + + @Override + public String toString() { + return Integer.toString(this.value); + } } diff --git a/jassparser/src/com/etheller/interpreter/ast/value/JassType.java b/jassparser/src/com/etheller/interpreter/ast/value/JassType.java index c72141e..67b6991 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/JassType.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/JassType.java @@ -7,8 +7,12 @@ public interface JassType { boolean isAssignableFrom(JassType value); + boolean isNullable(); + + JassValue getNullValue(); + public static final PrimitiveJassType INTEGER = new PrimitiveJassType("integer"); - public static final PrimitiveJassType STRING = new PrimitiveJassType("string"); + public static final PrimitiveJassType STRING = new StringJassType("string"); public static final PrimitiveJassType CODE = new PrimitiveJassType("code"); public static final PrimitiveJassType REAL = new RealJassType("real"); public static final PrimitiveJassType BOOLEAN = new PrimitiveJassType("boolean"); diff --git a/jassparser/src/com/etheller/interpreter/ast/value/PrimitiveJassType.java b/jassparser/src/com/etheller/interpreter/ast/value/PrimitiveJassType.java index 38bd250..8a71312 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/PrimitiveJassType.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/PrimitiveJassType.java @@ -12,6 +12,11 @@ public class PrimitiveJassType implements JassType { return value == this; } + @Override + public boolean isNullable() { + return false; + } + @Override public String getName() { return this.name; @@ -22,4 +27,9 @@ public class PrimitiveJassType implements JassType { return visitor.accept(this); } + @Override + public JassValue getNullValue() { + return null; + } + } diff --git a/jassparser/src/com/etheller/interpreter/ast/value/RealJassValue.java b/jassparser/src/com/etheller/interpreter/ast/value/RealJassValue.java index 49636dc..5340fbb 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/RealJassValue.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/RealJassValue.java @@ -8,11 +8,16 @@ public class RealJassValue implements JassValue { } public double getValue() { - return value; + return this.value; } @Override public TYPE visit(final JassValueVisitor visitor) { return visitor.accept(this); } + + @Override + public String toString() { + return Double.toString(this.value); + } } diff --git a/jassparser/src/com/etheller/interpreter/ast/value/StringJassType.java b/jassparser/src/com/etheller/interpreter/ast/value/StringJassType.java new file mode 100644 index 0000000..022af8a --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/StringJassType.java @@ -0,0 +1,18 @@ +package com.etheller.interpreter.ast.value; + +public class StringJassType extends PrimitiveJassType { + + public StringJassType(final String name) { + super(name); + } + + @Override + public boolean isNullable() { + return true; + } + + @Override + public JassValue getNullValue() { + return new StringJassValue(null); + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticJassValueVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticJassValueVisitor.java new file mode 100644 index 0000000..1e100ac --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticJassValueVisitor.java @@ -0,0 +1,72 @@ +package com.etheller.interpreter.ast.value.visitor; + +import com.etheller.interpreter.ast.expression.ArithmeticSign; +import com.etheller.interpreter.ast.value.ArrayJassValue; +import com.etheller.interpreter.ast.value.BooleanJassValue; +import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.HandleJassType; +import com.etheller.interpreter.ast.value.HandleJassValue; +import com.etheller.interpreter.ast.value.IntegerJassValue; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.JassValueVisitor; +import com.etheller.interpreter.ast.value.RealJassValue; +import com.etheller.interpreter.ast.value.StringJassValue; + +public class ArithmeticJassValueVisitor implements JassValueVisitor { + public static final ArithmeticJassValueVisitor INSTANCE = new ArithmeticJassValueVisitor(); + private JassValue rightHand; + private ArithmeticSign sign; + + public ArithmeticJassValueVisitor reset(final JassValue rightHand, final ArithmeticSign sign) { + this.rightHand = rightHand; + this.sign = sign; + return this; + } + + @Override + public JassValue accept(final BooleanJassValue value) { + return this.rightHand.visit(ArithmeticLeftHandBooleanJassValueVisitor.INSTANCE.reset(value, this.sign)); + } + + @Override + public JassValue accept(final IntegerJassValue value) { + return this.rightHand.visit(ArithmeticLeftHandIntegerJassValueVisitor.INSTANCE.reset(value, this.sign)); + } + + @Override + public JassValue accept(final RealJassValue value) { + return this.rightHand.visit(ArithmeticLeftHandRealJassValueVisitor.INSTANCE.reset(value, this.sign)); + } + + @Override + public JassValue accept(final StringJassValue value) { + if (this.rightHand == null) { + return this.sign.apply(value.getValue(), null); + } + return this.rightHand.visit(ArithmeticLeftHandStringJassValueVisitor.INSTANCE.reset(value, this.sign)); + } + + @Override + public JassValue accept(final CodeJassValue value) { + throw new UnsupportedOperationException("Cannot perform arithmetic on code"); + } + + @Override + public JassValue accept(final ArrayJassValue value) { + throw new UnsupportedOperationException("Cannot perform arithmetic on array"); + } + + @Override + public JassValue accept(final HandleJassValue value) { + final HandleJassType leftHandType = value.getType(); + if (!leftHandType.isNullable()) { + throw new UnsupportedOperationException("Cannot operate on null for type: " + leftHandType.getName()); + } + // TODO would be nice not to have to call getNullValue here... + if (this.rightHand == null) { + return this.sign.apply(value, leftHandType.getNullValue()); + } + return this.rightHand.visit(ArithmeticLeftHandHandleJassValueVisitor.INSTANCE.reset(value, this.sign)); + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandBooleanJassValueVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandBooleanJassValueVisitor.java new file mode 100644 index 0000000..959462c --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandBooleanJassValueVisitor.java @@ -0,0 +1,64 @@ +package com.etheller.interpreter.ast.value.visitor; + +import com.etheller.interpreter.ast.expression.ArithmeticSign; +import com.etheller.interpreter.ast.value.ArrayJassValue; +import com.etheller.interpreter.ast.value.BooleanJassValue; +import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.HandleJassValue; +import com.etheller.interpreter.ast.value.IntegerJassValue; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.JassValueVisitor; +import com.etheller.interpreter.ast.value.RealJassValue; +import com.etheller.interpreter.ast.value.StringJassValue; + +public class ArithmeticLeftHandBooleanJassValueVisitor implements JassValueVisitor { + public static final ArithmeticLeftHandBooleanJassValueVisitor INSTANCE = new ArithmeticLeftHandBooleanJassValueVisitor(); + + private BooleanJassValue leftHand; + private ArithmeticSign sign; + + public ArithmeticLeftHandBooleanJassValueVisitor reset(final BooleanJassValue leftHand, final ArithmeticSign sign) { + this.leftHand = leftHand; + this.sign = sign; + return this; + } + + @Override + public JassValue accept(final BooleanJassValue value) { + return this.sign.apply(this.leftHand, value); + } + + @Override + public JassValue accept(final IntegerJassValue value) { + throw new UnsupportedOperationException("Cannot perform boolean arithmetic on integer"); + } + + @Override + public JassValue accept(final RealJassValue value) { + throw new UnsupportedOperationException("Cannot perform boolean arithmetic on real"); + } + + @Override + public JassValue accept(final StringJassValue value) { + throw new UnsupportedOperationException("Cannot perform arithmetic on string"); + // uncomment the below if you decide you build a mod where I2S is no longer + // necessary, probably: +// return new StringJassValue(this.leftHand.toString() + value.getValue()); + } + + @Override + public JassValue accept(final CodeJassValue value) { + throw new UnsupportedOperationException("Cannot perform arithmetic on code"); + } + + @Override + public JassValue accept(final ArrayJassValue value) { + throw new UnsupportedOperationException("Cannot perform arithmetic on array"); + } + + @Override + public JassValue accept(final HandleJassValue value) { + throw new UnsupportedOperationException("Cannot perform arithmetic on handle"); + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandHandleJassValueVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandHandleJassValueVisitor.java new file mode 100644 index 0000000..c156fb5 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandHandleJassValueVisitor.java @@ -0,0 +1,60 @@ +package com.etheller.interpreter.ast.value.visitor; + +import com.etheller.interpreter.ast.expression.ArithmeticSign; +import com.etheller.interpreter.ast.value.ArrayJassValue; +import com.etheller.interpreter.ast.value.BooleanJassValue; +import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.HandleJassValue; +import com.etheller.interpreter.ast.value.IntegerJassValue; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.JassValueVisitor; +import com.etheller.interpreter.ast.value.RealJassValue; +import com.etheller.interpreter.ast.value.StringJassValue; + +public class ArithmeticLeftHandHandleJassValueVisitor implements JassValueVisitor { + public static final ArithmeticLeftHandHandleJassValueVisitor INSTANCE = new ArithmeticLeftHandHandleJassValueVisitor(); + private HandleJassValue leftHand; + private ArithmeticSign sign; + + public ArithmeticLeftHandHandleJassValueVisitor reset(final HandleJassValue leftHand, final ArithmeticSign sign) { + this.leftHand = leftHand; + this.sign = sign; + return this; + } + + @Override + public JassValue accept(final BooleanJassValue value) { + throw new UnsupportedOperationException("Cannot perform handle comparison on boolean"); + } + + @Override + public JassValue accept(final IntegerJassValue value) { + throw new UnsupportedOperationException("Cannot perform handle comparison on integer"); + } + + @Override + public JassValue accept(final RealJassValue value) { + throw new UnsupportedOperationException("Cannot perform handle comparison on real"); + } + + @Override + public JassValue accept(final StringJassValue value) { + throw new UnsupportedOperationException("Cannot perform handle comparison on string"); + } + + @Override + public JassValue accept(final CodeJassValue value) { + throw new UnsupportedOperationException("Cannot perform handle comparison on code"); + } + + @Override + public JassValue accept(final ArrayJassValue value) { + throw new UnsupportedOperationException("Cannot perform handle comparison on array"); + } + + @Override + public JassValue accept(final HandleJassValue value) { + return this.sign.apply(this.leftHand, value); + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandIntegerJassValueVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandIntegerJassValueVisitor.java new file mode 100644 index 0000000..b1ad616 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandIntegerJassValueVisitor.java @@ -0,0 +1,63 @@ +package com.etheller.interpreter.ast.value.visitor; + +import com.etheller.interpreter.ast.expression.ArithmeticSign; +import com.etheller.interpreter.ast.value.ArrayJassValue; +import com.etheller.interpreter.ast.value.BooleanJassValue; +import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.HandleJassValue; +import com.etheller.interpreter.ast.value.IntegerJassValue; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.JassValueVisitor; +import com.etheller.interpreter.ast.value.RealJassValue; +import com.etheller.interpreter.ast.value.StringJassValue; + +public class ArithmeticLeftHandIntegerJassValueVisitor implements JassValueVisitor { + public static final ArithmeticLeftHandIntegerJassValueVisitor INSTANCE = new ArithmeticLeftHandIntegerJassValueVisitor(); + private IntegerJassValue leftHand; + private ArithmeticSign sign; + + public ArithmeticLeftHandIntegerJassValueVisitor reset(final IntegerJassValue leftHand, final ArithmeticSign sign) { + this.leftHand = leftHand; + this.sign = sign; + return this; + } + + @Override + public JassValue accept(final BooleanJassValue value) { + throw new UnsupportedOperationException("Cannot perform integer arithmetic on boolean"); + } + + @Override + public JassValue accept(final IntegerJassValue value) { + return this.sign.apply(this.leftHand, value); + } + + @Override + public JassValue accept(final RealJassValue value) { + return this.sign.apply(this.leftHand, value); + } + + @Override + public JassValue accept(final StringJassValue value) { + throw new UnsupportedOperationException("Cannot perform arithmetic on string (Did you mean to use I2S?)"); + // uncomment the below if you decide you build a mod where I2S is no longer + // necessary, probably: +// return new StringJassValue(this.leftHand.toString() + value.getValue()); + } + + @Override + public JassValue accept(final CodeJassValue value) { + throw new UnsupportedOperationException("Cannot perform arithmetic on code"); + } + + @Override + public JassValue accept(final ArrayJassValue value) { + throw new UnsupportedOperationException("Cannot perform arithmetic on array"); + } + + @Override + public JassValue accept(final HandleJassValue value) { + throw new UnsupportedOperationException("Cannot perform arithmetic on handle"); + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandNullJassValueVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandNullJassValueVisitor.java new file mode 100644 index 0000000..bb80887 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandNullJassValueVisitor.java @@ -0,0 +1,64 @@ +package com.etheller.interpreter.ast.value.visitor; + +import com.etheller.interpreter.ast.expression.ArithmeticSign; +import com.etheller.interpreter.ast.value.ArrayJassValue; +import com.etheller.interpreter.ast.value.BooleanJassValue; +import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.HandleJassType; +import com.etheller.interpreter.ast.value.HandleJassValue; +import com.etheller.interpreter.ast.value.IntegerJassValue; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.JassValueVisitor; +import com.etheller.interpreter.ast.value.RealJassValue; +import com.etheller.interpreter.ast.value.StringJassValue; + +public class ArithmeticLeftHandNullJassValueVisitor implements JassValueVisitor { + public static final ArithmeticLeftHandNullJassValueVisitor INSTANCE = new ArithmeticLeftHandNullJassValueVisitor(); + private ArithmeticSign sign; + + public ArithmeticLeftHandNullJassValueVisitor reset(final ArithmeticSign sign) { + this.sign = sign; + return this; + } + + @Override + public JassValue accept(final BooleanJassValue value) { + throw new UnsupportedOperationException("Invalid binary operation: null and boolean"); + } + + @Override + public JassValue accept(final IntegerJassValue value) { + throw new UnsupportedOperationException("Invalid binary operation: null and integer"); + } + + @Override + public JassValue accept(final RealJassValue value) { + throw new UnsupportedOperationException("Invalid binary operation: null and real"); + } + + @Override + public JassValue accept(final StringJassValue value) { + return this.sign.apply(null, value.getValue()); + } + + @Override + public JassValue accept(final CodeJassValue value) { + throw new UnsupportedOperationException("Invalid binary operation: null and code"); + } + + @Override + public JassValue accept(final ArrayJassValue value) { + throw new UnsupportedOperationException("Invalid binary operation: null and array"); + } + + @Override + public JassValue accept(final HandleJassValue value) { + final HandleJassType rightHandType = value.getType(); + if (!rightHandType.isNullable()) { + throw new UnsupportedOperationException("Cannot operate on null for type: " + rightHandType.getName()); + } + // TODO would be nice not to have to call getNullValue here... + return this.sign.apply(rightHandType.getNullValue(), value); + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandRealJassValueVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandRealJassValueVisitor.java new file mode 100644 index 0000000..5ea569b --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandRealJassValueVisitor.java @@ -0,0 +1,63 @@ +package com.etheller.interpreter.ast.value.visitor; + +import com.etheller.interpreter.ast.expression.ArithmeticSign; +import com.etheller.interpreter.ast.value.ArrayJassValue; +import com.etheller.interpreter.ast.value.BooleanJassValue; +import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.HandleJassValue; +import com.etheller.interpreter.ast.value.IntegerJassValue; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.JassValueVisitor; +import com.etheller.interpreter.ast.value.RealJassValue; +import com.etheller.interpreter.ast.value.StringJassValue; + +public class ArithmeticLeftHandRealJassValueVisitor implements JassValueVisitor { + public static final ArithmeticLeftHandRealJassValueVisitor INSTANCE = new ArithmeticLeftHandRealJassValueVisitor(); + private RealJassValue leftHand; + private ArithmeticSign sign; + + public ArithmeticLeftHandRealJassValueVisitor reset(final RealJassValue leftHand, final ArithmeticSign sign) { + this.leftHand = leftHand; + this.sign = sign; + return this; + } + + @Override + public JassValue accept(final BooleanJassValue value) { + throw new UnsupportedOperationException("Cannot perform integer arithmetic on boolean"); + } + + @Override + public JassValue accept(final IntegerJassValue value) { + return this.sign.apply(this.leftHand, value); + } + + @Override + public JassValue accept(final RealJassValue value) { + return this.sign.apply(this.leftHand, value); + } + + @Override + public JassValue accept(final StringJassValue value) { + throw new UnsupportedOperationException("Cannot perform arithmetic on string (Did you mean to use I2R?)"); + // uncomment the below if you decide you build a mod where I2S is no longer + // necessary, probably: +// return new StringJassValue(this.leftHand.toString() + value.getValue()); + } + + @Override + public JassValue accept(final CodeJassValue value) { + throw new UnsupportedOperationException("Cannot perform arithmetic on code"); + } + + @Override + public JassValue accept(final ArrayJassValue value) { + throw new UnsupportedOperationException("Cannot perform arithmetic on array"); + } + + @Override + public JassValue accept(final HandleJassValue value) { + throw new UnsupportedOperationException("Cannot perform arithmetic on handle"); + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandStringJassValueVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandStringJassValueVisitor.java new file mode 100644 index 0000000..b642a00 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArithmeticLeftHandStringJassValueVisitor.java @@ -0,0 +1,61 @@ +package com.etheller.interpreter.ast.value.visitor; + +import com.etheller.interpreter.ast.expression.ArithmeticSign; +import com.etheller.interpreter.ast.value.ArrayJassValue; +import com.etheller.interpreter.ast.value.BooleanJassValue; +import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.HandleJassValue; +import com.etheller.interpreter.ast.value.IntegerJassValue; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.JassValueVisitor; +import com.etheller.interpreter.ast.value.RealJassValue; +import com.etheller.interpreter.ast.value.StringJassValue; + +public class ArithmeticLeftHandStringJassValueVisitor implements JassValueVisitor { + public static final ArithmeticLeftHandStringJassValueVisitor INSTANCE = new ArithmeticLeftHandStringJassValueVisitor(); + private StringJassValue leftHand; + private ArithmeticSign sign; + + public ArithmeticLeftHandStringJassValueVisitor reset(final StringJassValue leftHand, final ArithmeticSign sign) { + this.leftHand = leftHand; + this.sign = sign; + return this; + } + + @Override + public JassValue accept(final BooleanJassValue value) { + throw new UnsupportedOperationException("Cannot perform string operation on boolean"); + } + + @Override + public JassValue accept(final IntegerJassValue value) { + throw new UnsupportedOperationException( + "Cannot perform string operation on integer (Did you mean to use I2S?)"); + } + + @Override + public JassValue accept(final RealJassValue value) { + throw new UnsupportedOperationException("Cannot perform string operation on real (Did you mean to use I2R?)"); + } + + @Override + public JassValue accept(final StringJassValue value) { + return this.sign.apply(this.leftHand.getValue(), value.getValue()); + } + + @Override + public JassValue accept(final CodeJassValue value) { + throw new UnsupportedOperationException("Cannot perform string operation on code"); + } + + @Override + public JassValue accept(final ArrayJassValue value) { + throw new UnsupportedOperationException("Cannot perform string operation on array"); + } + + @Override + public JassValue accept(final HandleJassValue value) { + throw new UnsupportedOperationException("Cannot perform string operation on handle"); + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/NegateJassValueVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/NegateJassValueVisitor.java new file mode 100644 index 0000000..752325f --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/NegateJassValueVisitor.java @@ -0,0 +1,55 @@ +package com.etheller.interpreter.ast.value.visitor; + +import com.etheller.interpreter.ast.value.ArrayJassValue; +import com.etheller.interpreter.ast.value.BooleanJassValue; +import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.HandleJassValue; +import com.etheller.interpreter.ast.value.IntegerJassValue; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.JassValueVisitor; +import com.etheller.interpreter.ast.value.RealJassValue; +import com.etheller.interpreter.ast.value.StringJassValue; + +public class NegateJassValueVisitor implements JassValueVisitor { + private static final NegateJassValueVisitor INSTANCE = new NegateJassValueVisitor(); + + public static NegateJassValueVisitor getInstance() { + return INSTANCE; + } + + @Override + public JassValue accept(final IntegerJassValue value) { + return new IntegerJassValue(-value.getValue()); + } + + @Override + public JassValue accept(final RealJassValue value) { + return new RealJassValue(-value.getValue()); + } + + @Override + public JassValue accept(final BooleanJassValue value) { + throw new IllegalStateException("Unable to apply numeric unary negative sign to boolean"); + } + + @Override + public JassValue accept(final StringJassValue value) { + throw new IllegalStateException("Unable to apply numeric unary negative sign to string"); + } + + @Override + public JassValue accept(final CodeJassValue value) { + throw new IllegalStateException("Unable to apply numeric unary negative sign to code"); + } + + @Override + public JassValue accept(final ArrayJassValue value) { + throw new IllegalStateException("Unable to apply numeric unary negative sign to array"); + } + + @Override + public JassValue accept(final HandleJassValue value) { + throw new IllegalStateException("Unable to apply numeric unary negative sign to handle"); + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/visitors/JassArgumentsVisitor.java b/jassparser/src/com/etheller/interpreter/ast/visitors/JassArgumentsVisitor.java index cd44e48..0442df4 100644 --- a/jassparser/src/com/etheller/interpreter/ast/visitors/JassArgumentsVisitor.java +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/JassArgumentsVisitor.java @@ -1,9 +1,11 @@ package com.etheller.interpreter.ast.visitors; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import com.etheller.interpreter.JassBaseVisitor; +import com.etheller.interpreter.JassParser.EmptyArgumentContext; import com.etheller.interpreter.JassParser.ListArgumentContext; import com.etheller.interpreter.JassParser.SingleArgumentContext; import com.etheller.interpreter.ast.expression.JassExpression; @@ -28,4 +30,9 @@ public class JassArgumentsVisitor extends JassBaseVisitor> list.add(0, this.argumentExpressionHandler.expressionVisitor.visit(ctx.expression())); return list; } + + @Override + public List visitEmptyArgument(final EmptyArgumentContext ctx) { + return Collections.emptyList(); + } } diff --git a/jassparser/src/com/etheller/interpreter/ast/visitors/JassExpressionVisitor.java b/jassparser/src/com/etheller/interpreter/ast/visitors/JassExpressionVisitor.java index cb5e077..2c52f30 100644 --- a/jassparser/src/com/etheller/interpreter/ast/visitors/JassExpressionVisitor.java +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/JassExpressionVisitor.java @@ -4,27 +4,48 @@ import java.util.Collections; import java.util.List; import com.etheller.interpreter.JassBaseVisitor; +import com.etheller.interpreter.JassParser.AdditionExpressionContext; import com.etheller.interpreter.JassParser.ArgsListContext; import com.etheller.interpreter.JassParser.ArrayReferenceExpressionContext; +import com.etheller.interpreter.JassParser.BooleanAndExpressionContext; +import com.etheller.interpreter.JassParser.BooleanGreaterExpressionContext; +import com.etheller.interpreter.JassParser.BooleanGreaterOrEqualsExpressionContext; +import com.etheller.interpreter.JassParser.BooleanLessExpressionContext; +import com.etheller.interpreter.JassParser.BooleanLessOrEqualsExpressionContext; +import com.etheller.interpreter.JassParser.BooleanOrExpressionContext; +import com.etheller.interpreter.JassParser.DivisionExpressionContext; +import com.etheller.interpreter.JassParser.EqualsExpressionContext; import com.etheller.interpreter.JassParser.FalseExpressionContext; import com.etheller.interpreter.JassParser.FunctionCallExpressionContext; import com.etheller.interpreter.JassParser.FunctionReferenceExpressionContext; import com.etheller.interpreter.JassParser.IntegerLiteralExpressionContext; +import com.etheller.interpreter.JassParser.MultiplicationExpressionContext; +import com.etheller.interpreter.JassParser.NegateExpressionContext; +import com.etheller.interpreter.JassParser.NotEqualsExpressionContext; import com.etheller.interpreter.JassParser.NotExpressionContext; +import com.etheller.interpreter.JassParser.NullExpressionContext; import com.etheller.interpreter.JassParser.ParentheticalExpressionContext; +import com.etheller.interpreter.JassParser.RawcodeLiteralExpressionContext; +import com.etheller.interpreter.JassParser.RealLiteralExpressionContext; import com.etheller.interpreter.JassParser.ReferenceExpressionContext; import com.etheller.interpreter.JassParser.StringLiteralExpressionContext; +import com.etheller.interpreter.JassParser.SubtrationExpressionContext; import com.etheller.interpreter.JassParser.TrueExpressionContext; +import com.etheller.interpreter.ast.expression.ArithmeticJassExpression; +import com.etheller.interpreter.ast.expression.ArithmeticSigns; import com.etheller.interpreter.ast.expression.ArrayRefJassExpression; import com.etheller.interpreter.ast.expression.FunctionCallJassExpression; import com.etheller.interpreter.ast.expression.FunctionReferenceJassExpression; import com.etheller.interpreter.ast.expression.JassExpression; import com.etheller.interpreter.ast.expression.LiteralJassExpression; +import com.etheller.interpreter.ast.expression.NegateJassExpression; import com.etheller.interpreter.ast.expression.NotJassExpression; import com.etheller.interpreter.ast.expression.ReferenceJassExpression; import com.etheller.interpreter.ast.value.BooleanJassValue; import com.etheller.interpreter.ast.value.IntegerJassValue; +import com.etheller.interpreter.ast.value.RealJassValue; import com.etheller.interpreter.ast.value.StringJassValue; +import com.etheller.warsmash.util.RawcodeUtils; public class JassExpressionVisitor extends JassBaseVisitor { private final ArgumentExpressionHandler argumentExpressionHandler; @@ -56,6 +77,21 @@ public class JassExpressionVisitor extends JassBaseVisitor { return new LiteralJassExpression(new IntegerJassValue(Integer.parseInt(ctx.INTEGER().getText()))); } + @Override + public JassExpression visitRawcodeLiteralExpression(final RawcodeLiteralExpressionContext ctx) { + final String stringLiteralText = ctx.RAWCODE().getText(); + String parsedString = stringLiteralText.substring(1, stringLiteralText.length() - 1).replace("\\\\", "\\"); + while (parsedString.length() < 4) { + parsedString += '\0'; + } + return new LiteralJassExpression(new IntegerJassValue(RawcodeUtils.toInt(parsedString))); + } + + @Override + public JassExpression visitRealLiteralExpression(final RealLiteralExpressionContext ctx) { + return new LiteralJassExpression(new RealJassValue(Double.parseDouble(ctx.REAL().getText()))); + } + @Override public JassExpression visitFunctionReferenceExpression(final FunctionReferenceExpressionContext ctx) { return new FunctionReferenceJassExpression(ctx.ID().getText()); @@ -76,9 +112,91 @@ public class JassExpressionVisitor extends JassBaseVisitor { return new LiteralJassExpression(BooleanJassValue.TRUE); } + @Override + public JassExpression visitNullExpression(final NullExpressionContext ctx) { + return new LiteralJassExpression(null); + } + + @Override + public JassExpression visitEqualsExpression(final EqualsExpressionContext ctx) { + return new ArithmeticJassExpression(visit(ctx.boolEqualityExpression()), visit(ctx.boolComparisonExpression()), + ArithmeticSigns.EQUALS); + } + + @Override + public JassExpression visitNotEqualsExpression(final NotEqualsExpressionContext ctx) { + return new ArithmeticJassExpression(visit(ctx.boolEqualityExpression()), visit(ctx.boolComparisonExpression()), + ArithmeticSigns.NOT_EQUALS); + } + @Override public JassExpression visitNotExpression(final NotExpressionContext ctx) { - return new NotJassExpression(visit(ctx.expression())); + return new NotJassExpression(visit(ctx.baseExpression())); + } + + @Override + public JassExpression visitNegateExpression(final NegateExpressionContext ctx) { + return new NegateJassExpression(visit(ctx.baseExpression())); + } + + @Override + public JassExpression visitAdditionExpression(final AdditionExpressionContext ctx) { + return new ArithmeticJassExpression(visit(ctx.simpleArithmeticExpression()), visit(ctx.multDivExpression()), + ArithmeticSigns.ADD); + } + + @Override + public JassExpression visitSubtrationExpression(final SubtrationExpressionContext ctx) { + return new ArithmeticJassExpression(visit(ctx.simpleArithmeticExpression()), visit(ctx.multDivExpression()), + ArithmeticSigns.SUBTRACT); + } + + @Override + public JassExpression visitBooleanOrExpression(final BooleanOrExpressionContext ctx) { + return new ArithmeticJassExpression(visit(ctx.boolExpression()), visit(ctx.boolAndsExpression()), + ArithmeticSigns.OR); + } + + @Override + public JassExpression visitBooleanAndExpression(final BooleanAndExpressionContext ctx) { + return new ArithmeticJassExpression(visit(ctx.boolAndsExpression()), visit(ctx.boolEqualityExpression()), + ArithmeticSigns.AND); + } + + @Override + public JassExpression visitBooleanLessExpression(final BooleanLessExpressionContext ctx) { + return new ArithmeticJassExpression(visit(ctx.boolComparisonExpression()), + visit(ctx.simpleArithmeticExpression()), ArithmeticSigns.LESS); + } + + @Override + public JassExpression visitBooleanGreaterExpression(final BooleanGreaterExpressionContext ctx) { + return new ArithmeticJassExpression(visit(ctx.boolComparisonExpression()), + visit(ctx.simpleArithmeticExpression()), ArithmeticSigns.GREATER); + } + + @Override + public JassExpression visitBooleanGreaterOrEqualsExpression(final BooleanGreaterOrEqualsExpressionContext ctx) { + return new ArithmeticJassExpression(visit(ctx.boolComparisonExpression()), + visit(ctx.simpleArithmeticExpression()), ArithmeticSigns.GREATER_OR_EQUALS); + } + + @Override + public JassExpression visitBooleanLessOrEqualsExpression(final BooleanLessOrEqualsExpressionContext ctx) { + return new ArithmeticJassExpression(visit(ctx.boolComparisonExpression()), + visit(ctx.simpleArithmeticExpression()), ArithmeticSigns.LESS_OR_EQUALS); + } + + @Override + public JassExpression visitMultiplicationExpression(final MultiplicationExpressionContext ctx) { + return new ArithmeticJassExpression(visit(ctx.multDivExpression()), visit(ctx.baseExpression()), + ArithmeticSigns.MULTIPLY); + } + + @Override + public JassExpression visitDivisionExpression(final DivisionExpressionContext ctx) { + return new ArithmeticJassExpression(visit(ctx.multDivExpression()), visit(ctx.baseExpression()), + ArithmeticSigns.DIVIDE); } @Override diff --git a/jassparser/src/com/etheller/interpreter/ast/visitors/JassGlobalsVisitor.java b/jassparser/src/com/etheller/interpreter/ast/visitors/JassGlobalsVisitor.java index f5fcfc2..df1b4e7 100644 --- a/jassparser/src/com/etheller/interpreter/ast/visitors/JassGlobalsVisitor.java +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/JassGlobalsVisitor.java @@ -38,13 +38,18 @@ public class JassGlobalsVisitor extends JassBaseVisitor { public Void visitDefinitionGlobal(final DefinitionGlobalContext ctx) { final JassType type = this.jassTypeVisitor.visit(ctx.type()); final JassType arrayPrimType = type.visit(ArrayPrimitiveTypeVisitor.getInstance()); - if (arrayPrimType != null) { - this.globals.createGlobalArray(ctx.ID().getText(), type); + try { + if (arrayPrimType != null) { + this.globals.createGlobalArray(ctx.ID().getText(), type); + } + else { + this.globals.createGlobal(ctx.ID().getText(), type, + this.jassExpressionVisitor.visit(ctx.assignTail().expression()).evaluate(this.globals, + EMPTY_LOCAL_SCOPE, JassProgramVisitor.EMPTY_TRIGGER_SCOPE)); + } } - else { - this.globals.createGlobal(ctx.ID().getText(), type, - this.jassExpressionVisitor.visit(ctx.assignTail().expression()).evaluate(this.globals, - EMPTY_LOCAL_SCOPE, JassProgramVisitor.EMPTY_TRIGGER_SCOPE)); + catch (final Exception exc) { + throw new RuntimeException(ctx.getText(), exc); } return null; } diff --git a/jassparser/src/com/etheller/interpreter/ast/visitors/JassProgramVisitor.java b/jassparser/src/com/etheller/interpreter/ast/visitors/JassProgramVisitor.java index 2c5e0d2..59a2a95 100644 --- a/jassparser/src/com/etheller/interpreter/ast/visitors/JassProgramVisitor.java +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/JassProgramVisitor.java @@ -34,7 +34,8 @@ public class JassProgramVisitor extends JassBaseVisitor { private final JassGlobalsVisitor jassGlobalsVisitor = new JassGlobalsVisitor(this.globals, this.jassTypeVisitor, this.jassExpressionVisitor); private final JassParametersVisitor jassParametersVisitor = new JassParametersVisitor(this.jassTypeVisitor); - private final JassStatementVisitor jassStatementVisitor = new JassStatementVisitor(this.argumentExpressionHandler); + private final JassStatementVisitor jassStatementVisitor = new JassStatementVisitor(this.argumentExpressionHandler, + this.jassTypeVisitor); @Override public Void visitBlock(final BlockContext ctx) { diff --git a/jassparser/src/com/etheller/interpreter/ast/visitors/JassStatementVisitor.java b/jassparser/src/com/etheller/interpreter/ast/visitors/JassStatementVisitor.java index 14ac829..cbffaa0 100644 --- a/jassparser/src/com/etheller/interpreter/ast/visitors/JassStatementVisitor.java +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/JassStatementVisitor.java @@ -5,33 +5,51 @@ import java.util.List; import com.etheller.interpreter.JassBaseVisitor; import com.etheller.interpreter.JassParser.ArrayedAssignmentStatementContext; +import com.etheller.interpreter.JassParser.BasicLocalContext; import com.etheller.interpreter.JassParser.CallStatementContext; +import com.etheller.interpreter.JassParser.DefinitionLocalContext; +import com.etheller.interpreter.JassParser.ExitWhenStatementContext; import com.etheller.interpreter.JassParser.IfElseIfStatementContext; import com.etheller.interpreter.JassParser.IfElseStatementContext; +import com.etheller.interpreter.JassParser.LoopStatementContext; +import com.etheller.interpreter.JassParser.ReturnNothingStatementContext; import com.etheller.interpreter.JassParser.ReturnStatementContext; import com.etheller.interpreter.JassParser.SetStatementContext; import com.etheller.interpreter.JassParser.SimpleIfStatementContext; import com.etheller.interpreter.JassParser.StatementContext; import com.etheller.interpreter.ast.statement.JassArrayedAssignmentStatement; import com.etheller.interpreter.ast.statement.JassCallStatement; +import com.etheller.interpreter.ast.statement.JassExitWhenStatement; import com.etheller.interpreter.ast.statement.JassIfElseIfStatement; import com.etheller.interpreter.ast.statement.JassIfElseStatement; import com.etheller.interpreter.ast.statement.JassIfStatement; +import com.etheller.interpreter.ast.statement.JassLocalDefinitionStatement; +import com.etheller.interpreter.ast.statement.JassLocalStatement; +import com.etheller.interpreter.ast.statement.JassLoopStatement; +import com.etheller.interpreter.ast.statement.JassReturnNothingStatement; import com.etheller.interpreter.ast.statement.JassReturnStatement; import com.etheller.interpreter.ast.statement.JassSetStatement; import com.etheller.interpreter.ast.statement.JassStatement; public class JassStatementVisitor extends JassBaseVisitor { private final ArgumentExpressionHandler argumentExpressionHandler; + private final JassTypeVisitor jassTypeVisitor; - public JassStatementVisitor(final ArgumentExpressionHandler argumentExpressionHandler) { + public JassStatementVisitor(final ArgumentExpressionHandler argumentExpressionHandler, + final JassTypeVisitor jassTypeVisitor) { this.argumentExpressionHandler = argumentExpressionHandler; + this.jassTypeVisitor = jassTypeVisitor; } @Override public JassStatement visitCallStatement(final CallStatementContext ctx) { - return new JassCallStatement(ctx.getStart().getLine(), ctx.functionExpression().ID().getText(), - this.argumentExpressionHandler.argumentsVisitor.visit(ctx.functionExpression().argsList())); + try { + return new JassCallStatement(ctx.getStart().getLine(), ctx.functionExpression().ID().getText(), + this.argumentExpressionHandler.argumentsVisitor.visit(ctx.functionExpression().argsList())); + } + catch (final Exception exc) { + throw new RuntimeException(ctx.getText(), exc); + } } @Override @@ -46,6 +64,17 @@ public class JassStatementVisitor extends JassBaseVisitor { this.argumentExpressionHandler.expressionVisitor.visit(ctx.expression())); } + @Override + public JassStatement visitReturnNothingStatement(final ReturnNothingStatementContext ctx) { + return new JassReturnNothingStatement(ctx.getStart().getLine()); + } + + @Override + public JassStatement visitExitWhenStatement(final ExitWhenStatementContext ctx) { + return new JassExitWhenStatement(ctx.getStart().getLine(), + this.argumentExpressionHandler.expressionVisitor.visit(ctx.expression())); + } + @Override public JassStatement visitIfElseIfStatement(final IfElseIfStatementContext ctx) { final List thenStatements = new ArrayList<>(); @@ -72,6 +101,15 @@ public class JassStatementVisitor extends JassBaseVisitor { elseStatements); } + @Override + public JassStatement visitLoopStatement(final LoopStatementContext ctx) { + final List statements = new ArrayList<>(); + for (final StatementContext statementCtx : ctx.statements().statement()) { + statements.add(visit(statementCtx)); + } + return new JassLoopStatement(ctx.getStart().getLine(), statements); + } + @Override public JassStatement visitSimpleIfStatement(final SimpleIfStatementContext ctx) { final List thenStatements = new ArrayList<>(); @@ -88,4 +126,17 @@ public class JassStatementVisitor extends JassBaseVisitor { this.argumentExpressionHandler.expressionVisitor.visit(ctx.expression(0)), this.argumentExpressionHandler.expressionVisitor.visit(ctx.expression(1))); } + + @Override + public JassStatement visitBasicLocal(final BasicLocalContext ctx) { + return new JassLocalStatement(ctx.getStart().getLine(), ctx.ID().getText(), + this.jassTypeVisitor.visit(ctx.type())); + } + + @Override + public JassStatement visitDefinitionLocal(final DefinitionLocalContext ctx) { + return new JassLocalDefinitionStatement(ctx.getStart().getLine(), ctx.ID().getText(), + this.jassTypeVisitor.visit(ctx.type()), + this.argumentExpressionHandler.expressionVisitor.visit(ctx.assignTail().expression())); + } } diff --git a/core/src/com/etheller/warsmash/util/RawcodeUtils.java b/jassparser/src/com/etheller/warsmash/util/RawcodeUtils.java similarity index 100% rename from core/src/com/etheller/warsmash/util/RawcodeUtils.java rename to jassparser/src/com/etheller/warsmash/util/RawcodeUtils.java diff --git a/resources/UI/FrameDef/SmashUI/ToolTip.fdf b/resources/UI/FrameDef/SmashUI/ToolTip.fdf index bb30920..330c525 100644 --- a/resources/UI/FrameDef/SmashUI/ToolTip.fdf +++ b/resources/UI/FrameDef/SmashUI/ToolTip.fdf @@ -64,4 +64,26 @@ Frame "SIMPLEFRAME" "SmashToolTipIconResource" { Font "InfoPanelTextFont",0.0085, Text "275", } +} +Frame "FRAME" "SmashHoverTip" { + Frame "BACKDROP" "SmashHoverTipBackdrop" { + SetAllPoints, + DecorateFileNames, + BackdropTileBackground, + BackdropBackground "ToolTipBackground", + BackdropCornerFlags "UL|UR|BL|BR|T|L|B|R", + BackdropCornerSize 0.008, + BackdropBackgroundSize 0.036, + BackdropBackgroundInsets 0.0025 0.0025 0.0025 0.0025, + BackdropEdgeFile "ToolTipBorder", + BackdropBlendAll, + } + Frame "TEXT" "SmashHoverTipText" { + DecorateFileNames, + FrameFont "MasterFont", 0.010, "", + FontJustificationH JUSTIFYLEFT, + FontJustificationV JUSTIFYTOP, + FontFlags "FIXEDSIZE", + FontColor 1.0 1.0 1.0 1.0, + } } \ No newline at end of file