From b2900f50fc6de40d4127670e8449b23441b01c81 Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 5 Jul 2020 10:39:43 -0400 Subject: [PATCH] More work on collisions to fix issues --- .../etheller/warsmash/WarsmashGdxMapGame.java | 48 ++- .../etheller/warsmash/parsers/jass/Jass2.java | 211 ++++++++++- .../parsers/jass/triggers/BoolExprAnd.java | 21 ++ .../jass/triggers/BoolExprCondition.java | 27 ++ .../parsers/jass/triggers/BoolExprFilter.java | 27 ++ .../parsers/jass/triggers/BoolExprNot.java | 19 + .../parsers/jass/triggers/BoolExprOr.java | 21 ++ .../parsers/jass/triggers/TriggerAction.java | 28 ++ .../jass/triggers/TriggerCondition.java | 28 ++ .../com/etheller/warsmash/util/Quadtree.java | 205 ++++++++++ .../viewer5/handlers/w3x/War3MapViewer.java | 161 +++----- .../handlers/w3x/environment/PathingGrid.java | 73 +--- .../handlers/w3x/rendersim/RenderUnit.java | 8 +- .../handlers/w3x/simulation/CSimulation.java | 23 +- .../handlers/w3x/simulation/CUnit.java | 37 ++ .../handlers/w3x/simulation/CWidget.java | 4 +- .../w3x/simulation/CWorldCollision.java | 112 ++++++ .../w3x/simulation/orders/CMoveOrder.java | 351 ++++++++++-------- .../pathing/CPathfindingProcessor.java | 130 ++++++- .../warsmash/desktop/DesktopLauncher.java | 9 +- .../expression/ArrayRefJassExpression.java | 13 +- .../FunctionCallJassExpression.java | 14 +- .../FunctionReferenceJassExpression.java | 8 +- .../ast/expression/JassExpression.java | 3 +- .../ast/expression/LiteralJassExpression.java | 6 +- .../ast/expression/NotJassExpression.java | 7 +- .../expression/ReferenceJassExpression.java | 8 +- .../ast/function/AbstractJassFunction.java | 8 +- .../ast/function/JassFunction.java | 3 +- .../ast/function/NativeJassFunction.java | 7 +- .../ast/function/UserJassFunction.java | 11 +- .../interpreter/ast/scope/GlobalScope.java | 14 +- .../ast/scope/TriggerExecutionScope.java | 16 + .../ast/scope/trigger/Trigger.java | 74 ++++ .../trigger/TriggerBooleanExpression.java | 8 + .../JassArrayedAssignmentStatement.java | 8 +- .../ast/statement/JassCallStatement.java | 8 +- .../ast/statement/JassIfElseIfStatement.java | 11 +- .../ast/statement/JassIfElseStatement.java | 11 +- .../ast/statement/JassIfStatement.java | 9 +- .../ast/statement/JassReturnStatement.java | 6 +- .../ast/statement/JassSetStatement.java | 8 +- .../ast/statement/JassStatement.java | 3 +- .../ast/value/BooleanJassValue.java | 9 + .../ast/visitors/JassGlobalsVisitor.java | 5 +- .../ast/visitors/JassProgramVisitor.java | 4 +- resources/Scripts/common.jui | 24 ++ resources/Scripts/melee.jui | 2 +- 48 files changed, 1371 insertions(+), 480 deletions(-) create mode 100644 core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprAnd.java create mode 100644 core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprCondition.java create mode 100644 core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprFilter.java create mode 100644 core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprNot.java create mode 100644 core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprOr.java create mode 100644 core/src/com/etheller/warsmash/parsers/jass/triggers/TriggerAction.java create mode 100644 core/src/com/etheller/warsmash/parsers/jass/triggers/TriggerCondition.java create mode 100644 core/src/com/etheller/warsmash/util/Quadtree.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/scope/TriggerExecutionScope.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/scope/trigger/Trigger.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/scope/trigger/TriggerBooleanExpression.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 378c380..65cf307 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -66,7 +66,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private final Rectangle tempRect = new Rectangle(); private CameraManager portraitCameraManager; - private MdxComplexInstance portraitInstance; private final float[] cameraPositionTemp = new float[3]; private final float[] cameraTargetTemp = new float[3]; @@ -136,13 +135,14 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.viewer.worldScene.enableAudio(); this.viewer.enableAudio(); try { - this.viewer.loadMap("Pathing.w3x"); + this.viewer.loadMap("Maps\\Campaign\\NightElf03.w3m"); } catch (final IOException e) { throw new RuntimeException(e); } this.cameraManager = new CameraManager(); + this.cameraManager.setupCamera(this.viewer.worldScene); System.out.println("Loaded"); @@ -272,7 +272,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.cameraManager.target.add(this.cameraVelocity.x * deltaTime, this.cameraVelocity.y * deltaTime, 0); this.cameraManager.target.z = Math.max( this.viewer.terrain.getGroundHeight(this.cameraManager.target.x, this.cameraManager.target.y), - this.viewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)); + this.viewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)) - 256; this.cameraManager.updateCamera(); this.portraitCameraManager.updateCamera(); this.viewer.updateAndRender(); @@ -283,9 +283,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv // this.font.draw(this.batch, Integer.toString(Gdx.graphics.getFramesPerSecond()), 0, 0); // this.batch.end(); - if ((this.portraitInstance != null) - && (this.portraitInstance.sequenceEnded || (this.portraitInstance.sequence == -1))) { - StandSequence.randomPortraitSequence(this.portraitInstance); + if ((this.portraitCameraManager.modelInstance != null) + && (this.portraitCameraManager.modelInstance.sequenceEnded + || (this.portraitCameraManager.modelInstance.sequence == -1))) { + StandSequence.randomPortraitSequence(this.portraitCameraManager.modelInstance); } Gdx.gl30.glDisable(GL30.GL_SCISSOR_TEST); @@ -429,6 +430,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv class CameraManager { public com.etheller.warsmash.viewer5.handlers.mdx.Camera modelCamera; + private MdxComplexInstance modelInstance; private CanvasProvider canvas; private Camera camera; private float moveSpeed; @@ -483,13 +485,9 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.position = this.position.add(this.target); if (this.modelCamera != null) { this.modelCamera.getPositionTranslation(WarsmashGdxMapGame.this.cameraPositionTemp, - WarsmashGdxMapGame.this.portraitInstance.sequence, - WarsmashGdxMapGame.this.portraitInstance.frame, - WarsmashGdxMapGame.this.portraitInstance.counter); + this.modelInstance.sequence, this.modelInstance.frame, this.modelInstance.counter); this.modelCamera.getTargetTranslation(WarsmashGdxMapGame.this.cameraTargetTemp, - WarsmashGdxMapGame.this.portraitInstance.sequence, - WarsmashGdxMapGame.this.portraitInstance.frame, - WarsmashGdxMapGame.this.portraitInstance.counter); + this.modelInstance.sequence, this.modelInstance.frame, this.modelInstance.counter); this.position.set(this.modelCamera.position); this.target.set(this.modelCamera.targetPosition); @@ -596,7 +594,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final RenderUnit rayPickUnit = this.viewer.rayPickUnit(screenX, worldScreenY); if ((rayPickUnit != null) && (rayPickUnit.playerIndex != this.selectedUnit.playerIndex)) { if (this.viewer.orderSmart(rayPickUnit)) { - StandSequence.randomPortraitTalkSequence(this.portraitInstance); + StandSequence.randomPortraitTalkSequence(this.portraitCameraManager.modelInstance); this.selectedSoundCount = 0; } } @@ -609,7 +607,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv System.out.println(x + "," + y); this.viewer.terrain.logRomp(x, y); if (this.viewer.orderSmart(clickLocationTemp.x, clickLocationTemp.y)) { - StandSequence.randomPortraitTalkSequence(this.portraitInstance); + StandSequence.randomPortraitTalkSequence(this.portraitCameraManager.modelInstance); this.selectedSoundCount = 0; } } @@ -647,29 +645,29 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv if (selectionChanged) { final MdxModel portraitModel = unit.portraitModel; if (portraitModel != null) { - if (this.portraitInstance != null) { - this.portraitScene.removeInstance(this.portraitInstance); + if (this.portraitCameraManager.modelInstance != null) { + this.portraitScene.removeInstance(this.portraitCameraManager.modelInstance); } - this.portraitInstance = (MdxComplexInstance) portraitModel.addInstance(); - this.portraitInstance.setSequenceLoopMode(1); - this.portraitInstance.setScene(this.portraitScene); - this.portraitInstance.setVertexColor(unit.instance.vertexColor); + this.portraitCameraManager.modelInstance = (MdxComplexInstance) portraitModel.addInstance(); + this.portraitCameraManager.modelInstance.setSequenceLoopMode(1); + this.portraitCameraManager.modelInstance.setScene(this.portraitScene); + this.portraitCameraManager.modelInstance.setVertexColor(unit.instance.vertexColor); if (portraitModel.getCameras().size() > 0) { this.portraitCameraManager.modelCamera = portraitModel.getCameras().get(0); } - this.portraitInstance.setTeamColor(unit.playerIndex); + this.portraitCameraManager.modelInstance.setTeamColor(unit.playerIndex); } } if (playedNewSound) { - StandSequence.randomPortraitTalkSequence(this.portraitInstance); + StandSequence.randomPortraitTalkSequence(this.portraitCameraManager.modelInstance); } } else { this.selectedUnit = null; - if (this.portraitInstance != null) { - this.portraitScene.removeInstance(this.portraitInstance); + if (this.portraitCameraManager.modelInstance != null) { + this.portraitScene.removeInstance(this.portraitCameraManager.modelInstance); } - this.portraitInstance = null; + this.portraitCameraManager.modelInstance = null; this.portraitCameraManager.modelCamera = null; } } diff --git a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java index c37b1e6..95e853f 100644 --- a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java +++ b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java @@ -14,11 +14,16 @@ import com.etheller.interpreter.JassLexer; import com.etheller.interpreter.JassParser; import com.etheller.interpreter.ast.function.JassFunction; import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; +import com.etheller.interpreter.ast.scope.trigger.Trigger; +import com.etheller.interpreter.ast.scope.trigger.TriggerBooleanExpression; import com.etheller.interpreter.ast.value.BooleanJassValue; +import com.etheller.interpreter.ast.value.HandleJassType; import com.etheller.interpreter.ast.value.HandleJassValue; import com.etheller.interpreter.ast.value.JassValue; import com.etheller.interpreter.ast.value.StringJassValue; import com.etheller.interpreter.ast.value.visitor.IntegerJassValueVisitor; +import com.etheller.interpreter.ast.value.visitor.JassFunctionJassValueVisitor; import com.etheller.interpreter.ast.value.visitor.ObjectJassValueVisitor; import com.etheller.interpreter.ast.value.visitor.RealJassValueVisitor; import com.etheller.interpreter.ast.value.visitor.StringJassValueVisitor; @@ -28,6 +33,13 @@ import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; +import com.etheller.warsmash.parsers.jass.triggers.BoolExprAnd; +import com.etheller.warsmash.parsers.jass.triggers.BoolExprCondition; +import com.etheller.warsmash.parsers.jass.triggers.BoolExprFilter; +import com.etheller.warsmash.parsers.jass.triggers.BoolExprNot; +import com.etheller.warsmash.parsers.jass.triggers.BoolExprOr; +import com.etheller.warsmash.parsers.jass.triggers.TriggerAction; +import com.etheller.warsmash.parsers.jass.triggers.TriggerCondition; import com.etheller.warsmash.units.Element; public class Jass2 { @@ -84,9 +96,19 @@ public class Jass2 { public JUIEnvironment(final JassProgramVisitor jassProgramVisitor, final DataSource dataSource, final Viewport uiViewport, final RootFrameListener rootFrameListener) { + final GlobalScope globals = jassProgramVisitor.getGlobals(); + final HandleJassType frameHandleType = globals.registerHandleType("framehandle"); + final HandleJassType framePointType = globals.registerHandleType("framepointtype"); + final HandleJassType triggerType = globals.registerHandleType("trigger"); + final HandleJassType triggerActionType = globals.registerHandleType("triggeraction"); + final HandleJassType triggerConditionType = globals.registerHandleType("triggercondition"); + final HandleJassType boolExprType = globals.registerHandleType("boolexpr"); + final HandleJassType conditionFuncType = globals.registerHandleType("conditionfunc"); + final HandleJassType filterType = globals.registerHandleType("filterfunc"); jassProgramVisitor.getJassNativeManager().createNative("LogError", new JassFunction() { @Override - public JassValue call(final List arguments, final GlobalScope globalScope) { + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { final String stringValue = arguments.get(0).visit(StringJassValueVisitor.getInstance()); System.err.println(stringValue); return null; @@ -94,27 +116,29 @@ public class Jass2 { }); jassProgramVisitor.getJassNativeManager().createNative("ConvertFramePointType", new JassFunction() { @Override - public JassValue call(final List arguments, final GlobalScope globalScope) { + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { final int value = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); - return new HandleJassValue(jassProgramVisitor.getGlobals().framePointType, - FramePoint.values()[value]); + return new HandleJassValue(framePointType, FramePoint.values()[value]); } }); jassProgramVisitor.getJassNativeManager().createNative("CreateRootFrame", new JassFunction() { @Override - public JassValue call(final List arguments, final GlobalScope globalScope) { + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { final String skinArg = arguments.get(0).visit(StringJassValueVisitor.getInstance()); final Element skin = GameUI.loadSkin(dataSource, skinArg); final GameUI gameUI = new GameUI(dataSource, skin, uiViewport); JUIEnvironment.this.gameUI = gameUI; JUIEnvironment.this.skin = skin; rootFrameListener.onCreate(gameUI); - return new HandleJassValue(jassProgramVisitor.getGlobals().frameHandleType, gameUI); + return new HandleJassValue(frameHandleType, gameUI); } }); jassProgramVisitor.getJassNativeManager().createNative("LoadTOCFile", new JassFunction() { @Override - public JassValue call(final List arguments, final GlobalScope globalScope) { + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { final String tocFileName = arguments.get(0).visit(StringJassValueVisitor.getInstance()); try { JUIEnvironment.this.gameUI.loadTOCFile(tocFileName); @@ -127,7 +151,8 @@ public class Jass2 { }); jassProgramVisitor.getJassNativeManager().createNative("CreateSimpleFrame", new JassFunction() { @Override - public JassValue call(final List arguments, final GlobalScope globalScope) { + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { final String templateName = arguments.get(0).visit(StringJassValueVisitor.getInstance()); final UIFrame ownerFrame = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); final int createContext = arguments.get(2).visit(IntegerJassValueVisitor.getInstance()); @@ -135,12 +160,13 @@ public class Jass2 { final UIFrame simpleFrame = JUIEnvironment.this.gameUI.createSimpleFrame(templateName, ownerFrame, createContext); - return new HandleJassValue(jassProgramVisitor.getGlobals().frameHandleType, simpleFrame); + return new HandleJassValue(frameHandleType, simpleFrame); } }); jassProgramVisitor.getJassNativeManager().createNative("FrameSetAbsPoint", new JassFunction() { @Override - public JassValue call(final List arguments, final GlobalScope globalScope) { + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { final UIFrame frame = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); final FramePoint framePoint = arguments.get(1) .visit(ObjectJassValueVisitor.getInstance()); @@ -154,7 +180,8 @@ public class Jass2 { }); jassProgramVisitor.getJassNativeManager().createNative("FramePositionBounds", new JassFunction() { @Override - public JassValue call(final List arguments, final GlobalScope globalScope) { + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { final UIFrame frame = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); frame.positionBounds(uiViewport); return null; @@ -162,11 +189,171 @@ public class Jass2 { }); jassProgramVisitor.getJassNativeManager().createNative("SkinGetField", new JassFunction() { @Override - public JassValue call(final List arguments, final GlobalScope globalScope) { + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { final String fieldName = arguments.get(0).visit(StringJassValueVisitor.getInstance()); return new StringJassValue(JUIEnvironment.this.skin.getField(fieldName)); } }); + jassProgramVisitor.getJassNativeManager().createNative("CreateTrigger", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new HandleJassValue(triggerType, new Trigger()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("DestroyTrigger", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Trigger trigger = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + trigger.destroy(); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("EnableTrigger", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Trigger trigger = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + trigger.setEnabled(true); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("DisableTrigger", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Trigger trigger = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + trigger.setEnabled(false); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("IsTriggerEnabled", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Trigger trigger = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return BooleanJassValue.of(trigger.isEnabled()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("Condition", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final JassFunction func = arguments.get(0).visit(JassFunctionJassValueVisitor.getInstance()); + return new HandleJassValue(conditionFuncType, new BoolExprCondition(func)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("Filter", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final JassFunction func = arguments.get(0).visit(JassFunctionJassValueVisitor.getInstance()); + return new HandleJassValue(filterType, new BoolExprFilter(func)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("DestroyCondition", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final BoolExprCondition trigger = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + System.err.println( + "DestroyCondition called but in Java we don't have a destructor, so we need to unregister later when that is implemented"); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("DestroyFilter", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final BoolExprFilter trigger = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + System.err.println( + "DestroyFilter called but in Java we don't have a destructor, so we need to unregister later when that is implemented"); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("DestroyBoolExpr", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final TriggerBooleanExpression trigger = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + System.err.println( + "DestroyBoolExpr called but in Java we don't have a destructor, so we need to unregister later when that is implemented"); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("And", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final TriggerBooleanExpression operandA = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + final TriggerBooleanExpression operandB = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + return new HandleJassValue(boolExprType, new BoolExprAnd(operandA, operandB)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("Or", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final TriggerBooleanExpression operandA = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + final TriggerBooleanExpression operandB = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + return new HandleJassValue(boolExprType, new BoolExprOr(operandA, operandB)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("Not", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final TriggerBooleanExpression operand = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + return new HandleJassValue(boolExprType, new BoolExprNot(operand)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("TriggerAddCondition", 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 TriggerBooleanExpression condition = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + final int index = whichTrigger.addCondition(condition); + return new HandleJassValue(triggerConditionType, + new TriggerCondition(condition, whichTrigger, index)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("TriggerRemoveCondition", 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 TriggerCondition condition = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + if (condition.getTrigger() != whichTrigger) { + throw new IllegalArgumentException("Unable to remove condition, wrong trigger"); + } + whichTrigger.removeConditionAtIndex(condition.getConditionIndex()); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("TriggerAddAction", 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 JassFunction actionFunc = arguments.get(1).visit(JassFunctionJassValueVisitor.getInstance()); + final int actionIndex = whichTrigger.addAction(actionFunc); + return new HandleJassValue(triggerActionType, + new TriggerAction(whichTrigger, actionFunc, actionIndex)); + } + }); } } } diff --git a/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprAnd.java b/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprAnd.java new file mode 100644 index 0000000..26deb4e --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprAnd.java @@ -0,0 +1,21 @@ +package com.etheller.warsmash.parsers.jass.triggers; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; +import com.etheller.interpreter.ast.scope.trigger.TriggerBooleanExpression; + +public class BoolExprAnd implements TriggerBooleanExpression { + private final TriggerBooleanExpression operandA; + private final TriggerBooleanExpression operandB; + + public BoolExprAnd(final TriggerBooleanExpression operandA, final TriggerBooleanExpression operandB) { + this.operandA = operandA; + this.operandB = operandB; + } + + @Override + public boolean evaluate(final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { + return this.operandA.evaluate(globalScope, triggerScope) && this.operandB.evaluate(globalScope, triggerScope); + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprCondition.java b/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprCondition.java new file mode 100644 index 0000000..34ef292 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprCondition.java @@ -0,0 +1,27 @@ +package com.etheller.warsmash.parsers.jass.triggers; + +import java.util.Collections; + +import com.etheller.interpreter.ast.function.JassFunction; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; +import com.etheller.interpreter.ast.scope.trigger.TriggerBooleanExpression; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.visitor.BooleanJassValueVisitor; + +public class BoolExprCondition implements TriggerBooleanExpression { + private final JassFunction takesNothingReturnsBooleanFunction; + + public BoolExprCondition(final JassFunction returnsBooleanFunction) { + this.takesNothingReturnsBooleanFunction = returnsBooleanFunction; + } + + @Override + public boolean evaluate(final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { + final JassValue booleanJassReturnValue = this.takesNothingReturnsBooleanFunction.call(Collections.EMPTY_LIST, + globalScope, triggerScope); + final Boolean booleanReturnValue = booleanJassReturnValue.visit(BooleanJassValueVisitor.getInstance()); + return booleanReturnValue.booleanValue(); + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprFilter.java b/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprFilter.java new file mode 100644 index 0000000..b3a6e27 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprFilter.java @@ -0,0 +1,27 @@ +package com.etheller.warsmash.parsers.jass.triggers; + +import java.util.Collections; + +import com.etheller.interpreter.ast.function.JassFunction; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; +import com.etheller.interpreter.ast.scope.trigger.TriggerBooleanExpression; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.visitor.BooleanJassValueVisitor; + +public class BoolExprFilter implements TriggerBooleanExpression { + private final JassFunction takesNothingReturnsBooleanFunction; + + public BoolExprFilter(final JassFunction returnsBooleanFunction) { + this.takesNothingReturnsBooleanFunction = returnsBooleanFunction; + } + + @Override + public boolean evaluate(final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { + final JassValue booleanJassReturnValue = this.takesNothingReturnsBooleanFunction.call(Collections.EMPTY_LIST, + globalScope, triggerScope); + final Boolean booleanReturnValue = booleanJassReturnValue.visit(BooleanJassValueVisitor.getInstance()); + return booleanReturnValue.booleanValue(); + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprNot.java b/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprNot.java new file mode 100644 index 0000000..0f2cc52 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprNot.java @@ -0,0 +1,19 @@ +package com.etheller.warsmash.parsers.jass.triggers; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; +import com.etheller.interpreter.ast.scope.trigger.TriggerBooleanExpression; + +public class BoolExprNot implements TriggerBooleanExpression { + private final TriggerBooleanExpression operand; + + public BoolExprNot(final TriggerBooleanExpression operand) { + this.operand = operand; + } + + @Override + public boolean evaluate(final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { + return this.operand.evaluate(globalScope, triggerScope); + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprOr.java b/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprOr.java new file mode 100644 index 0000000..a59cd83 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprOr.java @@ -0,0 +1,21 @@ +package com.etheller.warsmash.parsers.jass.triggers; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; +import com.etheller.interpreter.ast.scope.trigger.TriggerBooleanExpression; + +public class BoolExprOr implements TriggerBooleanExpression { + private final TriggerBooleanExpression operandA; + private final TriggerBooleanExpression operandB; + + public BoolExprOr(final TriggerBooleanExpression operandA, final TriggerBooleanExpression operandB) { + this.operandA = operandA; + this.operandB = operandB; + } + + @Override + public boolean evaluate(final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { + return this.operandA.evaluate(globalScope, triggerScope) || this.operandB.evaluate(globalScope, triggerScope); + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/jass/triggers/TriggerAction.java b/core/src/com/etheller/warsmash/parsers/jass/triggers/TriggerAction.java new file mode 100644 index 0000000..7620f13 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/jass/triggers/TriggerAction.java @@ -0,0 +1,28 @@ +package com.etheller.warsmash.parsers.jass.triggers; + +import com.etheller.interpreter.ast.function.JassFunction; +import com.etheller.interpreter.ast.scope.trigger.Trigger; + +public class TriggerAction { + private final Trigger trigger; + private final JassFunction actionFunc; + private final int actionIndex; + + public TriggerAction(final Trigger trigger, final JassFunction actionFunc, final int actionIndex) { + this.trigger = trigger; + this.actionFunc = actionFunc; + this.actionIndex = actionIndex; + } + + public Trigger getTrigger() { + return this.trigger; + } + + public JassFunction getActionFunc() { + return this.actionFunc; + } + + public int getActionIndex() { + return this.actionIndex; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/jass/triggers/TriggerCondition.java b/core/src/com/etheller/warsmash/parsers/jass/triggers/TriggerCondition.java new file mode 100644 index 0000000..8ee32d7 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/jass/triggers/TriggerCondition.java @@ -0,0 +1,28 @@ +package com.etheller.warsmash.parsers.jass.triggers; + +import com.etheller.interpreter.ast.scope.trigger.Trigger; +import com.etheller.interpreter.ast.scope.trigger.TriggerBooleanExpression; + +public class TriggerCondition { + private final TriggerBooleanExpression boolexpr; + private final Trigger trigger; + private final int conditionIndex; + + public TriggerCondition(final TriggerBooleanExpression boolexpr, final Trigger trigger, final int index) { + this.boolexpr = boolexpr; + this.trigger = trigger; + this.conditionIndex = index; + } + + public TriggerBooleanExpression getBoolexpr() { + return this.boolexpr; + } + + public Trigger getTrigger() { + return this.trigger; + } + + public int getConditionIndex() { + return this.conditionIndex; + } +} diff --git a/core/src/com/etheller/warsmash/util/Quadtree.java b/core/src/com/etheller/warsmash/util/Quadtree.java new file mode 100644 index 0000000..d3ae6d3 --- /dev/null +++ b/core/src/com/etheller/warsmash/util/Quadtree.java @@ -0,0 +1,205 @@ +package com.etheller.warsmash.util; + +import java.util.function.Consumer; + +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.utils.Array; + +public class Quadtree { + private static final int MAX_DEPTH = 64; + private static final int SPLIT_THRESHOLD = 6; + + private final Rectangle bounds; + private Quadtree northeast; + private Quadtree northwest; + private Quadtree southwest; + private Quadtree southeast; + private final Array> nodes = new Array<>(); + private boolean leaf = true; + private final NodeAdder nodeAdder = new NodeAdder(); + private final UniqueNodeAdder uniqueNodeAdder = new UniqueNodeAdder(); + + public Quadtree(final Rectangle bounds) { + this.bounds = bounds; + } + + public void add(final T object, final Rectangle bounds) { + add(new Node(object, bounds), 0); + } + + public void remove(final T object, final Rectangle bounds) { + remove(object, bounds, null); + } + + public void translate(final T object, final Rectangle prevBoundsToUpdate, final float xShift, final float yShift) { + final Node node = remove(object, prevBoundsToUpdate, null); + prevBoundsToUpdate.x += xShift; + prevBoundsToUpdate.y += yShift; + add(node, 0); + } + + public boolean intersectsAnythingOtherThan(final T sourceObjectToIgnore, final Rectangle bounds) { + if (this.leaf) { + for (int i = 0; i < this.nodes.size; i++) { + final Node node = this.nodes.get(i); + if ((node.object != sourceObjectToIgnore) && node.bounds.overlaps(bounds)) { + return true; + } + } + return false; + } + else { + if (this.northeast.bounds.overlaps(bounds)) { + if (this.northeast.intersectsAnythingOtherThan(sourceObjectToIgnore, bounds)) { + return true; + } + } + if (this.northwest.bounds.overlaps(bounds)) { + if (this.northwest.intersectsAnythingOtherThan(sourceObjectToIgnore, bounds)) { + return true; + } + } + if (this.southwest.bounds.overlaps(bounds)) { + if (this.southwest.intersectsAnythingOtherThan(sourceObjectToIgnore, bounds)) { + return true; + } + } + if (this.southeast.bounds.overlaps(bounds)) { + if (this.southeast.intersectsAnythingOtherThan(sourceObjectToIgnore, bounds)) { + return true; + } + } + return false; + } + } + + private void add(final Node node, final int depth) { + if (this.leaf) { + if ((this.nodes.size >= SPLIT_THRESHOLD) && (depth < MAX_DEPTH)) { + split(depth); + // then dont return and add as a nonleaf + } + else { + this.nodes.add(node); + return; + } + } + if (this.northeast.bounds.overlaps(node.bounds)) { + this.northeast.add(node, depth + 1); + } + if (this.northwest.bounds.overlaps(node.bounds)) { + this.northwest.add(node, depth + 1); + } + if (this.southwest.bounds.overlaps(node.bounds)) { + this.southwest.add(node, depth + 1); + } + if (this.southeast.bounds.overlaps(node.bounds)) { + this.southeast.add(node, depth + 1); + } + } + + private void split(final int depth) { + final int splitDepth = depth + 1; + final float halfWidth = this.bounds.width / 2; + final float x = this.bounds.x; + final float xMidpoint = x + halfWidth; + final float halfHeight = this.bounds.height / 2; + final float y = this.bounds.y; + final float yMidpoint = y + halfHeight; + this.northeast = new Quadtree<>(new Rectangle(xMidpoint, yMidpoint, halfWidth, halfHeight)); + this.northwest = new Quadtree<>(new Rectangle(x, yMidpoint, halfWidth, halfHeight)); + this.southwest = new Quadtree<>(new Rectangle(x, y, halfWidth, halfHeight)); + this.southeast = new Quadtree<>(new Rectangle(xMidpoint, y, halfWidth, halfHeight)); + this.leaf = false; + this.nodes.forEach(this.nodeAdder.reset(splitDepth)); + this.nodes.clear(); + } + + private Node remove(final T object, final Rectangle bounds, final Quadtree parent) { + Node returnValue = null; + if (this.leaf) { + for (int i = 0; i < this.nodes.size; i++) { + if (this.nodes.get(i).object == object) { + returnValue = this.nodes.removeIndex(i); + break; + } + } + } + else { + if (this.northeast.bounds.overlaps(bounds)) { + returnValue = this.northeast.remove(object, bounds, this); + } + if (this.northwest.bounds.overlaps(bounds)) { + returnValue = this.northwest.remove(object, bounds, this); + } + if (this.southwest.bounds.overlaps(bounds)) { + returnValue = this.southwest.remove(object, bounds, this); + } + if (this.southeast.bounds.overlaps(bounds)) { + returnValue = this.southeast.remove(object, bounds, this); + } + mergeIfNecessary(); + } + return returnValue; + } + + private void mergeIfNecessary() { + if (this.northeast.leaf && this.northwest.leaf && this.southwest.leaf && this.southeast.leaf) { + final int children = this.northeast.nodes.size + this.northwest.nodes.size + this.southwest.nodes.size + + this.southeast.nodes.size; // might include duplicates + if (children <= SPLIT_THRESHOLD) { + this.leaf = true; + addAllUnique(this.northeast.nodes); + addAllUnique(this.northwest.nodes); + addAllUnique(this.southwest.nodes); + addAllUnique(this.southeast.nodes); + this.northeast = this.northwest = this.southwest = this.southeast = null; + } + } + } + + private void addAllUnique(final Array> nodes) { + nodes.forEach(this.uniqueNodeAdder); + } + + private static final class Node { + private final T object; + private final Rectangle bounds; + + public Node(final T object, final Rectangle bounds) { + this.object = object; + this.bounds = bounds; + } + } + + private final class NodeAdder implements Consumer> { + private int splitDepth; + + private NodeAdder reset(final int splitDepth) { + this.splitDepth = splitDepth; + return this; + } + + @Override + public void accept(final Node node) { + add(node, this.splitDepth); + } + } + + private final class UniqueNodeAdder implements Consumer> { + + private UniqueNodeAdder reset() { + return this; + } + + @Override + public void accept(final Node node) { + for (int i = 0; i < Quadtree.this.nodes.size; i++) { + if (Quadtree.this.nodes.get(i) == node) { + return; + } + } + Quadtree.this.nodes.add(node); + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index a84e636..fdba8bc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -21,6 +21,7 @@ import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.math.collision.Ray; @@ -141,6 +142,7 @@ public class War3MapViewer extends ModelViewer { public List selModels = new ArrayList<>(); public List selected = new ArrayList<>(); private DataTable unitAckSoundsTable; + private DataTable miscData; private MdxComplexInstance confirmationInstance; public CSimulation simulation; private float updateTime; @@ -153,6 +155,8 @@ public class War3MapViewer extends ModelViewer { private final Map filePathToPathingMap = new HashMap<>(); + private final List selectionCircleSizes = new ArrayList<>(); + public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { super(dataSource, canvas); this.gameDataSource = dataSource; @@ -223,7 +227,21 @@ public class War3MapViewer extends ModelViewer { try (InputStream terrainSlkStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UnitAckSounds.slk")) { this.unitAckSoundsTable.readSLK(terrainSlkStream); } - + this.miscData = new DataTable(worldEditStrings); + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + this.selectionCircleSizes.clear(); + final Element selectionCircleData = this.miscData.get("SelectionCircle"); + final int selectionCircleNumSizes = selectionCircleData.getFieldValue("NumSizes"); + for (int i = 0; i < selectionCircleNumSizes; i++) { + final String indexString = i < 10 ? "0" + i : Integer.toString(i); + final float size = selectionCircleData.getFieldFloatValue("Size" + indexString); + final String texture = selectionCircleData.getField("Texture" + indexString); + final String textureDotted = selectionCircleData.getField("TextureDotted" + indexString); + this.selectionCircleSizes.add(new SelectionCircleSize(size, texture, textureDotted)); + } + this.selectionCircleScaleFactor = selectionCircleData.getFieldFloatValue("ScaleFactor"); } public GenericResource loadMapGeneric(final String path, final FetchDataTypeName dataType, @@ -367,7 +385,8 @@ public class War3MapViewer extends ModelViewer { return simulationAttackProjectile; } - }, this.terrain.pathingGrid); + }, this.terrain.pathingGrid, + new Rectangle(centerOffset[0], centerOffset[1], (mapSize[0] * 128f) - 128, (mapSize[1] * 128f) - 128)); if (this.doodadsAndDestructiblesLoaded) { this.loadDoodadsAndDestructibles(modifications); @@ -770,27 +789,29 @@ public class War3MapViewer extends ModelViewer { final Map splats = new HashMap(); for (final RenderUnit unit : units) { if (unit.row != null) { - if (unit.radius > 0) { - final float radius = unit.radius; - String path; - // TODO these radius values must be read from UI\MiscData.txt instead - if (radius < 100) { - path = "ReplaceableTextures\\Selection\\SelectionCircleSmall.blp"; + if (unit.selectionScale > 0) { + final float selectionSize = unit.selectionScale * this.selectionCircleScaleFactor; + String path = null; + for (int i = 0; i < this.selectionCircleSizes.size(); i++) { + final SelectionCircleSize selectionCircleSize = this.selectionCircleSizes.get(i); + if ((selectionSize < selectionCircleSize.size) + || (i == (this.selectionCircleSizes.size() - 1))) { + path = selectionCircleSize.texture; + break; + } } - else if (radius < 300) { - path = "ReplaceableTextures\\Selection\\SelectionCircleMed.blp"; - } - else { - path = "ReplaceableTextures\\Selection\\SelectionCircleLarge.blp"; + if (!path.toLowerCase().endsWith(".blp")) { + path += ".blp"; } if (!splats.containsKey(path)) { splats.put(path, new Splat()); } final float x = unit.location[0]; final float y = unit.location[1]; + System.out.println("Selecting a unit at " + x + "," + y); final float z = unit.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); - splats.get(path).locations - .add(new float[] { x - radius, y - radius, x + radius, y + radius, z + 5 }); + splats.get(path).locations.add(new float[] { x - (selectionSize / 2), y - (selectionSize / 2), + x + (selectionSize / 2), y + (selectionSize / 2), z + 5 }); splats.get(path).unitMapping.add(new Consumer() { @Override public void accept(final SplatMover t) { @@ -875,109 +896,19 @@ public class War3MapViewer extends ModelViewer { return sel; } - public List selectUnitOld(final float x, final float y, final boolean toggle) { - final float[] ray = rayHeap; - mousePosHeap.set(x, y); - this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - final Vector3 dir = normalHeap; - dir.x = ray[3] - ray[0]; - dir.y = ray[4] - ray[1]; - dir.z = ray[5] - ray[2]; - dir.nor(); - // TODO good performance, do not create vectors on every check - final Vector3 eMid = new Vector3(); - final Vector3 eSize = new Vector3(); - final Vector3 rDir = new Vector3(); - - RenderUnit entity = null; - float entDist = 1e6f; - - for (final RenderUnit unit : this.units) { - final float radius = unit.radius; - final float[] location = unit.location; - final MdxComplexInstance instance = unit.instance; - eMid.set(0, 0, radius / 2); - eSize.set(radius, radius, radius); - - eMid.add(location[0], location[1], location[2]); - eMid.sub(ray[0], ray[1], ray[2]); - eMid.scl(1 / eSize.x, 1 / eSize.y, 1 / eSize.z); - rDir.x = dir.x / eSize.x; - rDir.y = dir.y / eSize.y; - rDir.z = dir.z / eSize.z; - final float dlen = rDir.len2(); - final float dp = Math.max(0, rDir.dot(eMid)) / dlen; - if (dp > entDist) { - continue; - } - rDir.scl(dp); - if (rDir.dst2(eMid) < 1.0) { - entity = unit; - entDist = dp; - } - } - List sel; - if (entity != null) { - if (toggle) { - sel = new ArrayList<>(this.selected); - final int idx = sel.indexOf(entity); - if (idx >= 0) { - sel.remove(idx); - } - else { - sel.add(entity); - } - } - else { - sel = Arrays.asList(entity); - } - } - else { - sel = Collections.emptyList(); - } - this.doSelectUnit(sel); - return sel; - } - public RenderUnit rayPickUnit(final float x, final float y) { final float[] ray = rayHeap; mousePosHeap.set(x, y); this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - final Vector3 dir = normalHeap; - dir.x = ray[3] - ray[0]; - dir.y = ray[4] - ray[1]; - dir.z = ray[5] - ray[2]; - dir.nor(); - // TODO good performance, do not create vectors on every check - final Vector3 eMid = new Vector3(); - final Vector3 eSize = new Vector3(); - final Vector3 rDir = new Vector3(); + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx RenderUnit entity = null; - float entDist = 1e6f; - for (final RenderUnit unit : this.units) { - final float radius = unit.radius; - final float[] location = unit.location; final MdxComplexInstance instance = unit.instance; - eMid.set(0, 0, radius / 2); - eSize.set(radius, radius, radius); - - eMid.add(location[0], location[1], location[2]); - eMid.sub(ray[0], ray[1], ray[2]); - eMid.scl(1 / eSize.x, 1 / eSize.y, 1 / eSize.z); - rDir.x = dir.x / eSize.x; - rDir.y = dir.y / eSize.y; - rDir.z = dir.z / eSize.z; - final float dlen = rDir.len2(); - final float dp = Math.max(0, rDir.dot(eMid)) / dlen; - if (dp > entDist) { - continue; - } - rDir.scl(dp); - if (rDir.dst2(eMid) < 1.0) { + if (instance.isVisible(this.worldScene.camera) + && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap)) { entity = unit; - entDist = dp; } } return entity; @@ -1047,6 +978,7 @@ public class War3MapViewer extends ModelViewer { } private static final int MAXIMUM_ACCEPTED = 1 << 30; + private float selectionCircleScaleFactor; /** * Returns a power of two size for the given target capacity. @@ -1135,4 +1067,15 @@ public class War3MapViewer extends ModelViewer { StandSequence.randomStandSequence(instance); } + private static final class SelectionCircleSize { + private final float size; + private final String texture; + private final String textureDotted; + + public SelectionCircleSize(final float size, final String texture, final String textureDotted) { + this.size = size; + this.texture = texture; + this.textureDotted = textureDotted; + } + } } 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 289bcfd..9d20357 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 @@ -1,13 +1,10 @@ package com.etheller.warsmash.viewer5.handlers.w3x.environment; import java.awt.image.BufferedImage; -import java.util.Arrays; import java.util.HashMap; import java.util.Map; import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; public class PathingGrid { private static final Map movetpToMovementType = new HashMap<>(); @@ -21,10 +18,6 @@ public class PathingGrid { private final short[] pathingGrid; private final short[] dynamicPathingOverlay; // for buildings and trees - private final short[] unitPathingOverlay; // for constantly moving units - private final short[] ignorePathingOverlay; // to prevent unit collide with self - private final int[] unitsAtLocationCount; // number of units at location, probably really inefficient, should - // probably change later private final int[] pathingGridSizes; private final float[] centerOffset; @@ -33,53 +26,6 @@ public class PathingGrid { this.pathingGrid = terrainPathing.getPathing(); this.pathingGridSizes = terrainPathing.getSize(); this.dynamicPathingOverlay = new short[this.pathingGrid.length]; - this.unitPathingOverlay = new short[this.pathingGrid.length]; - this.ignorePathingOverlay = new short[this.pathingGrid.length]; - Arrays.fill(this.ignorePathingOverlay, (short) 0xFFFF); - this.unitsAtLocationCount = new int[this.pathingGrid.length]; - } - - public void resetUnitCollisionPathing(final Iterable units) { - Arrays.fill(this.unitPathingOverlay, (short) 0); - Arrays.fill(this.unitsAtLocationCount, 0); - for (final CUnit unit : units) { - final CUnitType unitType = unit.getUnitType(); - final MovementType movementType = unitType.getMovementType(); - if (!unitType.isBuilding() && (movementType != null)) { - final float collisionSize = unitType.getCollisionSize(); - final float maxX = unit.getX() + collisionSize; - final float maxY = unit.getY() + collisionSize; - final short obstructionByte; - switch (movementType) { - case FLOAT: - obstructionByte = (short) (PathingGrid.PathingFlags.UNSWIMABLE); - break; - case AMPHIBIOUS: - obstructionByte = (short) (PathingGrid.PathingFlags.UNWALKABLE - | PathingGrid.PathingFlags.UNSWIMABLE); - break; - case FLY: - obstructionByte = (short) (PathingGrid.PathingFlags.UNFLYABLE); - break; - default: - case FOOT: - case DISABLED: - case HORSE: - case HOVER: - obstructionByte = (short) (PathingGrid.PathingFlags.UNWALKABLE); - break; - } - for (float minX = unit.getX() - collisionSize; minX < maxX; minX += 32f) { - for (float minY = unit.getY() - collisionSize; minY < maxY; minY += 32f) { - final int yy = getCellY(minY); - final int xx = getCellX(minX); - final int index = (yy * this.pathingGridSizes[0]) + xx; - this.unitPathingOverlay[index] |= obstructionByte; - this.unitsAtLocationCount[index]++; - } - } - } - } } // this blit function is basically copied from HiveWE, maybe remember to mention @@ -193,8 +139,7 @@ public class PathingGrid { public short getCellPathing(final int cellX, final int cellY) { final int index = (cellY * this.pathingGridSizes[0]) + cellX; - return (short) ((this.pathingGrid[index] | this.dynamicPathingOverlay[index] | this.unitPathingOverlay[index]) - & this.ignorePathingOverlay[index]); + return (short) (this.pathingGrid[index] | this.dynamicPathingOverlay[index]); } public boolean isPathable(final float x, final float y, final PathingType pathingType) { @@ -247,22 +192,6 @@ public class PathingGrid { return false; } - public void setUnitIgnore(final float unitX, final float unitY, final float collisionSize, final boolean ignore) { - final float maxX = unitX + collisionSize; - final float maxY = unitY + collisionSize; - final short ignoreFlag = (short) (ignore ? 0 : 0xFFFF); - for (float minX = unitX - collisionSize; minX < maxX; minX += 32f) { - for (float minY = unitY - collisionSize; minY < maxY; minY += 32f) { - final int cellY = getCellY(minY); - final int cellX = getCellX(minX); - final int index = (cellY * this.pathingGridSizes[0]) + cellX; - if (this.unitsAtLocationCount[index] == 1) { - this.ignorePathingOverlay[index] = ignoreFlag; - } - } - } - } - public boolean isCellPathable(final int x, final int y, final MovementType pathingType, final float collisionSize) { return isPathable(getWorldX(x), getWorldY(y), pathingType, collisionSize); } 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 c0a87b9..948c1e2 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 @@ -43,7 +43,7 @@ public class RenderUnit { public final MdxComplexInstance instance; public final MutableGameObject row; public final float[] location = new float[3]; - public float radius; + public float selectionScale; public UnitSoundset soundset; public final MdxModel portraitModel; public int playerIndex; @@ -101,7 +101,7 @@ public class RenderUnit { (row.getFieldAsInteger(green, 0)) / 255f, (row.getFieldAsInteger(blue, 0)) / 255f }); instance.uniformScale(row.getFieldAsFloat(scale, 0)); - this.radius = row.getFieldAsFloat(War3MapViewer.UNIT_SELECT_SCALE, 0) * 36; + this.selectionScale = row.getFieldAsFloat(War3MapViewer.UNIT_SELECT_SCALE, 0); } this.instance = instance; @@ -148,7 +148,9 @@ public class RenderUnit { final float distanceToSimulation = (float) Math.sqrt((simDx * simDx) + (simDy * simDy)); final int speed = this.simulationUnit.getSpeed(); final float speedDelta = speed * deltaTime; - if (distanceToSimulation > speedDelta) { + if ((distanceToSimulation > speedDelta) && (deltaTime < 1.0)) { + // The 1.0 here says that after 1 second of lag, units just teleport to show + // where they actually are this.x += (speedDelta * simDx) / distanceToSimulation; this.y += (speedDelta * simDy) / distanceToSimulation; } 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 e423fa8..0b3f0cc 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 @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; @@ -23,18 +24,20 @@ public class CSimulation { private final ProjectileCreator projectileCreator; private int gameTurnTick = 0; private final PathingGrid pathingGrid; + private final CWorldCollision worldCollision; private final CPathfindingProcessor pathfindingProcessor; public CSimulation(final MutableObjectData parsedUnitData, final MutableObjectData parsedAbilityData, - final ProjectileCreator projectileCreator, final PathingGrid pathingGrid) { + final ProjectileCreator projectileCreator, final PathingGrid pathingGrid, final Rectangle entireMapBounds) { this.projectileCreator = projectileCreator; this.pathingGrid = pathingGrid; - this.pathfindingProcessor = new CPathfindingProcessor(pathingGrid); this.unitData = new CUnitData(parsedUnitData); this.abilityData = new CAbilityData(parsedAbilityData); this.units = new ArrayList<>(); this.projectiles = new ArrayList<>(); this.handleIdAllocator = new HandleIdAllocator(); + this.worldCollision = new CWorldCollision(entireMapBounds); + this.pathfindingProcessor = new CPathfindingProcessor(pathingGrid, this.worldCollision); } public CUnitData getUnitData() { @@ -54,6 +57,9 @@ public class CSimulation { final CUnit unit = this.unitData.create(this, this.handleIdAllocator.createId(), playerIndex, typeId, x, y, facing); this.units.add(unit); + if (!unit.getUnitType().isBuilding()) { + this.worldCollision.addUnit(unit); + } return unit; } @@ -67,13 +73,14 @@ public class CSimulation { return this.pathingGrid; } - public List findNaiveSlowPath(final float startX, final float startY, final float goalX, - final float goalY, final PathingGrid.MovementType movementType, final float collisionSize) { - return this.pathfindingProcessor.findNaiveSlowPath(startX, startY, goalX, goalY, movementType, collisionSize); + public List findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit, final float startX, + final float startY, final Point2D.Float goal, final PathingGrid.MovementType movementType, + final float collisionSize) { + return this.pathfindingProcessor.findNaiveSlowPath(ignoreIntersectionsWithThisUnit, startX, startY, goal, + movementType, collisionSize); } public void update() { - this.pathingGrid.resetUnitCollisionPathing(this.units); for (final CUnit unit : this.units) { unit.update(this); } @@ -90,4 +97,8 @@ public class CSimulation { public int getGameTurnTick() { return this.gameTurnTick; } + + public CWorldCollision getWorldCollision() { + return this.worldCollision; + } } 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 27491b5..b67c0e9 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 @@ -5,6 +5,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Queue; +import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; @@ -25,6 +26,8 @@ public class CUnit extends CWidget { private final Queue orderQueue = new LinkedList<>(); private final CUnitType unitType; + private Rectangle collisionRectangle; + public CUnit(final int handleId, final int playerIndex, final float x, final float y, final float life, final War3ID typeId, final float facing, final float mana, final int maximumLife, final int maximumMana, final int speed, final CUnitType unitType) { @@ -156,4 +159,38 @@ public class CUnit extends CWidget { return this.unitType; } + public void setCollisionRectangle(final Rectangle collisionRectangle) { + this.collisionRectangle = collisionRectangle; + } + + public Rectangle getCollisionRectangle() { + return this.collisionRectangle; + } + + public void setX(final float newX, final CWorldCollision collision) { + final float prevX = getX(); + if (!this.unitType.isBuilding()) { + setX(newX); + collision.translate(this, newX - prevX, 0); + } + } + + public void setY(final float newY, final CWorldCollision collision) { + final float prevY = getY(); + if (!this.unitType.isBuilding()) { + setY(newY); + collision.translate(this, 0, newY - prevY); + } + } + + public void setPoint(final float newX, final float newY, final CWorldCollision collision) { + final float prevX = getX(); + final float prevY = getY(); + setX(newX); + setY(newY); + if (!this.unitType.isBuilding()) { + collision.translate(this, newX - prevX, newY - prevY); + } + } + } 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 b59a667..3250aca 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 @@ -29,11 +29,11 @@ public abstract class CWidget { return this.life; } - public void setX(final float x) { + protected void setX(final float x) { this.x = x; } - public void setY(final float y) { + protected void setY(final float y) { this.y = y; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java new file mode 100644 index 0000000..ae90c5e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java @@ -0,0 +1,112 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +import com.badlogic.gdx.math.Rectangle; +import com.etheller.warsmash.util.Quadtree; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; + +public class CWorldCollision { + private final Quadtree groundUnitCollision; + private final Quadtree airUnitCollision; + private final Quadtree seaUnitCollision; + + public CWorldCollision(final Rectangle entireMapBounds) { + this.groundUnitCollision = new Quadtree<>(entireMapBounds); + this.airUnitCollision = new Quadtree<>(entireMapBounds); + this.seaUnitCollision = new Quadtree<>(entireMapBounds); + } + + public void addUnit(final CUnit unit) { + if (unit.getUnitType().isBuilding()) { + throw new IllegalArgumentException("Cannot add building to the CWorldCollision"); + } + Rectangle bounds = unit.getCollisionRectangle(); + if (bounds == null) { + final float collisionSize = unit.getUnitType().getCollisionSize(); + bounds = new Rectangle(unit.getX() - collisionSize, unit.getY() - collisionSize, collisionSize * 2, + collisionSize * 2); + unit.setCollisionRectangle(bounds); + } + final MovementType movementType = unit.getUnitType().getMovementType(); + if (movementType != null) { + switch (movementType) { + case AMPHIBIOUS: + this.seaUnitCollision.add(unit, bounds); + this.groundUnitCollision.add(unit, bounds); + break; + case FLOAT: + this.seaUnitCollision.add(unit, bounds); + break; + case FLY: + this.airUnitCollision.add(unit, bounds); + break; + default: + case DISABLED: + case FOOT: + case HORSE: + case HOVER: + this.groundUnitCollision.add(unit, bounds); + break; + } + } + } + + public boolean intersectsAnythingOtherThan(final Rectangle newPossibleRectangle, final CUnit sourceUnitToIgnore, + final MovementType movementType) { + if (movementType != null) { + switch (movementType) { + case AMPHIBIOUS: + if (this.seaUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle)) { + return true; + } + if (this.groundUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle)) { + return true; + } + return false; + case FLOAT: + return this.seaUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle); + case FLY: + return this.airUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle); + default: + case DISABLED: + case FOOT: + case HORSE: + case HOVER: + return this.groundUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle); + } + } + return false; + } + + public void translate(final CUnit unit, final float xShift, final float yShift) { + if (unit.getUnitType().isBuilding()) { + throw new IllegalArgumentException("Cannot add building to the CWorldCollision"); + } + final MovementType movementType = unit.getUnitType().getMovementType(); + final Rectangle bounds = unit.getCollisionRectangle(); + if (movementType != null) { + switch (movementType) { + case AMPHIBIOUS: + final float oldX = bounds.x; + final float oldY = bounds.y; + this.seaUnitCollision.translate(unit, bounds, xShift, yShift); + bounds.x = oldX; + bounds.y = oldY; + this.groundUnitCollision.translate(unit, bounds, xShift, yShift); + break; + case FLOAT: + this.seaUnitCollision.translate(unit, bounds, xShift, yShift); + break; + case FLY: + this.airUnitCollision.translate(unit, bounds, xShift, yShift); + break; + default: + case DISABLED: + case FOOT: + case HORSE: + case HOVER: + this.groundUnitCollision.translate(unit, bounds, xShift, yShift); + break; + } + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java index afdeade..4e770a3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java @@ -1,8 +1,10 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; import java.awt.geom.Point2D; +import java.awt.geom.Point2D.Float; import java.util.List; +import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; @@ -10,24 +12,24 @@ import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.Moveme import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; 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.CWorldCollision; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor; public class CMoveOrder implements COrder { + private static final Rectangle tempRect = new Rectangle(); private final CUnit unit; - private final float targetX; - private final float targetY; private boolean wasWithinPropWindow = false; private List path = null; private final CPathfindingProcessor.GridMapping gridMapping; + private final Point2D.Float target; public CMoveOrder(final CUnit unit, final float targetX, final float targetY) { this.unit = unit; - this.targetX = targetX; - this.targetY = targetY; this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( unit.getUnitType().getCollisionSize()) ? CPathfindingProcessor.GridMapping.CORNERS : CPathfindingProcessor.GridMapping.CELLS; + this.target = new Point2D.Float(targetX, targetY); } @Override @@ -37,175 +39,198 @@ public class CMoveOrder implements COrder { final MovementType movementType = this.unit.getUnitType().getMovementType(); final PathingGrid pathingGrid = simulation.getPathingGrid(); + final CWorldCollision worldCollision = simulation.getWorldCollision(); final float collisionSize = this.unit.getUnitType().getCollisionSize(); - pathingGrid.setUnitIgnore(prevX, prevY, collisionSize, true); - try { - final float startFloatingX = prevX; - final float startFloatingY = prevY; - final float goalFloatingX = this.targetX; - final float goalFloatingY = this.targetY; - if (this.path == null) { - this.path = simulation.findNaiveSlowPath(startFloatingX, startFloatingY, goalFloatingX, goalFloatingY, - movementType, collisionSize); - System.out.println("init path " + this.path); - // check for smoothing - if (!this.path.isEmpty()) { - float lastX = startFloatingX; - float lastY = startFloatingY; - float smoothingGroupStartX = startFloatingX; - float smoothingGroupStartY = startFloatingY; - final Point2D.Float firstPathElement = this.path.get(0); - double totalPathDistance = firstPathElement.distance(lastX, lastY); - lastX = firstPathElement.x; - lastY = firstPathElement.y; - int smoothingStartIndex = -1; - for (int i = 0; i < (this.path.size() - 1); i++) { - final Point2D.Float nextPossiblePathElement = this.path.get(i + 1); - totalPathDistance += nextPossiblePathElement.distance(lastX, lastY); - if ((totalPathDistance < (1.15 - * nextPossiblePathElement.distance(smoothingGroupStartX, smoothingGroupStartY))) - && pathingGrid.isPathable((smoothingGroupStartX + nextPossiblePathElement.x) / 2, - (smoothingGroupStartY + nextPossiblePathElement.y) / 2, movementType)) { - if (smoothingStartIndex == -1) { - smoothingStartIndex = i; - } - } - else { - if (smoothingStartIndex != -1) { - for (int j = smoothingStartIndex; j < i; j++) { - this.path.remove(j); - } - i = smoothingStartIndex; - } - smoothingStartIndex = -1; - final Point2D.Float smoothGroupNext = this.path.get(i); - smoothingGroupStartX = smoothGroupNext.x; - smoothingGroupStartY = smoothGroupNext.y; - totalPathDistance = nextPossiblePathElement.distance(smoothGroupNext); - } - lastX = nextPossiblePathElement.x; - lastY = nextPossiblePathElement.y; - } - if (smoothingStartIndex != -1) { - for (int j = smoothingStartIndex; j < (this.path.size() - 1); j++) { - final Point2D.Float removed = this.path.remove(j); - } - } - } - } - float currentTargetX; - float currentTargetY; - if (this.path.size() < 2) { - currentTargetX = this.targetX; - currentTargetY = this.targetY; - } - else { - final Point2D.Float nextPathElement = this.path.get(0); - currentTargetX = nextPathElement.x; - currentTargetY = nextPathElement.y; - } - - float deltaX = currentTargetX - prevX; - float deltaY = currentTargetY - prevY; - final double goalAngleRad = Math.atan2(deltaY, deltaX); - float goalAngle = (float) Math.toDegrees(goalAngleRad); - if (goalAngle < 0) { - goalAngle += 360; - } - float facing = this.unit.getFacing(); - float delta = goalAngle - facing; - final float propulsionWindow = simulation.getUnitData().getPropulsionWindow(this.unit.getTypeId()); - final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); - final int speed = this.unit.getSpeed(); - - if (delta < -180) { - delta = 360 + delta; - } - if (delta > 180) { - delta = -360 + delta; - } - final float absDelta = Math.abs(delta); - - if ((absDelta <= 1.0) && (absDelta != 0)) { - this.unit.setFacing(goalAngle); - } - else { - float angleToAdd = Math.signum(delta) * (float) Math.toDegrees(turnRate); - if (absDelta < Math.abs(angleToAdd)) { - angleToAdd = delta; - } - facing += angleToAdd; - this.unit.setFacing(facing); - } - if (absDelta < propulsionWindow) { - final float speedTick = speed * WarsmashConstants.SIMULATION_STEP_TIME; - double continueDistance = speedTick; - do { - boolean done; - float nextX, nextY; - final double travelDistance = Math.sqrt((deltaX * deltaX) + (deltaY * deltaY)); - if (travelDistance <= continueDistance) { - nextX = currentTargetX; - nextY = currentTargetY; - continueDistance = continueDistance - travelDistance; - done = true; - } - else { - final double radianFacing = Math.toRadians(facing); - nextX = (prevX + (float) (Math.cos(radianFacing) * continueDistance)); - nextY = (prevY + (float) (Math.sin(radianFacing) * continueDistance)); - continueDistance = 0; - done = (this.gridMapping.getX(pathingGrid, nextX) == this.gridMapping.getX(pathingGrid, - currentTargetX)) - && (this.gridMapping.getY(pathingGrid, nextY) == this.gridMapping.getY(pathingGrid, - currentTargetY)); - } - if (pathingGrid.isPathable(nextX, nextY, movementType, ((int) collisionSize / 16) * 16)) { - this.unit.setX(nextX); - this.unit.setY(nextY); - if (done) { - if (this.path.isEmpty()) { - return true; - } - else { - this.path.remove(0); - if (this.path.size() < 2) { - currentTargetX = this.targetX; - currentTargetY = this.targetY; - } - else { - final Point2D.Float firstPathElement = this.path.get(0); - currentTargetX = firstPathElement.x; - currentTargetY = firstPathElement.y; - } - deltaY = currentTargetY - prevY; - deltaX = currentTargetX - prevX; - } + final float startFloatingX = prevX; + final float startFloatingY = prevY; + if (this.path == null) { + this.path = simulation.findNaiveSlowPath(this.unit, startFloatingX, startFloatingY, this.target, + movementType, collisionSize); + System.out.println("init path " + this.path); + // check for smoothing + if (!this.path.isEmpty()) { + this.path.add(this.target); + float lastX = startFloatingX; + float lastY = startFloatingY; + float smoothingGroupStartX = startFloatingX; + float smoothingGroupStartY = startFloatingY; + final Point2D.Float firstPathElement = this.path.get(0); + double totalPathDistance = firstPathElement.distance(lastX, lastY); + lastX = firstPathElement.x; + lastY = firstPathElement.y; + int smoothingStartIndex = -1; + for (int i = 0; i < (this.path.size() - 1); i++) { + final Point2D.Float nextPossiblePathElement = this.path.get(i + 1); + totalPathDistance += nextPossiblePathElement.distance(lastX, lastY); + if ((totalPathDistance < (1.15 + * nextPossiblePathElement.distance(smoothingGroupStartX, smoothingGroupStartY))) + && pathingGrid.isPathable((smoothingGroupStartX + nextPossiblePathElement.x) / 2, + (smoothingGroupStartY + nextPossiblePathElement.y) / 2, movementType)) { + if (smoothingStartIndex == -1) { + smoothingStartIndex = i; } } else { - this.path = simulation.findNaiveSlowPath(startFloatingX, startFloatingY, goalFloatingX, - goalFloatingY, movementType, collisionSize); - System.out.println("new path " + this.path); + if (smoothingStartIndex != -1) { + for (int j = i - 1; j >= smoothingStartIndex; j--) { + this.path.remove(j); + } + i = smoothingStartIndex; + } + smoothingStartIndex = -1; + final Point2D.Float smoothGroupNext = this.path.get(i); + smoothingGroupStartX = smoothGroupNext.x; + smoothingGroupStartY = smoothGroupNext.y; + totalPathDistance = nextPossiblePathElement.distance(smoothGroupNext); + } + lastX = nextPossiblePathElement.x; + lastY = nextPossiblePathElement.y; + } + if (smoothingStartIndex != -1) { + for (int j = smoothingStartIndex; j < (this.path.size() - 1); j++) { + final Point2D.Float removed = this.path.remove(j); + } + } + } + } + float currentTargetX; + float currentTargetY; + if (this.path.isEmpty()) { + currentTargetX = this.target.x; + currentTargetY = this.target.y; + } + else { + final Point2D.Float nextPathElement = this.path.get(0); + currentTargetX = nextPathElement.x; + currentTargetY = nextPathElement.y; + } + + float deltaX = currentTargetX - prevX; + float deltaY = currentTargetY - prevY; + double goalAngleRad = Math.atan2(deltaY, deltaX); + float goalAngle = (float) Math.toDegrees(goalAngleRad); + if (goalAngle < 0) { + goalAngle += 360; + } + float facing = this.unit.getFacing(); + float delta = goalAngle - facing; + final float propulsionWindow = simulation.getUnitData().getPropulsionWindow(this.unit.getTypeId()); + final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); + final int speed = this.unit.getSpeed(); + + if (delta < -180) { + delta = 360 + delta; + } + if (delta > 180) { + delta = -360 + delta; + } + float absDelta = Math.abs(delta); + + if ((absDelta <= 1.0) && (absDelta != 0)) { + this.unit.setFacing(goalAngle); + } + else { + float angleToAdd = Math.signum(delta) * (float) Math.toDegrees(turnRate); + if (absDelta < Math.abs(angleToAdd)) { + angleToAdd = delta; + } + facing += angleToAdd; + this.unit.setFacing(facing); + } + if (absDelta < propulsionWindow) { + final float speedTick = speed * WarsmashConstants.SIMULATION_STEP_TIME; + double continueDistance = speedTick; + do { + boolean done; + float nextX, nextY; + final double travelDistance = Math.sqrt((deltaX * deltaX) + (deltaY * deltaY)); + if (travelDistance <= continueDistance) { + nextX = currentTargetX; + nextY = currentTargetY; + continueDistance = continueDistance - travelDistance; + done = true; + } + else { + final double radianFacing = Math.toRadians(facing); + nextX = (prevX + (float) (Math.cos(radianFacing) * continueDistance)); + nextY = (prevY + (float) (Math.sin(radianFacing) * continueDistance)); + continueDistance = 0; +// done = (this.gridMapping.getX(pathingGrid, nextX) == this.gridMapping.getX(pathingGrid, +// currentTargetX)) +// && (this.gridMapping.getY(pathingGrid, nextY) == this.gridMapping.getY(pathingGrid, +// currentTargetY)); + done = false; + } + tempRect.set(this.unit.getCollisionRectangle()); + tempRect.setCenter(nextX, nextY); + if (pathingGrid.isPathable(nextX, nextY, movementType, collisionSize)// ((int) collisionSize / 16) * 16 + && !worldCollision.intersectsAnythingOtherThan(tempRect, this.unit, movementType)) { + this.unit.setPoint(nextX, nextY, worldCollision); + if (done) { if (this.path.isEmpty()) { return true; } - } - this.wasWithinPropWindow = true; - } - while (continueDistance > 0); - } - else { - // If this happens, the unit is facing the wrong way, and has to turn before - // moving. - this.wasWithinPropWindow = false; - } + else { + final Float removed = this.path.remove(0); + System.out.println( + "We think we reached " + removed + " because are at " + nextX + "," + nextY); + if (this.path.isEmpty()) { + currentTargetX = this.target.x; + currentTargetY = this.target.y; + } + else { + final Point2D.Float firstPathElement = this.path.get(0); + currentTargetX = firstPathElement.x; + currentTargetY = firstPathElement.y; + } + deltaY = currentTargetY - prevY; + deltaX = currentTargetX - prevX; + System.out.println("new target: " + currentTargetX + "," + currentTargetY); + System.out.println("new delta: " + deltaX + "," + deltaY); + goalAngleRad = Math.atan2(deltaY, deltaX); + goalAngle = (float) Math.toDegrees(goalAngleRad); + if (goalAngle < 0) { + goalAngle += 360; + } + facing = this.unit.getFacing(); + delta = goalAngle - facing; - return false; + if (delta < -180) { + delta = 360 + delta; + } + if (delta > 180) { + delta = -360 + delta; + } + absDelta = Math.abs(delta); + if (absDelta >= propulsionWindow) { + this.wasWithinPropWindow = false; + return false; + } + } + } + } + else { + this.path = simulation.findNaiveSlowPath(this.unit, startFloatingX, startFloatingY, this.target, + movementType, collisionSize); + System.out.println("new path " + this.path); + if (this.path.isEmpty()) { + return true; + } + else { + this.path.add(this.target); + } + } + this.wasWithinPropWindow = true; + } + while (continueDistance > 0); } - finally { - pathingGrid.setUnitIgnore(prevX, prevY, collisionSize, false); + else { + // If this happens, the unit is facing the wrong way, and has to turn before + // moving. + this.wasWithinPropWindow = false; } + + return false; } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java index 6e5276e..5c79c45 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java @@ -7,16 +7,22 @@ import java.util.LinkedList; import java.util.List; import java.util.PriorityQueue; +import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWorldCollision; public class CPathfindingProcessor { + private static final Rectangle tempRect = new Rectangle(); private final PathingGrid pathingGrid; + private final CWorldCollision worldCollision; private final Node[][] nodes; private final Node[][] cornerNodes; private Node goal; - public CPathfindingProcessor(final PathingGrid pathingGrid) { + public CPathfindingProcessor(final PathingGrid pathingGrid, final CWorldCollision worldCollision) { this.pathingGrid = pathingGrid; + this.worldCollision = worldCollision; this.nodes = new Node[pathingGrid.getHeight()][pathingGrid.getWidth()]; this.cornerNodes = new Node[pathingGrid.getHeight() + 1][pathingGrid.getWidth() + 1]; for (int i = 0; i < this.nodes.length; i++) { @@ -46,12 +52,19 @@ public class CPathfindingProcessor { * @param goal * @return */ - public List findNaiveSlowPath(final float startX, final float startY, final float goalX, - final float goalY, final PathingGrid.MovementType movementType, final float collisionSize) { + public List findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit, final float startX, + final float startY, final Point2D.Float goal, final PathingGrid.MovementType movementType, + final float collisionSize) { + final float goalX = goal.x; + final float goalY = goal.y; + if (!this.pathingGrid.isPathable(goalX, goalY, movementType, collisionSize)) { + return Collections.emptyList(); + } System.out.println("beginning findNaiveSlowPath for " + startX + "," + startY + "," + goalX + "," + goalY); if ((startX == goalX) && (startY == goalY)) { return Collections.emptyList(); } + tempRect.set(0, 0, collisionSize * 2, collisionSize * 2); Node[][] searchGraph; GridMapping gridMapping; if (isCollisionSizeBetterSuitedForCorners(collisionSize)) { @@ -65,47 +78,116 @@ public class CPathfindingProcessor { System.out.println("using cells"); } this.goal = searchGraph[gridMapping.getY(this.pathingGrid, goalY)][gridMapping.getX(this.pathingGrid, goalX)]; - final Node start = searchGraph[gridMapping.getY(this.pathingGrid, startY)][gridMapping.getX(this.pathingGrid, - startX)]; + final int startGridY = gridMapping.getY(this.pathingGrid, startY); + final int startGridX = gridMapping.getX(this.pathingGrid, startX); for (int i = 0; i < searchGraph.length; i++) { for (int j = 0; j < searchGraph[i].length; j++) { final Node node = searchGraph[i][j]; node.g = Float.POSITIVE_INFINITY; node.f = Float.POSITIVE_INFINITY; node.cameFrom = null; + node.cameFromDirection = null; } } - start.g = 0; - start.f = h(start); final PriorityQueue openSet = new PriorityQueue<>(new Comparator() { @Override public int compare(final Node a, final Node b) { return Double.compare(f(a), f(b)); } }); - openSet.add(start); + + final Node start = searchGraph[startGridY][startGridX]; + int startGridMinX; + int startGridMinY; + int startGridMaxX; + int startGridMaxY; + if (startX > start.point.x) { + startGridMinX = startGridX; + startGridMaxX = startGridX + 1; + } + else if (startX < start.point.x) { + startGridMinX = startGridX - 1; + startGridMaxX = startGridX; + } + else { + startGridMinX = startGridX; + startGridMaxX = startGridX; + } + if (startY > start.point.y) { + startGridMinY = startGridY; + startGridMaxY = startGridY + 1; + } + else if (startY < start.point.y) { + startGridMinY = startGridY - 1; + startGridMaxY = startGridY; + } + else { + startGridMinY = startGridY; + startGridMaxY = startGridY; + } + for (int cellX = startGridMinX; cellX <= startGridMaxX; cellX++) { + for (int cellY = startGridMinY; cellY <= startGridMaxY; cellY++) { + if ((cellX >= 0) && (cellX < this.pathingGrid.getWidth()) && (cellY >= 0) + && (cellY < this.pathingGrid.getHeight())) { + final Node possibleNode = searchGraph[cellY][cellX]; + final float x = possibleNode.point.x; + final float y = possibleNode.point.y; + if (pathableBetween(ignoreIntersectionsWithThisUnit, startX, startY, movementType, collisionSize, x, + y)) { + + final double tentativeScore = possibleNode.point.distance(startX, startY); + possibleNode.g = tentativeScore; + possibleNode.f = tentativeScore + h(possibleNode); + openSet.add(possibleNode); + + } + } + } + } while (!openSet.isEmpty()) { Node current = openSet.poll(); if (current == this.goal) { final LinkedList totalPath = new LinkedList<>(); Direction lastCameFromDirection = null; - while (current.cameFrom != null) { - if ((lastCameFromDirection == null) || (current.cameFromDirection != lastCameFromDirection)) { - totalPath.addFirst(current.point); - lastCameFromDirection = current.cameFromDirection; - } + + if ((current.cameFrom != null) + && pathableBetween(ignoreIntersectionsWithThisUnit, current.cameFrom.point.x, + current.cameFrom.point.y, movementType, collisionSize, goalX, goalY)) { + // do some basic smoothing to walk straight to the goal if it is not obstructed, + // skipping the last grid location + totalPath.addFirst(goal); current = current.cameFrom; } + else { + totalPath.addFirst(goal); + totalPath.addFirst(current.point); + } + lastCameFromDirection = current.cameFromDirection; + Node lastNode = null; + while (current.cameFrom != null) { + lastNode = current; + current = current.cameFrom; + if ((lastCameFromDirection == null) || (current.cameFromDirection != lastCameFromDirection) + || (current.cameFromDirection == null)) { + if ((current.cameFromDirection != null) || (lastNode == null) + || !pathableBetween(ignoreIntersectionsWithThisUnit, startX, startY, movementType, + collisionSize, lastNode.point.x, lastNode.point.y)) { + // Add the point if it's not the first one, or if we can only complete + // the journey by specifically walking to the first one + totalPath.addFirst(current.point); + lastCameFromDirection = current.cameFromDirection; + } + } + } return totalPath; } for (final Direction direction : Direction.VALUES) { final float x = current.point.x + (direction.xOffset * 32); final float y = current.point.y + (direction.yOffset * 32); - if (this.pathingGrid.contains(x, y) && this.pathingGrid.isPathable(x, y, movementType, collisionSize) - && this.pathingGrid.isPathable(current.point.x, y, movementType, collisionSize) - && this.pathingGrid.isPathable(x, current.point.y, movementType, collisionSize)) { + if (this.pathingGrid.contains(x, y) && pathableBetween(ignoreIntersectionsWithThisUnit, current.point.x, + current.point.y, movementType, collisionSize, x, y)) { double turnCost; if ((current.cameFromDirection != null) && (direction != current.cameFromDirection)) { turnCost = 0.25; @@ -131,6 +213,22 @@ public class CPathfindingProcessor { return Collections.emptyList(); } + private boolean pathableBetween(final CUnit ignoreIntersectionsWithThisUnit, final float startX, final float startY, + final PathingGrid.MovementType movementType, final float collisionSize, final float x, final float y) { + return this.pathingGrid.isPathable(x, y, movementType, collisionSize) + && this.pathingGrid.isPathable(startX, y, movementType, collisionSize) + && this.pathingGrid.isPathable(x, startY, movementType, collisionSize) + && isPathableDynamically(x, y, ignoreIntersectionsWithThisUnit, movementType) + && isPathableDynamically(x, startY, ignoreIntersectionsWithThisUnit, movementType) + && isPathableDynamically(startX, y, ignoreIntersectionsWithThisUnit, movementType); + } + + private boolean isPathableDynamically(final float x, final float y, final CUnit ignoreIntersectionsWithThisUnit, + final PathingGrid.MovementType movementType) { + return !this.worldCollision.intersectsAnythingOtherThan(tempRect.setCenter(x, y), + ignoreIntersectionsWithThisUnit, movementType); + } + public static boolean isCollisionSizeBetterSuitedForCorners(final float collisionSize) { return (((2 * (int) collisionSize) / 32) % 2) == 1; } diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index e6362ad..2404830 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -5,6 +5,7 @@ import org.lwjgl.opengl.GL31; import org.lwjgl.opengl.GL32; import org.lwjgl.opengl.GL33; +import com.badlogic.gdx.Graphics.DisplayMode; import com.badlogic.gdx.audio.Sound; import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; @@ -67,10 +68,10 @@ public class DesktopLauncher { config.gles30ContextMajorVersion = 3; config.gles30ContextMinorVersion = 3; config.samples = 16; -// config.fullscreen = true; -// final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); -// config.width = desktopDisplayMode.width; -// config.height = desktopDisplayMode.height; + config.fullscreen = true; + final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); + config.width = desktopDisplayMode.width; + config.height = desktopDisplayMode.height; new LwjglApplication(new WarsmashGdxMapGame(), config); } } diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/ArrayRefJassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/ArrayRefJassExpression.java index 1ae050e..175cc19 100644 --- a/jassparser/src/com/etheller/interpreter/ast/expression/ArrayRefJassExpression.java +++ b/jassparser/src/com/etheller/interpreter/ast/expression/ArrayRefJassExpression.java @@ -3,6 +3,7 @@ package com.etheller.interpreter.ast.expression; import com.etheller.interpreter.ast.Assignable; 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.ArrayJassValue; import com.etheller.interpreter.ast.value.JassValue; import com.etheller.interpreter.ast.value.visitor.ArrayJassValueVisitor; @@ -18,11 +19,12 @@ public class ArrayRefJassExpression implements JassExpression { } @Override - public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope) { - Assignable variable = localScope.getAssignableLocal(identifier); - final JassValue index = indexExpression.evaluate(globalScope, localScope); + public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { + Assignable variable = localScope.getAssignableLocal(this.identifier); + final JassValue index = this.indexExpression.evaluate(globalScope, localScope, triggerScope); if (variable == null) { - variable = globalScope.getAssignableGlobal(identifier); + variable = globalScope.getAssignableGlobal(this.identifier); } if (variable.getValue() == null) { throw new RuntimeException("Unable to use subscript on uninitialized variable"); @@ -30,7 +32,8 @@ public class ArrayRefJassExpression implements JassExpression { final ArrayJassValue arrayValue = variable.getValue().visit(ArrayJassValueVisitor.getInstance()); if (arrayValue != null) { return arrayValue.get(index.visit(IntegerJassValueVisitor.getInstance())); - } else { + } + else { throw new RuntimeException("Not an array"); } } diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/FunctionCallJassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/FunctionCallJassExpression.java index 9cdece9..7d87b6a 100644 --- a/jassparser/src/com/etheller/interpreter/ast/expression/FunctionCallJassExpression.java +++ b/jassparser/src/com/etheller/interpreter/ast/expression/FunctionCallJassExpression.java @@ -6,6 +6,7 @@ import java.util.List; import com.etheller.interpreter.ast.function.JassFunction; 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 FunctionCallJassExpression implements JassExpression { @@ -18,17 +19,18 @@ public class FunctionCallJassExpression implements JassExpression { } @Override - public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope) { - final JassFunction functionByName = globalScope.getFunctionByName(functionName); + public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { + final JassFunction functionByName = globalScope.getFunctionByName(this.functionName); if (functionByName == null) { - throw new RuntimeException("Undefined function: " + functionName); + throw new RuntimeException("Undefined function: " + this.functionName); } final List evaluatedExpressions = new ArrayList<>(); - for (final JassExpression expr : arguments) { - final JassValue evaluatedExpression = expr.evaluate(globalScope, localScope); + for (final JassExpression expr : this.arguments) { + final JassValue evaluatedExpression = expr.evaluate(globalScope, localScope, triggerScope); evaluatedExpressions.add(evaluatedExpression); } - return functionByName.call(evaluatedExpressions, globalScope); + return functionByName.call(evaluatedExpressions, globalScope, triggerScope); } } diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/FunctionReferenceJassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/FunctionReferenceJassExpression.java index 0e9a2b9..3fac3ff 100644 --- a/jassparser/src/com/etheller/interpreter/ast/expression/FunctionReferenceJassExpression.java +++ b/jassparser/src/com/etheller/interpreter/ast/expression/FunctionReferenceJassExpression.java @@ -3,6 +3,7 @@ package com.etheller.interpreter.ast.expression; import com.etheller.interpreter.ast.function.JassFunction; 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.CodeJassValue; import com.etheller.interpreter.ast.value.JassValue; @@ -14,10 +15,11 @@ public class FunctionReferenceJassExpression implements JassExpression { } @Override - public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope) { - final JassFunction functionByName = globalScope.getFunctionByName(identifier); + public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { + final JassFunction functionByName = globalScope.getFunctionByName(this.identifier); if (functionByName == null) { - throw new RuntimeException("Unable to find function: " + identifier); + throw new RuntimeException("Unable to find function: " + this.identifier); } return new CodeJassValue(functionByName); } diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/JassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/JassExpression.java index edc47af..d9e16ea 100644 --- a/jassparser/src/com/etheller/interpreter/ast/expression/JassExpression.java +++ b/jassparser/src/com/etheller/interpreter/ast/expression/JassExpression.java @@ -2,8 +2,9 @@ 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; public interface JassExpression { - JassValue evaluate(GlobalScope globalScope, LocalScope localScope); + JassValue evaluate(GlobalScope globalScope, LocalScope localScope, TriggerExecutionScope triggerScope); } diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/LiteralJassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/LiteralJassExpression.java index c989de6..426c3d3 100644 --- a/jassparser/src/com/etheller/interpreter/ast/expression/LiteralJassExpression.java +++ b/jassparser/src/com/etheller/interpreter/ast/expression/LiteralJassExpression.java @@ -2,6 +2,7 @@ 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; public class LiteralJassExpression implements JassExpression { @@ -12,8 +13,9 @@ public class LiteralJassExpression implements JassExpression { } @Override - public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope) { - return value; + public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { + return this.value; } } diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/NotJassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/NotJassExpression.java index 37f16d0..eff46df 100644 --- a/jassparser/src/com/etheller/interpreter/ast/expression/NotJassExpression.java +++ b/jassparser/src/com/etheller/interpreter/ast/expression/NotJassExpression.java @@ -2,8 +2,8 @@ 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.BooleanJassValueVisitor; import com.etheller.interpreter.ast.value.visitor.NotJassValueVisitor; public class NotJassExpression implements JassExpression { @@ -14,7 +14,8 @@ public class NotJassExpression implements JassExpression { } @Override - public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope) { - return this.expression.evaluate(globalScope, localScope).visit(NotJassValueVisitor.getInstance()); + public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { + return this.expression.evaluate(globalScope, localScope, triggerScope).visit(NotJassValueVisitor.getInstance()); } } diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/ReferenceJassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/ReferenceJassExpression.java index b952a4e..2df0831 100644 --- a/jassparser/src/com/etheller/interpreter/ast/expression/ReferenceJassExpression.java +++ b/jassparser/src/com/etheller/interpreter/ast/expression/ReferenceJassExpression.java @@ -3,6 +3,7 @@ package com.etheller.interpreter.ast.expression; import com.etheller.interpreter.ast.Assignable; 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 ReferenceJassExpression implements JassExpression { @@ -13,10 +14,11 @@ public class ReferenceJassExpression implements JassExpression { } @Override - public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope) { - final Assignable local = localScope.getAssignableLocal(identifier); + public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { + final Assignable local = localScope.getAssignableLocal(this.identifier); if (local == null) { - return globalScope.getGlobal(identifier); + return globalScope.getGlobal(this.identifier); } return local.getValue(); } diff --git a/jassparser/src/com/etheller/interpreter/ast/function/AbstractJassFunction.java b/jassparser/src/com/etheller/interpreter/ast/function/AbstractJassFunction.java index ba8a557..af9107f 100644 --- a/jassparser/src/com/etheller/interpreter/ast/function/AbstractJassFunction.java +++ b/jassparser/src/com/etheller/interpreter/ast/function/AbstractJassFunction.java @@ -4,6 +4,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.value.JassType; import com.etheller.interpreter.ast.value.JassValue; import com.etheller.interpreter.ast.value.visitor.JassTypeGettingValueVisitor; @@ -24,7 +25,8 @@ public abstract class AbstractJassFunction implements JassFunction { } @Override - public final JassValue call(final List arguments, final GlobalScope globalScope) { + public final JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { if (arguments.size() != this.parameters.size()) { throw new RuntimeException("Invalid number of arguments passed to function"); } @@ -41,9 +43,9 @@ public abstract class AbstractJassFunction implements JassFunction { } localScope.createLocal(parameter.getIdentifier(), parameter.getType(), argument); } - return innerCall(arguments, globalScope, localScope); + return innerCall(arguments, globalScope, triggerScope, localScope); } protected abstract JassValue innerCall(final List arguments, final GlobalScope globalScope, - final LocalScope localScope); + TriggerExecutionScope triggerScope, final LocalScope localScope); } diff --git a/jassparser/src/com/etheller/interpreter/ast/function/JassFunction.java b/jassparser/src/com/etheller/interpreter/ast/function/JassFunction.java index b5b2ec0..b4878ae 100644 --- a/jassparser/src/com/etheller/interpreter/ast/function/JassFunction.java +++ b/jassparser/src/com/etheller/interpreter/ast/function/JassFunction.java @@ -3,8 +3,9 @@ package com.etheller.interpreter.ast.function; import java.util.List; import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.value.JassValue; public interface JassFunction { - JassValue call(List arguments, GlobalScope globalScope); + JassValue call(List arguments, GlobalScope globalScope, TriggerExecutionScope triggerScope); } diff --git a/jassparser/src/com/etheller/interpreter/ast/function/NativeJassFunction.java b/jassparser/src/com/etheller/interpreter/ast/function/NativeJassFunction.java index 14605bf..1cfbf0d 100644 --- a/jassparser/src/com/etheller/interpreter/ast/function/NativeJassFunction.java +++ b/jassparser/src/com/etheller/interpreter/ast/function/NativeJassFunction.java @@ -4,6 +4,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.value.JassType; import com.etheller.interpreter.ast.value.JassValue; @@ -15,12 +16,12 @@ public class NativeJassFunction extends AbstractJassFunction { final JassFunction impl) { super(parameters, returnType); this.name = name; - implementation = impl; + this.implementation = impl; } @Override protected JassValue innerCall(final List arguments, final GlobalScope globalScope, - final LocalScope localScope) { - return implementation.call(arguments, globalScope); + final TriggerExecutionScope triggerScope, final LocalScope localScope) { + 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 105a6da..92dff1f 100644 --- a/jassparser/src/com/etheller/interpreter/ast/function/UserJassFunction.java +++ b/jassparser/src/com/etheller/interpreter/ast/function/UserJassFunction.java @@ -4,6 +4,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.JassStatement; import com.etheller.interpreter.ast.value.JassType; import com.etheller.interpreter.ast.value.JassValue; @@ -26,17 +27,17 @@ public final class UserJassFunction extends AbstractJassFunction { @Override public JassValue innerCall(final List arguments, final GlobalScope globalScope, - final LocalScope localScope) { - for (final JassStatement statement : statements) { - final JassValue returnValue = statement.execute(globalScope, localScope); + final TriggerExecutionScope triggerScope, final LocalScope localScope) { + for (final JassStatement statement : this.statements) { + final JassValue returnValue = statement.execute(globalScope, localScope, triggerScope); if (returnValue != null) { - if (returnValue.visit(JassTypeGettingValueVisitor.getInstance()) != returnType) { + if (returnValue.visit(JassTypeGettingValueVisitor.getInstance()) != this.returnType) { throw new RuntimeException("Invalid return type"); } return returnValue; } } - if (JassType.NOTHING != returnType) { + if (JassType.NOTHING != this.returnType) { throw new RuntimeException("Invalid return type"); } return null; diff --git a/jassparser/src/com/etheller/interpreter/ast/scope/GlobalScope.java b/jassparser/src/com/etheller/interpreter/ast/scope/GlobalScope.java index 8e374c6..45262e5 100644 --- a/jassparser/src/com/etheller/interpreter/ast/scope/GlobalScope.java +++ b/jassparser/src/com/etheller/interpreter/ast/scope/GlobalScope.java @@ -22,15 +22,11 @@ public final class GlobalScope { private final HandleTypeSuperTypeLoadingVisitor handleTypeSuperTypeLoadingVisitor = new HandleTypeSuperTypeLoadingVisitor(); public final HandleJassType handleType; - public final HandleJassType frameHandleType; - public final HandleJassType framePointType; private static int lineNumber; public GlobalScope() { this.handleType = registerHandleType("handle");// the handle type - this.frameHandleType = registerHandleType("framehandle"); - this.framePointType = registerHandleType("framepointtype"); registerPrimitiveType(JassType.BOOLEAN); registerPrimitiveType(JassType.INTEGER); registerPrimitiveType(JassType.CODE); @@ -47,7 +43,7 @@ public final class GlobalScope { return lineNumber; } - private HandleJassType registerHandleType(final String name) { + public HandleJassType registerHandleType(final String name) { final HandleJassType handleJassType = new HandleJassType(null, name); this.types.put(name, handleJassType); return handleJassType; @@ -130,7 +126,13 @@ public final class GlobalScope { final HandleJassType handleSuperType = superType.visit(HandleJassTypeVisitor.getInstance()); if (handleSuperType != null) { final JassType jassType = this.types.get(type); - jassType.visit(this.handleTypeSuperTypeLoadingVisitor.reset(handleSuperType)); + if (jassType != null) { + jassType.visit(this.handleTypeSuperTypeLoadingVisitor.reset(handleSuperType)); + } + else { + throw new RuntimeException( + "unable to declare type " + type + " because it does not exist natively"); + } } else { throw new RuntimeException("type " + type + " cannot extend primitive type " + supertype); diff --git a/jassparser/src/com/etheller/interpreter/ast/scope/TriggerExecutionScope.java b/jassparser/src/com/etheller/interpreter/ast/scope/TriggerExecutionScope.java new file mode 100644 index 0000000..ded54e9 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/scope/TriggerExecutionScope.java @@ -0,0 +1,16 @@ +package com.etheller.interpreter.ast.scope; + +import com.etheller.interpreter.ast.scope.trigger.Trigger; + +public class TriggerExecutionScope { + private final Trigger triggeringTrigger; + + public TriggerExecutionScope(final Trigger triggeringTrigger) { + this.triggeringTrigger = triggeringTrigger; + } + + public Trigger getTriggeringTrigger() { + return this.triggeringTrigger; + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/scope/trigger/Trigger.java b/jassparser/src/com/etheller/interpreter/ast/scope/trigger/Trigger.java new file mode 100644 index 0000000..6c46ad3 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/scope/trigger/Trigger.java @@ -0,0 +1,74 @@ +package com.etheller.interpreter.ast.scope.trigger; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.etheller.interpreter.ast.function.JassFunction; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; + +public class Trigger { + private final List conditions = new ArrayList<>(); + private final List actions = new ArrayList<>(); + private int evalCount; + private int execCount; + private boolean enabled = true; + // used for eval + private transient final TriggerExecutionScope triggerExecutionScope = new TriggerExecutionScope(this); + + public int addAction(final JassFunction function) { + final int index = this.actions.size(); + this.actions.add(function); + return index; + } + + public int addCondition(final TriggerBooleanExpression boolexpr) { + final int index = this.conditions.size(); + this.conditions.add(boolexpr); + return index; + } + + public void removeCondition(final TriggerBooleanExpression boolexpr) { + this.conditions.remove(boolexpr); + } + + public void removeConditionAtIndex(final int conditionIndex) { + this.conditions.remove(conditionIndex); + } + + public int getEvalCount() { + return this.evalCount; + } + + public int getExecCount() { + return this.execCount; + } + + public boolean evaluate(final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { + for (final TriggerBooleanExpression condition : this.conditions) { + if (!condition.evaluate(globalScope, triggerScope)) { + return false; + } + } + return true; + } + + public void execute(final GlobalScope globalScope) { + for (final JassFunction action : this.actions) { + action.call(Collections.emptyList(), globalScope, this.triggerExecutionScope); + } + } + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(final boolean enabled) { + this.enabled = enabled; + } + + public void destroy() { + + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/scope/trigger/TriggerBooleanExpression.java b/jassparser/src/com/etheller/interpreter/ast/scope/trigger/TriggerBooleanExpression.java new file mode 100644 index 0000000..8463317 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/scope/trigger/TriggerBooleanExpression.java @@ -0,0 +1,8 @@ +package com.etheller.interpreter.ast.scope.trigger; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; + +public interface TriggerBooleanExpression { + boolean evaluate(GlobalScope globalScope, TriggerExecutionScope triggerScope); +} diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassArrayedAssignmentStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassArrayedAssignmentStatement.java index 73ccb82..5b80156 100644 --- a/jassparser/src/com/etheller/interpreter/ast/statement/JassArrayedAssignmentStatement.java +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassArrayedAssignmentStatement.java @@ -4,6 +4,7 @@ import com.etheller.interpreter.ast.Assignable; 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.ArrayJassValue; import com.etheller.interpreter.ast.value.JassValue; import com.etheller.interpreter.ast.value.visitor.ArrayJassValueVisitor; @@ -24,10 +25,11 @@ public class JassArrayedAssignmentStatement implements JassStatement { } @Override - public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { globalScope.setLineNumber(this.lineNo); Assignable variable = localScope.getAssignableLocal(this.identifier); - final JassValue index = this.indexExpression.evaluate(globalScope, localScope); + final JassValue index = this.indexExpression.evaluate(globalScope, localScope, triggerScope); if (variable == null) { variable = globalScope.getAssignableGlobal(this.identifier); } @@ -37,7 +39,7 @@ public class JassArrayedAssignmentStatement implements JassStatement { final ArrayJassValue arrayValue = variable.getValue().visit(ArrayJassValueVisitor.getInstance()); if (arrayValue != null) { arrayValue.set(index.visit(IntegerJassValueVisitor.getInstance()), - this.expression.evaluate(globalScope, localScope)); + this.expression.evaluate(globalScope, localScope, triggerScope)); } else { throw new RuntimeException("Not an array"); diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassCallStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassCallStatement.java index 23330e6..a5e9635 100644 --- a/jassparser/src/com/etheller/interpreter/ast/statement/JassCallStatement.java +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassCallStatement.java @@ -7,6 +7,7 @@ import com.etheller.interpreter.ast.expression.JassExpression; import com.etheller.interpreter.ast.function.JassFunction; 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 JassCallStatement implements JassStatement { @@ -21,7 +22,8 @@ public class JassCallStatement implements JassStatement { } @Override - public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { globalScope.setLineNumber(this.lineNo); final JassFunction functionByName = globalScope.getFunctionByName(this.functionName); if (functionByName == null) { @@ -29,10 +31,10 @@ public class JassCallStatement implements JassStatement { } final List evaluatedExpressions = new ArrayList<>(); for (final JassExpression expr : this.arguments) { - final JassValue evaluatedExpression = expr.evaluate(globalScope, localScope); + final JassValue evaluatedExpression = expr.evaluate(globalScope, localScope, triggerScope); evaluatedExpressions.add(evaluatedExpression); } - functionByName.call(evaluatedExpressions, globalScope); + functionByName.call(evaluatedExpressions, globalScope, triggerScope); // throw away return value return null; } diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseIfStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseIfStatement.java index ae6a2de..5f1f208 100644 --- a/jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseIfStatement.java +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseIfStatement.java @@ -5,6 +5,7 @@ import java.util.List; 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.visitor.BooleanJassValueVisitor; @@ -23,18 +24,20 @@ public class JassIfElseIfStatement implements JassStatement { } @Override - public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { globalScope.setLineNumber(this.lineNo); - if (this.condition.evaluate(globalScope, localScope).visit(BooleanJassValueVisitor.getInstance())) { + if (this.condition.evaluate(globalScope, localScope, triggerScope) + .visit(BooleanJassValueVisitor.getInstance())) { for (final JassStatement statement : this.thenStatements) { - final JassValue returnValue = statement.execute(globalScope, localScope); + final JassValue returnValue = statement.execute(globalScope, localScope, triggerScope); if (returnValue != null) { return returnValue; } } } else { - return this.elseifTail.execute(globalScope, localScope); + return this.elseifTail.execute(globalScope, localScope, triggerScope); } return null; } diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseStatement.java index 10912eb..20b9719 100644 --- a/jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseStatement.java +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseStatement.java @@ -5,6 +5,7 @@ import java.util.List; 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.visitor.BooleanJassValueVisitor; @@ -23,11 +24,13 @@ public class JassIfElseStatement implements JassStatement { } @Override - public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { globalScope.setLineNumber(this.lineNo); - if (this.condition.evaluate(globalScope, localScope).visit(BooleanJassValueVisitor.getInstance())) { + if (this.condition.evaluate(globalScope, localScope, triggerScope) + .visit(BooleanJassValueVisitor.getInstance())) { for (final JassStatement statement : this.thenStatements) { - final JassValue returnValue = statement.execute(globalScope, localScope); + final JassValue returnValue = statement.execute(globalScope, localScope, triggerScope); if (returnValue != null) { return returnValue; } @@ -35,7 +38,7 @@ public class JassIfElseStatement implements JassStatement { } else { for (final JassStatement statement : this.elseStatements) { - final JassValue returnValue = statement.execute(globalScope, localScope); + final JassValue returnValue = statement.execute(globalScope, localScope, triggerScope); if (returnValue != null) { return returnValue; } diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassIfStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassIfStatement.java index 9d14c30..abe42a0 100644 --- a/jassparser/src/com/etheller/interpreter/ast/statement/JassIfStatement.java +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassIfStatement.java @@ -5,6 +5,7 @@ import java.util.List; 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.visitor.BooleanJassValueVisitor; @@ -28,11 +29,13 @@ public class JassIfStatement implements JassStatement { } @Override - public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { globalScope.setLineNumber(this.lineNo); - if (this.condition.evaluate(globalScope, localScope).visit(BooleanJassValueVisitor.getInstance())) { + if (this.condition.evaluate(globalScope, localScope, triggerScope) + .visit(BooleanJassValueVisitor.getInstance())) { for (final JassStatement statement : this.thenStatements) { - final JassValue returnValue = statement.execute(globalScope, localScope); + final JassValue returnValue = statement.execute(globalScope, localScope, triggerScope); if (returnValue != null) { return returnValue; } diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassReturnStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassReturnStatement.java index 1f8542e..adb1b4e 100644 --- a/jassparser/src/com/etheller/interpreter/ast/statement/JassReturnStatement.java +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassReturnStatement.java @@ -3,6 +3,7 @@ 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; public class JassReturnStatement implements JassStatement { @@ -15,9 +16,10 @@ public class JassReturnStatement implements JassStatement { } @Override - public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { globalScope.setLineNumber(this.lineNo); - return this.expression.evaluate(globalScope, localScope); + return this.expression.evaluate(globalScope, localScope, triggerScope); } } diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassSetStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassSetStatement.java index 0b0cde7..e8249d4 100644 --- a/jassparser/src/com/etheller/interpreter/ast/statement/JassSetStatement.java +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassSetStatement.java @@ -4,6 +4,7 @@ import com.etheller.interpreter.ast.Assignable; 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; public class JassSetStatement implements JassStatement { @@ -18,14 +19,15 @@ public class JassSetStatement implements JassStatement { } @Override - public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { globalScope.setLineNumber(this.lineNo); final Assignable local = localScope.getAssignableLocal(this.identifier); if (local != null) { - local.setValue(this.expression.evaluate(globalScope, localScope)); + local.setValue(this.expression.evaluate(globalScope, localScope, triggerScope)); } else { - globalScope.setGlobal(this.identifier, this.expression.evaluate(globalScope, localScope)); + globalScope.setGlobal(this.identifier, this.expression.evaluate(globalScope, localScope, triggerScope)); } return null; } diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassStatement.java index 196f415..5c2099d 100644 --- a/jassparser/src/com/etheller/interpreter/ast/statement/JassStatement.java +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassStatement.java @@ -2,10 +2,11 @@ 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; public interface JassStatement { // When a value is returned, this indicates a RETURN statement, // and will end outer execution - JassValue execute(GlobalScope globalScope, LocalScope localScope); + JassValue execute(GlobalScope globalScope, LocalScope localScope, TriggerExecutionScope triggerScope); } diff --git a/jassparser/src/com/etheller/interpreter/ast/value/BooleanJassValue.java b/jassparser/src/com/etheller/interpreter/ast/value/BooleanJassValue.java index adbeabb..602a8f5 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/BooleanJassValue.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/BooleanJassValue.java @@ -27,4 +27,13 @@ public class BooleanJassValue implements JassValue { return TRUE; } } + + public static BooleanJassValue of(final boolean flag) { + if (flag) { + return TRUE; + } + else { + return FALSE; + } + } } diff --git a/jassparser/src/com/etheller/interpreter/ast/visitors/JassGlobalsVisitor.java b/jassparser/src/com/etheller/interpreter/ast/visitors/JassGlobalsVisitor.java index 5273cd0..f5fcfc2 100644 --- a/jassparser/src/com/etheller/interpreter/ast/visitors/JassGlobalsVisitor.java +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/JassGlobalsVisitor.java @@ -42,8 +42,9 @@ public class JassGlobalsVisitor extends JassBaseVisitor { 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)); + this.globals.createGlobal(ctx.ID().getText(), type, + this.jassExpressionVisitor.visit(ctx.assignTail().expression()).evaluate(this.globals, + EMPTY_LOCAL_SCOPE, JassProgramVisitor.EMPTY_TRIGGER_SCOPE)); } 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 c113899..2c5e0d2 100644 --- a/jassparser/src/com/etheller/interpreter/ast/visitors/JassProgramVisitor.java +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/JassProgramVisitor.java @@ -15,9 +15,11 @@ import com.etheller.interpreter.ast.function.JassFunction; import com.etheller.interpreter.ast.function.JassNativeManager; import com.etheller.interpreter.ast.function.UserJassFunction; import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.statement.JassStatement; public class JassProgramVisitor extends JassBaseVisitor { + public static final TriggerExecutionScope EMPTY_TRIGGER_SCOPE = new TriggerExecutionScope(null); private final GlobalScope globals = new GlobalScope(); private final JassNativeManager jassNativeManager = new JassNativeManager(); private final JassTypeVisitor jassTypeVisitor = new JassTypeVisitor(this.globals); @@ -85,7 +87,7 @@ public class JassProgramVisitor extends JassBaseVisitor { final JassFunction mainFunction = this.globals.getFunctionByName("main"); if (mainFunction != null) { try { - mainFunction.call(Collections.EMPTY_LIST, this.globals); + mainFunction.call(Collections.EMPTY_LIST, this.globals, EMPTY_TRIGGER_SCOPE); } catch (final Exception exc) { throw new RuntimeException("Exception on Line " + GlobalScope.getLineNumber(), exc); diff --git a/resources/Scripts/common.jui b/resources/Scripts/common.jui index bcd74f7..1a969fb 100644 --- a/resources/Scripts/common.jui +++ b/resources/Scripts/common.jui @@ -19,6 +19,11 @@ type framehandle extends handle type framepointtype extends handle +type trigger extends handle +type triggeraction extends handle +type triggercondition extends handle +type boolexpr extends handle +type conditionfunc extends boolexpr native LogError takes string message returns nothing constant native ConvertFramePointType takes integer i returns framepointtype @@ -92,3 +97,22 @@ native FramePositionBounds takes framehandle frame returns // string "UI\\Console\\Human\\HumanUI-TimeIndicator.mdl" // when the "Human" skin was loaded with CreateRootFrame native SkinGetField takes string field returns string + +//============================================================================ +// Native trigger interface +// +native CreateTrigger takes nothing returns trigger +native DestroyTrigger takes trigger whichTrigger returns nothing +native EnableTrigger takes trigger whichTrigger returns nothing +native DisableTrigger takes trigger whichTrigger returns nothing +native IsTriggerEnabled takes trigger whichTrigger returns boolean + +native TriggerAddCondition takes trigger whichTrigger, boolexpr condition returns triggercondition +native TriggerRemoveCondition takes trigger whichTrigger, triggercondition whichCondition returns nothing +native TriggerClearConditions takes trigger whichTrigger returns nothing + +native TriggerAddAction takes trigger whichTrigger, code actionFunc returns triggeraction +native TriggerRemoveAction takes trigger whichTrigger, triggeraction whichAction returns nothing +native TriggerClearActions takes trigger whichTrigger returns nothing +native TriggerEvaluate takes trigger whichTrigger returns boolean +native TriggerExecute takes trigger whichTrigger returns nothing \ No newline at end of file diff --git a/resources/Scripts/melee.jui b/resources/Scripts/melee.jui index 44823fb..2186536 100644 --- a/resources/Scripts/melee.jui +++ b/resources/Scripts/melee.jui @@ -1,7 +1,7 @@ globals // Defaults for testing: - constant string SKIN = "Human" + constant string SKIN = "NightElf" // Major UI components framehandle ROOT_FRAME framehandle CONSOLE_UI